一、声明式事务
声明式事务实现方式主要有2种,一种为通过使用Spring的
<tx:advice>
定义事务通知与AOP相关配置实现,另为一种通过@Transactional
实现事务管理实现
1.1 @Transactional方式
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务注解-->
<tx:annotation-driven transaction-manager="txManager"/>
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED)
,具体参数跟上面<tx:method>
中一样
Spring提供的@Transaction注解事务管理,内部同样是利用环绕通知TransactionInterceptor实现事务的开启及关闭。使用
@Transactional
注意点:
- 如果在接口、实现类或方法上都指定了@Transactional 注解,则优先级顺序为方法>实现类>接口;
- 建议只在实现类或实现类的方法上使用@Transactional,而不要在接口上使用,这是因为如果使用JDK代理机制(基于接口的代理)是没问题;而使用使用CGLIB代理(继承)机制时就会遇到问题,因为其使用基于类的代理而不是接口,这是因为接口上的@Transactional注解是“不能继承的”;
1.2 <tx:advice>方式
<!--
<tx:advice>定义事务通知,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,
并通过<tx:attributes>指定具体需要拦截的方法
<tx:method>拦截方法,其中参数有:
name:方法名称,将匹配的方法注入事务管理,可用通配符
propagation:事务传播行为,
isolation:事务隔离级别定义;默认为“DEFAULT”
timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
read-only:事务只读设置,默认为false,表示不是只读;
rollback-for:需要触发回滚的异常定义,可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚;
no-rollback-for:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割;
-->
<tx:advice id="advice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 拦截save开头的方法,事务传播行为为:REQUIRED:必须要有事务, 如果没有就在上下文创建一个 -->
<tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="" read-only="false" no-rollback-for="" rollback-for=""/>
<!-- 支持,如果有就有,没有就没有 -->
<tx:method name="*" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!-- 定义切入点,expression为切人点表达式,如下是指定impl包下的所有方法,具体以自身实际要求自定义 -->
<aop:config>
<aop:pointcut expression="execution(* com.kaizhi.*.service.impl.*.*(..))" id="pointcut"/>
<!--<aop:advisor>定义切入点,与通知,把tx与aop的配置关联,才是完整的声明事务配置 -->
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>
关于事务传播行为与隔离级别,可参考 http://blog.csdn.net/liaohaojian/article/details/68488150
注意点:
- 事务回滚异常只能为RuntimeException异常,而Checked Exception异常不回滚,捕获异常不抛出也不会回滚,但可以强制事务回滚:TransactionAspectSupport.currentTransactionStatus().isRollbackOnly();
- 解决“自我调用”而导致的不能设置正确的事务属性问题,可参考http://www.iteye.com/topic/1122740
二、事务相关概念
2.1 事务特性
事务Transaction,它是一些列严密操作动作,要么都操作完成,要么都回滚撤销。
事务具备ACID四种特性,ACID是Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)和Durability(持久性)的英文缩写。
- (1)原子性(Atomicity):事务最基本的操作单元,要么全部成功,要么全部失败,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
- (2)一致性(Consistency)
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。- (3)隔离性(Isolation)
指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。- (4)持久性(Durability)
指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
2.2 事务的隔离级别
事务的隔离级别
- (1)read uncommited:是最低的事务隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。
- (2)read commited:保证一个事物提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。
- (3)repeatable read:这种事务隔离级别可以防止脏读,不可重复读。但是可能会出现幻象读。它除了保证一个事务不能被另外一个事务读取未提交的数据之外还避免了以下情况产生(不可重复读)。
- (4)serializable:这是花费最高代价但最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读之外,还避免了幻象读。
- (5)isolation_default:用底层数据库的默认隔离级别,数据库管理员设置什么就是什么。
丢失更新、脏读、幻读、不可重复读、概念说明
- 丢失更新:两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,这是由于没有加锁造成的。
- 脏读:指当一个事务正字访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。
- 幻读:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)。
- 不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事物内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。
各类隔离级别和产生的现象如下表所示
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | 可能 | 可能 | 可能 |
读已提交 | 不可能 | 可能 | 可能 |
可重复读 | 不可能 | 不可能 | 可能 |
可序列化 | 不可能 | 不可能 | 不可能 |
选取隔离级别的出发点在于两点:性能和数据一致性。数据库的隔离级别从读未提交到可序列化,系统性能直线下降。在实际工作中,注解@Transactional隔离级别的默认值为Isolation.DEFAULT,其含义是默认的,随数据库默认值的变化而变化。因为对不同的数据库而言,隔离级别的支持是不一样的。在MySQL中可支持4种隔离级别,而默认的是可重复读的隔离级别。而在Oracle中只支持读已提交和可序列化两种隔离级别,默认值是读已提交。
幻读与不可重复读的区别:幻读的重点在于插入与删除,即第二次查询会发现比第一次查询数据变少或者变多了,以至于给人一种幻象一样,而不可重复读重点在于修改,即第二次查询会发现查询结果比第一次查询结果不一致,即第一次结果已经不可重现了。
数据库隔离级别越高,执行代价越高,并发执行能力越差,因此在实际项目开发使用时要综合考虑,为了考虑并发性能一般使用提交读隔离级别,它能避免丢失更新和脏读,尽管不可重复读和幻读不能避免,但可以在可能出现的场合使用悲观锁或乐观锁来解决这些问题。
悲观锁与乐观锁可参考:http://blog.csdn.net/liaohaojian/article/details/62416972
2.3 事务的传播特性
事务传播行为就是多个事务方法调用时,如何定义方法间事务的传播。Spring定义了7中传播行为:
- (1)propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是Spring默认的选择。
- (2)propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
- (3)propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
- (4)propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
- (5)propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- (6)propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
- (7)propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。
传播方式的总结:事务7种传播方式:假如a方法调b方法 传播行为的注解是修饰在b方法上的。
- requered:以b方法为准方式: a方法无事务,b方法有,b用事务单独执行,a有,b也有,则b加入到a事务一起执行。
- supports:以a为准的方式,ab都有,加入a,a没有则b也不用事务。
- mandatory:以a为准,要求a必须有事务,ab都有时,b加入a,如果a没有事务抛出异常。
- requered new:a,b各自处理各自的事务,互不干涉,各自回滚自己的事务。
- not supported: b一定以非事务方式执行,a自己用自己的事务。
- never:不允许a用事务,如果a有事务就抛异常。
- nested:嵌套事务,父回滚,子也回滚,子回滚,父可以扑捉异常吃掉不回滚。
三、典型错误
3.1 错误捕获异常
@Transactional注解只能在抛出RuntimeException或者Error时才会触发事务的回滚,常见的非RuntimeException是不会触发事务的回滚的。但是我们平时做业务处理时,需要捕获异常,所以可以手动抛出RuntimeException异常或者添加rollbackFor = Exception.class(也可以指定相应异常)
3.2 @Transactional自调用失效问题
注解@Transactional的底层实现是Spring AOP技术,而Spring AOP技术使用的是动态代理。这就意味着对于静态(static)方法和非public方法,注解@Transactional是失效的。还有一个更为隐秘的情况,而且在使用过程中极其容易犯错误的——自调用。所谓自调用就是一个类的一个方法去调用自身另一个方法的过程。
3.3 事务超时
数据库相关各种超时时间
Spring事务管理-超时时间
Transaction timed out: deadline问题
MySQL5.7问题排查工具show engine innodb status 和InnoDB监控器
各类数据库查询慢sql的方法
SHOW ENGINE INNODB STATUS;