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开发大全》