Spring 注解@Transactional

事物的相关

事务的四个属性:原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。

1.原子性(Atomic)

最重要的原则,也是最容易理解的原则。被事务管理的所有方法,要么一起被提交,要么一起回滚。

举例:在股票交易时,除了记录交易的过程,还要更新交易完成之后的账户状态。

2.一致性(Consistency)

事务在系统完整性中实施一致性,如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于新有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。

3.隔离性(Isolation)

简单的说:在处理一个事务的时候,如果有一个事务同时处理,必须等待这个事务处理完毕,才能进行下一次处理

举例:一个厕所就一个马桶,必须一个人上完,例外一个人再进去上.

4.持久性(Durability)

持久性意味着一旦事务执行成功,在系统中产生的所有变化将是永久的。应该存在一些检查点防止在系统失败时丢失信息。甚至硬件本身失败,系统的状态仍能通过在日志中记录事务完成的任务进行重建。

举例:在执行事务的时候,突然停电,等再来电的时候,有个事务记录通知重新执行下这个事务.

事务隔离级别

一个事务必须与其它事务进行隔离的程度。较低的隔离级别可以增加并发,但代价是降低数据的正确性。相反,较高的隔离级别可以确保数据的正确性,但可能对并发产生负面影响。

数据库并发操作存在的异常情况:(当没有事务存在的情况下会发生以下情况)

1.更新丢失(Lostupdate):同时改,更新丢失

2.脏读取(DirtyReads):一个在读另外一个没提交的数据,为脏读(有可能人家不提交了)

3.非重复读取(Non-repeatableReads):一个不停在读,一个在修改或者删除(数据总量可能不变或减少)

4.两次更新问题(Secondlostupdatesproblem):一个读完修改,另外一个读完修改,后者成功(与更新丢失区别是,那是同时该,这个是一前一后)

5.幻读(PhantomReads):一个不停在读,一个在添加一条记录(数据总量变了)与非重复读的区别就是那个是修改删除,这个是添加

事务隔离级别

为了避免上面出现几种情况在标准SQL规范中定义了4个事务隔离级别,不同隔离级别对事务处理不同。

1.未授权读取:也称未提交读.如果一个事务开始写数据,其他写数据的动作将等待,但是允许其他事务读取,(排他锁)

2.授权读取:也称提交读.

3.可重复读取:禁止不可重复读取和脏读取。但是有时可能出现幻影数据

4.串行(Serializable):也称可串行读。提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作事务访问到。事务隔离的最高级别,事务之间完全隔离。如果事务在可串行读隔离级别上运行,则可以保证任何并发重叠事务均是串行的。

1.Spring事务管理简介

(1)Spring为多种不同类型的事务管理机制提供统一编程模型,这些事务管理模型包括JTA、JDBC、Hibernate、JPA和JDO。

(2)Spring支持声明式事务管理(使用XML文档配置(或者Annotation)结合AOP实现的事务管理)。

(3)为代码嵌入式(programmatic)的事务管理提供API接口,与复杂的JTA接口相比要简单的多。

(4)能够与Spring的数据抽象访问完美结合。

Spring事务管理的优点

一般认为,JavaEE中有两种类型的事务管理方式,分别是全局(global)和局部(local)事务。它们各自有优缺点。

全局事务

全局事务主要是指通过JTA管理多个数据库或者消息队列的事务处理。JTA接口使用复杂,并且必须结合JNDI才能使用,因此限制了应用代码潜在的重用性。

局部事务

局部事务是资源(数据库)相关的,例如JDBC的connection。尽管局部事务使用简单,但是其缺点也是很明显的:局部事务不能在多个源(数据库)间统一管理事务,此外事务管理使用嵌入式代码,不利于代码逻辑的清晰展现。

Spring一致性编程模型

Spring解决了全局和局部事务的缺陷,使用一致性编程模型,一次编码,可以在不同的事务策略和运行环境中迁移。此外,Spring提供两种类型的事务管理方式,分别是代码嵌入式和声明式,更多的人喜欢使用声明式。

Spring事务抽象

Spring的事务管理通过org.springframework.transaction.PlatformactionManager接口表示:

public interface PlatformTransactionManager {
  TransactionStatus getTransaction(TransactionDefinition definition)
    throws TransactionException;
 
  void commit(TransactionStatus status) throws TransactionException;
 
  void rollback(TransactionStatus status) throws TransactionException;
}

PlatformTransactionManager是一个服务提供商接口(SPI),针对不同类型的底层事务框架,Spring提供了不同的PlatFormTransactionManager实现版本,在使用过程中选择适当的实现版本管理底层事务框架即可,常见的有:

(1)JDBCorg.springframework.jdbc.datasource.DataSourceTransactionManager

(2)JTAorg.springframework.transaction.jta.JtaTransactionManager

(3)Hibernateorg.springframework.orm.hibernate3.HibernateTransactionManager

下面分析PlatFormTransactionManager中的方法,getTransaction方法,通过TransactionDefinition传入参数返回TransactionStatus对象。TransactionStatus可能表示一个新的事务,或者表示一个已经存在的事务(如果当前线程的调用栈中已经存在一个事务)。在JavaEE中经常将事务与一个具体的执行线程相关联,因此,在获取事务时,如果当前线程中已经存在事务,即将存在的事务返回,否则创建一个新事务返回。

TransactionDefinition接口规范如下:

(1)Isolation:事务隔离等级(是数据库事务的一个概念,不同数据库支持不同的事务隔离等级)

(2)Propagation:事务传播方式,通过设置可以决定,当一个事务方法准备执行前,线程栈中已经存在一个事务,可选的策略为:使用先前的事务作为即将执行的方法事务,或者,将先前的事务暂停,创建一个全新的事务处理当前方法,处理结束后,在返回先前的事务。

(3)Timeout:如果事务持续的时间超时,则启动底层事务管理的回滚机制,回滚当前事务。

(4)Read-onlystatus:Read-only事务主要应用于代码中仅读取数据,并未对数据修改的情况。在某些情况下,Read-only事务可以优化事务管理,例如底层事务框架使用Hibernate时。

TransactionStatus提供了简单的方式控制事务执行和查询事务状态,如下所示

public interface TransactionStatus extends SavepointManager {
 
    boolean isNewTransaction();
 
    boolean hasSavepoint();
 
    void setRollbackOnly();
 
    boolean isRollbackOnly();
 
    void flush();
 
    boolean isCompleted();
 
}

针对不同的底层事务框架,需要选择正确的PlatformTransactionManager实现版本,下面提供Hibernate的配置方法:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="mappingResources">
  <list>
    <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
  </list>
  </property>
  <property name="hibernateProperties">
    <value>
      hibernate.dialect=${hibernate.dialect}
    </value>
  </property>
</bean>
 
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
  <property name="sessionFactory" ref="sessionFactory" />
</bean>

从上述配置文件可以看出,需要在txManager中指定HibernateSessionfactory。在Hibernate中通过Sessionfactory中的OpenSession方法创建Session,之后使用Session中的beginTransaction方法开启事务。将sessionFactory注入到HibernateTransactionManager中的属性后,Spring即可通过这个sessionFactory控制Hibernate事务管理。所以,Spring事务管理仍然是依赖于底层的事务管理框架,而Spring只是提供对事务的一种抽象,这种抽象能够在多种事务框架间迁移。当需要新的事务管理框架和策略支持时,无需更改代码,只需更改配置文件即可完成这种事务框架间的迁移。

声明式(Declarative)事务管理

声明式事务管理以EJB的CMT十分类似,它们的主要区别在于:

(1)CMT依赖于JTA,只适用于全局事务管理,Spring不单可以适用于JTA,同时也适用于局部事务管理(例如:JDBC,JPA,Hibernate和JDO等)。

(2)Spring声明式事务管理可以应用于任意的类管理,而EJBs只是用于特殊类管理。

(3)Spring的回滚规则与EJB不同。

(4)Spring可以通过AOP规则在回滚过程中插入用户定义行为,也可以同事务管理advice一起定义任意的advice。(advice是AOP的概念)

(5)Spring不支持跨越多个远程调用的事务管理。一般认为Spring的事务最多在一个请求范围内完成,如果需要跨越多个请求过程管理事务,那么应当选择EJB,但是这种情况并不多见。

回滚规则的制定十分重要。当然,可以使用传统的方式,调用TransactionStatus中的setRollback方法,通过代码回滚事务;但是,更常用的是定义回滚规则,当应用程序执行过程中满足了某项回滚规则(定义制定Runtimeexception出发事务回滚),当前事务应当自动回滚。通过Spring的声明式事务管理,无需在代码中加入任何与事务相关的内容,即可完成按回滚规则管理事务的方式。Spring与EJB相似,仅对未处理的运行时异常(runtimeexception)回滚。

理解声明式事务实现机制

Spring通过元数据(metadata,包括XML或者annotation)配置声明式事务管理,并使用AOP代理机制最终实现事务管理。过程大致是,开发者通过元数据标明需要事务管理的方法(通过AOP中的cut-point),同时提供事务管理的策略advice(通过TX的advice),并结合实际提供的PlatformTransactionManager。当用户调用了事务管理方法时,系统使用具体的PlatformTransactionManager实现,根据advice提供的策略创建(或者获取)一个事务,之后执行方法内部逻辑,当出现异常时,回滚当前事务,否则执行结束,提交事务,清理当前事务。整个过程是使用AOP代理完成的,AOP代理在方法代码前添加事务启动逻辑,在方法执行后添加事务提交和清理逻辑,并且代理监控方法中可能出现的异常,当有未捕获异常抛出时,代理使用相应的回滚逻辑回滚事务。(有关代理的更进一步说明请参考Spring官方文档AOP章节)。整个过程如下图所示:

publicinterfaceFooService{

FoogetFoo(StringfooName);

FoogetFoo(StringfooName,StringbarName);

voidinsertFoo(Foofoo);

voidupdateFoo(Foofoo);

}

publicclassDefaultFooServiceimplementsFooService{

publicFoogetFoo(StringfooName){

thrownewUnsupportedOperationException();

}

publicFoogetFoo(StringfooName,StringbarName){

thrownewUnsupportedOperationException();

}

publicvoidinsertFoo(Foofoo){

thrownewUnsupportedOperationException();

}

publicvoidupdateFoo(Foofoo){

thrownewUnsupportedOperationException();

}

}

下面声明get方法为read-only,而其他方法为默认。

<bean id="fooService" class="**.service.DefaultFooService"/>
  <tx:advice id="txAdvice" transaction-manager="txManager">
  <!-- the transactional semantics... -->
  <tx:attributes>
    <!-- all methods starting with 'get' are read-only -->
    <tx:method name="get*" read-only="true"/>
    <!-- other methods use the default transaction settings (see below) -->
    <tx:method name="*"/>
  </tx:attributes>
  </tx:advice>
 
  <!-- ensure that the above transactional advice runs for any execution
    of an operation defined by the FooService interface -->
  <aop:config>
  <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
  <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
  </aop:config>
 
  <!-- don't forget the DataSource -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
  <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
  <property name="username" value="scott"/>
  <property name="password" value="tiger"/>
  </bean>
 
  <!-- similarly, don't forget the PlatformTransactionManager -->
  <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
  </bean>

正如上例所述的那样,通过<tx:advice/>标签设置了“所有以get开始的方法需要使用read-only事务管理策略,而其它方法使用默认的事务管理策略”。transaction-manager属性用于指定具体的PlatformTransactionManager实现,txAdvice需要使用txManager驱动具体的事务管理过程。因此,pointcut标明需要添加事务代理的方法,通过advice配置了事务属性和事务管理者,当需要事务支持的方法被调用时,aop使用advice找到PlatformTransactionManager,而PlatformTransactionManager使用底层的具体事务管理架构,最终实现事务管理功能。

回滚配置

默认情况下,Spring只会对方法中的未检测运行异常回滚,而被检测的异常不会导致事务回滚。

可以通过如下配置方法甚至配置一个检测的异常回滚:

<tx:advice id="txAdvice" transaction-manager="txManager">
  <tx:attributes>
  <tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
  <tx:method name="*"/>
  </tx:attributes>
</tx:advice>

当然也可以使用下面方法,使一个未检测异常不发生回滚,当这个异常发生时,Spring将提交事务(而不是回滚):

<tx:advice id="txAdvice">
  <tx:attributes>
  <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
  <tx:method name="*"/>
  </tx:attributes>
</tx:advice>

为不同的bean配置不同的advice

<aop:config>
    <aop:pointcut id="defaultServiceOperation"
          expression="execution(你的自己的配置.service.*Service.*(..))"/>
    <aop:pointcut id="noTxServiceOperation"
          expression="execution(你的自己的配置.service.ddl.DefaultDdlManager.*(..))"/>
    <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>
 
    <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>
 
  </aop:config>
  <bean id="fooService" class="x.y.service.DefaultFooService"/>
  <bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>
 
  <tx:advice id="defaultTxAdvice">
    <tx:attributes>
      <tx:method name="get*" read-only="true"/>
      <tx:method name="*"/>
    </tx:attributes>
  </tx:advice>
 
  <tx:advice id="noTxAdvice">
    <tx:attributes>
      <tx:method name="*" propagation="NEVER"/>
    </tx:attributes>
  </tx:advice>

Spring事务隔离级别:

PROPAGATION_REQUIRED如果没事务,就新建事务

简单的说:一个方法要运行,先看看自己在不在一个事务内部,如果在,好我什么都不说,好好工作(不启新事务),如果不在,我新建一个事务出来,然后在这个新建事务内运行我(我就是那个方法);

PROPAGATION_SUPPORTS有事务就支持当前事务,没有事务就已非事务执行

简单的说:一个方法比较随意,我上面有事务,我就在事务里运行,如果没事务,我就是非事务运行.

PROPAGATION_MANDATORY有事务的话就支持当前事务,没有就抛异常

简单的说:这个方法必须在事务中运行,不然就要大叫:我异常了.他只能被父事务调用

PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,就把当前事务挂起,然后建立一个事务出来执行,执行完毕,再去执行刚才挂起的那个事务.

简单的说:我不管上面有没有事务在,我是要新建事务的,如果没有好说,我自己做自己的事,如果有,那要先等我,我将事务处理完毕后,再处理外部事务吧.如果我处理失败,我自己回滚,不影响外面的事务,你回滚不回滚不关我的事.

PROPAGATION_NOT_SUPPORTED以非事务工作,如果当前有事务,那就挂起当前事务

简单的说:声明自己的是个非事务,如果有事务和我同时执行,那事务先挂起,等我运行完,你在处理

PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,就抛异常.

简单的说:一个方法,就是不让事务包着,包着就出异常

PROPAGATION_NESTED

理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。

而Nested事务的好处是他有一个savepoint。也就是说ServiceB.methodB失败回滚,那么ServiceA.methodA也会回滚到savepoint点上,ServiceA.methodA可以选择另外一个分支,比如ServiceC.methodC,继续执行,来尝试完成自己的事务。但是这个事务并没有在EJB标准中定义

(1)propagation默认是Required

(2)Isolation默认是Default

(3)Transaction默认是read/write

(4)timeout默认-1

(5)rollback-for

(6)no-rollback-for

相关推荐