转载http://www.mamicode.com/info-detail-1248286.htmlhtml
写这篇博客以前我首先读了《Spring in action》,以后在网上看了一些关于Spring事务管理的文章,感受都没有讲全,这里就将书上的和网上关于事务的知识总结一下,参考的文章以下:java
理解事务以前,先讲一个你平常生活中最常干的事:取钱。
好比你去ATM机取1000块钱,大致有两个步骤:首先输入密码金额,银行卡扣掉1000元钱;而后ATM出1000元钱。这两个步骤必须是要么都执行要么都不执行。若是银行卡扣除了1000块可是ATM出钱失败的话,你将会损失1000元;若是银行卡扣钱失败可是ATM却出了1000块,那么银行将损失1000元。因此,若是一个步骤成功另外一个步骤失败对双方都不是好事,若是无论哪个步骤失败了之后,整个取钱过程都能回滚,也就是彻底取消全部操做的话,这对双方都是极好的。
事务就是用来解决相似问题的。事务是一系列的动做,它们综合在一块儿才是一个完整的工做单元,这些动做必须所有完成,若是有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过同样。
在企业级应用程序开发中,事务管理必不可少的技术,用来确保数据的完整性和一致性。
事务有四个特性:ACIDspring
- 原子性(Atomicity):事务是一个原子操做,由一系列动做组成。事务的原子性确保动做要么所有完成,要么彻底不起做用。
- 一致性(Consistency):一旦事务完成(无论成功仍是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不该该被破坏。
- 隔离性(Isolation):可能有许多事务会同时处理相同的数据,所以每一个事务都应该与其余事务隔离开来,防止数据损坏。
- 持久性(Durability):一旦事务完成,不管发生什么系统错误,它的结果都不该该受到影响,这样就能从任何系统崩溃中恢复过来。一般状况下,事务的结果被写到持久化存储器中。
Spring事务管理的实现有许多细节,若是对整个接口框架有个大致了解会很是有利于咱们理解事务,下面经过讲解Spring的事务接口来了解Spring实现事务的具体策略。
Spring事务管理涉及的接口的联系以下:sql
Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,经过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,可是具体的实现就是各个平台本身的事情了。此接口的内容以下:数据库
Public interface PlatformTransactionManager()...{ // 由TransactionDefinition获得TransactionStatus对象 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; // 提交 Void commit(TransactionStatus status) throws TransactionException; // 回滚 Void rollback(TransactionStatus status) throws TransactionException; }
从这里可知具体的具体的事务管理机制对Spring来讲是透明的,它并不关心那些,那些是对应各个平台须要关心的,因此Spring事务管理的一个优势就是为不一样的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA。下面分别介绍各个平台框架实现事务管理的机制。express
若是应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你须要使用以下的XML将其装配到应用程序的上下文定义中:编程
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
实际上,DataSourceTransactionManager是经过调用java.sql.Connection来管理事务,然后者是经过DataSource获取到的。经过调用链接的commit()方法来提交事务,一样,事务失败则经过调用rollback()方法进行回滚。后端
若是应用程序的持久化是经过Hibernate实习的,那么你须要使用HibernateTransactionManager。对于Hibernate3,须要在Spring上下文定义中添加以下的<bean>
声明:安全
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
sessionFactory属性须要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,然后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。session
Hibernate多年来一直是事实上的Java持久化标准,可是如今Java持久化API做为真正的Java持久化标准进入你们的视野。若是你计划使用JPA的话,那你须要使用Spring的JpaTransactionManager来处理事务。你须要在Spring中这样配置JpaTransactionManager:
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
JpaTransactionManager只须要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTransactionManager将与由工厂所产生的JPA EntityManager合做来构建事务。
若是你没有使用以上所述的事务管理,或者是跨越了多个事务管理源(好比两个或者是多个不一样的数据源),你就须要使用JtaTransactionManager:
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManagerName" value="java:/TransactionManager" /> </bean>
JtaTransactionManager将事务管理的责任委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象,其中事务成功完成经过UserTransaction.commit()方法提交,事务失败经过UserTransaction.rollback()方法回滚。
上面讲到的事务管理器接口PlatformTransactionManager经过getTransaction(TransactionDefinition definition)方法来获得事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。
那么什么是事务属性呢?事务属性能够理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面,如图所示:
而TransactionDefinition接口内容以下:
public interface TransactionDefinition { int getPropagationBehavior(); // 返回事务的传播行为 int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另一个事务能够看到本事务内的哪些数据 int getTimeout(); // 返回事务必须在多少秒内完成 boolean isReadOnly(); // 事务是否只读,事务管理器可以根据这个返回值进行优化,确保事务是只读的 }
咱们能够发现TransactionDefinition正好用来定义事务属性,下面详细介绍一下各个事务属性。
事务的第一个方面是传播行为(propagation behavior)。当事务方法被另外一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在本身的事务中运行。Spring定义了七种传播行为:
传播行为 | 含义 |
---|---|
PROPAGATION_REQUIRED | 表示当前方法必须运行在事务中。若是当前事务存在,方法将会在该事务中运行。不然,会启动一个新的事务 |
PROPAGATION_SUPPORTS | 表示当前方法不须要事务上下文,可是若是存在当前事务的话,那么该方法会在这个事务中运行 |
PROPAGATION_MANDATORY | 表示该方法必须在事务中运行,若是当前事务不存在,则会抛出一个异常 |
PROPAGATION_REQUIRED_NEW | 表示当前方法必须运行在它本身的事务中。一个新的事务将被启动。若是存在当前事务,在该方法执行期间,当前事务会被挂起。若是使用JTATransactionManager的话,则须要访问TransactionManager |
PROPAGATION_NOT_SUPPORTED | 表示该方法不该该运行在事务中。若是存在当前事务,在该方法运行期间,当前事务将被挂起。若是使用JTATransactionManager的话,则须要访问TransactionManager |
PROPAGATION_NEVER | 表示当前方法不该该运行在事务上下文中。若是当前正有一个事务在运行,则会抛出异常 |
PROPAGATION_NESTED | 表示若是当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务能够独立于当前事务进行单独地提交或回滚。若是当前事务不存在,那么其行为与PROPAGATION_REQUIRED同样。注意各厂商对这种传播行为的支持是有所差别的。能够参考资源管理器的文档来确认它们是否支持嵌套事务 |
注:如下具体讲解传播行为的内容参考自Spring事务机制详解
(1)PROPAGATION_REQUIRED 若是存在一个事务,则支持当前事务。若是没有事务则开启一个新的事务。
//事务属性 PROPAGATION_REQUIRED methodA{ …… methodB(); …… }
//事务属性 PROPAGATION_REQUIRED methodB{ …… }
使用spring声明式事务,spring使用AOP来支持声明式事务,会根据事务属性,自动在方法调用以前决定是否开启一个事务,并在方法执行以后决定事务提交或回滚事务。
单独调用methodB方法:
main{ metodB(); }
至关于
Main{ Connection con=null; try{ con = getConnection(); con.setAutoCommit(false); //方法调用 methodB(); //提交事务 con.commit(); } Catch(RuntimeException ex) { //回滚事务 con.rollback(); } finally { //释放资源 closeCon(); } }
Spring保证在methodB方法中全部的调用都得到到一个相同的链接。在调用methodB时,没有一个存在的事务,因此得到一个新的链接,开启了一个新的事务。
单独调用MethodA时,在MethodA内又会调用MethodB.
执行效果至关于:
main{ Connection con = null; try{ con = getConnection(); methodA(); con.commit(); } catch(RuntimeException ex) { con.rollback(); } finally { closeCon(); } }
调用MethodA时,环境中没有事务,因此开启一个新的事务.当在MethodA中调用MethodB时,环境中已经有了一个事务,因此methodB就加入当前事务。
(2)PROPAGATION_SUPPORTS 若是存在一个事务,支持当前事务。若是没有事务,则非事务的执行。可是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少量不一样。
//事务属性 PROPAGATION_REQUIRED methodA(){ methodB(); } //事务属性 PROPAGATION_SUPPORTS methodB(){ …… }
单纯的调用methodB时,methodB方法是非事务的执行的。当调用methdA时,methodB则加入了methodA的事务中,事务地执行。
(3)PROPAGATION_MANDATORY 若是已经存在一个事务,支持当前事务。若是没有一个活动的事务,则抛出异常。
//事务属性 PROPAGATION_REQUIRED methodA(){ methodB(); } //事务属性 PROPAGATION_MANDATORY methodB(){ …… }
当单独调用methodB时,由于当前没有一个活动的事务,则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);当调用methodA时,methodB则加入到methodA的事务中,事务地执行。
(4)PROPAGATION_REQUIRES_NEW 老是开启一个新的事务。若是一个事务已经存在,则将这个存在的事务挂起。
//事务属性 PROPAGATION_REQUIRED methodA(){ doSomeThingA(); methodB(); doSomeThingB(); } //事务属性 PROPAGATION_REQUIRES_NEW methodB(){ …… }
调用A方法:
main(){ methodA(); }
至关于
main(){ TransactionManager tm = null; try{ //得到一个JTA事务管理器 tm = getTransactionManager(); tm.begin();//开启一个新的事务 Transaction ts1 = tm.getTransaction(); doSomeThing(); tm.suspend();//挂起当前事务 try{ tm.begin();//从新开启第二个事务 Transaction ts2 = tm.getTransaction(); methodB(); ts2.commit();//提交第二个事务 } Catch(RunTimeException ex) { ts2.rollback();//回滚第二个事务 } finally { //释放资源 } //methodB执行完后,恢复第一个事务 tm.resume(ts1); doSomeThingB(); ts1.commit();//提交第一个事务 } catch(RunTimeException ex) { ts1.rollback();//回滚第一个事务 } finally { //释放资源 } }
在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码能够看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于 ts1。若是methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所作的结果依然被提交。而除了 methodB以外的其它代码致使的结果却被回滚了。使用PROPAGATION_REQUIRES_NEW,须要使用 JtaTransactionManager做为事务管理器。
(5)PROPAGATION_NOT_SUPPORTED 老是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也须要使用JtaTransactionManager做为事务管理器。(代码示例同上,可同理推出)
(6)PROPAGATION_NEVER 老是非事务地执行,若是存在一个活动事务,则抛出异常。
(7)PROPAGATION_NESTED若是一个活动的事务存在,则运行在一个嵌套的事务中. 若是没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager做为事务管理器。须要JDBC 驱动的java.sql.Savepoint类。有一些JTA的事务管理器实现可能也提供了一样的功能。使用PROPAGATION_NESTED,还须要把PlatformTransactionManager的nestedTransactionAllowed属性设为true;而 nestedTransactionAllowed属性值默认为false。
//事务属性 PROPAGATION_REQUIRED methodA(){ doSomeThingA(); methodB(); doSomeThingB(); } //事务属性 PROPAGATION_NESTED methodB(){ …… }
若是单独调用methodB方法,则按REQUIRED属性执行。若是调用methodA方法,至关于下面的效果:
main(){ Connection con = null; Savepoint savepoint = null; try{ con = getConnection(); con.setAutoCommit(false); doSomeThingA(); savepoint = con2.setSavepoint(); try{ methodB(); } catch(RuntimeException ex) { con.rollback(savepoint); } finally { //释放资源 } doSomeThingB(); con.commit(); } catch(RuntimeException ex) { con.rollback(); } finally { //释放资源 } }
当methodB方法调用以前,调用setSavepoint方法,保存当前的状态到savepoint。若是methodB方法调用失败,则恢复到以前保存的状态。可是须要注意的是,这时的事务并无进行提交,若是后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的全部操做。
嵌套事务一个很是重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所作的动做。而内层事务操做失败并不会引发外层事务的回滚。
PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:它们很是相似,都像一个嵌套事务,若是不存在一个活动的事务,都会开启一个新的事务。使用 PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务同样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它须要JTA事务管理器的支持。
使用PROPAGATION_NESTED时,外层事务的回滚能够引发内层事务的回滚。而内层事务的异常并不会致使外层事务的回滚,它是一个真正的嵌套事务。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED时,须要JDBC 3.0以上驱动及1.4以上的JDK版本支持。其它的JTA TrasactionManager实现可能有不一样的支持方式。
PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被彻底 commited 或 rolled back 而不依赖于外部事务, 它拥有本身的隔离范围, 本身的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。
另外一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 若是这个嵌套事务失败, 咱们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。
因而可知, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 彻底是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 若是外部事务 commit, 嵌套事务也会被 commit, 这个规则一样适用于 roll back.
PROPAGATION_REQUIRED应该是咱们首先的事务传播行为。它可以知足咱们大多数的事务需求。
事务的第二个维度就是隔离级别(isolation level)。隔离级别定义了一个事务可能受其余并发事务影响的程度。
(1)并发事务引发的问题
在典型的应用程序中,多个事务并发运行,常常会操做相同的数据来完成各自的任务。并发虽然是必须的,但可能会致使一下的问题。
- 脏读(Dirty reads)——脏读发生在一个事务读取了另外一个事务改写但还没有提交的数据时。若是改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
- 不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,可是每次都获得不一样的数据时。这一般是由于另外一个并发事务在两次查询期间进行了更新。
- 幻读(Phantom read)——幻读与不可重复读相似。它发生在一个事务(T1)读取了几行数据,接着另外一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些本来不存在的记录。
不可重复读与幻读的区别
不可重复读的重点是修改:
一样的条件, 你读取过的数据, 再次读取出来发现值不同了
例如:在事务1中,Mary 读取了本身的工资为1000,操做并无完成
con1 = getConnection(); select salary from employee empId ="Mary";
在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务.
con2 = getConnection(); update employee set salary = 2000; con2.commit();
在事务1中,Mary 再次读取本身的工资时,工资变为了2000
//con1 select salary from employee empId ="Mary";
在一个事务中先后两次读取的结果并不一致,致使了不可重复读。
幻读的重点在于新增或者删除:
一样的条件, 第1次和第2次读出来的记录数不同
例如:目前工资为1000的员工有10人。事务1,读取全部工资为1000的员工。
con1 = getConnection(); Select * from employee where salary =1000;
共读取10条记录
这时另外一个事务向employee表插入了一条员工记录,工资也为1000
con2 = getConnection(); Insert into employee(empId,salary) values("Lili",1000); con2.commit();
事务1再次读取全部工资为1000的员工
//con1 select * from employee where salary =1000;
共读取到了11条记录,这就产生了幻像读。
从总的结果来看, 彷佛不可重复读和幻读都表现为两次读取的结果不一致。但若是你从控制的角度来看, 二者的区别就比较大。
对于前者, 只须要锁住知足条件的记录。
对于后者, 要锁住知足条件及其相近的记录。
(2)隔离级别
隔离级别 | 含义 |
---|---|
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 最低的隔离级别,容许读取还没有提交的数据变动,可能会致使脏读、幻读或不可重复读 |
ISOLATION_READ_COMMITTED | 容许读取并发事务已经提交的数据,能够阻止脏读,可是幻读或不可重复读仍有可能发生 |
ISOLATION_REPEATABLE_READ | 对同一字段的屡次读取结果都是一致的,除非数据是被自己事务本身所修改,能够阻止脏读和不可重复读,但幻读仍有可能发生 |
ISOLATION_SERIALIZABLE | 最高的隔离级别,彻底服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,由于它一般是经过彻底锁定事务相关的数据库表来实现的 |
事务的第三个特性是它是否为只读事务。若是事务只对后端的数据库进行该操做,数据库能够利用事务的只读特性来进行一些特定的优化。经过将事务设置为只读,你就能够给数据库一个机会,让它应用它认为合适的优化措施。
为了使应用程序很好地运行,事务不能运行太长的时间。由于事务可能涉及对后端数据库的锁定,因此长时间的事务会没必要要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务若是没有执行完毕,那么就会自动回滚,而不是一直等待其结束。
事务五边形的最后一个方面是一组规则,这些规则定义了哪些异常会致使事务回滚而哪些不会。默认状况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)
可是你能够声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。一样,你还能够声明事务遇到特定的异常不回滚,即便这些异常是运行期异常。
上面讲到的调用PlatformTransactionManager接口的getTransaction()的方法获得的是TransactionStatus接口的一个实现,这个接口的内容以下:
public interface TransactionStatus{ boolean isNewTransaction(); // 是不是新的事物 boolean hasSavepoint(); // 是否有恢复点 void setRollbackOnly(); // 设置为只回滚 boolean isRollbackOnly(); // 是否为只回滚 boolean isCompleted; // 是否已完成 }
能够发现这个接口描述的是一些处理事务提供简单的控制事务执行和查询事务状态的方法,在回滚或提交的时候须要应用对应的事务状态。
Spring提供了对编程式事务和声明式事务的支持,编程式事务容许用户在代码中精肯定义事务的边界,而声明式事务(基于AOP)有助于用户将操做与事务规则进行解耦。
简单地说,编程式事务侵入到了业务代码里面,可是提供了更加详细的事务管理;而声明式事务因为基于AOP,因此既能起到事务管理的做用,又能够不影响业务代码的具体实现。
Spring提供两种方式的编程式事务管理,分别是:使用TransactionTemplate和直接使用PlatformTransactionManager。
采用TransactionTemplate和采用其余Spring模板,如JdbcTempalte和HibernateTemplate是同样的方法。它使用回调方法,把应用程序从处理取得和释放资源中解脱出来。如同其余模板,TransactionTemplate是线程安全的。代码片断:
TransactionTemplate tt = new TransactionTemplate(); // 新建一个TransactionTemplate Object result = tt.execute( new TransactionCallback(){ public Object doTransaction(TransactionStatus status){ updateOperation(); return resultOfUpdateOperation(); } }); // 执行execute方法进行事务管理
使用TransactionCallback()能够返回一个值。若是使用TransactionCallbackWithoutResult则没有返回值。
示例代码以下:
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); //定义一个某个框架平台的TransactionManager,如JDBC、Hibernate dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 设置数据源 DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定义事务属性 transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 设置传播行为属性 TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 得到事务状态 try { // 数据库操做 dataSourceTransactionManager.commit(status);// 提交 } catch (Exception e) { dataSourceTransactionManager.rollback(status);// 回滚 }
注:如下配置代码参考自Spring事务配置的五种方式
根据代理机制的不一样,总结了五种Spring事务的配置方式,配置文件以下:
(1)每一个Bean都有一个代理
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="configLocation" value="classpath:hibernate.cfg.xml" /> <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" /> </bean> <!-- 定义事务管理器(声明式的事务) --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <!-- 配置DAO --> <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="userDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <!-- 配置事务管理器 --> <property name="transactionManager" ref="transactionManager" /> <property name="target" ref="userDaoTarget" /> <property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" /> <!-- 配置事务属性 --> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> </beans>
(2)全部Bean共享一个代理基类
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="configLocation" value="classpath:hibernate.cfg.xml" /> <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" /> </bean> <!-- 定义事务管理器(声明式的事务) --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="transactionBase" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" lazy-init="true" abstract="true"> <!-- 配置事务管理器 --> <property name="transactionManager" ref="transactionManager" /> <!-- 配置事务属性 --> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <!-- 配置DAO --> <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="userDao" parent="transactionBase" > <property name="target" ref="userDaoTarget" /> </bean> </beans>
(3)使用拦截器
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="configLocation" value="classpath:hibernate.cfg.xml" /> <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" /> </bean> <!-- 定义事务管理器(声明式的事务) --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> <property name="transactionManager" ref="transactionManager" /> <!-- 配置事务属性 --> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>*Dao</value> </list> </property> <property name="interceptorNames"> <list> <value>transactionInterceptor</value> </list> </property> </bean> <!-- 配置DAO --> <bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> </beans>
(4)使用tx标签配置的拦截器
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <context:annotation-config /> <context:component-scan base-package="com.bluesky" /> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="configLocation" value="classpath:hibernate.cfg.xml" /> <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" /> </bean> <!-- 定义事务管理器(声明式的事务) --> <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="*" propagation="REQUIRED" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="interceptorPointCuts" expression="execution(* com.bluesky.spring.dao.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="interceptorPointCuts" /> </aop:config> </beans>
(5)全注解
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <context:annotation-config /> <context:component-scan base-package="com.bluesky" /> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="configLocation" value="classpath:hibernate.cfg.xml" /> <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" /> </bean> <!-- 定义事务管理器(声明式的事务) --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> </beans>
此时在DAO上需加上@Transactional注解,以下:
package com.bluesky.spring.dao; import java.util.List; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import org.springframework.stereotype.Component; import com.bluesky.spring.domain.User; @Transactional @Component("userDao") public class UserDaoImpl extends HibernateDaoSupport implements UserDao { public List<User> listUsers() { return this.getSession().createQuery("from User").list(); } }
注:该实例参考自Spring中的事务管理实例详解
首先是数据库表
book(isbn, book_name, price)
account(username, balance)
book_stock(isbn, stock)
而后是XML配置
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <import resource="applicationContext-db.xml" /> <context:component-scan base-package="com.springinaction.transaction"> </context:component-scan> <tx:annotation-driven transaction-manager="txManager"/> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> </beans>
使用的类
BookShopDao
package com.springinaction.transaction; public interface BookShopDao { // 根据书号获取书的单价 public int findBookPriceByIsbn(String isbn); // 更新书的库存,使书号对应的库存-1 public void updateBookStock(String isbn); // 更新用户的帐户余额:account的balance-price public void updateUserAccount(String username, int price); }
BookShopDaoImpl
package com.springinaction.transaction; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository("bookShopDao") public class BookShopDaoImpl implements BookShopDao { @Autowired private JdbcTemplate JdbcTemplate; @Override public int findBookPriceByIsbn(String isbn) { String sql = "SELECT price FROM book WHERE isbn = ?"; return JdbcTemplate.queryForObject(sql, Integer.class, isbn); } @Override public void updateBookStock(String isbn) { //检查书的库存是否足够,若不够,则抛出异常 String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?"; int stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn); if (stock == 0) { throw new BookStockException("库存不足!"); } String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?"; JdbcTemplate.update(sql, isbn); } @Override public void updateUserAccount(String username, int price) { //检查余额是否不足,若不足,则抛出异常 String sql2 = "SELECT balance FROM account WHERE username = ?"; int balance = JdbcTemplate.queryForObject(sql2, Integer.class, username); if (balance < price) { throw new UserAccountException("余额不足!"); } String sql = "UPDATE account SET balance = balance - ? WHERE username = ?"; JdbcTemplate.update(sql, price, username); } }
BookShopService
package com.springinaction.transaction; public interface BookShopService { public void purchase(String username, String isbn); }
BookShopServiceImpl
package com.springinaction.transaction; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service("bookShopService") public class BookShopServiceImpl implements BookShopService { @Autowired private BookShopDao bookShopDao; /** * 1.添加事务注解 * 使用propagation 指定事务的传播行为,即当前的事务方法被另一个事务方法调用时如何使用事务。 * 默认取值为REQUIRED,即便用调用方法的事务 * REQUIRES_NEW:使用本身的事务,调用的事务方法的事务被挂起。 * * 2.使用isolation 指定事务的隔离级别,最经常使用的取值为READ_COMMITTED * 3.默认状况下 Spring 的声明式事务对全部的运行时异常进行回滚,也能够经过对应的属性进行设置。一般状况下,默认值便可。 * 4.使用readOnly 指定事务是否为只读。 表示这个事务只读取数据但不更新数据,这样能够帮助数据库引擎优化事务。若真的是一个只读取数据库值得方法,应设置readOnly=true * 5.使用timeOut 指定强制回滚以前事务能够占用的时间。 */ @Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED, noRollbackFor={UserAccountException.class}, readOnly=true, timeout=3) @Override public void purchase(String username, String isbn) { //1.获取书的单价 int price = bookShopDao.findBookPriceByIsbn(isbn); //2.更新书的库存 bookShopDao.updateBookStock(isbn); //3.更新用户余额 bookShopDao.updateUserAccount(username, price); } }
Cashier
package com.springinaction.transaction; import java.util.List; public interface Cashier { public void checkout(String username, List<String>isbns); }
CashierImpl:CashierImpl.checkout和bookShopService.purchase联合测试了事务的传播行为
package com.springinaction.transaction; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service("cashier") public class CashierImpl implements Cashier { @Autowired private BookShopService bookShopService; @Transactional @Override public void checkout(String username, List<String> isbns) { for(String isbn : isbns) { bookShopService.purchase(username, isbn); } } }
BookStockException
package com.springinaction.transaction; public class BookStockException extends RuntimeException { private static final long serialVersionUID = 1L; public BookStockException() { super(); // TODO Auto-generated constructor stub } public BookStockException(String arg0, Throwable arg1, boolean arg2, boolean arg3) { super(arg0, arg1, arg2, arg3); // TODO Auto-generated constructor stub } public BookStockException(String arg0, Throwable arg1) { super(arg0, arg1); // TODO Auto-generated constructor stub } public BookStockException(String arg0) { super(arg0); // TODO Auto-generated constructor stub } public BookStockException(Throwable arg0) { super(arg0); // TODO Auto-generated constructor stub } }
UserAccountException
package com.springinaction.transaction; public class UserAccountException extends RuntimeException { private static final long serialVersionUID = 1L; public UserAccountException() { super(); // TODO Auto-generated constructor stub } public UserAccountException(String arg0, Throwable arg1, boolean arg2, boolean arg3) { super(arg0, arg1, arg2, arg3); // TODO Auto-generated constructor stub } public UserAccountException(String arg0, Throwable arg1) { super(arg0, arg1); // TODO Auto-generated constructor stub } public UserAccountException(String arg0) { super(arg0); // TODO Auto-generated constructor stub } public UserAccountException(Throwable arg0) { super(arg0); // TODO Auto-generated constructor stub } }
测试类
package com.springinaction.transaction; import java.util.Arrays; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringTransitionTest { private ApplicationContext ctx = null; private BookShopDao bookShopDao = null; private BookShopService bookShopService = null; private Cashier cashier = null; { ctx = new ClassPathXmlApplicationContext("config/transaction.xml"); bookShopDao = ctx.getBean(BookShopDao.class); bookShopService = ctx.getBean(BookShopService.class); cashier = ctx.getBean(Cashier.class); } @Test public void testBookShopDaoFindPriceByIsbn() { System.out.println(bookShopDao.findBookPriceByIsbn("1001")); } @Test public void testBookShopDaoUpdateBookStock(){ bookShopDao.updateBookStock("1001"); } @Test public void testBookShopDaoUpdateUserAccount(){ bookShopDao.updateUserAccount("AA", 100); } @Test public void testBookShopService(){ bookShopService.purchase("AA", "1001"); } @Test public void testTransactionPropagation(){ cashier.checkout("AA", Arrays.asList("1001", "1002")); } }
OVER