1. 声明式事务和编程式事务
编程式事务需要在代码中加入处理事务的逻辑,需要显式调用如
beginTransaction();
...
commmit();
...
rollback();
...
connection.autoCommit(false);
...
connection.commit();
声明式事务需要在方法外部添加注解,或者在配置文件中定义,将事务管理的部分从业务代码中抽取出来以声明的方式进行事务管理。
而Spring的AOP将事务管理作为一个横切点进行模块化,很好的实现了声明式事务
2. @Transaction事务注解
- 写在方法上,只在当前方法有效
- 写在类上,当前类所有的方法都会使用事务
- 如果类和方法同时存在@Transaction,以方法层为准
- 标记在方法层上,事务管理控制力度更细,建议放在业务逻辑层,因为业务逻辑层存在嵌套调用
3. 事务属性
3.1 isolation - 事务隔离级别
用于处理事务并发所产生的问题
事务隔离级别 | 脏读 | 可重复读 | 幻读 |
---|---|---|---|
读未提交 | √ | √ | √ |
读已提交 | × | √ | √ |
可重复读 | × | × | √ |
串行化 | × | × | × |
-
读未提交(READ_UNCOMMITTED)
不做任何事务隔离,所有并发问题都会有,除非只做读取,不做写入。
-
读已提交(READ_COMMITTED)
不允许读取未提交的数据,可以避免脏读问题,但是不可重复读
-
可重复读(REPEATABLE_READ)
与读已提交相对应,会在事务中对查询的数据添加共享锁,修改的数据添加排他锁(行锁),这样其他事务不能对该事务查询和修改的数据添加排它锁进行修改,保证数据一致。
但是没办法限制其他事务进行插入和删除操作,会出现幻读问题。 -
序列化(SERIALIZABLE)
会对表上锁,避免其他事务对该表所有操作(新增,修改,删除),避免了脏读,不可重复读,幻读等问题。但由于事务的堵塞,性能非常受影响。
并发安全:SERIALIZABLE > REPEATABLE_READ > READ_COMMITTED > READ_UNCOMMITTED
运行效率:READ_UNCOMMITTED > READ_COMMITTED > REPEATABLE_READ > SERIALIZABLE
当不设置隔离级别,默认使用数据库隔离级别。
3.2 propagation - 事务传播行为
事务传播类型 | 外部不存在事务 | 外部存在事务 | |
---|---|---|---|
REQUIRED(默认) | 开启新的事务 | 融合到外部事务 | 适用增删改查 |
SUPPORTS | 不开启新的事务 | 融合到外部事务 | 适用查询 |
REQUIRES_NEW | 开启新的事务 | 挂起外部事务,开启新事务 | 适用外部事务和内部事务不相关 |
NOT_SUPPORTED | 不开启新的事务 | 挂起外部事务 | 不常用 |
MANDATORY | 抛出异常 | 融合到外部事务 | 不常用 |
NEWSTED | 开启新的事务 | 开启新事务为外部事务的子事务 | |
NEVER | 不开启新的事务 | 抛出异常 | 不常用 |
-
REQUIRED
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { methodB(); // do something } @Transactional(propagation = Propagation.REQUIRED) public void methodB() { // do something }
当单独调用methodB(),由于当前上下文没有事务,会创建新事务
单独调用methodA(),由于当前上下文没有事务,会创建新事务;执行到methodB()时,上下文已有事务,就会融入当前事务。 -
SUPPORTS
如果存在事务,则支持当前事务。如果没有事务则非事务执行。
@Transactional(propagation = Propagation.REQUIRED) public void methodA() { methodB(); // do something } // 事务属性为SUPPORTS @Transactional(propagation = Propagation.SUPPORTS) public void methodB() { // do something }
单独调用methodB()时,非事务的执行。
调用methodA()时,methodB()加入methodA()的事务执行。 -
REQUIRES_NEW
会开启一个新的事务,如果当前已经存在事务,则会把当前事务挂起。
需要使用 JtaTransactionManager作为事务管理器。@Transactional(propagation = Propagation.REQUIRED) public void methodA() { doSomeThingA(); methodB(); doSomeThingB(); // do something else } // 事务属性为REQUIRES_NEW @Transactional(propagation = Propagation.REQUIRES_NEW) public void methodB() { // do something }
当调用methodA()时相当于
{ TransactionManager tm = null; try{ //获得一个JTA事务管理器 tm = getTransactionManager(); tm.begin();//开启一个新的事务 Transaction ts1 = tm.getTransaction(); doSomeThing(); tm.suspend();//挂起当前事务 try{ tm.begin();//重新开启第二个事务 Transaction ts2 = tm.getTransaction(); methodB(); ts2.commit();//提交第二个事务 } Catch(RunTimeException ex) { ts2.rollback();//回滚第二个事务 } finally { //释放资源 } //methodB执行完后,恢复第一个事务 tm.resume(ts1); doSomeThingB(); ts1.commit();//提交第一个事务 } catch(RunTimeException ex) { ts1.rollback();//回滚第一个事务 } finally { //释放资源 } }
在这里,我们把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。ts2是否成功并不依赖于 ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了 methodB之外的其它代码导致的结果却被回滚了
-
NEWSTED
会开启一个新事务,如果外部已存在事务,则为外部事务嵌套的子事务。外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务。
@Transactional(propagation = Propagation.REQUIRED) methodA(){ doSomeThingA(); methodB(); doSomeThingB(); } @Transactional(propagation = Propagation.NEWSTED) methodB(){ …… }
单独调用methodA方法是,相当于
{ Connection con = null; Savepoint savepoint = null; try{ con = getConnection(); con.setAutoCommit(false); doSomeThingA(); savepoint = con2.setSavepoint(); try{ methodB(); } catch(RuntimeException ex) { con.rollback(savepoint); } finally { //释放资源 } doSomeThingB(); con.commit(); } catch(RuntimeException ex) { con.rollback(); } finally { //释放资源 } }
当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。但是需要注意的是,这时的事务并没有进行提交,如果后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。
-
REQUIRED,REQUIRES_NEW,NESTED异同
NESTED和REQUIRED修饰的内部方法都属于外围方法事务,如果外围方法抛出异常,这两种方法的事务都会被回滚。但是REQUIRED是加入外围方法事务,所以和外围事务同属于一个事务,一旦REQUIRED事务抛出异常被回滚,外围方法事务也将被回滚。而NESTED是外围方法的子事务,有单独的保存点,所以NESTED方法抛出异常被回滚,不会影响到外围方法的事务。
NESTED和REQUIRES_NEW都可以做到内部方法事务回滚而不影响外围方法事务。但是因为NESTED是嵌套事务,所以外围方法回滚之后,作为外围方法事务的子事务也会被回滚。而REQUIRES_NEW是通过开启新的事务实现的,内部事务和外围事务是两个事务,外围事务回滚不会影响内部事务。
3.3 timeout - 超时属性
指定事务等待的最大时间(秒)
3.4 readOnly - 事务只读
只会设置在查询的业务方法中。
由于只读事务不存在数据的修改,因为数据库会对只读事务有一些相应的优化手段。
3.5 异常属性设置
- noRollbackFor - 异常不回滚
- rollbackFor - 异常回滚
- 默认对于RuntimeException 及其子类 采用的是回滚的策略;默认对于Exception 及其子类 采用的是提交的策略
4. 实战使用
- 如果当前业务方法是一组 增、改、删 可以这样设置事务 @Transactional
- 如果当前业务方法是一组 查询 可以这样设置事务 @Transactionl(readOnly=true)
- 如果当前业务方法是单个 查询 可以这样设置事务 @Transactionl(propagation=propagation.SUPPORTS ,readOnly=true)