事务传递机制

简介: 事务管理

@Slf4j
public abstract class SerialNumberBaseService implements SerialNumberService {

@Override
public String generate(SerialNumberIdEnum serialNumberIdEnum) {
List generateList = this.generate(serialNumberIdEnum, 1);
if (generateList == null || generateList.isEmpty()) {
throw new BusinessException("cannot generate : " + serialNumberIdEnum.toString());
}
return generateList.get(0);
}

@Override
public List<String> generate(SerialNumberIdEnum serialNumberIdEnum, int count) {
    SerialNumberInfoBO serialNumberInfoBO = serialNumberMap.get(serialNumberIdEnum.getSerialNumberId());
    if (serialNumberInfoBO == null) {
        throw new BusinessException("cannot found SerialNumberId : " + serialNumberIdEnum.toString());
    }
    return this.generateSerialNumberList(serialNumberInfoBO, count);
}

}

@Slf4j
@Service
public class SerialNumberMysqlService extends SerialNumberBaseService {

@Override
@Transactional(rollbackFor = Exception.class,transactionManager = "secondaryTransactionManager")
public List<String> generateSerialNumberList(SerialNumberInfoBO serialNumberInfo, int count) {

    // 获取当前事务状态
    TransactionStatus status = TransactionAspectSupport.currentTransactionStatus();
    log.info("当前事务是否是新事务: {}", status.isNewTransaction());
    log.info("当前事务是否有保存点: {}", status.hasSavepoint());

}

}
解释一下为什么这个事务会失效

在你的代码中,SerialNumberMysqlService 继承了 SerialNumberBaseService,并且在 SerialNumberMysqlService 中重写了 generateSerialNumberList 方法,并为其添加了 @Transactional 注解。然而,事务可能会失效,原因在于 Spring 的事务管理机制和代理机制的特性。

事务失效的原因分析

1. 事务注解的作用范围

Spring 的事务是通过动态代理实现的。只有通过 Spring 代理调用的方法,事务注解才会生效。如果直接在类内部调用方法(绕过代理),事务注解不会生效。

在你的代码中,SerialNumberBaseService 中的 generate 方法调用了 generateSerialNumberList 方法:

return this.generateSerialNumberList(serialNumberInfoBO, count);

这里直接通过 this 调用了 generateSerialNumberList 方法,绕过了 Spring 的代理机制。因此,事务注解不会生效。

2. 事务注解的继承问题

虽然 SerialNumberMysqlService 继承了 SerialNumberBaseService,但事务注解(@Transactional)不会被继承。事务注解的作用范围仅限于它被定义的方法上。因此,即使 SerialNumberMysqlService 重写了父类的方法并添加了事务注解,父类中的调用仍然不会触发事务管理。

3. 事务传播行为

即使事务注解生效,事务传播行为(Propagation.REQUIRED)决定了事务的共享或独立性。在你的代码中,事务注解被定义在 generateSerialNumberList 方法上,而不是在 generate 方法上。这意味着事务管理仅适用于 generateSerialNumberList 方法,而 generate 方法的调用不会触发事务。

事务失效的具体原因

在你的代码中:

@Override
public List<String> generate(SerialNumberIdEnum serialNumberIdEnum, int count) {
   
    SerialNumberInfoBO serialNumberInfoBO = serialNumberMap.get(serialNumberIdEnum.getSerialNumberId());
    if (serialNumberInfoBO == null) {
   
        throw new BusinessException("cannot found SerialNumberId : " + serialNumberIdEnum.toString());
    }
    return this.generateSerialNumberList(serialNumberInfoBO, count); // 直接调用,绕过代理
}

generateSerialNumberList 方法被直接调用,绕过了 Spring 的代理机制。因此,事务注解不会生效。

解决方案

方法 1:将事务注解移到父类的 generate 方法上

将事务注解放在父类的 generate 方法上,确保事务管理生效:

@Slf4j
public abstract class SerialNumberBaseService implements SerialNumberService {
   

    @Override
    @Transactional(rollbackFor = Exception.class, transactionManager = "secondaryTransactionManager")
    public String generate(SerialNumberIdEnum serialNumberIdEnum) {
   
        List<String> generateList = this.generate(serialNumberIdEnum, 1);
        if (generateList == null || generateList.isEmpty()) {
   
            throw new BusinessException("cannot generate : " + serialNumberIdEnum.toString());
        }
        return generateList.get(0);
    }

    @Override
    @Transactional(rollbackFor = Exception.class, transactionManager = "secondaryTransactionManager")
    public List<String> generate(SerialNumberIdEnum serialNumberIdEnum, int count) {
   
        SerialNumberInfoBO serialNumberInfoBO = serialNumberMap.get(serialNumberIdEnum.getSerialNumberId());
        if (serialNumberInfoBO == null) {
   
            throw new BusinessException("cannot found SerialNumberId : " + serialNumberIdEnum.toString());
        }
        return this.generateSerialNumberList(serialNumberInfoBO, count);
    }
}

这样可以确保事务管理生效,因为事务注解直接作用于父类的方法。

方法 2:通过代理调用方法

如果事务注解必须放在子类中,可以通过 Spring 的代理机制调用方法:

@Override
public List<String> generate(SerialNumberIdEnum serialNumberIdEnum, int count) {
   
    SerialNumberInfoBO serialNumberInfoBO = serialNumberMap.get(serialNumberIdEnum.getSerialNumberId());
    if (serialNumberInfoBO == null) {
   
        throw new BusinessException("cannot found SerialNumberId : " + serialNumberIdEnum.toString());
    }
    return ((SerialNumberBaseService) AopContext.currentProxy()).generateSerialNumberList(serialNumberInfoBO, count);
}

这里使用了 AopContext.currentProxy() 来获取当前的代理对象,从而确保事务注解生效。注意,使用 AopContext.currentProxy() 需要启用 expose-proxy

@EnableAspectJAutoProxy(exposeProxy = true)

方法 3:重构代码

将事务逻辑直接放在子类中,避免内部调用:

@Slf4j
@Service
public class SerialNumberMysqlService extends SerialNumberBaseService {
   

    @Override
    @Transactional(rollbackFor = Exception.class, transactionManager = "secondaryTransactionManager")
    public String generate(SerialNumberIdEnum serialNumberIdEnum) {
   
        List<String> generateList = generate(serialNumberIdEnum, 1);
        if (generateList == null || generateList.isEmpty()) {
   
            throw new BusinessException("cannot generate : " + serialNumberIdEnum.toString());
        }
        return generateList.get(0);
    }

    @Override
    @Transactional(rollbackFor = Exception.class, transactionManager = "secondaryTransactionManager")
    public List<String> generate(SerialNumberIdEnum serialNumberIdEnum, int count) {
   
        SerialNumberInfoBO serialNumberInfoBO = serialNumberMap.get(serialNumberIdEnum.getSerialNumberId());
        if (serialNumberInfoBO == null) {
   
            throw new BusinessException("cannot found SerialNumberId : " + serialNumberIdEnum.toString());
        }
        return generateSerialNumberList(serialNumberInfoBO, count);
    }
}

这样可以确保事务管理生效,因为事务注解直接作用于子类的方法。

总结

  • 事务失效的原因:直接调用方法绕过了 Spring 的代理机制,导致事务注解不生效。
  • 解决方案
    1. 将事务注解放在父类的 generate 方法上。
    2. 通过 Spring 的代理机制调用方法(如使用 AopContext.currentProxy())。
    3. 重构代码,将事务逻辑直接放在子类中。

根据你的需求选择合适的解决方案,以确保事务管理生效。

解释一下 事务注解的作用范围仅限于它被定义的方法上

在 Spring 中,@Transactional 注解的作用范围仅限于它被定义的方法上,这意味着事务管理器只会对被 @Transactional 注解直接修饰的方法生效。这一特性与 Java 的注解继承规则以及 Spring 的事务代理机制有关。

为什么事务注解的作用范围仅限于被定义的方法?

1. 注解的非继承性

在 Java 中,注解默认不会被继承。即使一个子类方法重写了父类的注解方法,子类也不会自动继承父类的注解。例如:

public abstract class Parent {
   
    @Transactional
    public abstract void method();
}

public class Child extends Parent {
   
    @Override
    public void method() {
   
        // 实现逻辑
    }
}

在上述代码中,虽然 Parent 类的 method 方法被 @Transactional 注解修饰,但 Child 类的 method 方法并不会自动继承这个注解。如果需要在子类中使用事务,必须显式地在子类方法上重新添加 @Transactional 注解。

2. Spring 的事务代理机制

Spring 的事务是通过动态代理实现的。当一个方法被 @Transactional 注解修饰时,Spring 会为该方法创建一个事务代理。这个代理会拦截对方法的调用,并在调用前后插入事务管理逻辑。然而,这种代理机制仅对通过代理调用的方法生效,而不会自动扩展到父类或其他未被注解的方法。

例如:

@Service
public class Child extends Parent {
   
    @Override
    @Transactional
    public void method() {
   
        super.method(); // 调用父类方法
    }
}

在上述代码中:

  • Child.method()@Transactional 注解修饰,因此事务管理器会为它创建代理。
  • 然而,super.method() 的调用是通过 this 发生的,绕过了 Spring 的代理机制。因此,即使父类方法原本有事务注解,事务管理也不会生效。

3. 事务注解的作用范围

@Transactional 注解的作用范围仅限于它被定义的方法,这意味着:

  • 事务管理器只会在被注解的方法上生效:Spring 的事务管理器会根据注解的定义来管理事务,而不会自动扩展到其他方法。
  • 子类方法不会自动继承父类的事务注解:即使子类方法重写了父类的事务方法,子类方法也不会自动继承父类的事务行为,除非显式地重新添加注解。

示例

假设你有以下代码:

public abstract class Parent {
   
    @Transactional
    public void parentMethod() {
   
        // 父类方法逻辑
    }
}

@Service
public class Child extends Parent {
   
    @Override
    public void parentMethod() {
   
        super.parentMethod(); // 调用父类方法
    }
}

在这种情况下:

  • Parent.parentMethod()@Transactional 注解修饰,但事务管理器不会自动生效,因为 Child 类没有显式地添加事务注解。
  • Child.parentMethod() 调用 super.parentMethod() 时,事务管理器不会介入,因为调用是通过 this 发生的,绕过了 Spring 的代理机制。

解决方法

为了确保事务管理生效,需要在子类方法上显式地添加 @Transactional 注解:

@Service
public class Child extends Parent {
   
    @Override
    @Transactional
    public void parentMethod() {
   
        super.parentMethod();
    }
}

这样,Spring 的事务管理器会为 Child.parentMethod() 创建代理,并确保事务管理生效。

总结

  • 事务注解的作用范围仅限于被定义的方法:Spring 的事务管理器只会对被 @Transactional 注解直接修饰的方法生效。
  • 子类方法不会自动继承父类的事务注解:即使子类方法重写了父类的事务方法,也需要显式地重新添加注解。
  • 事务管理依赖于 Spring 的代理机制:只有通过代理调用的方法,事务管理器才会介入。

因此,为了确保事务管理生效,需要在子类方法上显式地添加 @Transactional 注解,并确保方法调用通过 Spring 的代理机制进行。

相关文章
|
存储 算法 Java
G1原理—1.G1回收器的分区机制
本文深入探讨了G1垃圾回收器的多个核心概念与实现细节,包括分区(Region)管理、新生代动态扩展机制以及停顿预测模型。首先分析了G1中Region大小的计算规则及其对性能的影响,强调Region大小需为2的幂次以优化内存分配效率并避免碎片化。其次介绍了新生代内存分配方式及动态扩展流程,通过自由分区列表调整新生代大小以平衡GC时间和程序运行时间。最后重点解析了基于衰减算法的停顿预测模型,该模型利用历史GC数据加权平均来精准预测每次GC所需时间,从而确保满足用户设定的停顿时间目标。这些机制共同作用,使G1能够在大内存场景下实现高效垃圾回收与低延迟表现。
718 10
G1原理—1.G1回收器的分区机制
|
监控 Java 数据库
Spring事务中的@Transactional注解剖析
通过上述分析,可以看到 `@Transactional`注解在Spring框架中扮演着关键角色,它简化了事务管理的复杂度,让开发者能够更加专注于业务逻辑本身。合理运用并理解其背后的机制,对于构建稳定、高效的Java企业应用至关重要。
741 0
|
Java Spring
JDK动态代理和CGLIB动态代理的区别
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理: ● JDK动态代理只提供接口的代理,不支持类的代理Proxy.newProxyInstance(类加载器, 代理对象实现的所有接口, 代理执行器) ● CGLIB是通过继承的方式做的动态代理 , 如果某个类被标记为final,那么它是无法使用 CGLIB做动态代理的。Enhancer.create(父类的字节码对象, 代理执行器)
|
测试技术
单元测试问题之使用TestMe时利用JUnit 5的参数化测试特性如何解决
单元测试问题之使用TestMe时利用JUnit 5的参数化测试特性如何解决
576 2
|
安全 数据库 开发者
告别Navicat:彻底卸载指南及注意事项
【10月更文挑战第12天】 Navicat,作为一款广受数据库管理员和开发者喜爱的数据库管理工具,以其强大的功能和用户友好的界面著称。然而,有时出于各种原因,如软件升级、更换工具或系统维护,我们需要将其从系统中卸载。本文将提供一个详细的Navicat卸载指南,确保卸载过程既彻底又安全。
2815 6
|
SQL 关系型数据库 MySQL
详解MySQL覆盖索引、索引下推
1.覆盖索引 1.1.概述 覆盖索引,是为了避免“回表查询”,从而降低查询耗时的一种使用索引的方法,所以要聊覆盖索引首先我们要知道什么是"回表查询,“回表查询”是因为MySQL的索引结构决定的,是因为非聚集索引要找聚集索引拿数据而出现的现象,所以我们又要先了解MySQL中的聚集索引和非聚集索引。 文章的脉络就是先聊聚集索引、非聚集索引是怎么带来了“回表查询”的问题,然后怎么用用覆盖索引解决这个问题。
2648 0
|
算法 Java
基于java雪花算法工具类SnowflakeIdUtils-来自chatGPT
基于java雪花算法工具类SnowflakeIdUtils-来自chatGPT
906 3
java编写枚举校验类
java编写枚举校验类
420 0
|
Java 数据库 Spring
@Transactional 失效场景介绍
【2月更文挑战第5天】
1585 1
@Transactional 失效场景介绍
|
前端开发 开发者
【面试题】手写async await核心原理,再也不怕面试官问我async await原理
【面试题】手写async await核心原理,再也不怕面试官问我async await原理
619 1