Springcloud学习笔记50--@Transactional注解原理及常见失效场景分析

 1. @Transactional注解实现事务管理的原理

在实际项目中,用Spring进行事务控制,我们通常都用@Transactional注解。这个注解用法很简单,把原来jdbc繁琐的事务控制都浓缩在这个注解的使用上了。秉着“知其然,知其所以然”的心态,我们可以思考,这个注解那么牛掰,spring是如何实现的呢?这一切都得益于Spring那套强大的生态系统。

1.1 最简版的jdbc事务处理

先来看看最简版的jdbc事务处理:

复制代码
//获取连接
Connection conn = getConnnection();
Statement stat = conn.createStatement();
//设置为手动提交事务
conn.setAutoCommit(false);

//开启事务
conn.beginTX();

try{
    //执行CRUD操作
    stat.executeCRUD(String sql);
    stat.executeCRUD2(String sql);
    //省略一系列代码逻辑
    //...
}catch(Exception e){
    //事务回滚
    conn.rollBack();
    throw e;
}

//提交事务
conn.commitTX();
复制代码

1.2 Transactional注解实现原理

所谓万变不离其宗,注解方式的spring事务管理实现看似复杂,其实也就是基于最简版的jdbc事务管理,进行封装、延申和扩展,让开发者使用更方便、快捷。只要我们弄懂其根本的原理,再结合spring的一些特性和优势,spring事务管理问题也迎刃而解。

1.2.1 Transactional注解简述

先来看看Transactional注解里的几个属性元素

复制代码
public @interface Transactional {

    @AliasFor("transactionManager")
    String value() default "";//事务管理器别名
    @AliasFor("value")
    String transactionManager() default "";//事务管理器
    //传播性
    Propagation propagation() default Propagation.REQUIRED;
    //隔离级别
    Isolation isolation() default Isolation.DEFAULT;
    //超时时间
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
    //是否只读
    boolean readOnly() default false;
    //指定回滚异常
    Class<? extends Throwable>[] rollbackFor() default {};
    //指定回滚异常
    String[] rollbackForClassName() default {};
    //指定不回滚异常
    Class<? extends Throwable>[] noRollbackFor() default {};
    //指定不回滚异常
    String[] noRollbackForClassName() default {};
}
复制代码

可以总结一下Transactional注解里的几个属性定义的意图:

  1. 指定了事务管理器
  2. 指定了隔离级别
  3. 指定事务的传播性
  4. 指定哪些异常事务(不)作回滚
  5. 指定了事务超时时间

1.2.2 注解加载

Spring是通过单例模式将@Transactional注解中的内容加载进来的,中间有一些是BeanFactory的工作,我省去了,直接从注解相关的类开始写流程了,流程大致如下图所示:

 
 1.2.2.1 getTransactionAttribute获取事务属性
复制代码
// 判定方法的事务属性
public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    if (method.getDeclaringClass() == Object.class) {
        return null;
    }

    // 先从缓存中尝试获取属性信息.
    Object cacheKey = getCacheKey(method, targetClass);
    Object cached = this.attributeCache.get(cacheKey);
    if (cached != null) {
        //如果缓存的是一个无事务对象
        //直接返回空,这里使用==表示比较的对象引用是否相等.
        if (cached == NULL_TRANSACTION_ATTRIBUTE) {
            return null;
        } else {
            return (TransactionAttribute) cached;
        }
    } else {
        // 当没有缓存是,需要重新计算获取事务属性.
        TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
        if (txAttr == null) {
            //如果方法没有事务,直接放入空事务对象.
            this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
        } else {
            String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
            if (txAttr instanceof DefaultTransactionAttribute) {
                ((DefaultTransactionAttribute) txAttr).setDescriptor(methodIdentification);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr);
            }
            //将事务对象属性放到缓存中,缓存的key与类的类型和方法相关
            this.attributeCache.put(cacheKey, txAttr);
        }
        return txAttr;
    }
}
复制代码

1.2.2.2 computeTransactionAttribute计算事务属性

这个方法的主要功能是,根据方法和类的类型获取事务信息

复制代码
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    //如果事务是只适用于public方法,且当前方法的修饰不是public,那么直接返回空.
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
    }

    // The method may be on an interface, but we need attributes from the target class.
    // If the target class is null, the method will be unchanged.
    Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
    //调用findTransactionAttribute方法获取事务属性,但是这个方法比较简单,就不展开记录了,后面直接分解它调用的下一级方法
    // First try is the method in the target class.
    TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
    if (txAttr != null) {
        return txAttr;
    }

    // Second try is the transaction attribute on the target class.
    txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
    if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
        return txAttr;
    }

    if (specificMethod != method) {
        // Fallback is to look at the original method.
        txAttr = findTransactionAttribute(method);
        if (txAttr != null) {
            return txAttr;
        }
        // Last fallback is the class of the original method.
        txAttr = findTransactionAttribute(method.getDeclaringClass());
        if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
            return txAttr;
        }
    }

    return null;
}
复制代码

1.2.2.3 determineTransactionAttribute判定事务属性

这个方法主要是通过逐个调用解析器解析,获取事务属性

复制代码
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae) {
    for (TransactionAnnotationParser annotationParser : this.annotationParsers) {
        TransactionAttribute attr = annotationParser.parseTransactionAnnotation(ae);
        if (attr != null) {
            return attr;
        }
    }
    return null;
}
复制代码

1.2.2.4 parseTransactionAnnotation解析事务注解

复制代码
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
    AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
            ae, Transactional.class, false, false);
    if (attributes != null) {
        return parseTransactionAnnotation(attributes);
    } else {
        return null;
    }
}
复制代码
复制代码
//解析事务注解中的属性
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
    //这个地方事务属性对象是有很多默认值的,它继承自DefaultTransactionDefinition
    RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
    // 传播
    Propagation propagation = attributes.getEnum("propagation");
    rbta.setPropagationBehavior(propagation.value());
    // 隔离等级
    Isolation isolation = attributes.getEnum("isolation");
    rbta.setIsolationLevel(isolation.value());
    // 超时时间
    rbta.setTimeout(attributes.getNumber("timeout").intValue());
    // 是否只读
    rbta.setReadOnly(attributes.getBoolean("readOnly"));
    // 事务管理器bean的名称
    rbta.setQualifier(attributes.getString("value"));
    ArrayList<RollbackRuleAttribute> rollBackRules = new ArrayList<>();
    // 回滚相关配置
    Class<?>[] rbf = attributes.getClassArray("rollbackFor");
    for (Class<?> rbRule : rbf) {
        RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);
        rollBackRules.add(rule);
    }
    String[] rbfc = attributes.getStringArray("rollbackForClassName");
    for (String rbRule : rbfc) {
        RollbackRuleAttribute rule = new RollbackRuleAttribute(rbRule);
        rollBackRules.add(rule);
    }
    Class<?>[] nrbf = attributes.getClassArray("noRollbackFor");
    for (Class<?> rbRule : nrbf) {
        NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);
        rollBackRules.add(rule);
    }
    String[] nrbfc = attributes.getStringArray("noRollbackForClassName");
    for (String rbRule : nrbfc) {
        NoRollbackRuleAttribute rule = new NoRollbackRuleAttribute(rbRule);
        rollBackRules.add(rule);
    }
    rbta.getRollbackRules().addAll(rollBackRules);
    return rbta;
}
复制代码

好了到这里事务注解中的属性已近被加载到spring容器中了,通过cglib动态代理已经将注解加入到拦截链中了。

1.3 事务实现

经过前面的流程,事务属性已近被加载到spring容器中了,然后就要探索代码在运行过程中事务注解时中的属性是怎么被利用起来的。
拦截器是通过ProxyTransactionManagementConfiguration这个自动配置类加载进来的

复制代码
@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.3.1 动态代理流程

1.3.2 invokeWithinTransaction

复制代码
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);
    // 获取切点定义信息
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
        //开启事务,创建事务信息对象
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
        Object retVal = null;
        try {
            //方法调用,这个invocation是个功能函数,其实就是调用真正的那个方法
            retVal = invocation.proceedWithInvocation();
        } catch (Throwable ex) {
            // 回滚事务,执行异常抛出的操作
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        } finally {
            //保存事务状态信息
            cleanupTransactionInfo(txInfo);
        }
        //提交事务,执行方法结束后的操作
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }
    //下面的条件分支内的处理操作与上面差不多,就不看了 
    else {
        final ThrowableHolder throwableHolder = new ThrowableHolder();

        // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
        try {
            Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
                TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
                try {
                    return invocation.proceedWithInvocation();
                } 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);
                }
            });

            // Check result state: It might indicate a Throwable to rethrow.
            if (throwableHolder.throwable != null) {
                throw throwableHolder.throwable;
            }
            return result;
        } 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;
        }
    }
}
复制代码

1.3.3 determineTransactionManager事务管理器选择

复制代码
protected PlatformTransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
    // 直接返回默认的事务管理器
    if (txAttr == null || this.beanFactory == null) {
        return getTransactionManager();
    }
    String qualifier = txAttr.getQualifier();
    if (StringUtils.hasText(qualifier)) {
        //根据注解中的bean名称获取事务管理器
        return determineQualifiedTransactionManager(this.beanFactory, qualifier);
    } else if (StringUtils.hasText(this.transactionManagerBeanName)) {
        return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
    } else {
        PlatformTransactionManager defaultTransactionManager = getTransactionManager();
        if (defaultTransactionManager == null) {
            defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
            if (defaultTransactionManager == null) {
                //获取继承了PlatformTransactionManager类的管理器
                defaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class);
                this.transactionManagerCache.putIfAbsent(
                        DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
            }
        }
        return defaultTransactionManager;
    }
}
复制代码

1.3.4 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);
        }
        //如果rollback信息没有设置,默认return (ex instanceof RuntimeException || ex instanceof Error); 
        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 {
            // 不符合上面的情况,直接提交.
            // 不过在方法里面还判断了TransactionStatus.isRollbackOnly()如果成立,还是会执行回滚的.
            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;
            }
        }
    }
}
复制代码

1.3.5 commitTransactionAfterReturning 事件完成后的操作

复制代码
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
    if (txInfo != null && txInfo.getTransactionStatus() != null) {
        if (logger.isTraceEnabled()) {
            logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
        }
        //事务对应的任务完成之后,执行提交操作
        txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
    }
}
复制代码

 2. @Transactional 注解使用方法及常见的失效场景

对于开发人员来说 @Transactional 已经不陌生了,当我们需要保证数据执行前后都要一致的时候就需要用到事务,它可以保证方法中数据库操作要么同时成功、要么同时失败,使用 @Transactional 有很多需要注意的地方,不然会发现 @Transactional 为什么会失效了。

Spring 提供了很好的事务管理机制,Spring 事务管理分为编码式和声明式的两种方式。声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于@Transactional 注解的方式。 本文介绍的是基于 @Transactional 注解的事务管理。

    @Transactional(rollbackFor = Exception.class)
    public void transactional(String msg) throws Exception{
            System.out.println("执行事务操作");
    }

2.1 @Transactional 作用接口、类、类方法

1、作用于类:当把 @Transactional 注解放在类上时,表示所有该类的 public 方法都配置相同的事务属性信息。

2、作用于方法:当类配置了 @Transactional ,方法也配置了 @Transactional,方法的事务会覆盖类的事务配置信息。

3、作用于接口:不推荐这种使用方法,因为一旦标注在 Interface 上并且配置了Spring AOP 使用 CGLib 动态代理,将会导致 @Transactional 注解失效。

2.2 常见的失效场景

2.2.1 底层数据库引擎不支持事务

如果数据不支持事务,则 Spring 自然无法支持事务。例如我们常用的 MySQL,如果引擎采用的是MyISAM,是不支持事务操作的。改成 InnoDB 就可以了。

2.2.2 @Transactional 应用在非 public 修饰的方法上

如果 @Transactional 注解应用在非 public 修饰的方法上,@Transactional 将会失效,下面我们在标有 @Transactional 的任意方法上打个断点,在 idea 内能看到事务切面点如下图所示:

点击invokeWithinTransaction这个方法,在方法里面点击红框中的方法:

继续点击红框中的方法

 

就能看到如下红框中的这么一句话,不支持非 public 修饰的方法进行事务管理。

测试案例:

未加public修饰

复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    UserMapper userMapper;

    @PostMapping("/add")
    @Transactional(rollbackFor = Exception.class)
     void addUser(){
        User user=new User();
        user.setName("lucky");
        user.setAddress("tiantai");
        user.setAge(27);
        user.setGender("man");
        userMapper.insert(user);
        throw new RuntimeException();
     }
}
复制代码

抛出异常后,数据还是插入成功; 

添加public修饰后,在执行刚才的方法,并未插入第二条,可见事务生效:

2.2.3 常被 catch 掉

在整个事务方法中使用 try-catch,导致异常无法抛出,自然会导致事务失效。代码如下:

复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    UserMapper userMapper;

    @PostMapping("/add")
    @Transactional(rollbackFor = Exception.class)
     public void addUser(){
        try {
            User user=new User();
            user.setName("linda");
            user.setAddress("tiantai");
            user.setAge(27);
            user.setGender("man");
            userMapper.insert(user);
            throw new RuntimeException();
        } catch (RuntimeException e) {
            System.out.println("插入异常了");
        }
    }
}
复制代码

查看数据库,发现数据被插入了;

解决方法:异常原样抛出

在 catch 块添加 throw new RuntimeException(e);

2.2.4 类内部调用类内部@Transactional标注的方法

事务的管理是通过代理执行的方式生效的,如果是方法内部调用,将不会走代理逻辑,也就调用不到.

在类内部调用类内部@Transactional标注的方法。这种情况下也会导致事务不开启。示例代码如下。

复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    UserMapper userMapper;

    @PostMapping("/add")
    @Transactional(rollbackFor = Exception.class)
     public void addUser(){

        User user=new User();
        user.setName("peter");
        user.setAddress("tiantai");
        user.setAge(27);
        user.setGender("man");
        userMapper.insert(user);
        throw new RuntimeException();

    }

    @PostMapping("/addUserInfo")
    public void addUserInfo(){
        this.addUser();
    }
}
复制代码

此时查询数据库发现数据peter被插入了;

解决方法:
在当前类注入自己,调用 createUser1 时通过注入的 userService 调用

复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    UserMapper userMapper;

    @Autowired
    UserController userController;

    @PostMapping("/add")
    @Transactional(rollbackFor = Exception.class)
     public void addUser(){

        User user=new User();
        user.setName("alice");
        user.setAddress("tiantai");
        user.setAge(27);
        user.setGender("man");
        userMapper.insert(user);
        throw new RuntimeException();

    }

    @PostMapping("/addUserInfo")
    public void addUserInfo(){
        userController.addUser();
    }
}
复制代码

查看日志:

查看数据库表发现数据未插入

2.2.5 rollbackFor 属性设置错误

源码中已经有注释说明清楚,如下图:

默认情况下事务仅回滚运行时异常和 Error,不回滚检查异常(例如 IOException),因此如果方法中抛出了 IO 异常,默认情况下事务也会回滚失败。我们可以通过指定 @Transactional(rollbackFor = Exception.class) 的方式进行全异常捕获。

参考文献:https://blog.csdn.net/qq_20597727/article/details/84900994

 https://blog.csdn.net/baidu_39322753/article/details/100073169

https://blog.csdn.net/hxm_Code/article/details/100941563

https://blog.csdn.net/wuhuayangs/article/details/125058706

posted @   雨后观山色  阅读(1681)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示