Spring-声明式事务管理

spring框架的声明式事务管理是通过spring AOP实现的,与EJB CMT类似,可以将事务行为指定到单个方法级别,可以在事务上下文中调用setRollbackOnly()方法。两种方式区别如下:

  • 与JTA绑定的EJB CMT不同,spring框架的声明式事务管理适用于任何环境。通过简单的调整配置文件,可以使用JDBC、JPA或hibernate与JTA事务或本地事务协同工作。
  • 可以将spring框架声明式事务管理应用于任何类,而不仅仅是如EJB的特殊类。
  • spring框架提供了声明式的回滚规则,它提供了回滚规则的编程式和声明式支持。
  • spring框架能够通过使用AOP来自定义事务行为,如可以在事务回滚的情况下插入自定义行为。而使用EJB CMT则不同,除了setRollbackOnly()外,不能影响容器的事务管理。
  • spring框架不支持跨远程调用传播事务上下文。如需此功能建议使用EJB。通常情况下,使用事务的跨越远程调用的机会很少。
    回滚规则指定了哪些异常会导致自动回滚,可以在配置文件中以声明方式指定。尽管可以调用TransactionStatus对象上的setRollbackOnly()来回滚当前事务,但通常可以指定MyApplicationException必须总是导致回滚的规则。优点是业务对象不依赖于事务基础设施。
1、声明式事务管理

spring框架的声明式事务是通过AOP代理来启用支持的,并且事务性的Advice由元数据(基于XML或基于注解)驱动。AOP与事务元数据的结合产生了AOP代理,该代理使用TransactionInterceptor和适当的PlatformTransactionManager实现来驱动方法调用周围的事务。
示例:
1、导入依赖jar

<properties>
  <spring.version>5.1.1.RELEASE</spring.version>
</properties>
<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>${spring.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${spring.version}</version>
  </dependency>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.6.2</version>
  </dependency>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-jcl</artifactId>
    <version>2.6.2</version>
  </dependency>
  <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.6.2</version>
  </dependency>
  <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.5.0</version>
  </dependency>
  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.196</version>
    <scope>runtime</scope>
  </dependency>
</dependencies>

2、定义领域模型

public class User {
    private String username;
    private Integer age;

    public User(String username, Integer age) {
        this.username = username;
        this.age = age;
    }

    public String getUsername() {return username;}
    public void setUsername(String username) {this.username = username;}
    public Integer getAge() {return age;}
    public void setAge(Integer age) {this.age = age;}
}

public interface UserService {
    void saveUser(User user);
}

public class UserServiceImpl implements UserService {
    public void saveUser(User user) {
        throw new UnsupportedOperationException(); //模拟异常
    }
}

3、定义配置文件spring.xml

<?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.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop  
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 定义Aspect --> 
    <aop:config>
        <aop:pointcut id="userServiceOperation" 
            expression="execution(* com.spring.tx.service.UserService.*(..))"/>  
        <aop:advisor advice-ref="txAdvice" pointcut-ref="userServiceOperation"/>
    </aop:config>

    <!-- DataSource -->
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="org.h2.Driver"/>
        <property name="url" value="jdbc:h2:mem:testdb"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>
    
    <!-- PlatformTransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- 定义事务Advice --> 
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <!-- 所有“get”开头的都是只读 -->
            <tx:method name="get*" read-only="true"/>
            <!-- 其他方法,使用默认的事务设置 -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    
    <!-- 定义 bean -->
    <bean id="userService" class="com.spring.tx.service.UserServiceImpl" />
</beans>

4、定义主应用类

public class Application {
    public static void main(String[] args) {
        @SuppressWarnings("resource")
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        UserService UserService = context.getBean(UserService.class);
        UserService.saveUser(new User("小明", 10));
    }
}

5、运行结果

Exception in thread "main" java.lang.UnsupportedOperationException
    at com.spring.tx.service.UserServiceImpl.saveUser(UserServiceImpl.java:17)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.sun.proxy.$Proxy21.saveUser(Unknown Source)
    at com.waylau.spring.tx.Application.main(Application.java:24)

上述异常信息中,可以完整看到整个事务的管理过程,包括创建事务、获取连接,及遇到异常后的事务回滚、连接释放等过程。由此证明,事务在遇到特定异常时,是可以进行事务回滚的。

2、事务回滚

向spring框架是事务基础设施中指示事务的工作将被回滚的推荐方式,是从事务上下文中正在执行的代码中抛出一个异常。spring框架的事务基础设施代码会捕捉任何未处理的异常,因为它会唤起调用堆栈,并确定是否将事务标记为回滚。
在其默认配置中,spring框架的事务基础设施代码仅在运行时未检查的异常处标记用于事务回滚。如果要回滚,抛出的异常是RuntimeException的一个实例或子类。Error默认情况下也会导致回滚,但已检查的异常不会导致在默认配置中回滚。

<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>

如果不想在抛出异常时回滚事务,可以指定“no-rollback-for”。

<tx:advice id="txAdvice">
  <tx:attributes>
    <tx:method name="get*" read-only="true" no-rollback-for="InstrumentNotFoundException"/>
    <tx:method name="*"/>
  </tx:attributes>
</tx:advice>

当spring框架的事务基础设施捕获一个异常时,在检查配置的回滚规则以确定是否标记回滚事务时,最强的匹配规则将胜出。在以下配置情况下,除InstrumentNotFoundException之外的任何异常都会导致事务的回滚。

<tx:advice id="txAdvice">
  <tx:attributes>
    <tx:method name="*" read-only="true" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
  </tx:attributes>
</tx:advice>

还可以以编程方式指示所需的回滚:

public void resolvePosition() {
  try{
    //some business
  } catch(NoProductInStockException e) {
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  }
}
3、配置不同的事务策略

如果有多个服务层对象的场景,并且想对它们应用一个完全不同的事务配置。可以通过使用不同的pointcut和advice-ref属性值定义不同的<aop:advisor/>元素来执行此操作。
示例:使所有在com.spring.service包或子包中定义的类的实例都具有默认的事务配置。

<?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: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.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop  
        http://www.springframework.org/schema/aop/spring-aop.xsd">
  <aop:config>
    <aop:pointcut id="serviceOperation" expression="execution(* com.spring.service..*Service.*(..))"/>  
    <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation"/>
  </aop:config>
  <!-- 下面两个bean将会纳入事务 -->
  <bean id="fooService" class="com.spring.service.FooService" />
  <bean id="barService" class="com.spring.service.BarService" />

  <!-- 下面两个bean不会纳入事务 -->
  <bean id="aService" class="com.abc.service.TService" />
  <bean id="barManager" class="com.spring.service.BarManager" />
  
  <tx:advice id="txAdvice">
    <tx:attributes>
      <tx:method name="get*" read-only="true"/>
      <tx:method name="*"/>
    </tx:attributes>
  </tx:advice>
</beans>

示例:使用完全不同的事务配置两个不同的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: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.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop  
        http://www.springframework.org/schema/aop/spring-aop.xsd">
  <aop:config>
    <aop:pointcut id="defaultServiceOperation" expression="execution(* com.spring.service.*Service.*(..))"/>
    <aop:pointcut id="noTxServiceOperation" expression="execution(* com.spring.service.ddl.DefaultDdlManager.*(..))"/>
    <aop:advisor advice-ref="defaultTxAdvice" pointcut-ref="defaultServiceOperation"/>
    <aop:advisor advice-ref="noTxAdvice" pointcut-ref="noTxServiceOperation"/>
  </aop:config>
  <!-- 下面两个bean将会纳入不同的事务配置 -->
  <bean id="fooService" class="com.spring.service.FooService" />
  <bean id="ddlService" class="com.spring.service.ddl.DdlManager" />

  <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>
</beans>
4、@Transactional详解

除了基于XML的事务配置声明方法外,还可以使用基于注解的方法。注解的好处是声明事务的语义会使声明更接近受影响的代码,没有太多不必要的耦合。

<?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: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.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop  
        http://www.springframework.org/schema/aop/spring-aop.xsd">
  <bean id="fooService" class="com.spring.service.FooService" />
  <tx:annotation-driven transaction-manager="txManager"/>
  <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
  </bean>
  <!-- ... -->
</beans>

在使用代理时,应将@Transactional注解仅应用于具有public的方法,如果使用@Transactional注解标注protected、private或包的可见方法,虽不会引发错误,但注解的方法不会使用已配置的事务设置。
@Transactional注解可用于接口定义、接口上的方法、类定义或类上的public方法之前。仅有@Transactional注解是不足以激活事务行为的。@Transactional注解只是一些元数据,可以被一些具有事务感知的运行时基础设施使用,并且可以使用元数据来配置具有事务行为的bean。<tx:annotation-driven/>元素用于切换事务行为。
默认的@Transactional设置如下:

  • 传播设置为PROPAGATION_REQUIRED.
  • 隔离级别为ISOLATION_DEFAULT。
  • 事务是读写的。
  • 事务超时默认为基础事务系统的默认超时。如果超时不受支持,则默认为无。
  • 任何RuntimeException都会触发回滚,任何已检查的异常都不会触发回滚。
    @Transactional注解的属性及描述
  • value:string类型,指定要使用的事务管理器的可选限定符
  • propagation:枚举类型,设置事务的传播机制
  • isolation:枚举类型,设置事务的隔离级别
  • readOnly:Boolean类型,确认是读写还是只读事务
  • timeout:int类型,事务超时时间(S)
  • rollbackFor:class对象的数组,必须从Throwable派生,导致回滚的异常类数组
  • rollbackForClassName:class对象的数组,必须从Throwable派生,导致回滚的异常类名数组
  • noRollbackFor:class对象的数组,必须从Throwable派生,不能导致回滚的异常类数组
  • noRollbackForClassName:必须从Throwable派生的string类名数组,不允许回滚的异常类名数组
5、事务传播机制
public enum Propagation {
  REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
  SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
  MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
  REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
  NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
  NEVER(TransactionDefinition.PROPAGATION_NEVER),
  NESTED(TransactionDefinition.PROPAGATION_NESTED);
  ...
}
  • PROPAGATION_REQUIRED
    PROPAGATION_REQUIRED表示加入当前正要执行的事务不在另外一个事务中,则开启一个新事务。
    例如ServiceB.methodB()的事务级别定义为PROPAGATION_REQUIRED,那么由于执行ServiceA.methodA()时,ServiceA.methodA()已经开启了事务,此时调用ServiceB.methodB(),ServiceB.methodB()看到自己已经运行在ServiceA.methodA()的事务内部,就不再开启新的事务。如果ServiceA.methodA()运行时发现自己没有在事务中,它就会为自己分配一个事务。
    这样在ServiceA.methodA()或ServiceB.methodB()内的任何地方出现异常,事务都会回滚。即使ServiceB.methodB()的事务已经被提交,但ServiceA.methodA()在下面异常了要回滚,则ServiceB.methodB()也会回滚。
  • PROPAGATION_REQUIRES_NEW
    例如ServiceA.methodA()的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB()的事务级别定义为PROPAGATION_REQUIRES_NEW,那么当执行到ServiceB.methodB()时,ServiceA.methodA()所在的事务就会挂起,ServiceB.methodB()会开启一个新的事务。等ServiceB.methodB()的事务完成后,ServiceA.methodA()才继续执行。
    它与PROPAGATION_REQUIRED的区别在于,事务的回滚程度。因为ServiceB.methodB()是新开启一个事务,那么就是存在两个不同的事务。如果ServiceB.methodB()已经提交,那么ServiceA.methodA()失败回滚,ServiceB.methodB()不会回滚。如果ServiceB.methodB()失败回滚,若它抛出的异常被ServiceA.methodA()捕获,ServiceA.methodA()事务仍然可能提交。
  • PROPAGATION_NESTED
    PROPAGATION_NESTED使用具有可回滚到的多个保存点的单个物理事务。它与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另开启一个事务,将会与它的父事务相互独立,而PROPAGATION_NESTED的事务和它的父事务是相依的,它的提交要和它的父事务一起。如果父事务最后回滚,它也要回滚。如果子事务回滚或提交,不会导致父事务回滚或提交,但父事务回滚将导致子事务回滚。

--参考文献《Srping5开发大全》

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容

  • 一、事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供...
    Java高级新技术阅读 170评论 0 0
  • 一、事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供...
    芭蕾武阅读 1,694评论 3 12
  • 一、事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供...
    JAVA架构师的圈子阅读 530评论 0 5
  • 事务的嵌套概念 所谓事务的嵌套就是两个事务方法之间相互调用。spring事务开启 ,或者是基于接口的或者是基于类的...
    jackcooper阅读 1,420评论 0 10
  • 今天我们全家一起围在桌前,一起说说笑笑因为今天是全国人民的中秋节。 今天傍晚,爸爸妈妈在...
    有你而美阅读 194评论 0 0