目录
一、什么是事务
二、Spring事务的实现方式
1、编程式事务
2、声明式事务
三、自动操作事务的注解的三个属性
1、rollbackFor
2、isolation
3、propagation
前言:本文所见围绕的主题是事务,所以笔者先讲解什么是事务,先让大家了解事务,接着讲解在Spring中,如何对事务进行的实现,有两种方式,先讲手动创建的方式,再根据手动引出自动的方式,自动的方式是根据注解实现的,最后围绕注解再展开几个知识点~
一、什么是事务
例子:我们在王者荣耀里面充钱买皮肤这个行为,会包含几个操作:先充钱,再花钱购买皮肤,购买之后皮肤到达背包中,这几个操作缺一不可,如果你不充钱,就可以得到一个皮肤,腾讯怎么盈利?如果花了钱,却得不到皮肤,我们就白花钱了。。
我们进行的这一系列操作就是一个事务,事务具有原子性,要么这一组操作全部成功,要么全部失败~
那现在咱们知道了什么是事务,现在看一下,事务是如何操作的:
在MySQL中我们学了事务了,对事物的应用不仅存在于数据库中,它可以遍布在生活中的各处,比如上述例子中的买皮肤,还要银行转账,商场买东西等等,所以它可以存在于Spring中,下面咱们来看看在Spring中,它是如何实现的~
二、Spring事务的实现方式
在Spring中一共有两种实现方式:1:编程式事务 2:声明式事务,第一种是手写代码去操作事务,第二种是通过注解自动操作事务。
1、编程式事务
通过代码操作事务,需要引入引入两个对象:DataSourceTransactionManager(事务管理器) 和 TransactionDefinition(事务属性)
进行以下操作:
开启事务:
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
提交事务:
dataSourceTransactionManager.commit(transactionStatus);
回滚事务:
dataSourceTransactionManager.rollback(transactionStatus);
完整的代码:
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@Autowired
private UserService userService;
public String registry(String name,String password){
// 开启事务
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
//业务逻辑 用户注册
userService.registryUser(name,password);
//提交事务
dataSourceTransactionManager.commit(transactionStatus);
//回滚事务
//dataSourceTransactionManager.rollback(transactionStatus);
return "注册成功";
}
使用编程式事务太麻烦了,下面咱们看看使用注解的方式
2、声明式事务
使用@Transactional注解,就可以自动开启事务,自动提交事务,当抛出异常后,自动回滚事务,注意:只有当抛出异常时,才会回滚,不管方法里面是否报错,因为报了错,在内部可以捕获,当然,如果没有报错,也想回滚,可以手动抛出异常~
@Autowired
private UserService userService;
@Transactional
public String registry(String name,String password){
//⽤⼾注册
userService.registryUser(name,password);
return "注册成功";
}
@Autowired
private UserService userService;
@Transactional
public String registry(String name,String password){
//⽤⼾注册
userService.registryUser(name,password);
log.info("⽤⼾数据插⼊成功");
//对异常进⾏捕获
try {
//强制程序抛出异常
int a = 10/0;
}catch (Exception e){
// ⼿动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return "注册成功";
}
大概介绍一下 @Transactional注解吧
- 可以修饰public方法
- 它也可以修饰类,表明类下面的所有的public方法都可以自动操作事务;
三、自动操作事务的注解的三个属性
@Transcactional注解它有三个重要的属性:
rollbackFor,它是异常回滚属性,能够指定当方法抛出什么类型的异常,进行自动回滚操作。
Isolation,它用来设置事务的隔离级别。
propagation,它用来设置事务的传播机制。
1、rollbackFor
@Transcactional默认当抛出运行时异常和产生错误时,才会进行自动回滚,如果想要抛出其他类型异常,比如IOException异常也想回滚,就可以利用rollbackFor属性,设置值,rollbackFor = Exception.class,就可以只要抛出异常,不管什么类型,都可以回滚~
@Autowired
private UserService userService;
@Transactional(rollbackFor = Exception.class)
public String registry(String name,String password) throws IOException {
//⽤⼾注册
userService.registryUser(name,password);
log.info("⽤⼾数据插⼊成功");
if(true) {
throw new IOException();
}
return "注册成功";
}
2、isolation
isolation可以设置隔离级别,先介绍一下事务隔离级别的概念,然后在介绍SQL中分别有几种级别,详细介绍一下这几种级别,最后介绍Spring中的隔离级别~
概念:
事务隔离指的是:当多个事务并发进行时,他们之间进行相互隔离
事务隔离程度指的是:事务在读取和修改数据时能够接触和修改其他事务所作的修改的程度
事务隔离的目的是确保事务进行并发进行时,能够正确执行,保证数据的一致性和可靠性,因为在一个并发环境中,多个事务同时进行时,可能同时修改和读取某些数据,如果不进行隔离,可能会产生一些问题,比如脏读,不可重复读,幻读。
SQL中事务的隔离级别(由低到高开始介绍):
- 1、读取未提交(Read Uncommitted)
在该隔离级别下的事务可以看到其他事务未提交的数据,在这种级别下,如果某个未提交的数据,发生了回滚,但是有事务读取了,会造成脏读的问题,读取的这种数据叫做脏数据。(这是最低的隔离级别,并发性高,但是可靠性较差)
- 2、读取已提交(Read Committed)
在该隔离级别下,事务可以读取其他事务已经提交的数据,这解决了脏读的问题,但是某个事物若进行多次重复的sql查询,可能会得到不同的结果,这可能会造成不可以重复读的问题
- 3、可重复读(Repeatable Read)(MySQL中默认的事务隔离级别)
在该隔离级别下,在并发时,某个事物不会读到其他事物修改的数据,这就保证了数据的一致性,解决了不可以重复读的问题,但是又可能会造成幻读的问题,因为每次查询的都是同一组数据,有可能有事务在某一行插入了数据,但是我查询不到,我也就想在那一行插入插入,但是一直插入失败,这种现象叫做幻读。
- 4、串行化(Serializable)
由于采用上述三种隔离级别,都可能会产生问题,所以使用串行化,会解决这些问题,因为这种是一个事务一个事务的进行,谁也根本干扰不到谁~ 但是这种级别性能太低,一般不会采取的~
Spring中的事务隔离级别:
一共有五种,多了一个DEFAULT级别,这个级别表示,当前连接的数据库是用的哪种隔离级别,就用哪种隔离级别~
Isolation.DEFAULT : 以连接的数据库的事务隔离级别为主
Isolation.READ_UNCOMMITTED:读未提交,对应SQL标准中 READ UNCOMMITTED
Isolation.READ_COMMITTED:读已提交,对应SQL标准中 READ COMMITTED
Isolation.REPEATABLE_READ:可重复读,对应SQL标准中 REPEATABLE READ
Isolation.SERIALIZABLE:串⾏化,对应SQL标准中 SERIALIZABLE
@Transactional(isolation = Isolation.READ_COMMITTED)
3、propagation
学会用这个属性,要先了解事务传播机制
要说传播,肯定是发生在多个实体之间,否则怎么能传播呢?
在Spring中事务可以用注解@Transactional声明方法,事务现在为方法服务,但是现在如果一个事务方法被另一个事务方法调用,那被调用的方法使用哪个事务呢?该怎么进行事务呢?这就用到了事务传播机制~ 一共有七种
现在有两个方法,一个是调用者,一个是被调用者,上图中的当前事务指的是调用者这个方法所用的事务
通过使用propagation属性,使用上面的值,可以指定传播方式
现在咱们重点讲解两种传播机制:REQUIRED(默认值)和REQUIRES_NEW
- REQUIRED:被调用的方法如果使用的是这个传播机制,那么在被调用后,如果在调用者的方法使用了事务,那么被调用的方法使用对方的事务,如果调用者没有事务,则创建一个新的事务,共同使用~
特点:如果一个方法抛出了异常,那么所有参与其中的方法都会执行失败,发生回滚,因为所有方法使用的是同一个事务呀,要么同时成功,要么同时失败,这会保证数据的一致性~
- REQUIRES_NEW:被调用的方法使用这个传播机制,那么被调用后,会创建新事务,不会使用对方的事务,互不干扰
最后展示两个事务传播吧 :never和nested
- never:使用了这种事务的传播后,被调用后,不允许调用者有使用事务,如果有,就抛出异常
- nested:使用了这种事务传播后,被调用后,会使用调用者事务的子事务,子事务发生回滚,会使得父事务也发生回滚,并且如果还要其他方法也使用了父事务,也会连累其他方法执行失败~ 那该如何是好呢?可以只让当前的子事务的方法回滚,不影响父事务,怎么做的呢?在子事务的方法内,手动回滚。
那现在有个问题:nested和required的区别是什么?
使用nested会进行局部回滚,不会影响其他的事务发生回滚,而使用required不会局部回滚,一个事务回滚1,所有事物都要回滚,因为使用的是同一个事物。