原文出处: 张开涛
事务首先是一系列操作组成的工作单元,该工作单元内的操作是不可分割的,即要么所有操作都做,要么所有操作都不做,这就是事务。
事务必需满足ACID(原子性、一致性、隔离性和持久性)特性,缺一不可:
在实际项目开发中数据库操作一般都是并发执行的,即有多个事务并发执行,并发执行就可能遇到问题,目前常见的问题如下:
为了解决这些并发问题,需要通过数据库隔离级别来解决,在标准SQL规范中定义了四种隔离级别:
隔离级别越高,数据库事务并发执行性能越差,能处理的操作越少。因此在实际项目开发中为了考虑并发性能一般使用提交读隔离级别,它能避免丢失更新和脏读,尽管不可重复读和幻读不能避免,但可以在可能出现的场合使用悲观锁或乐观锁来解决这些问题。
数据库事务类型有本地事务和分布式事务:
Java事务类型有JDBC事务和JTA事务:
Java EE事务类型有本地事务和全局事务:
按是否通过编程实现事务有声明式事务和编程式事务;
Spring框架最核心功能之一就是事务管理,而且提供一致的事务管理抽象,这能帮助我们:
Spring支持声明式事务和编程式事务事务类型。
spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口
其中TransactionDefinition接口定义以下特性:
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用Hibernate的时候。
默认为读写事务。
Spring框架支持事务管理的核心是事务管理器抽象,对于不同的数据访问框架(如Hibernate)通过实现策略接口PlatformTransactionManager,从而能支持各种数据访问框架的事务管理,PlatformTransactionManager接口定义如下:
java代码:
public interface PlatformTransactionManager { TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException;}
TransactionDefinition接口定义如下:
java代码:
public interface TransactionDefinition { int getPropagationBehavior(); int getIsolationLevel(); int getTimeout(); boolean isReadOnly(); String getName();}
TransactionStatus接口定义如下:
java代码:
public interface TransactionStatus extends SavepointManager { boolean isNewTransaction(); boolean hasSavepoint(); void setRollbackOnly(); boolean isRollbackOnly(); void flush(); boolean isCompleted();}
Spring提供了许多内置事务管理器实现:
Spring不仅提供这些事务管理器,还提供对如JMS事务管理的管理器等,Spring提供一致的事务抽象如图9-1所示。
图9-1 Spring事务管理器
接下来让我们学习一下如何在Spring配置文件中定义事务管理器:
一、声明对本地事务的支持:
a)JDBC及iBATIS、MyBatis框架事务管理器
java代码:
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/></bean>
通过dataSource属性指定需要事务管理的单个javax.sql.DataSource对象。
b)Jdo事务管理器
java代码:
<bean id="txManager" class="org.springframework.orm.jdo.JdoTransactionManager"> <property name="persistenceManagerFactory" ref="persistenceManagerFactory"/></bean>
通过persistenceManagerFactory属性指定需要事务管理的javax.jdo.PersistenceManagerFactory对象。
c)Jpa事务管理器
java代码:
bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/></bean>
通过entityManagerFactory属性指定需要事务管理的javax.persistence.EntityManagerFactory对象。
还需要为entityManagerFactory对象指定jpaDialect属性,该属性所对应的对象指定了如何获取连接对象、开启事务、关闭事务等事务管理相关的行为。
java代码:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> …… <property name="jpaDialect" ref="jpaDialect"/></bean><bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
d)Hibernate事务管理器
java代码:
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"/></bean>
通过entityManagerFactory属性指定需要事务管理的org.hibernate.SessionFactory对象。
从上节编程式实现事务管理可以深刻体会到编程式事务的痛苦,即使通过代理配置方式也是不小的工作量。
本节将介绍声明式事务支持,使用该方式后最大的获益是简单,事务管理不再是令人痛苦的,而且此方式属于无侵入式,对业务逻辑实现无影响。
接下来先来看看声明式事务如何实现吧。
1、定义业务逻辑实现,此处使用ConfigUserServiceImpl和ConfigAddressServiceImpl:
2、定义配置文件(chapter9/service/ applicationContext-service-declare.xml):
2.1、XML命名空间定义,定义用于事务支持的tx命名空间和AOP支持的aop命名空间:
java代码:<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
2.2、业务实现配置,非常简单,使用以前定义的非侵入式业务实现:
java代码:<bean id="userService" class="cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl"> <property name="userDao" ref="userDao"/> <property name="addressService" ref="addressService"/></bean><bean id="addressService" class="cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl"> <property name="addressDao" ref="addressDao"/></bean>
2.3、事务相关配置:
java代码:<tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED"/> <tx:method name="*" propagation="REQUIRED" isolation="READ_COMMITTED" read-only="true"/> </tx:attributes></tx:advice>
java代码:
<tx:advice>:事务通知定义,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并通过< tx:attributes >指定具体需要拦截的方法; <tx:method name=”save*”>:表示将拦截以save开头的方法,被拦截的方法将应用配置的事务属性:propagation=”REQUIRED”表示传播行为是Required,isolation=”READ_COMMITTED”表示隔离级别是提交读;<tx:method name=”*”>:表示将拦截其他所有方法,被拦截的方法将应用配置的事务属性:propagation=”REQUIRED”表示传播行为是Required,isolation=”READ_COMMITTED”表示隔离级别是提交读,read-only=”true”表示事务只读;:AOP相关配置::切入点定义,定义名为”serviceMethod”的aspectj切入点,切入点表达式为”execution(* cn..chapter9.service..*.*(..))”表示拦截cn包及子包下的chapter9\. service包及子包下的任何类的任何方法;:Advisor定义,其中切入点为serviceMethod,通知为txAdvice。从配置中可以看出,将对cn包及子包下的chapter9\. service包及子包下的任何类的任何方法应用“txAdvice”通知指定的事务属性。
3、修改测试方法并测试该配置方式是否好用:
将TransactionTest 类的testServiceTransaction测试方法拷贝一份命名为testDeclareTransaction:
并在testDeclareTransaction测试方法内将:
4、执行测试,测试正常通过,说明该方式能正常工作,当调用save方法时将匹配到事务通知中定义的“”中指定的事务属性,而调用countAll方法时将匹配到事务通知中定义的“”中指定的事务属性。
声明式事务是如何实现事务管理的呢?还记不记得TransactionProxyFactoryBean实现配置式事务管理,配置式事务管理是通过代理方式实现,而声明式事务管理同样是通过AOP代理方式实现。
声明式事务通过AOP代理方式实现事务管理,利用环绕通知TransactionInterceptor实现事务的开启及关闭,而TransactionProxyFactoryBean内部也是通过该环绕通知实现的,因此可以认为是帮你定义了TransactionProxyFactoryBean,从而简化事务管理。
了解了实现方式后,接下来详细学习一下配置吧:
9.4.4配置详解 声明式事务管理通过配置来定义事务属性,配置方式如下所示:
java代码:<tx:advice id="……" transaction-manager="……"><tx:attributes> <tx:method name="……" propagation=" REQUIRED" isolation="READ_COMMITTED" timeout="-1" read-only="false" no-rollback-for="" rollback-for=""/> …… </tx:attributes></tx:advice><tx:advice>:id用于指定此通知的名字, transaction-manager用于指定事务管理器,默认的事务管理器名字为“transactionManager”;<tx:method>:用于定义事务属性即相关联的方法名;
name:定义与事务属性相关联的方法名,将对匹配的方法应用定义的事务属性,可以使用“”通配符来匹配一组或所有方法,如“save”将匹配以save开头的方法,而“*”将匹配所有方法;
propagation:事务传播行为定义,默认为“REQUIRED”,表示Required,其值可以通过TransactionDefinition的静态传播行为变量的“PROPAGATION”后边部分指定,如“TransactionDefinition.PROPAGATIONREQUIRED”可以使用“REQUIRED”指定;
isolation:事务隔离级别定义;默认为“DEFAULT”,其值可以通过TransactionDefinition的静态隔离级别变量的“ISOLATION”后边部分指定,如“TransactionDefinition. ISOLATIONDEFAULT”可以使用“DEFAULT”指定:
timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
read-only:事务只读设置,默认为false,表示不是只读;
rollback-for:需要触发回滚的异常定义,以“,”分割,默认任何RuntimeException 将导致事务回滚,而任何Checked Exception 将不导致事务回滚;异常名字定义和TransactionProxyFactoryBean中含义一样
no-rollback-for:不被触发进行回滚的 Exception(s);以“,”分割;异常名字定义和TransactionProxyFactoryBean中含义一样;
记不记得在配置方式中为了解决“自我调用”而导致的不能设置正确的事务属性问题,使用“((IUserService)AopContext.currentProxy()).otherTransactionMethod()”方式解决,在声明式事务要得到支持需要使用来开启。
9.4.5 多事务语义配置及最佳实践 什么是多事务语义?说白了就是为不同的Bean配置不同的事务属性,因为我们项目中不可能就几个Bean,而可能很多,这可能需要为Bean分组,为不同组的Bean配置不同的事务语义。在Spring中,可以通过配置多切入点和多事务通知并通过不同方式组合使用即可。
1、首先看下声明式事务配置的最佳实践吧:
<tx:advice id="txAdvice" transaction-manager="txManager"><tx:attributes> <tx:method name="save*" propagation="REQUIRED" /> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="create*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="merge*" propagation="REQUIRED" /> <tx:method name="del*" propagation="REQUIRED" /> <tx:method name="remove*" propagation="REQUIRED" /> <tx:method name="put*" propagation="REQUIRED" /> <tx:method name="get*" propagation="SUPPORTS" read-only="true" /> <tx:method name="count*" propagation="SUPPORTS" read-only="true" /> <tx:method name="find*" propagation="SUPPORTS" read-only="true" /> <tx:method name="list*" propagation="SUPPORTS" read-only="true" /> <tx:method name="*" propagation="SUPPORTS" read-only="true" /> </tx:attributes></tx:advice>
该声明式事务配置可以应付常见的CRUD接口定义,并实现事务管理,我们只需修改切入点表达式来拦截我们的业务实现从而对其应用事务属性就可以了,如果还有更复杂的事务属性直接添加即可,即
如果我们有一个batchSaveOrUpdate方法需要“REQUIRES_NEW”事务传播行为,则直接添加如下配置即可:
java代码:12、接下来看一下多事务语义配置吧,声明式事务最佳实践中已经配置了通用事务属性,因此可以针对需要其他事务属性的业务方法进行特例化配置:
java代码:<tx:advice id="noTxAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="*" propagation="NEVER" /> </tx:attributes></tx:advice>
该声明将对切入点匹配的方法所在事务应用“Never”传播行为。
多事务语义配置时,切入点一定不要叠加,否则将应用两次事务属性,造成不必要的错误及麻烦。
对声明式事务管理,Spring提供基于@Transactional注解方式来实现,但需要Java 5+。
注解方式是最简单的事务配置方式,可以直接在Java源代码中声明事务属性,且对于每一个业务类或方法如果需要事务都必须使用此注解。
接下来学习一下注解事务的使用吧:
1、定义业务逻辑实现:
package cn.javass.spring.chapter9.service.impl;//省略importpublic class AnnotationUserServiceImpl implements IUserService { private IUserDao userDao; private IAddressService addressService; public void setUserDao(IUserDao userDao) { this.userDao = userDao; } public void setAddressService(IAddressService addressService) { this.addressService = addressService; } @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED) @Override public void save(final UserModel user) { userDao.save(user); user.getAddress().setUserId(user.getId()); addressService.save(user.getAddress()); } @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, readOnly=true) @Override public int countAll() { return userDao.countAll(); }}
2、定义配置文件(chapter9/service/ applicationContext-service-annotation.xml):
2.1、XML命名空间定义,定义用于事务支持的tx命名空间和AOP支持的aop命名空间:
java代码:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
2.2、业务实现配置,非常简单,使用以前定义的非侵入式业务实现:
java代码:<bean id="userService" class="cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl"> <property name="userDao" ref="userDao"/> <property name="addressService" ref="addressService"/></bean><bean id="addressService" class="cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl"> <property name="addressDao" ref="addressDao"/></bean>
2.3、事务相关配置:
java代码:1<tx:annotation-driven transaction-manager="txManager"/>使用如上配置已支持声明式事务。
3、修改测试方法并测试该配置方式是否好用:
将TransactionTest 类的testServiceTransaction测试方法拷贝一份命名为testAnntationTransactionTest:
classpath:chapter9/service/applicationContext-service-annotation.xml"
userService.save(user);try { userService.save(user); Assert.fail();} catch (RuntimeException e) {}Assert.assertEquals(0, userService.countAll());Assert.assertEquals(0, addressService.countAll());
4、执行测试,测试正常通过,说明该方式能正常工作,因为在AnnotationAddressServiceImpl类的save方法中抛出异常,因此事务需要回滚,所以两个countAll操作都返回0。
9.4.7 @Transactional配置详解 Spring提供的用于开启对注解事务管理的支持,从而能识别Bean类上的@Transactional注解元数据,其具有以下属性:
transaction-manager:指定事务管理器名字,默认为transactionManager,当使用其他名字时需要明确指定;proxy-target-class:表示将使用的代码机制,默认false表示使用JDK代理,如果为true将使用CGLIB代理 order:定义事务通知顺序,默认Ordered.LOWEST_PRECEDENCE,表示将顺序决定权交给AOP来处理。Spring使用@Transactional 来指定事务属性,可以在接口、类或方法上指定,如果类和方法上都指定了@Transactional ,则方法上的事务属性被优先使用,具体属性如下:
value:指定事务管理器名字,默认使用指定的事务管理器,用于支持多事务管理器环境;propagation:指定事务传播行为,默认为Required,使用Propagation.REQUIRED指定;isolation:指定事务隔离级别,默认为“DEFAULT”,使用Isolation.DEFAULT指定;readOnly:指定事务是否只读,默认false表示事务非只读;timeout:指定事务超时时间,以秒为单位,默认-1表示事务超时将依赖于底层事务系统;rollbackFor:指定一组异常类,遇到该类异常将回滚事务;rollbackForClassname:指定一组异常类名字,其含义与中的rollback-for属性语义完全一样;noRollbackFor:指定一组异常类,即使遇到该类异常也将提交事务,即不回滚事务;noRollbackForClassname:指定一组异常类名字,其含义与中的no-rollback-for属性语义完全一样;Spring提供的@Transactional 注解事务管理内部同样利用环绕通知TransactionInterceptor实现事务的开启及关闭。
使用@Transactional注解事务管理需要特别注意以下几点:
如果在接口、实现类或方法上都指定了@Transactional 注解,则优先级顺序为方法>实现类>接口;建议只在实现类或实现类的方法上使用@Transactional,而不要在接口上使用,这是因为如果使用JDK代理机制是没问题,因为其使用基于接口的代理;而使用使用CGLIB代理机制时就会遇到问题,因为其使用基于类的代理而不是接口,这是因为接口上的@Transactional注解是“不能继承的”;
具体请参考基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。
在Spring代理机制下(不管是JDK动态代理还是CGLIB代理),“自我调用”同样不会应用相应的事务属性,其语义和中一样;
默认只对RuntimeException异常回滚;
在使用Spring代理时,默认只有在public可见度的方法的@Transactional 注解才是有效的,其它可见度(protected、private、包可见)的方法上即使有@Transactional 注解也不会应用这些事务属性的,Spring也不会报错,如果你非要使用非公共方法注解事务管理的话,可考虑使用AspectJ。