Spring源码分析


第八章 Spring 声明式事务的⽀持



文章目录

  • Spring源码分析
  • 前言
  • 一、Spring事务管理的两种方式
  • 二、事务的概念
  • 三、事务的四大特性
  • 三、事务的隔离级别
  • 四、事务的传播行为
  • (一) PROPAGATION_REQUIRED
  • 1.调用方法不开启事物
  • 2.调用方法开启事物
  • (二) PROPAGATION_REQUIRES_NEW
  • 1.调用方法不开启事物
  • 2.调用方法开启事物
  • 系列连接



前言

事务管理对于企业应用来说是至关重要的,当出现异常情况时,它也可以保证数据的一致性。接下来,我们来详细分析一下Spring的事物


一、Spring事务管理的两种方式

spring支持编程式事务管理和声明式事务管理两种方式。

  1. 编程式事务:在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
  2. 声明式事务:通过xml或者注解配置的方式达到事务控制的目的,叫做声明式事务

声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。
声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。

二、事务的概念

事务指逻辑上的⼀组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。从而确保了数据的准确与安全。
例如:A——B转帐,对应于如下两条sql语句:

/*转出账户减钱*/
 update account set money=money-100 where name=‘a’;
 /**转⼊账户加钱*/
 update account set money=money+100 where name=‘b’;

这两条语句的执行,要么全部成功,要么全部不成功。

三、事务的四大特性

大名鼎鼎的ACID

A - 原子性(Atomicity):

  • 原子性是指事务是⼀个不可分割的工作单位,事务中的操作要么都发⽣,要么都不发生。从操作的⻆度来描述,事务中的各个操作要么都成功要么都失败

C - 一致性(Consistency):

  • 事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态。例如转账前A有1000,B有1000。转账后A+B也得是2000。⼀致性是从数据的角度来说的,(1000,1000)->(900,1100),不应该出现(900,1000)

I - 隔离性(Isolation):

  • 事务的隔离性是多个用户并发访问数据库时,数据库为每⼀个用户开启的事务,每个事务不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
    比如:事务1给员工涨工资2000,但是事务1尚未被提交,员工发起事务2查询工资,发现工资涨了2000块钱,读到了事务1尚未提交的数据

D - 持久性(Durability):

  • 持久性是指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

三、事务的隔离级别

不考虑隔离级别,会出现以下情况:

  1. 脏读(Dirty read)
    脏读发生在一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了,那么第一个事务读取的数据就会是无效的。
  2. 不可重复读(Nonrepeatable read)
    不可重复读发生在一个事务执行相同的查询两次或两次以上,但每次查询结果都不相同时。这通常是由于另一个并发事务在两次查询之间更新了数据。
  3. 幻读(Phantom reads)
    幻读和不可重复读相似。当一个事务(T1)读取几行记录后,另一个并发事务(T2)插入了一些记录时,幻读就发生了。在后来的查询中,第一个事务(T1)就会发现一些原来没有的额外记录。

为解决上述几个问题,因此,数据库定义了四种隔离级别:

(1)Serializable(串行化):可避免脏读、不可重复读、虚读情况的发生。(串行化)
(2)Repeatable read(可重复读):可避免脏读、不可重复读情况的发⽣。(幻读有可能发⽣) 该机制下会对要update的行进行加锁
(3)Read committed(读已提交):可避免脏读情况发生。不可重复读和幻读⼀定会发生。
(4)Read uncommitted(读未提交):最低级别,以上情况均无法保证。(读未提交)
注意:级别依次升高,效率依次降低

四、事务的传播行为

事务往往在service层进行控制,如果出现service层方法A调用了另外⼀个service层方法B,A和B方法本身都已经被添加了事务控制,那么A调用B的时候,就需要进行事务的⼀些协商,这就叫做事务的传播行为。

A调用B,我们站在B的角度来观察来定义事务的传播行为

spring源码技术在项目中的应用_java


那么事物的传播行为有什么用呢?我们来验证一下:

首先,先在数据库中新建两张表:

CREATE TABLE `admin1` (
  `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL DEFAULT '',
  PRIMARY KEY(`id`)
)
ENGINE = InnoDB;
---------------------------------------------------
CREATE TABLE `admin2` (
  `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL DEFAULT '',
  PRIMARY KEY(`id`)
)
ENGINE = InnoDB;

然后编写相应的BeanDAO层代码:

@Getter
@Setter
public class Admin1{
    private Integer id;
    private String name;
}

@Getter
@Setter
public class Admin2{
    private Integer id;
    private String name;
}

public interface Admin1Mapper {
    int insert(User1 record);
    User1 selectByPrimaryKey(Integer id);
}

public interface Admin2Mapper {
    int insert(User2 record);
    User2 selectByPrimaryKey(Integer id);
}

最后也是具体验证的代码由service层实现,下面我们分情况列举:

(一) PROPAGATION_REQUIRED

新建service层代码,并添加注解 @Transactional(propagation = Propagation.REQUIRED)

@Service
public class Admin1ServiceImpl implements Admin1Service {
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insert(Admin1 admin){
        admin1Mapper.insert(admin);
    }
}

@Service
public class Admin2ServiceImpl implements Admin2Service {
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insert(Admin2 admin){
        admin2Mapper.insert(admin);
    }
    
	@Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertException(Admin2 admin){
        admin2Mapper.insert(admin);
        throw new RuntimeException();
    }
}

1.调用方法不开启事物

方法一:

@Override
	@Test
    public void notException(){
        Admin1 admin1 = new Admin1();
        admin1.setName("张三");
        admin1Service.insert(admin1);
        
        Admin2 admin2 = new Admin2();
        admin2.setName("李四");
        admin2Service.insert(admin2);
       
        throw new RuntimeException();
    }

方法二:

@Override
	@Test
    public void isException(){
        Admin1 admin1 = new Admin1();
        admin1.setName("张三");
        admin1Service.insert(admin1);
        
        Admin2 admin2 = new Admin2();
        admin2.setName("李四");
        admin2Service.insertException(admin2);
    }

分别运行:

  • 方法一:“张三”“李四”均出现在数据库当中
  • 方法二:“张三”出现在数据库当中

这个结果证明了调用方法在不开启事物的情况下,Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

2.调用方法开启事物

方法一:

@Test
	@Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void open_transactional(){
        Admin1 admin1 = new Admin1();
        admin1.setName("张三");
        admin1Service.insert(admin1);
        
        Admin2 admin2 = new Admin2();
        admin2.setName("李四");
        admin2Service.insert(admin2);
        
        throw new RuntimeException();
    }

方法二:

@Test
	@Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void open_transactional_exception(){
        Admin1 admin1 = new Admin1();
        admin1.setName("张三");
        admin1Service.insert(admin1);
        
        Admin2 admin2 = new Admin2();
        admin2.setName("李四");
        admin2Service.insertException(admin2);
        
    }

方法三:

@Test
	@Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void open_transactional_try_exception(){
        Admin1 admin1 = new Admin1();
        admin1.setName("张三");
        admin1Service.insert(admin1);
        
        Admin2 admin2 = new Admin2();
        admin2.setName("李四");
        try {
            admin2Service.insertException(admin2);
        } catch (Exception e) {
            System.out.println("方法回滚");
        }
        
    }

分别运行:

  • 方法一:“张三”“李四”均未出现在数据库中。
  • 方法二:“张三”“李四”均未出现在数据库中。
  • 方法三:“张三”“李四”均未出现在数据库中。

结果证明在调用方法开启事务的情况下Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,所有Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。

(二) PROPAGATION_REQUIRES_NEW

@Service
public class Admin1ServiceImpl implements Admin1Service {

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertRequiredNew(Admin1 admin1 ){
        admin1Mapper.insert(admin1 );
    }
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertRequired(Admin1 admin1 ){
        admin1Mapper.insert(admin1 );
    }
}

@Service
public class Admin2ServiceImpl implements Admin2Service{
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertRequiredNew(Admin2 admin2){
        admin2Mapper.insert(admin2);
    }
    
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertRequiresNewException(Admin2 admin2){
        admin2Mapper.insert(admin2);
        throw new RuntimeException();
    }
}

1.调用方法不开启事物

方法一:

@Override
	@Test
    public void notException(){
        Admin1 admin1 =new Admin1();
        admin1.setName("张三");
        admin1Service.insertRequiredNew(admin1);
        
        Admin2 admin2 = new Admin2();
        admin2.setName("李四");
        admin2Service.insertRequiredNew(admin2);
        throw new RuntimeException();
        
    }

方法二:

@Override
	@Test
    public void isException(){
        Admin1 admin1 = new Admin1();
        admin1.setName("张三");
        admin1Service.insertRequiredNew(admin1);
        
        Admin2 admin2 = new Admin2();
        admin2.setName("李四");
        admin2Service.insertRequiresNewException(admin2);
    }

运行结果:

  • 方法一:“张三”“李四”均出现在数据库中
  • 方法二:“张三”出现在数据库中

结果证明在调用方法未开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

2.调用方法开启事物

方法一:

@Override
	@Test
    @Transactional(propagation = Propagation.REQUIRED)
    public void notException(){
        Admin1 admin1 = new Admin1();
        admin1.setName("张三");
        admin1Service.insertRequired(admin1);
        
        Admin2 admin2 = new Admin2();
        admin2.setName("李四");
        admin2Service.insertRequiredNew(admin2);
        
        Admin2 admin3 = new Admin3();
        admin3.setName("王五");
        admin2Service.insertRequiredNew(admin3);
        throw new RuntimeException();
    }

方法二:

@Override
	@Test
    @Transactional(propagation = Propagation.REQUIRED)
    public void isException(){
        Admin1 admin1 = new Admin1();
        admin1.setName("张三");
        admin1Service.insertRequired(admin1);
        
        Admin2 admin2 = new Admin2();
        admin2.setName("李四");
        admin2Service.insertRequiresNew(admin2);
        
        Admin2 admin3 = new Admin2();
        admin3.setName("王五");
        user2Service.insertRequiresNewException(admin3);
    }

方法三:

@Override
	@Test
    @Transactional(propagation = Propagation.REQUIRED)
    public void isExceptionTransactionalTry(){
        Admin1 admin1 = new Admin1();
        admin1.setName("张三");
        admin1Service.addRequired(admin1);
        
        Admin2 admin2 =new Admin2();
        admin2.setName("李四");
        admin2Service.addRequiresNew(admin2);
        
        Admin2 admin3 = new Admin2();
        admin3.setName("王五");
        try {
            admin2Service.addRequiresNewException(admin3);
        } catch (Exception e) {
            System.out.println("回滚");
        }
    }

运行结果:

  • 方法一:“李四”“王五”出现在数据库中
  • 方法二:“李四”出现在数据库中
  • 方法三:“张三”“李四”出现在数据库中

结果证明在调用方法开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,且与调用方法事务也独立,内部方法之间、内部方法和调用方法事务均相互独立,互不干扰。