Spring源码分析
第八章 Spring 声明式事务的⽀持
文章目录
- Spring源码分析
- 前言
- 一、Spring事务管理的两种方式
- 二、事务的概念
- 三、事务的四大特性
- 三、事务的隔离级别
- 四、事务的传播行为
- (一) PROPAGATION_REQUIRED
- 1.调用方法不开启事物
- 2.调用方法开启事物
- (二) PROPAGATION_REQUIRES_NEW
- 1.调用方法不开启事物
- 2.调用方法开启事物
- 系列连接
前言
事务管理对于企业应用来说是至关重要的,当出现异常情况时,它也可以保证数据的一致性。接下来,我们来详细分析一下Spring的事物
一、Spring事务管理的两种方式
spring支持编程式事务管理和声明式事务管理两种方式。
- 编程式事务:在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
- 声明式事务:通过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):
- 持久性是指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
三、事务的隔离级别
不考虑隔离级别,会出现以下情况:
- 脏读(Dirty read)
脏读发生在一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了,那么第一个事务读取的数据就会是无效的。 - 不可重复读(Nonrepeatable read)
不可重复读发生在一个事务执行相同的查询两次或两次以上,但每次查询结果都不相同时。这通常是由于另一个并发事务在两次查询之间更新了数据。 - 幻读(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的角度来观察来定义事务的传播行为
那么事物的传播行为有什么用呢?我们来验证一下:
首先,先在数据库中新建两张表:
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;
然后编写相应的Bean
和DAO
层代码:
@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
修饰的内部方法依然会单独开启独立事务,且与调用方法事务也独立,内部方法之间、内部方法和调用方法事务均相互独立,互不干扰。