Spring @Transactional 事务机制

Spring @Transactional 事务机制

 

几个概念要清楚:事务的传播机制,事务的边界

 

工作原理

运行配置@Transactional注解的测试类的时候,具体会发生如下步骤

1)事务开始时,通过AOP机制,生成一个代理connection对象,并将其放入DataSource实例的某个与DataSourceTransactionManager相关的某处容器中。在接下来的整个事务中,客户代码都应该使用该connection连接数据库,执行所有数据库命令[不使用该connection连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚]

2)事务结束时,回滚在第1步骤中得到的代理connection对象上执行的数据库命令,然后关闭该代理connection对象

 

 

@Transactional不生效的情况

  1. @Transactional  在private 方法是不生效
  2. 在同一个bean里,嵌套的public方法@Transactional 也不生效

解决方法

      简单粗暴,有需要就在调用service中就开@Transactional,如果实在不行,像里面有调用第三方资源,唔想事务开得太久,可以考虑新建另外一个service去开@Transactional。

 

 

 

那么@Transactional如何工作?

 

实现了EntityManager接口的持久化上下文代理并不是声明式事务管理的唯一部分,事实上包含三个组成部分:

1. EntityManager Proxy本身

2. 事务的切面

3. 事务管理器

 

事务的切面

事务的切面是一个“around(环绕)”切面,在注解的业务方法前后都可以被调用。实现切面的具体类是TransactionInterceptor。

 

事务的切面有两个主要职责:

1. 在’before’时,切面提供一个调用点,来决定被调用业务方法应该在正在进行事务的范围内运行,还是开始一个新的独立事务。

2. 在’after’时,切面需要确定事务被提交,回滚或者继续运行。

3. 在’before’时,事务切面自身不包含任何决策逻辑,是否开始新事务的决策委派给事务管理器完成。

 

 

事务管理器

事务管理器需要解决下面两个问题:

新的Entity Manager是否应该被创建?

是否应该开始新的事务?

 

这些需要事务切面’before’逻辑被调用时决定。事务管理器的决策基于以下两点:

1. 事务是否正在进行

2. 事务方法的propagation属性(比如REQUIRES_NEW总要开始新事务)

 

如果事务管理器确定要创建新事务,那么将:

1. 创建一个新的entity manager

2. entity manager绑定到当前线程

3. 从数据库连接池中获取连接

4. 将连接绑定到当前线程

 

特点:

1. 使用ThreadLocal变量将entity manager和数据库连接都绑定到当前线程。

2. 事务运行时他们存储在线程中,当它们不再被使用时,事务管理器决定是否将他们清除。

3. 程序的任何部分如果需要当前的entity manager和数据库连接都可以从线程中获取。

 

 

EntityManager proxy

 

EntityManager proxy(前面已经介绍过)就是谜题的最后一部分。当业务方法调用entityManager.persist()时,这不是由entity manager直接调用的。

而是业务方法调用代理,代理从线程获取当前的entity manager,前面介绍过事务管理器将entity manager绑定到线程。

了解了@Transactional机制的各个部分,我们来看一下实现它的常用Spring配置。

 

 

 

根据上面所述,我们所使用的客户代码应该具有如下能力:

1)每次执行数据库命令的时候

如果在事务的上下文环境中,那么不直接创建新的connection对象,而是尝试从DataSource实例的某个与DataSourceTransactionManager相关的某处容器中获取connection对象;在非事务的上下文环境中,直接创建新的connection对象

2)每次执行完数据库命令的时候

如果在事务的上下文环境中,那么不直接关闭connection对象,因为在整个事务中都需要使用该connection对象,而只是释放本次数据库命令对该connection对象的持有;在非事务的上下文环境中,直接关闭该connection对象

 

 

在service类前加上@Transactional,声明这个service所有方法需要事务管理。每一个业务方法开始时都会打开一个事务。

Spring默认情况下会对运行期例外(RunTimeException)进行事务回滚。这个例外是unchecked

如果遇到checked意外就不回滚。

如何改变默认规则:

1 让checked例外也回滚:在整个方法前加上 @Transactional(rollbackFor=Exception.class)

2 让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)

3 不需要事务管理的(只查询的)方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)

4 如果不添加rollbackFor等属性,Spring碰到Unchecked Exceptions都会回滚,不仅是RuntimeException,也包括Error。

 

注意:

如果异常被try{}catch{}了,事务就不回滚了,如果想让事务回滚必须再往外抛try{}catch{throw Exception}。

 

 

 

 

@Transaction的属性

 



 

 

@Transactional之value

 

    value这里主要用来指定不同的事务管理器;主要用来满足在同一个系统中,存在不同的事务管理器。比如在Spring中,声明了两种事务管理器txManager1, txManager2。

 

    然后,用户可以根据这个参数来根据需要指定特定的txManager。

 

什么情况下使用?

 

   比如在一个系统中,需要访问多个数据源或者多个数据库,则必然会配置多个事务管理器的。

 

 

 

@Transactional之propagation

 

Propagation支持7种不同的传播机制:

 

 

REQUIRED

    

      业务方法需要在一个事务中运行,如果方法运行时,已处在一个事务中,那么就加入该事务,否则自己创建一个新的事务.这是spring默认的传播行为.。 

 

 

SUPPORTS  

 

      如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分,如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行。

 

 

MANDATORY

 

      只能在一个已存在事务中执行,业务方法不能发起自己的事务,如果业务方法在没有事务的环境下调用,就抛异常

 

 

REQUIRES_NEW

 

      业务方法总是会为自己发起一个新的事务,如果方法已运行在一个事务中,则原有事务被挂起,新的事务被创建,直到方法结束,新事务才结束,原先的事务才会恢复执行.

 

 

NOT_SUPPORTED

 

      声明方法需要事务,如果方法没有关联到一个事务,容器不会为它开启事务.如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行.

 

 

NEVER

 

      声明方法绝对不能在事务范围内执行,如果方法在某个事务范围内执行,容器就抛异常.只有没关联到事务,才正常执行.

 

 

NESTED

 

 

      如果一个活动的事务存在,则运行在一个嵌套的事务中.如果没有活动的事务,则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保证点.内部事务回滚不会对外部事务造成影响, 它只对DataSourceTransactionManager 事务管理器起效.

 

 

 

@Transactional之isolation

       

隔离级别所要解决的问题是在应用程序中,存在多个事务同时在运行之时,需要解决和处理好的问题。那首先来看看,一般会出现哪些问题呢?先来看看吧。

 

 

脏读(dirty read)

 

      一个事物更新了数据库中的某些数据,另一个事物读取了这些数据,这时前一个事物由于某些原因回滚了,那么第二个事物读取的数据就是“脏数据” 

 

 

不可重复读(non-repeatable read)

        

      一个事物需要两次查询同一数据,但两次查询中间可能有另外一个事物更改了这个数据,导致前一个事物两次读出的数据不一致。

        

 

幻读 (phantom read)

           

      一个事物两次查询同一个表,但两次查询中间可能有另外一个事物又向这个表中插入了一些新数据,导致前一个事物的两次查询不一致  

 

 

 

@Transactional中Isolation有具备的值

 

  • DEFAULT  使用各个数据库默认的隔离级别
  • Read Uncommited :读未提交数据( 会出现脏读,不可重复读,幻读  )
  • Read Commited :读已提交的数据(会出现不可重复读,幻读)
  • Repeatable Read :可重复读(会出现幻读)
  • Serializable :串行化

 

 

其与事务中容易出现的问题对应关系具体如下




 

      具体如何来设置具体的隔离界别,则依据业务系统具体可以容忍的程度而定。Serializable最为严格,然而效率最低;Read Uncommited效率最高,但是容易出现各种问题,中间的2个级别介于二者之间,故本质上是效率与出错概率的平衡与妥协。

 

 

 

 

 @Transactional之timeout

 

      用于设置事务处理的时间长度,阻止可能出现的长时间的阻塞系统或者占用系统资源。单位为秒。如果超时设置事务回滚,并抛出TransactionTimedOutException异常。

 

      spring事务超时 = 事务开始时到最后一个Statement创建时时间 + 最后一个Statement的执行时超时时间(即其queryTimeout)。

 

      关于事务超时时间的计算可以参考:http://jinnianshilongnian.iteye.com/blog/1986023

 

 

 

 

@Transactional之readOnly

 

      默认情况下是false,可以显示指定为true, 告诉程序该方法下使用的是只读操作,如果进行其他非读操作,则会跑出异常;这个紧紧适用于只有readOnly标识的情况下,当其与propagation机制同时使用之时,则会出现只读设置被覆盖的情况,比如在required的情况下。在使用 REQUIRED 传播模式时,会抛出一个只读连接异常。使用 JDBC 时是这样。使用基于 ORM 的框架时,只读标志只是对数据库的一个提示,并且一条基于 ORM 框架的指令(本例中是 hibernate)将对象缓存的 flush 模式设置为 NEVER,表示在这个工作单元中,该对象缓存不应与数据库同步。不过,REQUIRED 传播模式会覆盖所有这些内容,允许事务启动并工作,就好像没有设置只读标志一样。

 

 

      具体的详细内容可以参考: http://robinsoncrusoe.iteye.com/blog/825531

 

 

 

 

@Transactional之rollbackForClassName/rollbackFor

 

       Spring默认情况下会对运行期例外(RunTimeException)进行事务回滚。这个例外是unchecked,如果遇到checked意外就不回滚。

 

 

       用来指明回滚的条件是哪些异常类或者异常类名。用法比较简单,这些不再赘述。

 

 

 

@Transactional之noRollbackForClassName/noRollbackFor

       

       作用雷同于8, 用来指明在抛出特定异常的情况下,不进行数据库的事务回滚操作。

 

 

 

 

参考:

http://www.importnew.com/12300.html

猜你喜欢

转载自youyu4.iteye.com/blog/2339878