从上节编程式实现事务管理可以深刻体会到编程式事务的痛苦,即使通过代理配置方式也是不小的工作量。
本节将介绍声明式事务支持,使用该方式后最大的获益是简单,事务管理不再是令人痛苦的,而且此方式属于无侵入式,对业务逻辑实现无影响。
接下来先来看看声明式事务如何实现吧。
1 、定义业务逻辑实现,此处使用ConfigUserServiceImpl 和ConfigAddressServiceImpl :
2 、定义配置文件(chapter9/service/ applicationContext-service-declare.xml ):
2.1 、XML 命名空间定义,定义用于事务支持的tx 命名空间和AOP 支持的aop 命名空间:
<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 、业务实现配置,非常简单,使用以前定义的非侵入式业务实现:
<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 、事务相关配置:
<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>
<aop:config> <aop:pointcut id="serviceMethod" expression="execution(* cn..chapter9.service..*.*(..))"/> <aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice"/> </aop:config>
从配置中可以看出,将对cn包及子包下的chapter9. service包及子包下的任何类的任何方法应用“txAdvice”通知指定的事务属性。
3 、修改测试方法并测试该配置方式是否好用:
将TransactionTest 类的testServiceTransaction测试方法拷贝一份命名为testDeclareTransaction:
并在testDeclareTransaction测试方法内将:
classpath:chapter9/service/applicationContext-service.xml"
替换为:
classpath:chapter9/service/applicationContext-service-declare.xml"
4 、执行测试,测试正常通过,说明该方式能正常工作, 当调用save方法时将匹配到事务通知中定义的“<tx:method name=”save*”>”中指定的事务属性,而调用countAll方法时将匹配到事务通知中定义的“<tx:method name=”*”>”中指定的事务属性。
声明式事务是如何实现事务管理的呢?还记不记得TransactionProxyFactoryBean实现配置式事务管理,配置式事务管理是通过代理方式实现,而声明式事务管理同样是通过AOP代理方式实现。
声明式事务通过AOP代理方式实现事务管理,利用环绕通知TransactionInterceptor实现事务的开启及关闭,而TransactionProxyFactoryBean内部也是通过该环绕通知实现的,因此可以认为是<tx:tags/>帮你定义了TransactionProxyFactoryBean,从而简化事务管理。
了解了实现方式后,接下来详细学习一下配置吧:
声明式事务管理通过配置<tx:advice/>来定义事务属性,配置方式如下所示:
<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>
name : 定义与事务属性相关联的方法名,将对匹配的方法应用定义的事务属性,可以使用“*”通配符来匹配一组或所有方法,如“save*”将匹配以save开头的方法,而“*”将匹配所有方法;
propagation:事务传播行为定义,默认为“REQUIRED”,表示Required,其值可以通过TransactionDefinition的静态传播行为变量的“PROPAGATION_”后边部分指定,如“TransactionDefinition.PROPAGATION_REQUIRED”可以使用“REQUIRED”指定;
isolation:事务隔离级别定义;默认为“DEFAULT”,其值可以通过TransactionDefinition的静态隔离级别变量的“ISOLATION_”后边部分指定,如“TransactionDefinition. ISOLATION_DEFAULT”可以使用“DEFAULT”指定:
timeout : 事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
read-only : 事务只读设置,默认为false,表示不是只读;
rollback-for : 需要触发回滚的异常定义,以“,”分割,默认任何RuntimeException 将导致事务回滚,而任何Checked Exception 将不导致事务回滚;异常名字定义和TransactionProxyFactoryBean中含义一样
no-rollback-for : 不被触发进行回滚的 Exception(s);以“,”分割;异常名字定义和TransactionProxyFactoryBean中含义一样;
记不记得在配置方式中为了解决“自我调用”而导致的不能设置正确的事务属性问题,使用“ ((IUserService)AopContext.currentProxy()).otherTransactionMethod() ” 方式解决,在声明式事务要得到支持需要使用<aop:config expose-proxy=”true”>来开启。
什么是多事务语义?说白了就是为不同的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> <aop:config> <aop:pointcut id="txPointcut" expression="execution(* cn.javass..service.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" /> </aop:config>
该声明式事务配置可以应付常见的CRUD接口定义,并实现事务管理,我们只需修改切入点表达式来拦截我们的业务实现从而对其应用事务属性就可以了,如果还有更复杂的事务属性直接添加即可,即
如果我们有一个batchSaveOrUpdate方法需要“REQUIRES_NEW”事务传播行为,则直接添加如下配置即可:
<tx:method name="batchSaveOrUpdate" propagation="REQUIRES_NEW" />
2 、接下来看一下多事务语义配置吧, 声明式事务最佳实践中已经配置了通用事务属性,因此可以针对需要其他事务属性的业务方法进行特例化配置:
<tx:advice id="noTxAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="*" propagation="NEVER" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="noTxPointcut" expression="execution(* cn.javass..util.*.*())" /> <aop:advisor advice-ref="noTxPointcut" pointcut-ref="noTxAdvice" /> </aop:config>
该声明将对切入点匹配的方法所在事务应用“Never”传播行为。
多事务语义配置时,切入点一定不要叠加,否则将应用两次事务属性,造成不必要的错误及麻烦。
对声明式事务管理,Spring提供基于@Transactional注解方式来实现,但需要Java 5+。
注解方式是最简单的事务配置方式,可以直接在Java源代码中声明事务属性,且对于每一个业务类或方法如果需要事务都必须使用此注解。
接下来学习一下注解事务的使用吧:
1 、定义业务逻辑实现:
package cn.javass.spring.chapter9.service.impl; //省略import public 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 命名空间:
<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 、业务实现配置,非常简单,使用以前定义的非侵入式业务实现:
<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 、事务相关配置:
<tx:annotation-driven transaction-manager="txManager"/>
使用如上配置已支持声明式事务。
3 、修改测试方法并测试该配置方式是否好用:
将TransactionTest 类的testServiceTransaction测试方法拷贝一份命名为testAnntationTransactionTest:
将测试代码片段:
classpath:chapter9/service/applicationContext-service.xml"
替换为:
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。
Spring提供的<tx:annotation-driven/>用于开启对注解事务管理的支持,从而能识别Bean类上的@Transactional注解元数据,其具有以下属性:
Spring使用@Transaction来指定事务属性,可以在接口、类或方法上指定,如果类和方法上都指定了@Transaction,则方法上的事务属性被优先使用,具体属性如下:
Spring提供的@Transaction注解事务管理内部同样利用环绕通知TransactionInterceptor实现事务的开启及关闭。
使用@Transactional注解事务管理需要特别注意以下几点:
具体请参考 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。
Spring声明式事务实现其实就是Spring AOP+线程绑定实现,利用AOP实现开启和关闭事务,利用线程绑定(ThreadLocal)实现跨越多个方法实现事务传播。
由于我们不可能只使用一个事务通知,可能还有其他类型事务通知,而且如果这些通知中需要事务支持怎么办?这就牵扯到通知执行顺序的问题上了,因此如果可能与其他AOP通知协作的话,而且这些通知中需要使用声明式事务管理支持,事务通知应该具有最高优先级。
编程式事务时不推荐的,即使有很少事务操作,Spring发展到现在,没有理由使用编程式事务,只有在为了深入理解Spring事务管理才需要学习编程式事务使用。
推荐使用声明式事务,而且强烈推荐使用<tx:tags>方式的声明式事务,因为其是无侵入代码的,可以配置模板化的事务属性并运用到多个项目中。
而@Transaction注解事务,可以使用,不过作者更倾向于使用<tx:tags>声明式事务。
能保证项目正常工作的事务配置就是最好的。
所谓混合事务管理就是混合多种数据访问技术使用,如混合使用Spring JDBC + Hibernate,接下来让我们学习一下常见混合事务管理:
1、 Hibernate + Spring JDBC/iBATIS:使用HibernateTransactionManager即可支持;
2、 JPA + Spring JDBC/iBATIS:使用JpaTransactionManager即可支持;
3、 JDO + Spring JDBC/iBATIS:使用JtaTransactionManager即可支持;
混合事务管理最大问题在于如果我们使用第三方ORM框架,如Hibernate,会遇到一级及二级缓存问题,尤其是二级缓存可能造成如使用Spring JDBC和Hibernate查询出来的数据不一致等。
因此不建议使用这种混合使用和混合事务管理。