事务,是为了保障逻辑处理的原子性、一致性、隔离性、永久性。html
经过事务控制,能够避免由于逻辑处理失败而致使产生脏数据等等一系列的问题。java
事务有两个重要特性:spring
传播行为级别,定义的是事务的控制范围。通俗点说,执行到某段代码时,对已存在事务的不一样处理方式。数据库
Spring 对 JDBC 的事务隔离级别进行了补充和扩展,并提出了 7 种事务传播行为。express
PROPAGATION_REQUIRED,须要事务处理。有则使用,无则新建。这是 Spring 默认的事务传播行为。该级别的特性是,若是 Context 中已经存在事务,那么就将当前须要使用事务的代码加入到 Context 的事务中执行,若是当前 Context 中不存在事务,则新建一个事务执行代码。这个级别一般能知足大多数的业务场景。编程
PROPAGATION_SUPPORTS,支持事务处理。该级别的特性是,若是 Context 存在事务,则将代码加入到 Context 的事务中执行,若是 Context 中没有事务,则使用 非事务 的方式执行。session
PROPAGATION_MANDATORY,强制性要求事务。该级别的特性是,当要以事务的方式执行代码时,要求 Context 中必须已经存在事务,不然就会抛出异常!使用 MANDATORY 强制事务,能够有效地控制 “必须以事务执行的代码,却忘记给它加上事务控制” 这种状况的发生。举个简单的例子:有一个方法,对这个方法的要求是一旦被调用,该方法就必须包含在事务中才能正常执行,那么这个方法就适合设置为 PROPAGATION_MANDATORY 强制事务传播行为,从而在代码层面加以控制。并发
PROPAGATION_REQUIRES_NEW,每次都新建一个事务。该级别的特色是,当执行到一段须要事务的代码时,先判断 Context 中是否已经有事务存在,若是不存在,就新建一个事务;若是已经存在,就 suspend 挂起当前事务,而后建立一个新事务去执行,直到新事务执行完毕,才会恢复先前挂起的 Context 事务。分布式
PROPAGATION_NOT_SUPPORTED,不支持事务。该级别的特色是,若是发现当前 Context 中有事务存在,则挂起该事务,而后执行逻辑代码,执行完毕后,恢复先前挂起的 Context 事务。这个传播行为的事务,能够缩小事务处理过程的范围。举个简单例子,在一个事务中,须要调用一段非核心业务的逻辑操做 1000 次,若是将这段逻辑放在事务中,会致使该事务的范围变大、生命周期变长,为了不因事务范围扩大、周期变长而引起一些的事先没有考虑到的异常状况发生,能够将这段逻辑设置为 NOT_SUPPORTED 不支持事务传播行为。高并发
PROPAGATION_NEVER,对事务要求更严格,不能出现事务!该级别的特色是,设置了该级别的代码,在执行前一旦发现 Context 中有事务存在,就会抛出 Runtime 异常,强制中止执行,有我无他!
PROPAGATION_NESTED,嵌套事务。该级别的特色是,若是 Context 中存在事务 A,就将当前代码对应的事务 B 加入到 事务 A 内部,嵌套执行;若是 Context 中不存在事务,则新建事务执行代码。换句话说,事务 A 与事务 B 之间是父子关系,A 是父,B 是子。理解嵌套事务的关键点是:save point。
父、子事务嵌套、save point 的说明:
我在网上看到有一篇文章,采用代码的方式来解释事务传播行为级别,代码方式很清晰,一看就明白了。
首先准备以下两个 Service:
class ServiceA { void methodA() { ServiceB.methodB(); } } class ServiceB { void methodB() { } }
ServiceB.methodB()
的传播行为定义为 PROPAGATION_REQUIRED
, 那么在执行 ServiceA.methodA()
的时候,若 ServiceA.methodA()
已经开启了事务,这时调用 ServiceB.methodB()
,ServiceB.methodB()
将会运行在 ServiceA.methodA()
的事务内部,而再也不开启新的事务。而假如 ServiceA.methodA()
运行的时候发现本身没有在事务中,就会为它分配一个新事务。这样,在 ServiceA.methodA()
或者在 ServiceB.methodB()
内的任何地方出现异常,事务都会被回滚。即便 ServiceB.methodB()
的事务已经被ServiceA.methodA()
在接下来的过程当中 fail 要回滚,ServiceB.methodB()
也会跟着一块儿回滚。ServiceA.methodA()
的传播行为设置为 PROPAGATION_REQUIRED
,ServiceB.methodB()
的传播行为为 PROPAGATION_REQUIRES_NEW
,那么当执行到 ServiceB.methodB()
的时候,ServiceA.methodA()
所在的事务就会挂起,而 ServiceB.methodB()
会起一个新的事务,等待 ServiceB.methodB()
的事务完成之后,A的事务才会继续执行。PROPAGATION_REQUIRED
与 PROPAGATION_REQUIRES_NEW
的事务区别在于事务的回滚程度。由于 ServiceB.methodB
是新起一个事务,那么就是存在两个不一样的事务。若是 ServiceB.methodB
已经提交,那么 ServiceA.methodA
失败回滚,ServiceB.methodB
是不会回滚的。若是 ServiceB.methodB
失败回滚,若是它抛出的异常被 ServiceA.methodA
捕获,ServiceA.methodA
事务仍然可能会提交。ServiceA.methodA
的事务传播行为是 PROPAGATION_REQUIRED
,而 ServiceB.methodB
的事务传播行为是 PROPAGATION_NOT_SUPPORTED
,那么当执行到 ServiceB.methodB
时,ServiceA.methodA
的事务挂起,而ServiceB.methodB
以非事务的状态运行完以后,再继续 ServiceA.methodA
的事务。ServiceA.methodA
的事务传播行为是 PROPAGATION_REQUIRED
, 而 ServiceB.methodB
的事务级别是 PROPAGATION_NEVER
,那么 ServiceB.methodB
执行时就会抛出异常。在读取数据库的过程当中,若是两个事务并发执行,那么多个事务彼此之间,会对数据产生什么样的影响呢?
这里就引出了事务的第二个特性:数据隔离级别。
数据隔离级别,定义的是事务在数据库端读写方面的控制范围。
数据隔离级别分为 4 种:
第 1 种数据准确性最高,但相应地性能最差。第 4 种性能高,可是相应地读取数据的准确性低。
脏读、幻读、不可重复读都是并发事务的状况下,由于不一样的数据隔离级别而读取到不一样的内容。
脏读,即一个事务读到了另外一个事务还未提交的数据。若是脏读读取到的数据最终仍是提交了倒还好,但若是这条数据最终回滚了,那么这条数据对于刚刚读取到它的事务而言,就是一条脏数据。
不可重复读,不一样的事务读取同一条数据,读取到的内容是不一样的。也就是说,对某一条数据而言,不一样的事务以一样的重复操做读取,却产生了不一样的结果。
幻读,一个事务按照某种查询条件,第一次读取的数据量和第二次读取的数据量不同,就像幻觉同样,明明刚才查的是 N 条数据,再查一次就变成了 M 条(M <> N)。
假设一个逻辑操做须要检查的条件有 20 个,可否为了减少事务而将检查性的内容放到事务以外呢?
不少系统都是在 DAO 的内部开始启动事务,而后进行操做,最后提交或者回滚。这其中涉及到代码设计的问题。
小一些的系统能够采用这种方式来作,可是在一些比较大的系统,逻辑较为复杂的系统中,势必会将过多的业务逻辑嵌入到 DAO 中,致使 DAO 的复用性降低。因此这不是一个好的实践。
来回答这个问题,可否为了缩小事务,而将一些业务逻辑检查放到事务外面?
答案是:对于核心的业务检查逻辑,不能放到事务以外,并且必需要做为分布式下的并发控制!一旦在事务以外作检查,那么势必会形成事务A已经检查过的数据被事务B所修改,致使事务A徒劳无功并且出现并发问题,直接致使业务控制失败。
因此,在分布式的高并发环境下,对于核心业务逻辑的检查,要采用加锁机制。
好比事务开启须要读取一条数据进行验证,而后逻辑操做中须要对这条数据进行修改,最后提交。
这样的一个过程,若是读取并验证的代码放到事务以外,那么读取的数据极有可能已经被其余的事务修改,当前事务一旦提交,又会从新覆盖掉其余事务的数据,致使数据异常。
因此在进入当前事务的时候,必需要将这条数据锁住,例如使用 Oracle 的 for update
就是一个在分布式环境下颇有效的控制手段。
另外一种好的实践方式是使用编程式事务而非声明式事务,尤为是在较大规模的项目中。对于大量事务的声明配置,在代码量很是大的状况下,将是一种折磨。
将 DAO 保持针对一张表的最基本操做,而后业务逻辑的处理放入 manager 或 service 中进行,同时使用编程式事务能够更精确地控制事务范围。
特别注意的,对于事务内部一些可能抛出异常的状况,捕获异常时要谨慎,不能随便的 catch Exception,否则会致使事务的异常被吃掉而不能正常回滚。
Spring配置声明式事务:
编写业务逻辑方法:
(1)使用 xml 配置方式:
<!-- 配置SessionFactory --> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="configLocation"> <value>classpath:hibernate.cfg.xml</value> </property> </bean> <!-- 配置 Hibernate 事务管理器 --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"/> </bean> <!-- 定义通知:定义事务的传播行为 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRED"/> <tx:method name="del*" propagation="REQUIRED"/> <tx:method name="modify*" propagation="REQUIRED"/> <tx:method name="*" propagation="REQUIRED" read-only="true"/> </tx:attributes> </tx:advice> <!-- 声明哪些类哪些方法须要使用事务 --> <aop:config> <aop:pointcut id="transactionPC" expression="execution(* com.service.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPC"/> </aop:config> <!-- 普通 IOC 注入 --> <bean id="userManager" class="com.service.UserManagerImpl"> <property name="logManager" ref="logManager"/> <property name="sessionFactory" ref="sessionFactory"/> </bean> <bean id="logManager" class="com.service.LogManagerImpl"> <property name="sessionFactory" ref="sessionFactory"/> </bean>
关于 spring 配置中 read-only
的说明:
read-only
配置为 true,会告诉 spring 对应的事务应该被最优化为只读事务。
这是一个最优化提示。在一些状况下,一些事务策略可以起到显著的最优化效果,例如在使用 Object/Relational
映射工具(如:Hibernate 或 TopLink)时避免 dirty checking
(试图“刷新”)。
(2)使用 JPA 注解方式
@Service public class UserServiceImpl implements IUserService { @Resource IUserDAO userDAO; //启动 REQUIRED 默认事务传播行为的方法 @Transactional public void funNone() throws Exception { save(new UserEntity("aaa")); } //启动 REQUIRED 默认事务传播行为的方法 @Transactional(propagation = Propagation.REQUIRED) public void funRequire() throws Exception { save(new UserEntity("bbb")); } //启动 Nested 嵌套事务的方法 @Transactional(propagation = Propagation.NESTED) public void funNest() throws Exception { save(new UserEntity("ccc")); } //REQUIRES_NEW 事务的方法 @Transactional(propagation = Propagation.REQUIRES_NEW) public void funRequireNew() throws Exception { save(new UserEntity("ddd")); } }
参考文章:
(Spring事务传播性与隔离级别)[http://blog.csdn.net/edward0830ly/article/details/7569954]
http://blog.sina.com.cn/s/blog_4b5bc0110100z7jr.html