赞
踩
cglib
代理,cglib
代理底层是基于⽗⼦类来实现的, ⼦类是不能重载⽗类的private
⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效@Service public class UserServiceImpl implements UserService { /** 因为Spring事务是基于代理来实现的, 所以某个加了 @Transactional 的⽅法只有被代理对象调⽤时,那么这个注解才会⽣效, 否则 @Transactional 是不会⽣效的。 */ //普通方法A public int A(User user) { //插入操作 userDao.insertUser(user); // 调用本类中的事务方法B this.B(user.getId()); return 1; } //事务方法B @Transactional public String B(int id) { //修改操作 //新增操作 } }
由于配置了@Transactional就需要AOP拦截及事务的处理,可能影响系统性能。
上边说到方法内调用事务方法,会导致事务失效,@Async也一样。原因就在于:调用时用的this当前对象,没有使用代理对象,当然也就没有事务、异步等逻辑处理了,对此有三种解决方案
把该事务、异步方法放在别的@Service
类中,然后在当前方法中注入该 @Service
类,然后再调用该@Service类中的事务方法!
使用编程式事务
@Override public void placeOrder() { // 此处省略一堆逻辑 // TransactionCallbackWithoutResult 无返回参数 // TransactionCallback 有返回参数 transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { try { this.updateByTransactional(); } catch (Exception e) { log.error("下单失败", e); transactionStatus.setRollbackOnly(); } } }); } public void updateByTransactional() { // 修用户改余额和商品库存 accountMapper.update(); productMapper.update(); }
先从容器中获取到该代理类,然后用代理类调用事务方法
// hutool工具类获取当前代理类
ShopeeReceivableNotesService proxy = SpringUtil.getBean(this.getClass());
// 使用代理类调用事务方法,注意该方法也要在接口中存在!
proxy.removeAndSaveReceivable(beginTime, endTime, shopId, entityList);
Spring的事务和Spring的Aop,他们之间的套路非常相似,都是由以下三步组成:
@Import
注册一个创建动态代理的bean的后置处理器 和 BeanFactoryTransactionAttributeSourceAdvisor
、TransactionAttributeSource
、TransactionInterceptor
等处理事务相关的beanbean
时,通过bean后置处理器的postProcessBeforeInstantistion
方法找到@Trantional
注解标注的方法,并存入缓存,这点与AOP逻辑类似postProcessAfterInstantistion
方法中与当前正在创建的bean进行匹配,如果bean中的方法或者类上有@Trantional
注解,则为其创建动态代理!@Trantional
注解的方法或类时, 会进入TransactionInterceptor
中,触发代理逻辑。会先开启事务,然后分别处理嵌套事务和单一事务,以及两种事务的各种传播行为!如果抛异常则回滚方法,无异常则提交事务!下面就一一介绍这三步操作,在Spring事务中的实现。
从配置类查看事务源码,配置类如下:
@EnableTransactionManagement //开启事务
@EnableAspectJAutoProxy(exposeProxy = true) //开启Aop
@ComponentScan(basePackages = {"com.**"}) //包扫描
public class MainConfig {
//使用@EnableTransactionManagement注解前,
//请务必保证你已经配置了至少一个PlatformTransactionManager的Bean,否则会报错。
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
由@EnableTransactionManagement
进入源码,通过@Import
注解帮我们导入了事务组件TransactionManagementConfigurationSelector
,在容器初始化的时候注册。
/**
* @Import注解可以为我们容器中导入组件--->TransactionManagementConfigurationSelector
*/
@Import(TransactionManagementConfigurationSelector.class)
@Import
导入的组件会在refresh()
方法中的invokeBeanFactoryPostProcessors
中注册,如下:
// 调用我们的bean工厂的后置处理器.
invokeBeanFactoryPostProcessors(beanFactory);
TransactionManagementConfigurationSelector
代码如下:
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> { /** * 在springioc容器中加载bean定义的时候会回调我们的selectImports方法 * 方法的返回值是我们需要导入类的全类名路径.然后这个类就会被加载到容器中 */ @Override protected String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { /** * PROXY:jdk代理默认使用这个 * 为我们的容器中导入了二个组件 一个是AutoProxyRegistrar * 一个是ProxyTransactionManagementConfiguration */ case PROXY: return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()}; // ASPECTJ:这个需要使用AspectJ的编译器,麻烦,一般不使用 case ASPECTJ: return new String[] { TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME}; default: return null; } } }
在TransactionManagementConfigurationSelector
组件中,默认case的是PROXY,它给我们容器注册了两个组件,一个是AutoProxyRegistrar,另一个是ProxyTransactionManagementConfiguration,这两个组件非常重要,下面一一来介绍:
//事务调用这个方法注册bean的后置处理器(由AutoProxyRegistrar进入)
@Nullable
public static BeanDefinition registerAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}
//Aop调用这个方法注册bean的后置处理器
@Nullable
public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);
}
可以看到,两个方法都调用了registerOrEscalateApcAsRequired
方法,进去一探究竟,如何避免事务和Aop同时存在从而对一个bean创建多个动态代理呢?答案就是优先级覆盖,事务的优先级低于Aop,如果同时存在,Aop会覆盖事务注册的bean的后置处理器
private static BeanDefinition registerOrEscalateApcAsRequired( Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); //因为事务和Aop都是一样的名字,所以内部会根据优先级覆盖beanClass if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); if (!cls.getName().equals(apcDefinition.getBeanClassName())) { //当同时有事务和Aop,两者都会创建bean的后置处理器 //为了防止创建两个,在内部进行了优先级覆盖 //事务优先级为 1, Aop的优先级为 3 int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName()); int requiredPriority = findPriorityForClass(cls); //比较优先级,此时Aop会覆盖事务 if (currentPriority < requiredPriority) { apcDefinition.setBeanClassName(cls.getName()); } } return null; } RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); beanDefinition.setSource(source); beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); return beanDefinition; }
@Configuration
,给容器中注册了三个bean对象BeanFactoryTransactionAttributeSourceAdvisor
:事务的Advisor,封装了事务的切面信息,与Aop不同的是,Aop需要自定义切面逻辑,然后调用AspectJ来解析切面封装成Advisor。而事务不同,Spring内部帮我们封装好事务的逻辑,连Advisor在这里都直接帮我们设置好了
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() { BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); //设置事务的事务属性源AttributeSource,解析@Transactional注解,类似于切点 advisor.setTransactionAttributeSource(transactionAttributeSource()); //设置事务的通知逻辑 advisor.setAdvice(transactionInterceptor()); if (this.enableTx != null) { advisor.setOrder(this.enableTx.<Integer>getNumber("order")); } return advisor; }
TransactionAttributeSource
:获取事务属性对象,解析@Transactional
注解。只有带有@Transactional
的类才会被创建动态代理
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
TransactionInterceptor
:里边有Spring事务的逻辑,拦截事务方法,执行事务逻辑!
@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;
}
因为事务和Aop不一样,Aop根据不同的切点和Advice会有多个Advisor,而事务只有一个Advisor。也就是上一步开启事务时,已经放入容器的BeanFactoryTransactionAttributeSourceAdvisor类。接下来只需要拿当前创建的bean与事务的Advisor匹配,匹配成功则创建动态代理即可。这个过程与Aop达成了代码复用。
如何判断当前类具备事务,具有创建动态代理的资格?
答:从当前正在初始化的bean的所有的方法中,找到带有@Transactional的方法,根据方法优先原则,由 本类方法 ==> 接口方法 ==> 父类方法的顺序去找,如果找到,就表示有创建动态代理的资格。如果方法上都没有,则去类上面找,由 本类上 ==> 接口上 ==> 父类上 的顺序去找,如果找到,就表示有创建动态代理的资格。
如果具备创建动态代理的资格,就会为当前bean创建动态代理,这个过程与Aop一样,在此不多说,详情请看 Spring的Aop源码逻辑
如果启动事务,在容器启动时,Spring会在容器中注入TransactionInterceptor这个bean对象。这个bean里边有Spring事务的逻辑,作用是拦截事务方法,执行事务逻辑!
当为某个类创建了事务的动态代理后,就会执行TransactionInterceptor中处理Spring事务的逻辑。具体代码如下:
@Nullable protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable { //获取 事务属性源 TransactionAttributeSource tas = getTransactionAttributeSource(); //通过 事务属性源 获取 事务属性信息 final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); //获取我们配置的事务管理器 final PlatformTransactionManager tm = determineTransactionManager(txAttr); //从txAttr事务属性对象 中获取出标注了@Transactionl的方法名 final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); //处理声明式事务 if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { //如果有必要,则创建事务(开启事务) TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal; try { //调用钩子函数进行回调目标方法,事务也会责任链调用,不过责任链中只有一个 retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { //抛出异常进行回滚处理 completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { //清空我们的线程变量中transactionInfo的值 cleanupTransactionInfo(txInfo); } //提交事务 commitTransactionAfterReturning(txInfo); return retVal; } //处理编程式事务 ....... }
其中最主要的就是createTransactionIfNecessary这个方法,它内部封装了开启事务的操作,单事务和嵌套事务分别有不同的操作,大致流程如下图
Spring针对不同的传播行为有不同的处理,下面来了解一下事务的传播行为
事务传播行为 | 传播行为描述 | 使用方式 |
---|---|---|
REQUIRED(默认) | 如果当前存在事务,则加入该事务, 如果当前不存在事务,则创建一个新的事务。 | @Transactional(propagation = Propagation.REQUIRED)适用增删改查 |
SUPPORTS | 如果当前存在事务,则加入该事务; 如果当前不存在事务,则以非事务的方式继续运行。 | @Transactional(propagation = Propagation.SUPPORTS)适用查询 |
REQUIRES_NEW | 重新创建一个新的事务, 如果当前存在事务,延缓当前的事务。 两个事务的提交回滚互不干扰 | @Transactional(propagation = Propagation.REQUIRES_NEW)适用内部事务和外部事务不存在业务关联情况,如日志 |
NOT_SUPPORTED | 以非事务的方式运行,如果当前存在事务,暂停当前的事务。 | @Transactional(propagation = Propagation.NOT_SUPPORTED)不常用 |
NEVER | 以非事务的方式运行, 如果当前存在事务,则抛出异常。 | @Transactional(propagation = Propagation.NEVER )不常用 |
MANDATORY | 如果当前存在事务,则加入该事务; 如果当前不存在事务,则抛出异常。 | @Transactional(propagation = Propagation.MANDATORY)不常用 |
NESTED | 如果没有,就新建一个事务; 如果有,就在当前事务中嵌套其他事务。,外层影响内层, 内层不会影响外层 | @Transactional(propagation = Propagation.NESTED)不常用 |
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
在接下来fail要回滚,ServiceB.methodB
也要回滚PROPAGATION_SUPPORTS
:
PROPAGATION_REQUIRES_NEW
:
ServiceA.methodA
的事务级别为PROPAGATION_REQUIRED
,ServiceB.methodB
的事务级别为PROPAGATION_REQUIRES_NEW
, 那么当执行到ServiceB.methodB
的时候,ServiceA.methodA
所在的事务就会挂起,ServiceB.methodB
会起一个新的事务,等待ServiceB.methodB
的事务完成以后,PROPAGATION_REQUIRED
的事务区别在于事务的回滚程度了。因为ServiceB.methodB
是新起一个事务,那么就是存在两个不同的事务。如果ServiceB.methodB
已经提交,那么ServiceA.methodA
失败回滚,ServiceB.methodB
是不会回滚的。如果ServiceB.methodB
失败回滚,如果他抛出的异常被ServiceA.methodA
捕获,ServiceA.methodA
事务仍然可能提交。PROPAGATION_NOT_SUPPORTED
:
ServiceA.methodA
的事务级别是PROPAGATION_REQUIRED
,而ServiceB.methodB
的事务级别是PROPAGATION_NOT_SUPPORTED
ServiceB.methodB
时,ServiceA.methodA
的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA
的事务。PROPAGATION_NEVER
:
ServiceA.methodA
的事务级别是PROPAGATION_REQUIRED
, 而ServiceB.methodB
的事务级别是PROPAGATION_NEVER
, 那么ServiceB.methodB
就要抛出异常了。PROPAGATION_MANDATORY
:
PROPAGATION_NESTED
:
PROPAGATION_REQUIRES_NEW
的区别是,PROPAGATION_REQUIRES_NEW
另起一个事务,将会与他的父事务相互独立,而 PROPAGATION_NESTED
则是外部事务的子事务。也就是说,如果父事务最后回滚,他也要回滚的,父事务最后commit
,他也跟着commit
。ServiceB.methodB
失败回滚,那么ServiceA.methodA
也会回滚到savepoint
点上,ServiceA.methodA
可以选择另外一个分支,比如ServiceC.methodC
,继续执行,来尝试完成自己的事务。
接下来继续源码,看一下单事务与嵌套事务的区别!
单事务逻辑比较简单,如果是嵌套事务,比如以下操作
@Transactional(rollbackFor = Exception.class) //事务方法
public void pay(String accountId, double money) {
//查询余额
double blance = accountInfoDao.qryBlanceByUserId(accountId);
//嵌套事务,更新库存
this.updateProductStore(1);
}
@Transactional(propagation = Propagation.REQUIRES_NEW) //事务方法
public void updateProductStore(Integer productId) {
。。。。。。省略代码
}
下面来讲一下嵌套事务的流程,以上面伪代码为例,pay方法内部调用了updateProductStore
方法,会开启一个新事务,首先pay方法会开启一个事务,获取一个数据库连接Connection,封装到ConnectionHolder
,开启事务,绑定数据源和ConnectionHolder
到事务管理器TransactionSynchronizationManager
上!逻辑封装在doBegin
方法中:
protected void doBegin(Object transaction, TransactionDefinition definition) { //强制转化事物对象 DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; Connection con = null; try { //判断事务对象没有数据库连接持有器 if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { //通过数据源获取一个数据库连接对象,这个链接是我们自己设置的 Connection newCon = obtainDataSource().getConnection(); if (logger.isDebugEnabled()) { logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction"); } //把我们的数据库连接包装成一个ConnectionHolder对象 然后设置到我们的txObject对象中去 txObject.setConnectionHolder(new ConnectionHolder(newCon), true); } //标记当前的连接是一个同步事务 txObject.getConnectionHolder().setSynchronizedWithTransaction(true); con = txObject.getConnectionHolder().getConnection(); //为当前的事务设置隔离级别 Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); txObject.setPreviousIsolationLevel(previousIsolationLevel); //关闭自动提交 if (con.getAutoCommit()) { txObject.setMustRestoreAutoCommit(true); if (logger.isDebugEnabled()) { logger.debug("Switching JDBC Connection [" + con + "] to manual commit"); } con.setAutoCommit(false); } //判断事务为只读事务 prepareTransactionalConnection(con, definition); //设置事务激活 txObject.getConnectionHolder().setTransactionActive(true); //设置事务超时时间 int timeout = determineTimeout(definition); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); } // 绑定我们的数据源和连接到我们的同步管理器上 把数据源作为key,数据库连接作为value 设置到线程变量中 if (txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); } }
然后再次补充事务管理器信息,把当前事务的信息绑定到事务管理器TransactionSynchronizationManager
上
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
if (status.isNewSynchronization()) {
//绑定事务激活
TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
//当前事务的隔离级别
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
definition.getIsolationLevel() : null);
//是否为只读事务
TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
//事务的名称
TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
TransactionSynchronizationManager.initSynchronization();
}
}
然后回调目标方法,启动责任链调用,链上有 pay 和updateProductStore两个节点,此时进入pay节点
//调用钩子函数进行回调目标方法,事务也会责任链调用
retVal = invocation.proceedWithInvocation();
因为pay
方法内部还有updateProductStore
事务方法,所以pay
方法执行一半会暂停,通过递归的方式进入updateProductStore
方法,updateProductStore
方法属于内部事务,会走嵌套事务逻辑。此时内部事务(updateProductStore
)会挂起外部事务(pay
),把外部事务(pay
)的属性从TransactionSynchronizationManager
中取出来,再封装到SuspendedResourcesHolder
做一个临时存储。
然后在开启事务时把内部事务(updateProductStore
)的属性填充到已经为空的事务管理器TransactionSynchronizationManager
中。等内部事务方法(updateProductStore
)处理完毕并提交后,再把SuspendedResourcesHolder中临时存储的外部事务pay的事务信息取出,放入事务管理器TransactionSynchronizationManager
中,覆盖掉。再执行外部方法pay
,如果抛出异常,会进行回滚,最后清空我们的线程变量中transactionInfo
的值,提交事务。这才是嵌套事务的整个流程
注意: 异常回滚和内部事务提交都会清空事务同步管理器TransactionSynchronizationManager
的信息,意义在于:内部事务执行完毕,开始执行外部事务!
在@PostConstruct
注解上加@Transactional
,事务是不生效的!(与InitializingBean
接口中的afterPropertiesSet
也是如此!无法确保已完成所有后期处理,因此(实际上)不能有任何事务。确保工作正常的唯一方法是使用TransactionTemplate
。
@Service("something") public class Something { @Autowired @Qualifier("transactionManager") protected PlatformTransactionManager txManager; @PostConstruct private void init(){ TransactionTemplate tmpl = new TransactionTemplate(txManager); tmpl.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { //PUT YOUR CALL TO SERVICE HERE } }); } }
案例如下:
@Autowired @Qualifier("transactionManager") protected PlatformTransactionManager txManager; @PostConstruct public void updaeAll() { TransactionTemplate tmpl = new TransactionTemplate(txManager); tmpl.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { List<OrderEntity> list = orderService.list(); list.forEach(x -> { x.setAmount(6); //多个更新操作 orderService.saveOrUpdate(x); //在第五个操作时,抛出异常! if (x.getId() == 5) { int i = 1 / 0; } }); } }); }
上述代码运行,事务会回滚,如果不使用TransactionTemplate
,而单纯使用@Transactional注解
,事务不会生效!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。