简单回顾Spring事务失效的情况

事务失效

事务失效的情况说明
方法内部调用Spring 通过代理来实现 @Transactional 注解的功能。如果一个 @Transactional 方法直接调用了同一个类的另一个 @Transactional 方法,这种调用不会经过代理对象,因此事务管理不会生效
非公共方法Spring AOP 只能代理公共方法,因为它是通过动态代理实现的,只有公共方法才能被外部调用并被代理拦截。
返回类型不匹配Spring 事务代理要求 @Transactional 方法的返回类型必须与接口中定义的返回类型一致。
异常处理Spring 事务管理基于异常驱动,如果捕获了异常但没有将其抛出,事务将不会回滚。
继承问题如果子类重写了父类的方法,且该方法使用了 @Transactional 注解,Spring 代理可能不会代理子类的方法。
数据库不支持事务某些数据库引擎(如 MySQL 的 MyISAM)不支持事务。
数据库连接错误如果代码直接使用了数据源提供的连接,而不是通过 Spring 管理的连接,事务管理器将无法管理这些连接的事务。
缺少平台事务管理器如果没有配置 PlatformTransactionManager,Spring 无法管理事务。
事务传播行为如果 @Transactional 注解的传播行为设置不当,可能会导致事务提前提交或回滚。
并发问题高并发环境下,事务可能会因为锁竞争而导致问题。
事务超时事务如果运行时间过长,可能会被数据库自动回滚。
数据库驱动问题不兼容的数据库驱动可能导致事务管理不正常。
版本问题Spring、数据库连接池或数据库驱动的不兼容版本可能导致事务管理问题。
  1. 方法内部调用:由于spring底层是利用的动态代理实现事务的,同一个类方法调用使用的是this调用,所以会失效。
@Service
public class MyService {
    @Transactional
    public void methodA() {
        methodB(); // 这不会触发事务
    }

    @Transactional
    public void methodB() {
        // 数据库操作
    }
}
  1. 非公共方法:Spring AOP 只能代理公共方法,因为它是通过动态代理实现的,只有公共方法才能被外部调用并被代理拦截。(考虑jdk动态代理和cglib动态代理)
@Service
public class MyService {
    @Transactional
    private void myMethod() {
        // 数据库操作
    }
}
  1. 返回类型不匹配(performTransactionWrong方法不会走代理)
public interface MyService {
    String performTransaction();
}

@Service
public class MyServiceImpl implements MyService {

    @Override
    @Transactional
    public String performTransaction() {
        // 一些数据库操作
        return "Transaction Successful";
    }

    // 错误的返回类型,导致事务失效
    @Transactional
    public int performTransactionWrong() {
        // 一些数据库操作
        return 42; // 返回类型与接口不一致
    }
}
  • performTransactionWrong 方法:尽管这个方法也标记了 @Transactional,但它返回 int 类型,而接口中并没有对应的定义。如果通过接口调用这个方法,Spring 会直接调用目标对象中的实现,而不是通过代理。这将导致事务管理失效,因为调用没有经过 Spring 的代理逻辑。
  • spring采用jdk还是cglib?
    • 当目标类实现了一个或多个接口时,Spring 默认会使用 JDK 动态代理。这种方式只为接口生成代理,且只能代理接口中的方法。
    • 如果目标类没有实现任何接口,或者如果你显式配置了使用 CGLIB,Spring 会使用 CGLIB 生成目标类的子类作为代理。CGLIB 可以代理类中的所有公共和受保护方法。
  1. 异常被捕获
@Transactional
public void updateSomething() {
    try {
        // 数据库操作
        throw new RuntimeException();
    } catch (Exception e) {
        // 异常被捕获,没有抛出
    }
}
  1. 继承问题
@Transactional
public class ParentClass {
    public void myMethod() {
        // 数据库操作
    }
}

public class ChildClass extends ParentClass {
    @Override
    public void myMethod() {
        // 数据库操作
    }
}
  • 如果一个父类方法被标记为 @Transactional,而子类重写了这个方法,Spring 默认情况下不会为子类的重写方法创建新的代理。这是因为 Spring 只能代理父类,而子类的重写方法在代理创建时是不可知的。
  1. 数据库不支持事务
  • 某些数据库引擎(如 MySQL 的 MyISAM)不支持事务。可以使用innodb
  1. 数据库连接错误:(直接使用数据源提供的连接,而不是通过spring管理的连接,应该使用jdbcTemplate)
public class MyService {
    private DataSource dataSource;

    public void updateSomething() {
        Connection conn = dataSource.getConnection();
        try {
            PreparedStatement ps = conn.prepareStatement("...");
            // 使用 conn 执行数据库操作
        } finally {
            conn.close(); // 这不会触发 Spring 事务管理
        }
    }
}
  1. 缺少平台事务管理器:
  • 如果没有配置 PlatformTransactionManager,Spring 无法管理事务。
  1. 事务传播行为:
  • 常用的传播行为包括:
    • REQUIRED:如果当前存在事务,加入该事务;如果没有,则新建一个事务(默认行为)。
    • REQUIRES_NEW:无论当前是否存在事务,都新建一个事务,当前事务被挂起。
    • NESTED:如果当前存在事务,则在当前事务中嵌套执行;否则,新建一个事务。
  • 提前提交或回滚的情况
    • 提前提交:如果一个方法设置为 REQUIRES_NEW,当它被调用时会创建一个新的事务。若这个方法内发生了异常并导致回滚,只会回滚这个新事务,而外部事务仍然处于提交状态。这可能导致某些操作成功,而其他相关操作未被回滚,造成数据不一致。
    • 回滚:如果一个方法设置为 NESTED,而外部事务发生异常并回滚,嵌套事务可能不会被回滚。这样,如果嵌套事务的逻辑成功,但外部事务回滚,会导致一些操作被保留,从而造成数据不一致。
@Service
public class TransactionService {

    @Transactional
    public void outerMethod() {
        // 一些数据库操作
        innerMethod(); // 调用内层方法
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void innerMethod() {
        // 一些数据库操作
        // 可能抛出异常,导致事务回滚
    }
}
  1. 并发问题
  • 高并发环境下,事务可能会因为锁竞争而导致问题。
@Service
public class TransferService {

    @Transactional
    public void transfer(int fromAccountId, int toAccountId, BigDecimal amount) {
        // 从源账户中扣除金额
        Account fromAccount = accountRepository.findById(fromAccountId);
        fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
        accountRepository.save(fromAccount);

        // 从目标账户中添加金额
        Account toAccount = accountRepository.findById(toAccountId);
        toAccount.setBalance(toAccount.getBalance().add(amount));
        accountRepository.save(toAccount);
    }
}

假设有两个用户同时尝试进行以下转账操作:

用户 A:从账户 1 转账 100 元到账户 2。
用户 B:从账户 2 转账 100 元到账户 1。

并发执行:

用户 A 调用 transfer(1, 2, BigDecimal.valueOf(100)):
事务开始,查询账户 1 的余额并加锁。
扣除 100 元,更新账户 1。
用户 B 同时调用 transfer(2, 1, BigDecimal.valueOf(100)):
事务开始,查询账户 2 的余额并加锁。
扣除 100 元,更新账户 2。

死锁情况:

现在,用户 A 持有账户 1 的锁,并等待更新账户 2。
用户 B 持有账户 2 的锁,并等待更新账户 1。
这种情况下,两个事务都会无限期等待,导致死锁。

解决方案:

为避免锁竞争和死锁问题,可以采取以下措施:
优化锁策略:使用更细粒度的锁,尽量减少锁的持有时间。
调整事务顺序:确保所有事务按照相同的顺序获取锁,以避免死锁。
使用乐观锁:在更新数据时,不使用悲观锁,而是通过版本控制等机制来检测并处理并发冲突。
增加重试机制:当检测到死锁或超时后,可以设置事务重试机制,以便在稍后重新尝试执行。
  1. 超时
@Service
public class UserService {

    @Transactional(timeout = 5) // 5秒超时
    public void updateUserInfo(User user) {
        // 模拟长时间的操作
        Thread.sleep(10000); // 假设这段代码需要10秒
        userRepository.save(user);
    }
}

  • 超过 5 秒后,数据库会自动回滚该事务。

数据库的默认事务超时:

MySQL:没有默认的超时限制,事务会持续到显式提交或回滚。
PostgreSQL:默认情况下没有事务超时设置,事务将一直保持打开状态,直到提交或回滚。
Oracle:默认情况下也没有超时设置,事务会保持打开状态。
SQL Server:事务同样没有默认的超时设置。
  1. 数据库驱动问题:
  • 不兼容的数据库驱动可能导致事务管理不正常。
  1. 版本问题:
  • Spring、数据库连接池或数据库驱动的不兼容版本可能导致事务管理问题。
    • Spring 与数据库的兼容性
      • Spring 版本:不同版本的 Spring 可能引入了对事务管理的改进或更改,这可能影响与特定数据库的兼容性。例如,Spring 的某些事务管理特性可能依赖于数据库的特定行为或驱动实现。
      • 数据库驱动:不同版本的数据库驱动可能存在 bug 或不支持某些事务特性,这会直接影响 Spring 的事务管理能力。例如,某些驱动可能在处理连接的提交和回滚时存在问题。
    • 数据库连接池
      • 连接池版本:使用的数据库连接池(如 HikariCP、C3P0 或 DBCP)的版本也可能影响事务管理。如果连接池的实现存在问题(如连接未正确管理、事务未正确绑定),这会导致事务状态不一致。
      • 配置问题:连接池的配置不当(如超时设置、最大连接数等)可能导致在高并发场景下事务管理出现问题,比如连接泄漏、阻塞或事务超时。

如果还有兴趣了解Spring事务相关的内容,可以看这一篇:简单回顾Spring事务相关知识

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值