1. 事务的使用
Spring
中的事务有以下几种使用方式
- 编程式事务;
- 使用XML配置声明式事务;
- 使用注解配置声明式事务。
在实际应用中,很少通过编程来进行事务管理,一般多使用声明式事务,而随着注解在Spring
中流行开来,所以使用注解配置声明式事务会较多。
1.1. 编程式事务
使用TransactionTemplate
进行编程式事务的开发,实际开发中很少使用了。
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private TransactionTemplate transactionTemplate;
/**
* 编程式事务
*/
public void insertUser(boolean hasException){
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
userDao.insert();
System.out.println("...");
if (hasException) {
int i = 10 / 0;
}
}
});
}
}
1.2. 使用XML配置声明式事务
使用XML
进行声明式事务的配置又可以分为两种:
- 使用原始的
TransactionProxyFactoryBean
; - 基于
aop/tx
命名空间的配置。
其中TransactionProxyFactoryBean
还是Spring 1.0
时代的远古产物,它需要给每个需要使用事务的类进行单独的配置,比较繁琐。我们只需知道有这么个事就可以。
下面看看基于aop/tx
命名空间的配置
<?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:p="http://www.springframework.org/schema/p" 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-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<import resource="classpath:applicationContext-dao.xml"/>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<aop:config>
<aop:pointcut id="serviceMethod"
expression="execution(* io.zgc.spring.features.tx.usetype.xml.UserService.*(..))"/>
<aop:advisor pointcut-ref="serviceMethod"
advice-ref="txAdvice"/>
</aop:config>
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="insert*" rollback-for="Exception"/>
<tx:method name="update*"/>
</tx:attributes>
</tx:advice>
</beans>
可以看到事务的配置是基于AOP
的配置。
1.3. 使用注解配置声明式事务
我们可以在需要开启事务的地方使用注解@Transactional
,该注解可以使用在类或方法上。有两种方式可以使@Transactional
注解生效
- 使用
xml
配置文件的应用中,需要在配置文件中添加下列配置:
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
- 使用配置类的应用中,使用
@EnableTransactionManagement
开启事务管理
@EnableTransactionManagement
@ComponentScan("io.zgc.spring.features.tx")
@Configuration
public class TxConfig {
2. 事务的传播行为
当事务方法被另外一个事务方法调用时,必须指定事务应该如何传播。例如:方法可以继续在现有事务中运行,也可以开启一个新事务。
事务的传播行为可以通过事务的传播属性来指定,Spring
中定义了7种传播行为。
-
REQUIRED
:如果有事务在运行,当前的方法就使用这个事务,在这个事务中运行。否则,就开启一个新的事务,并在新的事务中运行。 -
REQUIRES_NEW
:当前的方法必须开启新的事务,并在新的事务中运行,如果调用的方法有事务在运行,就将它先挂起。 -
SUPPORTS
:如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中。 -
NOT_SUPPORTS
:当前的方法不应该运行在事务中,如果当前有运行的事务,就将它挂起。 -
MANDATORY
:当前方法运行在调用方法的事务中,如果不存在运行中的事务就抛出异常。 -
NEVER
:当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常。 -
NESTED
:如果有事务运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启动一个新的事务,并在自己的事务内运行。
常用的传播行为:REQUIRED
、REQUIRES_NEW
,其中Spring
默认的传播行为为:REQUIRED
。
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private UserService self;
@Transactional
public void insertUser(boolean hasException){
userDao.insert();
System.out.println("...");
if (hasException) {
int i = 10 / 0;
}
}
/**
* 一个用户都不会插入,事务的传播行为是REQUIRED
* 两个self.insertUser方法共用insetBatchUserWithRequred方法开启的事务
*
* 事务最终由于self.insertUser(true);执行抛出异常,而回滚
*/
@Transactional
public void insetBatchUserWithRequred() {
self.insertUser(false);
self.insertUser(true);
}
/**
* 会插入1个用户,因为事务的传播行为为REQUIRES_NEW
* self.insertUserRn(false); 开启单独的事务,并成功执行
* self.insertUserRn(true); 开启单独的事务,执行失败
*
* insetBatchUserWithRequredNew的事务不会受self.insertUserRn(true)开启的事务影响
*/
@Transactional
public void insetBatchUserWithRequredNew() {
self.insertUserRn(false);
self.insertUserRn(true);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertUserRn(boolean hasException){
userDao.insert();
System.out.println("...");
if (hasException) {
int i = 10 / 0;
}
}
}
使用事务的一些坑
日常开发过程中,遇到声明式事务(@Transactional
)和其他形式的事务时一定要亲自验证一波,确保事务是可以生效的。下面来看看关于事务常见的一些坑
private的方法中使用声明式事务
首先需要知道的是:在private方法中使用声明式事务是不会生效的
。除非特殊配置(比如使用 AspectJ
静态织入实现 AOP
),否则只有定义在 public
方法上的 @Transactional
才能生效。原因是, Spring
默认通过动态代理的方式实现 AOP
,对目标方法进行增强,private
方法无法代理到,Spring
自然也无法动态增强事务处理逻辑。
/**
* @Transactional 不能放在private方法上,不然事务不生效
* @param hasException
*/
@Transactional
private void insertUserPrivate(boolean hasException){
userDao.insert();
System.out.println("...");
if (hasException) {
int i = 10 / 0;
}
}
上面的insertUserPrivate
方法,即使抛出异常事务也不会回滚,这是因为它是个private
方法,而@Transactional
注解不能修饰private
方法。
通过this调用事务方法
要想@Transactional
生效,必须是通过代理的类调用目标方法,否则事务将不会生效。请看如下事务失效代码:
/**
* 自调用(this调用)事务方法,事务不会生效
* @param hasException
*/
public void insetUserWrong2(boolean hasException) {
insertUserPublic(hasException);
}
@Transactional
public void insertUserPublic(boolean hasException){
userDao.insert();
System.out.println("...");
if (hasException) {
int i = 10 / 0;
}
}
事务在抛出异常时依然不会回滚,这是因为是通过this
调用了事务方法,而this
指向的是目标对象,非代理对象,没有进行事务增加,所以事务不会起作用。
这种情况下,要想事务能够生效的话,可以将代理对象注入其中,并通过代理对象来调用事务方法。
@Service
public class UserService {
@Autowired
private UserDao userDao;
/** 要想事务生效,必须通过代理类来调用事务方法 */
@Autowired
private UserService self;
public void insertUserRight(boolean hasException) {
self.insertUserPublic(hasException);
}
@Transactional
public void insertUserPublic(boolean hasException){
userDao.insert();
System.out.println("...");
if (hasException) {
int i = 10 / 0;
}
}
}
异常处理不当导致事务失效
要想事务能够生效(即在抛出异常时,事务能够回滚),默认情况下需要满足两个条件:
- 事务方法在执行失败时,需要将异常抛出去;
- 抛出的异常需要是非受检异常(
RuntimeException
)。
当然上面两个条件是默认情况下的让事务生效的条件,不是说必须得是这样,是可以被打破的,下面举例来说明下
@Transactional
public void insertUserWrong3(boolean hasException){
try {
userDao.insert();
System.out.println("...");
if (hasException) {
int i = 10 / 0;
}
} catch (Exception e) {
System.out.println("------------------");
/* 异常被吃掉了,事务无法回滚 */
e.printStackTrace();
}
}
上面代码的事务之所以没法回滚是因为方法中对异常进行了捕获并没有继续抛出新的异常,所以效果和正常运行了代码的效果是一样的,不会将事务回滚。针对这种情况,如何让事务能够生效呢?除了继续抛出新的异常之外,还可以在catch
住异常后,手动回滚事务(TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
),代码如下:
@Transactional
public void insertUserRight3(boolean hasException){
try {
userDao.insert();
System.out.println("...");
if (hasException) {
int i = 10 / 0;
}
} catch (Exception e) {
e.printStackTrace();
// 手动设置让当前事务处于回滚状态
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
另外一个例子就是抛出了受检异常了,例如FileNotFoundException
,默认情况下,事务也是不生效的,除非设置rollbackFor
为Exception
。
@Transactional(rollbackFor = Exception.class)
public void insertUserRight4() throws FileNotFoundException {
userDao.insert();
System.out.println("...");
throw new FileNotFoundException();
}
4. 事务源码分析
4.1. 说明
本次源码分析使用Spring
的声明式事务,版本基于Spring 5.1.5.RELEASE
4.2. 示例程序
TxConfig.java
@EnableTransactionManagement
@ComponentScan("io.zgc.spring.features.tx")
@Configuration
public class TxConfig {
@Bean
public DataSource dataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("123456");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC");
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate() throws PropertyVetoException {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}
@Bean
public PlatformTransactionManager transactionManager () throws PropertyVetoException {
return new DataSourceTransactionManager(dataSource());
}
}
Client.java
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TxConfig.class);
UserService userService = applicationContext.getBean(UserService.class);
userService.insertUser();
}
}
UserService.java
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional
public void insertUser(){
userDao.insert();
System.out.println("...");
int i = 10/0;
}
}
4.3. @EnableTransactionManagement
我们先来看看开启声明式事务的注解@EnableTransactionManagement
,它的源码如下:
@Import({TransactionManagementConfigurationSelector.class})
public @interface EnableTransactionManagement {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}
TransactionManagementConfigurationSelector
是个ImportSelector
,默认情况下,它会给容器导入两个组件AutoProxyRegistrar
和ProxyTransactionManagementConfiguration
。
4.4. AutoProxyRegistrar
AutoProxyRegistrar
是一个ImportBeanDefinitionRegistrar
,它会检测导入者类上的某个注解是否带有属性mode
和proxyTargetClass
,如果检测到这些属性,在mode
为PROXY
时,它会给容器中注册一个InfrastructureAdvisorAutoProxyCreator
组件(APC
、auto proxy creator
)。
InfrastructureAdvisorAutoProxyCreator
是个APC,它的继承体系如下图所示:
从图中我们可以得出的结论:
-
InfrastructureAdvisorAutoProxyCreator
是个Bean后置处理器。 - 它和
AOP
源码中的AnnotationAwareAspectJAutoProxyCreator
类类似,都继承自AbstractAutoProxyCreator
,所以原理类似。
InfrastructureAdvisorAutoProxyCreator
会利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行调用;
4.5. ProxyTransactionManagementConfiguration
ProxyTransactionManagementConfiguration
是个配置类,它会给容器中注册事务增强器,主要包括三个组件:BeanFactoryTransactionAttributeSourceAdvisor
、TransactionAttributeSource
、TransactionInterceptor
。
@Configuration
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource());
advisor.setAdvice(transactionInterceptor());
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor() {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource());
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}
}
1)、BeanFactoryTransactionAttributeSourceAdvisor
是事务的advisor
。
2)、事务advisor
要用事务注解的信息,通过AnnotationTransactionAttributeSource
解析事务注解。
3)、TransactionInterceptor
是个advice
,它实现了 MethodInterceptor
;保存了事务属性信息,事务管理器;
4.6. TransactionInterceptor
TransactionInterceptor
继承了TransactionAspectSupport
类,实现MethodInterceptor
接口。源码如下:
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
public TransactionInterceptor(PlatformTransactionManager ptm, TransactionAttributeSource tas) {
setTransactionManager(ptm);
setTransactionAttributeSource(tas);
}
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
}
拦截方法invoke
最终会请求TransactionAspectSupport
的 invokeWithinTransaction
方法,这个方法就是处理事务的逻辑
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
} catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
} finally {
cleanupTransactionInfo(txInfo);
}