1. Spring @Transactional
概念:用户的一系列数据库操作,增删改查,这些操作可视为一个完整的逻辑处理工作单元,要么全部执行,要么全部不执行,是不可分割的工作单元。
1.1. Spring事务
- 编程式事务:类似于JDBC编程实现事务管理。管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,Spring推荐使用TransactionTemplate
- 声明式事务:管理建立在 AOP 之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务
- @Transactional注解:只能标注共有方法,可以加在方法以及类上,类上增加的整个类的所有公共方法都会支持事务。
- rollbackFor,遇到异常即回滚
- noRollbackFor,遇到指定异常不回滚
- timeout单位为秒,超时属性,事务在强制回滚之前可以保持多久,可以防止长期运行的事务占用资源
- readOnly,只读属性,表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务
1.2. Spring事务需要解决的问题
- serviceA方法调用了serviceB方法,两个方法都有事务,这个时候serviceB方法异常,是serviceB方法提交,还是两个一起回滚
- serviceA方法调用了serviceB方法,但是只有serviceA方法有事务,是否把serviceB也加入serviceA的事务,如果serviceB异常,是否回滚serviceA
- serviceA方法调用了serviceB方法,两者都有事务,serviceB方法已经正常执行完,但serviceA异常,是否需要回滚serviceB
1.3. 传播机制--7种事务传播机制
spring是用AOP来代理事务控制,是针对接口或类的,所以同一个service类中两个方法的调用,传播机制是不生效的。 原因:在spring中,当一个方法开启事务时,spring创建这个方法的类的bean对象,则创建该对象的代理对象。spring中调用bean对象的方法才会去判断方法上的注解。在代理bean对象中,一个方法调用本身的另一个方法,实则调用的代理对象的原始对象(不属于 spring bean)的方法,调用方法时不会去判断方法上的注解。这就是传播机制不生效的原因 解决:获取到当前service的代理类即可实现调用自己类的方法:自身类注入自己;AopContext.currentProxy来获取,但是此方法需要再启动类开启exposeProxy注释(@EnableAspectJAutoProxy( exposeProxy = true))
- PROPAGATION_REQUIRED
- spring的默认事务传播类型required:如果当前没有事务,则新建事务;
- 如果已经存在事务,则加入当前事务,合并成一个事务
- REQUIRES_NEW
- 新建事务,如果存在当前事务,则把当前事务挂起;
- 这个方法独立事务,不受调用者影响,调用者异常也不会影响当前事务提交
- NESTED
- 当前没有事务,会新建事务
- 有事务,会作为父级事务的一个子事务,方法结束后并没有提交,等父事务提交它才提交
- 如果它异常,父级可以捕获它的异常而不进行回滚,正常提交
- 如果父级异常,它必然回滚
- SUPPORTS
- 如果当前存在事务,就加入当前事务
- 如果不存在事务,则已无事务方式运行,和不写没区别
- NOT_SUPPORTED
- 非事务运行
- 如果当前存在事务,将当前事务挂起
- MANDATORY
- 如果当前有事务,则运行在当前事务中
- 如果当前没有事务,则抛出异常,即父方法必须有事务
- NEVER
- 以非事务方法运行,如果当前有事务即抛异常;不允许父方法有事务
1.4. Spring事务失效的11种场景
- 访问权限问题,spring要求被代理方法必须是public的。spring源码种,如果目标方法不是public,则TransactionAttribute返回null,不支持事务
- 方法用final修饰,也会导致失效,因为spring事务底层是aop,用了jdk的动态代理或者cglib的动态代理,会生成代理类,在代理类中实现事务功能(static修饰同样失效)
- 直接调用内部方法,解决方案应该注入自己调用或者使用AopContext.currentProxy来获取
- 未被Spring管理,当然无法使用spring事务
- 多线程调用,重新new一个线程调用带事务的方法,因为线程不同,获取到数据库连接不一样,从而是两个不同的事务。spring事务时通过数据库连接实现的,当前线程保存了一个map,key是数据源,value是数据库连接
- 表不支持事务,如myisam引擎
- 事务没有开启,如果springboot项目,事务默认是开启的,但spring项目,需要xml配置
- 事务传播特性,只有PROPAGATION_REQUIRED、REQUIRES_NEW、NESTED这三种才会创建新事务
- try...catch自己捕获了异常,导致事务不回滚
- 手动抛了spring事务不支持的异常,也不会回滚。spring事务,默认情况下只回滚RuntimeException运行时异常和Error错误,对于普通的非运行时异常,不会回滚
- 指定了rollbackFor异常回滚,但是并不是报的此类异常,也不会捕获回滚