Spring-声明式事务管理

2019-08-13  本文已影响0人  夏与清风

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

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设置如下:

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);
  ...
}

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

上一篇下一篇

猜你喜欢

热点阅读