9.4  声明式事务

9.4.1  声明式事务概述

从上节编程式实现事务管理可以深刻体会到编程式事务的痛苦,即使通过代理配置方式也是不小的工作量。

本节将介绍声明式事务支持,使用该方式后最大的获益是简单,事务管理不再是令人痛苦的,而且此方式属于无侵入式,对业务逻辑实现无影响。

接下来先来看看声明式事务如何实现吧。

9.4.2  声明式实现事务管理

1、定义业务逻辑实现,此处使用ConfigUserServiceImpl和ConfigAddressServiceImpl:

2、定义配置文件(chapter9/service/ applicationContext-service-declare.xml):

2.1、XML命名空间定义,定义用于事务支持的tx命名空间和AOP支持的aop命名空间:

java代码:

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:tx="http://www.springframework.org/schema/tx"
  4. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xsi:schemaLocation="
  6. http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  8. http://www.springframework.org/schema/tx
  9. http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
  10. http://www.springframework.org/schema/aop
  11. http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

2.2、业务实现配置,非常简单,使用以前定义的非侵入式业务实现:

java代码:

  1. <bean id="userService" class="cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl">
  2. <property name="userDao" ref="userDao"/>
  3. <property name="addressService" ref="addressService"/>
  4. </bean>
  5. <bean id="addressService" class="cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl">
  6. <property name="addressDao" ref="addressDao"/>
  7. </bean>

2.3、事务相关配置:

java代码:

  1. <tx:advice id="txAdvice" transaction-manager="txManager">
  2. <tx:attributes>
  3. <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
  4. <tx:method name="*" propagation="REQUIRED" isolation="READ_COMMITTED" read-only="true"/>
  5. </tx:attributes>
  6. </tx:advice>

 

java代码:

  1. <aop:config>
  2. <aop:pointcut id="serviceMethod" expression="execution(* cn..chapter9.service..*.*(..))"/>
  3. <aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice"/>
  4. </aop:config>
  • <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:config>:AOP相关配置:
  • <aop:pointcut/>:切入点定义,定义名为"serviceMethod"的aspectj切入点,切入点表达式为"execution(* cn..chapter9.service..*.*(..))"表示拦截cn包及子包下的chapter9. service包及子包下的任何类的任何方法;
  • <aop:advisor>:Advisor定义,其中切入点为serviceMethod,通知为txAdvice。

从配置中可以看出,将对cn包及子包下的chapter9. service包及子包下的任何类的任何方法应用“txAdvice”通知指定的事务属性。

3、修改测试方法并测试该配置方式是否好用:

将TransactionTest 类的testServiceTransaction测试方法拷贝一份命名为testDeclareTransaction:

并在testDeclareTransaction测试方法内将:

java代码:

  1. classpath:chapter9/service/applicationContext-service.xml"

替换为:

java代码:

  1. 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,从而简化事务管理。

了解了实现方式后,接下来详细学习一下配置吧:

9.4.4  <tx:advice/>配置详解

声明式事务管理通过配置<tx:advice/>来定义事务属性,配置方式如下所示:

java代码:

  1. <tx:advice id="……" transaction-manager="……">
  2. <tx:attributes>
  3. <tx:method name="……"
  4. propagation=" REQUIRED"
  5. isolation="READ_COMMITTED"
  6. timeout="-1"
  7. read-only="false"
  8. no-rollback-for=""
  9. rollback-for=""/>
  10. ……
  11. </tx:attributes>
  12. </tx:advice>
  • <tx:advice>:id用于指定此通知的名字, transaction-manager用于指定事务管理器,默认的事务管理器名字为“transactionManager”;
  • <tx:method>:用于定义事务属性即相关联的方法名;

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">来开启。

9.4.5  多事务语义配置及最佳实践

什么是多事务语义?说白了就是为不同的Bean配置不同的事务属性,因为我们项目中不可能就几个Bean,而可能很多,这可能需要为Bean分组,为不同组的Bean配置不同的事务语义。在Spring中,可以通过配置多切入点和多事务通知并通过不同方式组合使用即可。

       1、首先看下声明式事务配置的最佳实践吧:

java代码:

  1. <tx:advice id="txAdvice" transaction-manager="txManager">
  2. <tx:attributes>
  3. <tx:method name="save*" propagation="REQUIRED" />
  4. <tx:method name="add*" propagation="REQUIRED" />
  5. <tx:method name="create*" propagation="REQUIRED" />
  6. <tx:method name="insert*" propagation="REQUIRED" />
  7. <tx:method name="update*" propagation="REQUIRED" />
  8. <tx:method name="merge*" propagation="REQUIRED" />
  9. <tx:method name="del*" propagation="REQUIRED" />
  10. <tx:method name="remove*" propagation="REQUIRED" />
  11. <tx:method name="put*" propagation="REQUIRED" />
  12. <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
  13. <tx:method name="count*" propagation="SUPPORTS" read-only="true" />
  14. <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
  15. <tx:method name="list*" propagation="SUPPORTS" read-only="true" />
  16. <tx:method name="*" propagation="SUPPORTS" read-only="true" />
  17. </tx:attributes>
  18. </tx:advice>
  19. <aop:config>
  20. <aop:pointcut id="txPointcut" expression="execution(* cn.javass..service.*.*(..))" />
  21. <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />
  22. </aop:config>

该声明式事务配置可以应付常见的CRUD接口定义,并实现事务管理,我们只需修改切入点表达式来拦截我们的业务实现从而对其应用事务属性就可以了,如果还有更复杂的事务属性直接添加即可,即

如果我们有一个batchSaveOrUpdate方法需要“REQUIRES_NEW”事务传播行为,则直接添加如下配置即可:

java代码:

  1. <tx:method name="batchSaveOrUpdate" propagation="REQUIRES_NEW" />

2、接下来看一下多事务语义配置吧,声明式事务最佳实践中已经配置了通用事务属性,因此可以针对需要其他事务属性的业务方法进行特例化配置:

java代码:

  1. <tx:advice id="noTxAdvice" transaction-manager="txManager">
  2. <tx:attributes>
  3. <tx:method name="*" propagation="NEVER" />
  4. </tx:attributes>
  5. </tx:advice>
  6. <aop:config>
  7. <aop:pointcut id="noTxPointcut" expression="execution(* cn.javass..util.*.*())" />
  8. <aop:advisor advice-ref="noTxPointcut" pointcut-ref="noTxAdvice" />
  9. </aop:config>

该声明将对切入点匹配的方法所在事务应用“Never”传播行为。

多事务语义配置时,切入点一定不要叠加,否则将应用两次事务属性,造成不必要的错误及麻烦。

9.4.6  @Transactional实现事务管理

对声明式事务管理,Spring提供基于@Transactional注解方式来实现,但需要Java 5+。

注解方式是最简单的事务配置方式,可以直接在Java源代码中声明事务属性,且对于每一个业务类或方法如果需要事务都必须使用此注解。

接下来学习一下注解事务的使用吧:

1、定义业务逻辑实现:

java代码:

  1. package cn.javass.spring.chapter9.service.impl;
  2. //省略import
  3. public class AnnotationUserServiceImpl implements IUserService {
  4. private IUserDao userDao;
  5. private IAddressService addressService;
  6. public void setUserDao(IUserDao userDao) {
  7. this.userDao = userDao;
  8. }
  9. public void setAddressService(IAddressService addressService) {
  10. this.addressService = addressService;
  11. }
  12. @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED)
  13. @Override
  14. public void save(final UserModel user) {
  15. userDao.save(user);
  16. user.getAddress().setUserId(user.getId());
  17. addressService.save(user.getAddress());
  18. }
  19. @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, readOnly=true)
  20. @Override
  21. public int countAll() {
  22. return userDao.countAll();
  23. }
  24. }

2、定义配置文件(chapter9/service/ applicationContext-service-annotation.xml):

2.1、XML命名空间定义,定义用于事务支持的tx命名空间和AOP支持的aop命名空间:

java代码:

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:tx="http://www.springframework.org/schema/tx"
  4. xmlns:aop="http://www.springframework.org/schema/aop"
  5. xsi:schemaLocation="
  6. http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  8. http://www.springframework.org/schema/tx
  9. http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
  10. http://www.springframework.org/schema/aop
  11. http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

2.2、业务实现配置,非常简单,使用以前定义的非侵入式业务实现:

java代码:

  1. <bean id="userService" class="cn.javass.spring.chapter9.service.impl.ConfigUserServiceImpl">
  2. <property name="userDao" ref="userDao"/>
  3. <property name="addressService" ref="addressService"/>
  4. </bean>
  5. <bean id="addressService" class="cn.javass.spring.chapter9.service.impl.ConfigAddressServiceImpl">
  6. <property name="addressDao" ref="addressDao"/>
  7. </bean>

2.3、事务相关配置:

java代码:

  1. <tx:annotation-driven transaction-manager="txManager"/>

使用如上配置已支持声明式事务。

3、修改测试方法并测试该配置方式是否好用:

将TransactionTest 类的testServiceTransaction测试方法拷贝一份命名为testAnntationTransactionTest:

将测试代码片段:

java代码:

  1. classpath:chapter9/service/applicationContext-service.xml"

替换为:

java代码:

  1. classpath:chapter9/service/applicationContext-service-annotation.xml"

将测试代码段



java代码:

  1. userService.save(user);

替换为:



java代码:

  1. try {
  2. userService.save(user);
  3. Assert.fail();
  4. } catch (RuntimeException e) {
  5. }
  6. , userService.countAll());
  7. , addressService.countAll());

4、执行测试,测试正常通过,说明该方式能正常工作,因为在AnnotationAddressServiceImpl类的save方法中抛出异常,因此事务需要回滚,所以两个countAll操作都返回0。

9.4.7  @Transactional配置详解

Spring提供的<tx:annotation-driven/>用于开启对注解事务管理的支持,从而能识别Bean类上的@Transactional注解元数据,其具有以下属性:

  • transaction-manager:指定事务管理器名字,默认为transactionManager,当使用其他名字时需要明确指定;
  • proxy-target-class:表示将使用的代码机制,默认false表示使用JDK代理,如果为true将使用CGLIB代理
  • order:定义事务通知顺序,默认Ordered.LOWEST_PRECEDENCE,表示将顺序决定权交给AOP来处理。

Spring使用@Transaction来指定事务属性,可以在接口、类或方法上指定,如果类和方法上都指定了@Transaction,则方法上的事务属性被优先使用,具体属性如下:

  • value:指定事务管理器名字,默认使用<tx:annotation-driven/>指定的事务管理器,用于支持多事务管理器环境;
  • propagation:指定事务传播行为,默认为Required,使用Propagation.REQUIRED指定;
  • isolation:指定事务隔离级别,默认为“DEFAULT”,使用Isolation.DEFAULT指定;
  • readOnly:指定事务是否只读,默认false表示事务非只读;
  • timeout:指定事务超时时间,以秒为单位,默认-1表示事务超时将依赖于底层事务系统;
  • rollbackFor:指定一组异常类,遇到该类异常将回滚事务;
  • rollbackForClassname:指定一组异常类名字,其含义与<tx:method>中的rollback-for属性语义完全一样;
  • noRollbackFor:指定一组异常类,即使遇到该类异常也将提交事务,即不回滚事务;
  • noRollbackForClassname:指定一组异常类名字,其含义与<tx:method>中的no-rollback-for属性语义完全一样;

Spring提供的@Transaction注解事务管理内部同样利用环绕通知TransactionInterceptor实现事务的开启及关闭。

使用@Transactional注解事务管理需要特别注意以下几点:

  • 如果在接口、实现类或方法上都指定了@Transactional 注解,则优先级顺序为方法>实现类>接口;
  • 建议只在实现类或实现类的方法上使用@Transactional,而不要在接口上使用,这是因为如果使用JDK代理机制是没问题,因为其使用基于接口的代理;而使用使用CGLIB代理机制时就会遇到问题,因为其使用基于类的代理而不是接口,这是因为接口上的@Transactional注解是“不能继承的”
  • 在Spring代理机制下(不管是JDK动态代理还是CGLIB代理),“自我调用”同样不会应用相应的事务属性,其语义和<tx:tags>中一样;
  • 默认只对RuntimeException异常回滚;
  • 在使用Spring代理时,默认只有在public可见度的方法的@Transactional 注解才是有效的,其它可见度(protected、private、包可见)的方法上即使有@Transactional 注解也不会应用这些事务属性的,Spring也不会报错,如果你非要使用非公共方法注解事务管理的话,可考虑使用AspectJ。

9.4.9  与其他AOP通知协作

Spring声明式事务实现其实就是Spring AOP+线程绑定实现,利用AOP实现开启和关闭事务,利用线程绑定(ThreadLocal)实现跨越多个方法实现事务传播。

由于我们不可能只使用一个事务通知,可能还有其他类型事务通知,而且如果这些通知中需要事务支持怎么办?这就牵扯到通知执行顺序的问题上了,因此如果可能与其他AOP通知协作的话,而且这些通知中需要使用声明式事务管理支持,事务通知应该具有最高优先级。

9.4.10  声明式or编程式

编程式事务时不推荐的,即使有很少事务操作,Spring发展到现在,没有理由使用编程式事务,只有在为了深入理解Spring事务管理才需要学习编程式事务使用。

推荐使用声明式事务,而且强烈推荐使用<tx:tags>方式的声明式事务,因为其是无侵入代码的,可以配置模板化的事务属性并运用到多个项目中。

而@Transaction注解事务,可以使用,不过作者更倾向于使用<tx:tags>声明式事务。

能保证项目正常工作的事务配置就是最好的。

9.4.11  混合事务管理

所谓混合事务管理就是混合多种数据访问技术使用,如混合使用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查询出来的数据不一致等。

因此不建议使用这种混合使用和混合事务管理。

Spring的事务 之 9.4 声明式事务 ——跟我学spring3的更多相关文章

  1. 开涛spring3(9.4) - Spring的事务 之 9.4 声明式事务

    9.4  声明式事务 9.4.1  声明式事务概述 从上节编程式实现事务管理可以深刻体会到编程式事务的痛苦,即使通过代理配置方式也是不小的工作量. 本节将介绍声明式事务支持,使用该方式后最大的获益是简 ...

  2. Spring boot 入门五:springboot 开启声明式事务

    springboot开启事务很简单,只需要一个注解@Transactional 就可以了.因为在springboot中已经默认对jpa.jdbc.mybatis开启了事务.这里以spring整合myb ...

  3. 【Spring】Spring的事务管理 - 2、声明式事务管理(实现基于XML、Annotation的方式。)

    声明式事务管理 文章目录 声明式事务管理 基于XML方式的声明式事务 基于Annotation方式的声明式事务 简单记录 - 简单记录-Java EE企业级应用开发教程(Spring+Spring M ...

  4. 【spring 7】spring和Hibernate的整合:声明式事务

    一.声明式事务简介 Spring 的声明式事务管理在底层是建立在 AOP 的基础之上的.其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者 ...

  5. Spring(四)-- JdbcTemplate、声明式事务

    1.Spring提供的一个操作数据库的技术JdbcTemplate,是对Jdbc的封装.语法风格非常接近DBUtils.   JdbcTemplate可以直接操作数据库,加快效率,而且学这个JdbcT ...

  6. Spring事务:一种编程式事务,三种声明式事务

    事务隔离级别 隔离级别是指若干个并发的事务之间的隔离程度.TransactionDefinition 接口中定义了五个表示隔离级别的常量: TransactionDefinition.ISOLATIO ...

  7. Spring Boot2 系列教程 (十) | 实现声明式事务

    前言 如题,今天介绍 SpringBoot 的 声明式事务. Spring 的事务机制 所有的数据访问技术都有事务处理机制,这些技术提供了 API 用于开启事务.提交事务来完成数据操作,或者在发生错误 ...

  8. Spring源码窥探之:声明式事务

    1. 导入驱动,连接池,jdbc和AOP的依赖 <!-- c3p0数据库连接池 --> <dependency> <groupId>c3p0</groupId ...

  9. 【spring】之事物配置,声明式事务管理和基于@Transactional注解的使用

    http://blog.csdn.net/bao19901210/article/details/41724355

随机推荐

  1. java设计模式---状态模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述状态(State)模式的: 状态模式,又称状态对象模式(Pattern of Objects for States),状态模式是对象的行为 ...

  2. 12、Libgdx的图像之全屏和垂直同步

    (官网:www.libgdx.cn) 检测当前设置 判断是否设置全屏,可以通过如下方式: boolean fullscreen = Gdx.graphics.isFullscreen(); 设置全屏和 ...

  3. 从Perforce到Git的迁移

    公司经过多次兼并.收购之后,开发团队使用的工具自然会出现鱼龙混杂的现象.就拿源代码管理工具来说,我们同时在使用的就有Perforce.Team Foundation.Subversion等.为了节省成 ...

  4. 【Unity Shaders】Reflecting Your World —— Unity3D中简单的Cubemap反射

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  5. Touch Handling in Cocos2D 3.x(七)

    在touchMoved方法中寻找触摸在父节点(CCScene)中的位置并且移动CCDragSprite到其父节点的相应位置中去. 在我们在动作中观赏拖放机制之前,我们需要使用这个新实现的类.打开Mai ...

  6. Gradle实现的两种简单的多渠道打包方法

    本来计划今天发Android的官方技术文档的翻译--<Gradle插件用户指南>的第五章的,不过由于昨天晚上没译完,还差几段落,所以只好推后了. 今天就说一下使用Gradle进行类似友盟这 ...

  7. 解决Plugin is too old,please update to a more recent version,or set ANDROID_DAILY_OVERRIDE..

    今天遇到了很诡异的事情. 昨天晚上还好好的工程今天就挂了,提示如下错误: Plugin is too old,please update to a more recent version,or set ...

  8. 【一天一道LeetCode】#20. Valid Parentheses

    一天一道LeetCode系列 (一)题目 Given a string containing just the characters '(', ')', '{', '}', '[' and ']', ...

  9. 《java入门第一季》之面向对象

    之前的代码描述都是基于面向过程的,今天开始正式进入面向对象的世界.什么是对象?首先要区分类和对象的概念-- 现实世界中是如何描述一个事物的? 举例:学生 姓名,年龄,性别... 学习,吃饭,睡觉 属性 ...

  10. 嵌入式C语言查表法的项目应用

    嵌入式C实战项目开发技巧:如果对一个有规律的数组表进行位移操作 就像下面的这个表 之前写过上面这个标题的一篇文章,讲的是以位移的方式去遍历表中的数据,效率非常高,但是,如果要实现一个乱序的流水灯或者跑 ...