目录
前言
说起事务,我们第一时间想到的就是MySql数据库了,数据库也有事务,那么两者之间有什么联系吗?
联系:两者之间有联系,因为 Spring 事务通常用于管理应用程序中对数据库的事务操作。Spring 事务管理器通常与底层的数据库事务进行集成,以确保事务操作的正确性和可靠性。Spring 事务管理器可以与各种数据库管理系统集成,如 MySQL、Oracle、PostgreSQL 等,以提供跨数据库的一致性事务管理。
数据库事务学习:http://t.csdnimg.cn/0jTeu
一.Spring事务
在Spring中,事务的实现有两种方式:
编程式事务(⼿动写代码操作事务)。 声明式事务(利⽤注解⾃动开启和提交事务)两者当中,最为常见的是第二种方式!
二.Spring编程事务
这个只需要了解一下,所谓的编程式就是通过编写代码来控制事务的开始、提交、回滚等操作。而编程者,可以通过Spring提供的事务模板(TransactionTemplate)或者直接使用底层的事务管理器(PlatformTransactionManager)来实现编程式事务管理。
Spring事务模板(TransactionTemplate)
一个简化事务管理的工具类,它封装了事务的开始、提交、回滚等操作,使得在业务代码中可以更加方便地处理事务。
代码实现:
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
public void updateUser(User user) {
transactionTemplate.execute(status -> {
// 更新用户信息
userRepository.save(user);
return null;
});
}
}
不使用模板的情况下
SpringBoot 内置了两个对象,
- DataSourceTransactionManager ⽤来获取事务(开启事务)、提交和回滚事务的,
- TransactionDefinition 是事务的属性(如事务的传播行为、隔离级别、超时时间等),在获取事务的时候需要将TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus。
代码如下:
@RestController
public class UserController {
@Resource
private UserService userService;
// JDBC 事务管理器
@Resource
private DataSourceTransactionManager dataSourceTransactionManager;
// 定义事务属性
@Resource
private TransactionDefinition transactionDefinition;
@RequestMapping("/sava")
public Object save(User user) {
// 开启事务
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(transactionDefinition);
// 插⼊数据库
int result = userService.save(user);
// 提交事务
dataSourceTransactionManager.commit(transactionStatus);
// 回滚事务
// dataSourceTransactionManager.rollback(transactionStatus);
return result;
}
}
注:这种情况下,需要自己配置一个数据源!数据源是应用程序连接数据库的配置,它包含了连接数据库所需的信息,比如数据库的URL、用户名、密码等。
三.Spring声明式事务
声明式事务和编程式事务不一样,更为方便简单,只需要在方法前加上一个@Transactional 注解就可以实现了,不需要手动编程开启事务和提交事务,进入方法,自动开启事务,执行结束自动提交事务,有异常,自动回滚!
代码示例:
@RequestMapping("/save")
@Transactional
public Object save(User user) {
int result = userService.save(user);
return result;
}
3.1@Transactional
@Transactional 可以⽤来修饰⽅法或类:
- 修饰⽅法时:需要注意只能应⽤到 public ⽅法上,否则不⽣效。推荐此种⽤法。
- 修饰类时:表明该注解对该类中所有的 public ⽅法都⽣效。
参数设置:
参数 | 作用 |
value | 应用的事务管理器的名称 |
transactionManger | 事务管理器的 Bean 名称 |
propagation | 事务的传播行为 |
isolation | 隔离级别 |
timeout | 超时时间 |
readOnly | 只读特性 |
rollbackFor | 指定哪些异常会触发事务回滚 |
noRollbackFor | 指定哪些异常不会触发回滚 |
详细解释:
-
propagation(传播行为):
REQUIRED
:默认值,如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。REQUIRES_NEW
:每次都创建一个新的事务,如果当前存在事务,则挂起当前事务。SUPPORTS
:支持当前事务,如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式执行。NOT_SUPPORTED
:以非事务方式执行操作,如果当前存在事务,则挂起该事务。NEVER
:以非事务方式执行,如果当前存在事务,则抛出异常。MANDATORY
:当前必须存在事务,如果不存在则抛出异常。
-
isolation(隔离级别):
DEFAULT
:使用数据库默认的隔离级别。READ_UNCOMMITTED
:允许读取未提交的数据变更。READ_COMMITTED
:只能读取已提交的数据变更。REPEATABLE_READ
:确保在同一个事务中多次读取数据时,返回的结果是一致的。SERIALIZABLE
:最高的隔离级别,确保在同一个事务中多次读取数据时,结果始终一致。
-
timeout(超时时间):
- 指定事务的超时时间,单位为秒。如果事务在指定的时间内没有完成,则会被强制回滚。
-
readOnly(只读):
- 指定事务是否为只读事务。如果设置为 true,则表示该事务只读取数据,不进行写入操作,可以优化事务性能。
代码如下:
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Isolation;
@Transactional(
//如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
propagation = Propagation.REQUIRED,
//即只能读取已提交的数据变更
isolation = Isolation.READ_COMMITTED,
//事务超时时间为 60 秒,在指定的时间内事务没有完成,则会被强制回滚。
timeout = 60,
//只读事务,即只读取数据,不进行写入操作
readOnly = true,
//遇到 SQLException 和自定义的 MyException 异常时会触发事务回滚
rollbackFor = {SQLException.class, MyException.class},
//表示遇到 IOException 异常时不会触发事务回滚。
noRollbackFor = {IOException.class})
public void exampleTransactionalMethod() {
// 方法体
}
3.2工作原理
@Transactional 是基于 AOP 实现的,AOP ⼜是使⽤动态代理实现的。如果⽬标对象实现了接⼝,默 认情况下会采⽤ JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理。
-
AOP 切面: Spring 使用 AOP 将事务逻辑切面化,将事务管理从业务逻辑中分离出来。事务管理逻辑以切面(Aspect)的形式应用到需要事务支持的方法上。
-
事务管理器: 在 Spring 中,事务管理器(Transaction Manager)负责管理事务的生命周期,包括事务的开始、提交或回滚。Spring 提供了多种事务管理器的实现,如基于 JDBC 的事务管理器、基于 Hibernate 的事务管理器、基于 JPA 的事务管理器等。
-
事务拦截器: 当方法被调用时,Spring 的事务拦截器会检查该方法是否有
@Transactional
注解。如果有,则会根据注解中的配置开启事务,否则方法将按照非事务方法执行。
俺觉得了解工作原理,没那么重要,会使用就行,不行你们去网上看看工作原理图有多长!
四.事务隔离级别
Spring事务隔离级别和Mysql事务隔离级别不同,MySQL隔离级别只有四种,而Spring有五种。
- Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
- Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
- Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
- Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别。
- Isolation.SERIALIZABLE:串⾏化,可以解决所有并发问题,但性能太低。
不详细讲解了,在MYSQL里讲解过了。
五.事务传播机制
5.1事务传播机制是什么?
事务隔离级别:为了保证多个并发事务执⾏的可控性的(稳定性的),事务的传播机制:指多个事务之间进行 交互时,控制事务如何在方法调用之间传播和管理的机制 。
图例:
问:什么时候会进行交互呢?
-
方法调用:一个事务方法调用另一个事务方法时,可能会发生事务的传播和交互。例如,一个服务方法可能调用另一个服务方法来执行一些相关的业务逻辑,这两个方法可能在不同的事务中执行,因此就会涉及事务的交互。
-
远程调用:在分布式系统中,不同服务之间可能通过远程调用来进行通信和交互。例如,一个服务可能调用另一个服务的API来获取或更新数据,这些调用可能涉及到事务的传播和管理。
-
消息队列:在消息驱动的系统中,不同组件之间通过消息队列进行通信。例如,一个服务将消息发送到消息队列,另一个服务从队列中接收并处理消息,在这个过程中可能涉及到事务的传播和交互。
-
并发操作:在多线程或多进程的环境中,不同的操作可能会并发地访问共享资源。如果这些操作涉及到事务管理,就可能会涉及到事务的交互和并发控制。
所以你的意思是指:事务传播机制就是为了以上情况而产生的吗?
答:是的,事务传播机制定义了一个事务方法在调用其他事务方法时如何处理事务的行为
当一个事务方法调用另一个事务方法时,有多种情况可能发生,如以下:
- 嵌套事务:在一个事务中调用另一个事务,内部事务被包含在外部事务中,外部事务的提交和回滚会影响内部事务。
- 传播新事务:在调用另一个事务时,创建一个新的事务来执行,不依赖于外部事务。
- 使用已有事务:在调用另一个事务时,使用当前已存在的事务来执行,如果不存在事务,则抛出异常。
5.2Spring事务传播机制
在Spring框架定义了七种不同的事务传播行为:
- REQUIRED:如果当前存在事务,则加入该事务,如果不存在事务,则创建一个新的事务。
- SUPPORTS:如果当前存在事务,则加入该事务,如果不存在事务,则以非事务方式执行。
- MANDATORY:必须存在当前事务,否则抛出异常。
- REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则挂起该事务。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则挂起该事务。
- NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
- NESTED:如果当前存在事务,则在嵌套事务内执行,嵌套事务受到外部事务的影响,但是可以单独提交或回滚。
或许你觉得很麻烦,所以俺在网上找了一个归类图:
六.Spring事务的失效情况
spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了!常见情况有如下几种:
- 类内部调用:A 类的 a1 方法没有标注 @Transactional,a2 方法标注 @Transactional,在 a1 方法调用 a2。(ps:如果是A 类的 a1 方法标注 @Transactional,a2 方法没有标注@Transactional,在 a1 里面调用 a2,则生效)
- 方法不是public的:@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可 以开启 AspectJ 代理模式。
-
异常不匹配:@Transactional 未设置 rollbackFor 属性(抛出的异常没有被定义,默认为RuntimeException),而方法返回的是其它 Exception 等异常。
-
多线程情况:父线程调用子线程,父(子)线程抛出异常,子(父)线程不抛出异常:父(子)线程抛出线程,事务回滚,因为子(父)线程是独立存在,和父(子)线程不在同一个事务中,所以子(父)线程的修改并不会被回滚
-
数据库不支持事务。
-
没有被spring管理。
总结
虽然已经明白了Spring事务的知识,但是我这边需要提一个坑,面试的坑:
一个方法使用@Transactional
开启了事务,方法的业务中修改了A表,接着方法又调用异步服务修改A表,则会发生死锁问题。
原因:事务先给A表上了表锁,紧接着执行的异步方法是新开的线程,无法加入到主线程的事务中,所以需要释放了A表的锁才能够修改A表,而因为整个方法未执行完,事务未提交,所以就释放不了锁,最后造成死锁问题。
解决方案:不使用@Transactional,
自己编写事务,自己提交,然后开启异步服务。