事务是逻辑上的一组操作,要么都执行,要么都不执行。
Spring 对事务的支持取决于数据库 ,如果是使用Mysql的myisam引擎,那么就是从根本上就不支持事务。Spring事务的本质就是数据库对事务的支持,使用JDBC的事务管理机制,利用java.sql.Connection对象完成对事务的提交。
未使用Spring框架前,Java中事务实现示例代码如下:
import java.sql.Connection;import java.sql.DriverManager;public class DemoApplication {public static void main(String[] args) {// 1.获取连接Connection conn = DriverManager.getConnection();try {// 2.将自动提交设置为falseconn.setAutoCommit(false);/*-----------------*/// 3.执行一到多个CRUD操作/*-----------------*/// 4.1手动提交conn.commit();} catch (Exception e) {// 4.2一旦其中一个操作出错都将回滚,所有操作都不成功conn.rollback();} finally {// 5.关闭连接conn.colse();}}}
Spring框架则提供统一的事务抽象,无论是JTA、JDBC、Hibernate/JPA、Mybatis/Mybatis-Plus,Spring都使用统一的编程模型,使得应用程序可以很容易地在不同的事务框架之间进行切换。
Spring事务管理的核心接口是PlatformTransactionManager。接口PlatformTransactionManager定义事务操作的行为,PlatformTransactionManager依赖TransactionDefinition和TransactionStatus接口。
// 设置事务的传播行为以及隔离级别@Transactional(propagation = Propagation.NESTED, isolation = Isolation.READ_COMMITTED)public int upsert() {....}
注解属性
@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface Transactional {@AliasFor("transactionManager")String value() default "";@AliasFor("value")String transactionManager() default "";Propagation propagation() default Propagation.REQUIRED;Isolation isolation() default Isolation.DEFAULT;int timeout() default -1;boolean readOnly() default false;Class<? extends Throwable>[] rollbackFor() default {};String[] rollbackForClassName() default {};Class<? extends Throwable>[] noRollbackFor() default {};String[] noRollbackForClassName() default {};}
| 字段名 | 类型 | 含义 |
|---|---|---|
| value | String | value 主要用来指定不同的事务管理器,满足在同一个系统中,存在不同的事务管理器。如果在 Spring 中,配置了多个数据源声明了多个事务管理器,可以通过该参数来进行指定事务管理器 |
| propagation | enum: Propagation | 可选的事务传播行为设置 |
| isolation | enum: Isolation | 可选的事务隔离级别设置 |
| readOnly | boolean | 读写或只读事务,默认读写 |
| timeout | int (in seconds granularity) | 事务超时时间设置 |
| rollbackFor | Class对象数组,必须继承自Throwable | 导致事务回滚的异常类数组 |
| rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异常类名字数组 |
| noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 |
| noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异常类名字数组 |
事务传播机制 【propagation】
public interface TransactionDefinition {/*** 默认选择,如果当前方法没有事务就新建一个事务;如果已存在一个事务,就加入到这个事务中*/int PROPAGATION_REQUIRED = 0;/*** 如果当前方法存在事务,则加入到这个事务中,如果当前不存在事务,以非事务方式执行*/int PROPAGATION_SUPPORTS = 1;/*** 被修饰的方法必须在事务中,否则抛出异常*/int PROPAGATION_MANDATORY = 2;/*** 必须运行在自己的事务中,如果当前方法已经在事务中,挂起当前事务,新建一个*/int PROPAGATION_REQUIRES_NEW = 3;/*** 被修饰的方法不应该运行在事务中,如果存在当前事务,则该事务在方法运行期间被挂起*/int PROPAGATION_NOT_SUPPORTED = 4;/*** 被修饰的方法必须不在事务中,否则抛出异常*/int PROPAGATION_NEVER = 5;/*** 当前方法支持嵌套事务,嵌套事务与当前事务进行单独提交和回滚,如果不存在当前事务,则行为和Required一样*/int PROPAGATION_NESTED = 6;}
事务隔离级别【isolation】
同样是在TransctionDefinition类中定义的。
public interface TransactionDefinition {/*** 使用数据库本身的隔离级别,MySQL(RR)、Oracle(RC)*/int ISOLATION_DEFAULT = -1;/*** 读未提交*/int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;/*** 读已提交*/int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;/*** 可重复度*/int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;/*** 串行化*/int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;}
只读模式【readOnly】
readOnly=true的意义是什么?为什么我一个数据查询操作还要启用事务支持呢?
目的仅是优化数据库执行,mysql默认对每一个连接都启用autocommit模式,该模式下每发送一条sql执行完成后就自动提交事务,并开启一个事务。当readOnly=true时,所有的sql查询就都在一个事务里完成了。
@Transactional 事务注解原理
@Transactional 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。
@Overridepublic AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation.");}if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {return new JdkDynamicAopProxy(config);}return new ObjenesisCglibAopProxy(config);}else {return new JdkDynamicAopProxy(config);}}
@Transcational事务失效
- 数据库引擎不支持事务
- 没有被Spring管理
- 方法不是public
- 自调用
- 数据源没有配置事务管理器
- 传播机制设置成不支持事务
(@Transactional(propagation = Propagation.NOT_SUPPORTED)) - 异常不抛出,内部捕获
- 配置的回滚异常类型和抛出的异常类型不匹配
2. 没有被Spring管理
// 把@Service注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了。// @Servicepublic class OrderServiceImpl implements OrderService {@Transactionalpublic void updateOrder(Order order) {// update order}}
3. 方法不是 public 的
源码解析:https://blog.csdn.net/CPLASF_/article/details/115365665
4. Spring AOP 自调用问题
原因
Spring中会产生两个类,原类A和代理类AA,各自有方法M1、M2和PM1、PM2,假设(P)M2上有注解,方法增强,(P)M1调用(P)M2。当外部直接调用代理类AA的PM2时候,是方法增强的(preHandler-M2-postHandler),但如果外部调用AA的PM1,实际是调用M1方法,M1在AA中并没有增强,只是直接调用A的M1方法,M1再调用M2方法(原类A方法的M2并没有被增强),所以没有走到增强逻辑,所以事务失效。
示例
若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法,有@Transactional 注解的方法的事务会失效。
这是由于Spring AOP代理的原因造成的,因为只有当 @Transactional 注解的方法在类以外被调用的时候,Spring 事务管理才生效。
MyService 类中的method1()调用method2()就会导致method2()的事务失效。
@Servicepublic class MyService {private void method1() {method2();//......}@Transactionalpublic void method2() {//......}}
解决办法就是避免同一类中自调用或者使用 AspectJ 取代 Spring AOP 代理。
8. 配置的回滚异常类型和抛出的异常类型不匹配
// 这样事务也是不生效的,因为默认回滚的是:RuntimeException// 如果你想触发其他异常的回滚,需要在注解上配置一下 @Transactional(rollbackFor = Exception.class)@Servicepublic class OrderServiceImpl implements OrderService {@Transactionalpublic void updateOrder(Order order) {try {// update order} catch {throw new Exception("更新错误");}}}
