1 理解事务
事务:在软件开发领域,全有或全无的操作被称为事务。事务允许我们将几个操作组合成一个要么全部发生要么全部不发生的工作单元。
事务的特性:
- 原子性:事务是由一个或多个活动所组成的一个工作单元。原子性确保事务中的所有操作全部发生或者全部不发生。
- 一致性:一旦事务完成(无论成功还是失败),系统必须确保它所建模的业务处于一致的状态。
- 隔离性:事务允许多个用户对相同的数据进行操作,每个用户的操作不会与其他用户纠缠在一起。因此,事务应该被彼此隔离,避免发生同步读写相同数据的问题。
- 持久性:一旦事务完成,事务的结果应该被持久化,这样就能从系统崩溃中恢复过来。
2 Spring声明式事务
Spring对声明式事务的支持是通过使用Spring AOP框架实现的,这是很自然的一件事,因为事务是在应用程序功能之上的系统级服务。我们可以将Spring事务想象成将方法“包装”上事务边界的切面。
Spring提供了3种方法来声明事务式边界。
- TranscationProxyFactoryBean的代理Bean来实现声明式事务
- Spring的tx命名空间
- @Transactional注解方式
尽管TranscationProxyFactoryBean仍然可以使用,但是它实际上已经本淘汰了(会导致冗长的Spring配置文件),所以本文不对其进行详细介绍。
2.1 在applicationContext.xml中定义事务(Spring AOP的方式)
首先在applicationContext.xml中增加tx命名空间:
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"
Spring提供的tx配置命名空间,可以极大的简化Spring中的声明式事务。
<!-- 事务管理器 --> <bean scope="singleton" id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 声明式容器事务管理 ,transaction-manager指定事务管理器为transactionManager --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="check*" read-only="true" propagation="REQUIRED" /> <tx:method name="create*" propagation="REQUIRED" /> <tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/> </tx:attributes> </tx:advice> <aop:config proxy-target-class="false"> <!-- 只对业务逻辑层实施事务 --> <aop:advisor pointcut="execution(* com.wdcloud.jyx.bj.service.*Service.*(..))" advice-ref="txAdvice" /> </aop:config>
tx:advice配置了事务的管理者是transactionManager。同时,事务属性定义在<tx:attributes>元素中,该元素包含多个<tx:method>元素,<tx:method>的name属性指定对应的方法。
<tx:method>有多个属性来帮助定义方法的事务策略(文章最后有详细解释):
- isolation:指定事务的隔离级别
- propagation:定义事务的传播规则
- read-only:指定事务是否为只读
- rollback-for:事务遇到指定异常应回滚而不提交
- no-rollback-for:事务遇到指定异常继续运行而不回滚
- timeout:定义事务的超时时间
aop:config指定了一个pointcut去引用上边的advice。
这样就通过AOP的拦截机制实现了事务。
2.2 通过注解@Transactional定义事务
<tx:advice>已经极大的简化了Spring声明式事务所需要的XML,但如果我们还想进一步简化,则可以使用注解的方式。
<!-- 事务管理器 --> <bean scope="singleton" id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 事务注解驱动,标注@Transactional的类和方法将具有事务性 --> <tx:annotation-driven transaction-manager="transactionManager"/>
<tx:annotation-driven>元素告诉Spring检查上下文中所有的Bean并查找使用@Transaction注解的Bean,无论注解是用在类级别上还是方法级别上。
<tx:annotation-driven>会自动为使用了@Transactional注解的Bean添加事务通知,事务的属性是通过@Transactional注解的参数来定义的。
public class TestService{
@Autowired private WdSolrService wdSolrService; public void AnnotationTest1(){ wdSolrService.deleteAll();//执行成功 int i = 1 / 0; // 抛出运行时异常 wdSolrService.addBatch(solrListAdd);//未执行 } @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void AnnotationTest2(){ wdSolrService.deleteAll();// 因后面的异常而回滚 int i = 1 / 0; // 抛出运行时异常 wdSolrService.addBatch(solrListAdd);//未执行 } }
注意:@Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能
2.1 事务属性
在Spring中,声明式事务通过传播行为、隔离级别、是否只读、事务超时和回滚规则5种事务属性进行定义。
2.1.1 传播行为
传播行为定义了方法是否要在事务中运行,或者何时要创建一个事务或何时使用已有的事务。
事务传播行为类型 |
说明 |
PROPAGATION_REQUIRED |
传播行为的默认值,如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS |
支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY |
使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW |
新建事务(当前方法必须运行在自己的事务中),如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED |
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER |
以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED |
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED一样。 |
2.1.2 隔离级别
隔离级别定义了一个事务可能受其他并发事务影响的程度。
当多个事务并发运行,经常会操作相同的数据来完成各自的任务。并发虽然是必须的,但可能会导致一下的问题。
- 脏读(事务修改数据没提交,另一个事物提前读取):脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
- 不可重复读 :不可重复读发生在一个事务执行相同的查询两次或者两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间更新了数据。
- 幻读 : 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的数据。
可以通过完全隔离事务来防止发生上述问题,但是完全隔离会导致性能问题,因为它通常会涉及锁定数据库中的记录。
考虑到完全隔离会导致性能问题,而且并不是所有的应用程序都需要完全隔离,所以就会有多种隔离级别。
隔离级别 | 含义 |
ISOLATION_DEFAULT | 使用数据库默认的事务隔离级别 |
ISOLATION_READ_UNCOMMITTED | 这是事务最低的隔离级别,允许读取尚未提交的数据。这种隔离级别会产生脏读,不可重复读或幻读 |
ISOLATION_READ_COMMITTED | 允许读取并发事务已经提交的数据,可以避免脏读,但是可能会出现不可重复读或幻读 |
ISOLATION_REPEATABLE_READ | 对同一字段的多次读取结果是一致的,除非数据是被本事务自己修改的。可以阻止脏读和不可重复读,但幻读仍有可能发生 |
ISOLATION_SERIALIZABLE | 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。可以防止脏读,不可重复读和幻读的发生。 |
2.1.3 是否只读
如果事务只对后端数据库进行读操作,数据库可以利用事务的只读特性来进行一些特定的优化。
因为只读优化是在事务启动的时候由数据库实施的,只有对那些具备启动一个新事物的传播行为(PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW以及PROPAGATION_NESTED)的方法来说,将事务声明为只读才有意义。
- 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
- 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持read-only="true"表示该事务为只读事务,比如上面说的多条查询的这种情况可以使用只读事务,由于只读事务不存在数据的修改,因此数据库将会为只读事务提供一些优化手段,例如Oracle对于只读事务,不启动回滚段,不记录回滚log。
在将事务设置成只读后,相当于将数据库设置成只读数据库,此时若要进行写的操作,会出现错误。
2.1.4 事务超时
假如一个事务的运行时间变得特别长,因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。我们可以通过设定事务超时,使事务在特定的数秒后自动回滚,而不是等待结束。
因为超时时钟会在事务开始时启动,只有对那些具备启动一个新事物的传播行为(PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW以及PROPAGATION_NESTED)的方法来说,事务超时才有意义。
2.1.5 回滚规则
回滚规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常的时候不会回滚。
我们可以通过设置回滚规则使事务在遇到检查型异常的时候也执行回滚操作。
3.解决Transactional注解不回滚
1、检查你方法是不是public的,如果应用在protected、private或者 package可见度的方法上,也不会报错,不过事务设置不会起作用。
2、你的异常类型是不是unchecked异常
如果我想check异常也想回滚怎么办,注解上面写明异常类型即可
@Transactional(rollbackFor=Exception.class)
类似的还有norollbackFor,自定义不回滚的异常
3、数据库引擎要支持事务,如果是MySQL,注意表要使用支持事务的引擎,比如innodb,如果是myisam,事务是不起作用的
4、是否开启了对注解的解析
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
5、spring是否扫描到你这个包,如下是扫描到org.test下面的包
<context:component-scan base-package="org.test" ></context:component-scan>
6、检查是不是同一个类中的方法调用(如a方法调用同一个类中的b方法)
7、异常是不是被你catch住了
参考:
http://blog.csdn.net/liuwei063608/article/details/7784800
http://www.cnblogs.com/qqzy168/p/3307284.html
http://czj4451.iteye.com/blog/2037759
https://www.cnblogs.com/hunrry/p/9183209.html
转载请注明出处