文章目录
Spring 实现事务管理有如下两种方式:
- 编程式事务管理:
将事务管理代码嵌入到业务方法中来控制事务的提交和回滚,在编程式管理事务中,必须在每个事务操作中包含额外的事务管理代码。 - 声明式事务管理(推荐):
大多数情况下比编程式事务管理更好用,它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理,Spring声明式事务管理建立在AOP基础之上,是一个典型的横切关注点,通过环绕增强来实现,其原理是对方法前后进行拦截,然后在目标方法开始之前创建或加入一个事务,在执行完毕之后根据执行情况提交或回滚事务,其模型如下:public Object around(ProceedingJoinPoint joinPoint) throws Throwable { try { //开启事务 return joinPoint.proceed(); //提交事务 } catch (Throwable e) { //回滚事务 throw e; }finally { //释放资源 } }
一、如何实现声明式事务
- 添加spring-aspects-4.3.10.RELEASE.jar包
- 在Spring配置文件中添加如下配置:
<!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 启用事务注解 --> <tx:annotation-driven transaction-manager="transactionManager"/>
- 在Service层public方法上添加事务注解——@Transactional
注意:
- 一个类含有@Transactional注解修饰的方法,则Spring框架自动为该类创建代理对象,默认使用JDK创建代理对象,可以通过添加<aop:aspectj-autoproxy proxy-target-class=“true”/>使用CGLib创建代理对象,此时需要添加aspectjweaver-x.x.x.jar包。
- 不能在protected、默认或者private的方法上使用@Transactional注解,否则无效。
二、@Transactional注解属性
1. rollbackFor和rollbackForClassName
作用: 指定对哪些异常回滚事务
默认情况下,如果在事务中抛出了运行时异常(继承自RuntimeException异常类),则回滚事务;如果没有抛出任何异常,或者抛出了检查时异常,则依然提交事务。这种处理方式是大多数开发者希望的处理方式,也是 EJB 中的默认处理方式;但可以根据需要人为控制事务在抛出某些运行时异常时仍然提交事务,或者在抛出某些检查时异常时回滚事务。
- 示例1
书籍表中有50本书籍,每本书10元,一个人钱包有1元,欲买50本,则该行代码抛出MoneyException异常,但由于该异常为运行时异常,所以回滚事务,即下图“bookDao.update(bookId, count);”行代码执行失效!
- 示例2
书籍表中有50本书籍,每本书10元,一个人钱包有1元,欲买50本,则该行代码抛出MoneyException异常,但由于该异常为检查时异常,所以依然提交事务,即下图中“bookDao.update(bookId, count);”行代码执行生效!
- 示例3
书籍表中有50本书籍,每本书10元,一个人钱包有1元,欲买50本,则该行代码抛出MoneyException异常,尽管该异常为检查时异常,但由于@Transactional注解中添加了rollbackFor=MoneyException.class,所以回滚事务,即下图中“bookDao.update(bookId, count);”行代码执行失效!
注意
上例红框中代码不能try-catch处理异常,否则即便@Transactional注解中添加了rollbackFor=MoneyException.class,事务也不会回滚,如下代码:
书籍表中有50本书籍,每本书10元,一个人钱包有1元,欲买50本,则该行代码抛出MoneyException异常,尽管该异常为检查时异常,且@Transactional注解中添加了rollbackFor=MoneyException.class,但由于红框代码已经通过try-catch处理了异常,所以事务不回滚,即左边图片“bookDao.update(bookId, count);”行代码执行生效!
2. noRollbackFor和noRollbackForClassName
作用: 指定对哪些异常不回滚事务。
3. readOnly
作用:事务只读,指对事务性资源进行只读操作。
所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。由于只读的优化措施是在一个事务启动时由后端数据库实施的, 因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、 ROPAGATION_NESTED)的方法来说,将事务声明为只读才有意义。
示例:
@Transactional注解中添加了readOnly=true,但@Transactional注解修饰的方法涉及数据的修改,因此抛出如下异常:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
4. timeout
作用: 设置一个事务所允许执行的最长时长(单位:秒),如果超过该时长且事务还没有完成,则自动回滚事务且出现org.springframework.transaction.TransactionTimedOutException异常。
示例:
Thread.sleep(4000)会使得当前事务4秒之后结束,该时长超出了所允许的最长时长,因此事务自动回滚,书籍表库存递减操作无效,程序出现org.springframework.transaction.TransactionTimedOutException异常!
注意:
- 事务的开始往往会发生数据库的表锁或者被数据库优化为行锁,如果允许时间过长,那么这些数据会一直被锁定,最终影响系统的并发性,因此可以给这些事务设置超时时间以规避该问题。
- 由于超时是在一个事务启动的时候开始的,因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、ROPAGATION_NESTED)的方法来说,声明事务超时才有意义。
5. propagation
作用: 指定事务传播行为,一个事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继承在现有事务中运行,也可能开启一个新事物,并在自己的事务中运行。
Spring定义了如下7种事务传播行为:
-
REQUIRED:默认值,如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行,例子参见《事务传播行为之REQUIRED.docx》;
(具体例子参见REQUIRED事务传播行为示例) -
REQUIRES_NEW:当前方法必须启动新事务,并在它自己的事务内运行,如果有事务在运行,则把当前事务挂起,直到新的事务提交或者回滚才恢复执行,例子参见《事务传播行为之REQUIRES_NEW.docx》;
(具体例子参见REQUIRES_NEW事务传播行为示例) -
SUPPORTS:如果有事务在运行,当前的方法就在这个事务内运行,否则以非事务的方式运行;
-
NOT_SUPPORTED:当前的方法不应该运行在事务中,如果有运行的事务,则将它挂起;
-
NEVER:当前方法不应该运行在事务中,否则将抛出异常;
-
MANDATORY(mandatory [ˈmændətɔːri] adj.强制的):当前方法必须运行在事务内部,否则将抛出异常;
-
NESTED(nest [nest] v.嵌套):如果有事务在运行,当前的方法在这个事务的嵌套事务内运行,否则就启动一个新的事务,并在它自己的事务内运行,此时等价于REQUIRED。注意:对于NESTED内层事务而言,内层事务独立于外层事务,可以独立递交或者回滚,如果内层事务抛出的是运行异常,外层事务进行回滚,内层事务也会进行回滚。
6. isolation
作用: 指定事务隔离级别
Spring定义了如下5种事务隔离级别:(具体说明参见事务隔离级别)
- DEFAULT:默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常为READ_COMMITTED。
- READ_UNCOMMITTED:表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别可能出现脏读、不可重复读或幻读,因此很少使用该隔离级别。
- READ_COMMITTED:表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,但可能出现不可重复读或幻读,这也是大多数情况下的推荐值。
- REPEATABLE_READ:表示一个事务在整个过程中可以多次重复执行某个查询,且每次返回的记录都相同,除非数据被当前事务自生修改。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读,但可能出现幻读。
- SERIALIZABLE:表示所有的事务依次逐个执行,事务之间互不干扰,该级别可以防止脏读、不可重复读和幻读,但是这将严重影响程序的性能,因此通常情况下也不会用到该级别。
事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持:
- Oracle 支持READ_COMMITED和SERIALIZABLE两种事务隔离级别,默认为READ_COMMITED;
- MySQL 支持READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ和SERIALIZABLE四种事务隔离级别,默认为:REPEATABLE_READ。