1 数据库事务
1.1 什么是数据库事务
我们看一下百度百科对数据库事务的定义:数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。
可能概念有点抽象,其实数据库的事务,就是保证在一个事务操作内(一系列数据库操作语句),保证所有的操作要么都成功,要么都失败。
1.2 为什么要使用事务
那为什么要使用事务呢?举个经典的例子,小明给女朋友小红网上转账100元,通常会先将小明的余额减100元,再给小红的余额加100元。如果没有事务保证要么全部成功,要么全部失败,则会导致小明减少了100元,而小红的余额却没变,那岂不是造成各种投诉,系统也没办法使用了。
因此,事务是实际工作中最常见的,下面我们来看看spring当中的事务。
2 spring boot事务
为了demo项目构建方便,我们选择spring boot + jdbcTemplate + mysql 来说明。
2.1 实战
2.1.1 引入依赖
org.springframework.boot spring-boot-starter-jdbcmysql mysql-connector-java 8.0.11
2.1.2 定义表结构并初始化数据
表结构:
CREATE TABLE `t_user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '名称', `age` int DEFAULT NULL COMMENT '年龄', `follows` int DEFAULT NULL COMMENT '关注数', `fans` int DEFAULT NULL COMMENT '被关注数量', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
初始化数据:
INSERT INTO `t_user`(`id`, `name`, `age`, `follows`, `fans`) VALUES (1, 'ghj', 18, 0, 0);INSERT INTO `t_user`(`id`, `name`, `age`, `follows`, `fans`) VALUES (2, 'zhangsan', 19, 0, 0);
2.1.3 数据库相关配置
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useSSL=falsespring.datasource.username=rootspring.datasource.password=rootspring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
2.1.4 编写数据库entity及相关service
编写entity
@Data // 使用lombok插件public class User { private Long id; private String name; private Integer age; private Integer follows; private Integer fans;}
编写service和实现类
public interface TransactionService { void followUser();}
@Servicepublic class TransactionServiceImpl implements TransactionService { @Autowired JdbcTemplate jdbcTemplate; /*** * 2号用户关注1号用户 */ @Override @Transactional public void followUser() { // 2号用户 关注 + 1 jdbcTemplate.update("update t_user set follows = 1 where id =2"); int a = 1/0; // 1号用户 粉丝 + 1 jdbcTemplate.update("update t_user set fans = 1 where id =1"); }}
2.1.5 编写测试用例
@SpringBootTest(classes = DemoApplication.class)@WebAppConfigurationpublic class TransactionServiceTest { @Autowired TransactionService transactionService; @Test public void test(){ System.out.println("test start"); transactionService.followUser(); System.out.println("test end"); }}
运行,发现报错
java.lang.ArithmeticException: / by zero
查看数据库,所有update语句都失败了,说明事务生效了
![90e6bbbaf0ce71fa633b7681b011428a.png](https://i-blog.csdnimg.cn/blog_migrate/1408e5532d07ab7b37c269e18b9f120b.jpeg)
2.2 是不是一定生效呢
2.2.1 代码中捕获RuntimeException异常
我们将实现类,进行异常捕获,并抛出业务异常,如下
@Override@Transactionalpublic void followUser() throws BizException { try{ // 2号用户 关注 + 1 jdbcTemplate.update("update t_user set follows = 1 where id =2"); int a = 1/0; // 1号用户 粉丝 + 1 jdbcTemplate.update("update t_user set fans = 1 where id =1"); }catch (Exception e){ e.printStackTrace(); throw new BizException(); }}
业务异常BizException仅仅为了掩饰,并未特殊处理
public class BizException extends Exception{}
执行测试用例后发现,出现异常,但第一条sql生效了,如下图
![e678a3db73c065a67fa1064bf8895aef.png](https://i-blog.csdnimg.cn/blog_migrate/7d869ff5d7529027e645309e595efe2b.jpeg)
解决办法:将@Transactional注解指定rollbackFor为Exception.class即可
2.2.2 通过内部调用事务方法
我们将实现类代码,修改如下:
@Servicepublic class TransactionServiceImpl implements TransactionService { @Autowired JdbcTemplate jdbcTemplate; /*** * 2号用户关注1号用户 */ @Override public void followUser() throws BizException { followUser1(); } @Transactional public void followUser1(){ // 2号用户 关注 + 1 jdbcTemplate.update("update t_user set follows = 1 where id =2"); int a = 1/0; // 1号用户 粉丝 + 1 jdbcTemplate.update("update t_user set fans = 1 where id =1"); }}
通过调用内部事务方法,运行测试用例,发现事务竟然没生效,第一条sql又执行了,数据库如下:
![f7a2fca2c2d526512199d859b82068c0.png](https://i-blog.csdnimg.cn/blog_migrate/1ad1cdcf6492e9f3137aaa086c6a4efa.jpeg)
解决办法:给followUser()方法添加@Transactional注解
2.3 原理
由于spring boot自动装配,我们可以直接查看spring-boot-autoconfigure.jar下面/META-INF/spring.factories文件中EnableAutoConfiguration配置内容,如下
![2d9ce3863fd76d8e8bf704ea2a42381b.png](https://i-blog.csdnimg.cn/blog_migrate/d95bd60d04192d21a103a7069c278b36.jpeg)
可以看到,spring-boot的初始化入口是TransactionAutoConfiguration
@Configuration(proxyBeanMethods = false)@ConditionalOnClass(PlatformTransactionManager.class)@AutoConfigureAfter({ JtaAutoConfiguration.class, HibernateJpaAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, Neo4jDataAutoConfiguration.class })@EnableConfigurationProperties(TransactionProperties.class)public class TransactionAutoConfiguration { @Bean @ConditionalOnMissingBean public TransactionManagerCustomizers platformTransactionManagerCustomizers( ObjectProvider> customizers) { return new TransactionManagerCustomizers(customizers.orderedStream().collect(Collectors.toList())); } @Bean @ConditionalOnMissingBean @ConditionalOnSingleCandidate(ReactiveTransactionManager.class) public TransactionalOperator transactionalOperator(ReactiveTransactionManager transactionManager) { return TransactionalOperator.create(transactionManager); } @Configuration(proxyBeanMethods = false) @ConditionalOnSingleCandidate(PlatformTransactionManager.class) public static class TransactionTemplateConfiguration { @Bean @ConditionalOnMissingBean(TransactionOperations.class) public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) { return new TransactionTemplate(transactionManager); } } @Configuration(proxyBeanMethods = false) @ConditionalOnBean(TransactionManager.class) @ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class) public static class EnableTransactionManagementConfiguration { @Configuration(proxyBeanMethods = false) @EnableTransactionManagement(proxyTargetClass = false) @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false) public static class JdkDynamicAutoProxyConfiguration { } @Configuration(proxyBeanMethods = false) @EnableTransactionManagement(proxyTargetClass = true) @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true) public static class CglibAutoProxyConfiguration { } }}
主要需要分析注解和内部类@ConditionalOnClass(PlatformTransactionManager.class)和内部类EnableTransactionManagementConfiguration
@ConditionalOnClass(PlatformTransactionManager.class)即引入PlatformTransactionManager.class时这个自动配置生效,由于该类在spring-tx.jar(spring事务的核心包)内,所以引入并肯定生效了。
@EnableTransactionManagement
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Import(TransactionManagementConfigurationSelector.class)public @interface EnableTransactionManagement { //proxyTargetClass = false表示是JDK动态代理支持接口代理。true表示是Cglib代理支持子类继承代理。 boolean proxyTargetClass() default false; //事务通知模式(切面织入方式),默认代理模式(同一个类中方法互相调用拦截器不会生效),可以选择增强型AspectJ AdviceMode mode() default AdviceMode.PROXY; //连接点上有多个通知时,排序,默认最低。值越大优先级越低。 int order() default Ordered.LOWEST_PRECEDENCE;}
重点看类注解@Import(TransactionManagementConfigurationSelector.class)
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector { /** * Returns {@link ProxyTransactionManagementConfiguration} or * {@code AspectJ(Jta)TransactionManagementConfiguration} for {@code PROXY} * and {@code ASPECTJ} values of {@link EnableTransactionManagement#mode()}, * respectively. */ @Override protected String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { case PROXY: return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()}; case ASPECTJ: return new String[] {determineTransactionAspectClass()}; default: return null; } } private String determineTransactionAspectClass() { return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ? TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME : TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME); }}
如上,最终会执行selectImports方法导入需要加载的类,我们只看proxy模式下,载入了AutoProxyRegistrar、ProxyTransactionManagementConfiguration2个类
AutoProxyRegistrar类
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar { private final Log logger = LogFactory.getLog(getClass()); @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { boolean candidateFound = false; Set annTypes = importingClassMetadata.getAnnotationTypes(); for (String annType : annTypes) { AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType); if (candidate == null) { continue; } Object mode = candidate.get("mode"); Object proxyTargetClass = candidate.get("proxyTargetClass"); if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() && Boolean.class == proxyTargetClass.getClass()) { candidateFound = true; if (mode == AdviceMode.PROXY) {//代理模式 AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); if ((Boolean) proxyTargetClass) {// cglib代理 AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); return; } } } } if (!candidateFound && logger.isInfoEnabled()) { String name = getClass().getSimpleName(); logger.info(String.format("%s was imported but no annotations were found " + "having both 'mode' and 'proxyTargetClass' attributes of type " + "AdviceMode and boolean respectively. This means that auto proxy " + "creator registration and configuration may not have occurred as " + "intended, and components may not be proxied as expected. Check to " + "ensure that %s has been @Import'ed on the same class where these " + "annotations are declared; otherwise remove the import of %s " + "altogether.", name, name, name)); } }}
代理模式AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);最终调用的是org.springframework.aop.config.AopConfigUtils#registerOrEscalateApcAsRequired
private static BeanDefinition registerOrEscalateApcAsRequired( Class> cls, BeanDefinitionRegistry registry, @Nullable Object source) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); if (!cls.getName().equals(apcDefinition.getBeanClassName())) { int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName()); int requiredPriority = findPriorityForClass(cls); // 如果下标大于已存在的内部自动代理构造器 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; }
通过findPriorityForClass方法可以知道,如下APC_PRIORITY_LIST
private static final List> APC_PRIORITY_LIST = new ArrayList>();/*** 优先级上升list*/static { APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class); APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class); APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);}
由于InfrastructureAdvisorAutoProxyCreator是基础构建类,通过postProcessAfterInitialization进行类增强
@Override public Object postProcessBeforeInstantiation(Class> beanClass, String beanName) { Object cacheKey = getCacheKey(beanClass, beanName); if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) { if (this.advisedBeans.containsKey(cacheKey)) { return null; } if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return null; } } // Create proxy here if we have a custom TargetSource. // Suppresses unnecessary default instantiation of the target bean: // The TargetSource will handle target instances in a custom fashion. TargetSource targetSource = getCustomTargetSource(beanClass, beanName); if (targetSource != null) { if (StringUtils.hasLength(beanName)) { this.targetSourcedBeans.add(beanName); } Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource); Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } return null; }
上面对AutoProxyRegistrar类分析完了,在看ProxyTransactionManagementConfiguration类
ProxyTransactionManagementConfiguration类
@Configuration(proxyBeanMethods = false)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration { @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor( TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) { BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); advisor.setTransactionAttributeSource(transactionAttributeSource); // 事务的核心 事务拦截器 advisor.setAdvice(transactionInterceptor); if (this.enableTx != null) { advisor.setOrder(this.enableTx.getNumber("order")); } return advisor; } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionAttributeSource transactionAttributeSource() { return new AnnotationTransactionAttributeSource(); } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) // 事务拦截器 public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) { TransactionInterceptor interceptor = new TransactionInterceptor(); interceptor.setTransactionAttributeSource(transactionAttributeSource); if (this.txManager != null) { interceptor.setTransactionManager(this.txManager); } return interceptor; }}
直接看事务拦截器TransactionInterceptor,
@Override@Nullablepublic 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);}@Nullableprotected Object invokeWithinTransaction(Method method, @Nullable Class> targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, the method is non-transactional. TransactionAttributeSource tas = getTransactionAttributeSource(); final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); final TransactionManager tm = determineTransactionManager(txAttr); if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) { ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> { if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) { throw new TransactionUsageException( "Unsupported annotated transaction on suspending function detected: " + method + ". Use TransactionalOperator.transactional extensions instead."); } ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType()); if (adapter == null) { throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " + method.getReturnType()); } return new ReactiveTransactionSupport(adapter); }); return txSupport.invokeWithinTransaction( method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm); } PlatformTransactionManager ptm = asPlatformTransactionManager(tm); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) { // Standard transaction demarcation with getTransaction and commit/rollback calls. TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification); Object retVal; 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); } if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) { // Set rollback-only in case of Vavr failure matching our rollback rules... TransactionStatus status = txInfo.getTransactionStatus(); if (status != null && txAttr != null) { retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status); } } commitTransactionAfterReturning(txInfo); return retVal; } else { Object result; final ThrowableHolder throwableHolder = new ThrowableHolder(); // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in. try { result = ((CallbackPreferringPlatformTransactionManager) ptm).execute(txAttr, status -> { TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status); try { Object retVal = invocation.proceedWithInvocation(); if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) { // Set rollback-only in case of Vavr failure matching our rollback rules... retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status); } return retVal; } catch (Throwable ex) { if (txAttr.rollbackOn(ex)) { // A RuntimeException: will lead to a rollback. if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } else { throw new ThrowableHolderException(ex); } } else { // A normal return value: will lead to a commit. throwableHolder.throwable = ex; return null; } } finally { cleanupTransactionInfo(txInfo); } }); } catch (ThrowableHolderException ex) { throw ex.getCause(); } catch (TransactionSystemException ex2) { if (throwableHolder.throwable != null) { logger.error("Application exception overridden by commit exception", throwableHolder.throwable); ex2.initApplicationException(throwableHolder.throwable); } throw ex2; } catch (Throwable ex2) { if (throwableHolder.throwable != null) { logger.error("Application exception overridden by commit exception", throwableHolder.throwable); } throw ex2; } // Check result state: It might indicate a Throwable to rethrow. if (throwableHolder.throwable != null) { throw throwableHolder.throwable; } return result; } }
2.3.1 针对捕获RuntimeException异常
通过上面的源码,可以知道事务回滚的方法是TransactionAspectSupport#completeTransactionAfterThrowing
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) { if (txInfo != null && txInfo.getTransactionStatus() != null) { if (logger.isTraceEnabled()) { logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "] after exception: " + ex); } if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) { try { txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); } catch (TransactionSystemException ex2) { logger.error("Application exception overridden by rollback exception", ex); ex2.initApplicationException(ex); throw ex2; } catch (RuntimeException | Error ex2) { logger.error("Application exception overridden by rollback exception", ex); throw ex2; } } else { // We don't roll back on this exception. // Will still roll back if TransactionStatus.isRollbackOnly() is true. try { txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } catch (TransactionSystemException ex2) { logger.error("Application exception overridden by commit exception", ex); ex2.initApplicationException(ex); throw ex2; } catch (RuntimeException | Error ex2) { logger.error("Application exception overridden by commit exception", ex); throw ex2; } } } }
可以看到txInfo.transactionAttribute.rollbackOn(ex)为true则进行回滚,而该方法为
@Overridepublic boolean rollbackOn(Throwable ex) { return (ex instanceof RuntimeException || ex instanceof Error);}
因此只有Error和RuntimeException才会进行回滚
2.3.2 针对内部调用
从上面介绍的源码可以知道,其实事务的核心原理是AOP,如果没有@Transactional注解,调用的方法其实是本类,并没有被获取被事务增强的代理类,因此也就不存在事务的问题,都会直接提交。
2.4 其他不生效的场景
- MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB
- 事务未交给spring管理
- 设置spring事务级别为NOT_SUPPORTED不支持事务,如果当前有事务则挂起事务运行
3 结论
最主要的是我们要了解spring 事务的原理,只有了解其原理,在不生效的情况下,能知道如何解决。