@Transactional +自定义注解不生效_spring 事务一定生效吗

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

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

解决办法:将@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

解决办法:给followUser()方法添加@Transactional注解

2.3 原理

由于spring boot自动装配,我们可以直接查看spring-boot-autoconfigure.jar下面/META-INF/spring.factories文件中EnableAutoConfiguration配置内容,如下

2d9ce3863fd76d8e8bf704ea2a42381b.png

可以看到,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 事务的原理,只有了解其原理,在不生效的情况下,能知道如何解决。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值