事务管理——@Transactional

@Transactional是 Java 中用于声明式事务管理的关键注解,通常与 Spring 框架一起使用。它允许开发者通过简单的注解方式来管理事务,而无需手动编写事务控制代码(如 beginTransaction()commit()rollback()等)。

一、基本概念

​事务(Transaction)​ ​ 是数据库操作的基本单位,它具有以下四个特性,通常称为 ​​ACID​​ 特性:

  1. ​原子性(Atomicity)​​:事务中的所有操作要么全部成功,要么全部失败回滚。

  2. ​一致性(Consistency)​​:事务执行前后,数据库从一个一致状态转变到另一个一致状态。

  3. ​隔离性(Isolation)​​:多个事务并发执行时,一个事务的执行不应影响其他事务。

  4. ​持久性(Durability)​​:一旦事务提交,其结果就是永久性的,即使系统崩溃也不会丢失。

@Transactional注解的作用就是让方法或类在运行时自动具备事务管理的能力,确保上述 ACID 特性得以实现。


二、@Transactional的使用场景

@Transactional主要用于以下场景:

  • 数据库的增删改操作(如 INSERTUPDATEDELETE),尤其是需要保证数据一致性的业务逻辑。

  • 需要在多个数据库操作之间保持原子性的业务方法。

  • 需要处理并发访问时的数据一致性问题。

​注意​ ​:@Transactional通常用于服务层(Service Layer),而不是控制层(Controller Layer)或数据访问层(DAO Layer),以实现业务逻辑的事务管理。


三、@Transactional的基本用法

1. 在方法上使用

在需要事务管理的方法上添加 @Transactional注解,Spring 会自动为该方法创建和管理事务。

java 复制代码
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void createUser(User user) {
        // 业务逻辑
        userRepository.save(user);
        // 其他数据库操作
    }
}

createUser方法被 @Transactional注解标记,Spring 会在方法执行前 开启事务,方法执行成功后提交事务,如果方法抛出未捕获的异常,则回滚事务。

2. 在类上使用

@Transactional注解应用于整个类,表示该类中的所有公共方法都默认具有事务管理的能力。

java 复制代码
@Service
@Transactional
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public void createUser(User user) {
        userRepository.save(user);
    }

    public void updateUser(User user) {
        userRepository.update(user);
    }
}
复制代码
注意​​:在类级别使用 @Transactional时,如果某个方法需要不同的事务配置,可以在方法级别重新声明 @Transactional以覆盖类级别的配置。

四、@Transactional的配置属性

@Transactional注解提供了多个属性,允许开发者根据具体需求进行细粒度的事务配置:

属性 类型 说明
value/ transactionManager String 指定事务管理器的 Bean 名称。默认使用 Spring 容器中配置的事务管理器。
propagation Propagation 事务的传播行为,定义事务的嵌套规则。
isolation Isolation 事务的隔离级别,定义事务之间的可见性。
timeout int 事务的超时时间(以秒为单位),超时后事务将被回滚。
readOnly boolean 是否为只读事务。只读事务通常用于查询操作,可以提高性能。
rollbackFor Class<? extends Throwable>[] 指定哪些异常类型会触发事务回滚。默认情况下,仅对未检查异常(继承自 RuntimeException)和 Error进行回滚。
noRollbackFor Class<? extends Throwable>[] 指定哪些异常类型不会触发事务回滚。

1. 传播行为(Propagation)

传播行为定义了事务的嵌套规则,即当一个事务方法调用另一个事务方法时,事务应该如何传播。Spring 提供了以下几种传播行为:

  • Propagation.REQUIRED(默认):如果当前存在事务,则加入该事务;如果不存在,则新建一个事务。

  • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果不存在,则以非事务方式执行。

  • Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果不存在,则抛出异常。

  • Propagation.REQUIRES_NEW:无论当前是否存在事务,都新建一个事务,并挂起当前事务(如果存在)。

  • Propagation.NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务。

  • Propagation.NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

  • Propagation.NESTED:如果当前存在事务,则在嵌套事务内执行;如果不存在,则新建一个事务。嵌套事务可以独立于外部事务进行提交或回滚,但外部事务回滚时,嵌套事务也会回滚。

java 复制代码
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void performIndependentOperation() {
    // 该方法将在一个新的事务中执行,与调用者的事务独立
}

2. 隔离级别(Isolation)

隔离级别定义了一个事务与其他并发事务之间的可见性,防止并发事务之间的干扰。Spring 支持以下几种隔离级别:

  • Isolation.DEFAULT(默认):使用底层数据库的默认隔离级别。

  • Isolation.READ_UNCOMMITTED:允许读取未提交的数据变更,可能导致脏读、不可重复读和幻读。

  • Isolation.READ_COMMITTED:只允许读取已提交的数据,防止脏读,但可能出现不可重复读和幻读。

  • Isolation.REPEATABLE_READ:确保在同一事务内多次读取同一数据时结果一致,防止脏读和不可重复读,但可能出现幻读。

  • Isolation.SERIALIZABLE:最高的隔离级别,通过完全锁定相关数据来防止脏读、不可重复读和幻读,但性能较低。

java 复制代码
@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateUserData() {
    // 该方法将在 READ_COMMITTED 隔离级别下执行
}

3. 超时时间(Timeout)

timeout属性用于设置事务的超时时间(以秒为单位)。如果事务在指定的时间内未完成,则会被自动回滚。

java 复制代码
@Transactional(timeout = 30)
public void longRunningProcess() {
    // 该方法的事务超时时间为 30 秒
}

4. 回滚规则(Rollback For / No Rollback For)

  • rollbackFor:指定哪些异常类型会触发事务回滚。默认情况下,仅对未检查异常(继承自 RuntimeException)和 Error进行回滚。如果需要对某些已检查异常(继承自 Exception)也进行回滚,可以通过此属性指定。

  • noRollbackFor:指定哪些异常类型不会触发事务回滚。即使这些异常被抛出,事务也不会回滚。

java 复制代码
@Transactional(rollbackFor = {CustomException.class}, noRollbackFor = {IllegalArgumentException.class})
public void processWithCustomRules() throws CustomException {
    // 该方法在抛出 CustomException 时会回滚事务,而抛出 IllegalArgumentException 时不会回滚
}

五、@Transactional的工作原理

Spring 的 @Transactional注解依赖于 ​​AOP(面向切面编程)​ ​ 来实现事务管理。具体来说,Spring 通过动态代理(JDK 动态代理或 CGLIB)在运行时为目标方法创建代理对象,代理对象在调用目标方法前后插入事务管理的逻辑。

@Transactional的大致工作流程:

  1. ​代理创建​ ​:Spring 容器在启动时,会扫描带有 @Transactional注解的类,并为这些类创建代理对象。代理对象会拦截对目标方法的调用。

  2. ​事务拦截​ ​:当调用被 @Transactional注解标记的方法时,代理对象首先会检查是否存在活动的事务。根据 propagation属性的配置,决定是加入现有事务、新建事务还是以非事务方式执行。

  3. ​事务开启​ ​:如果需要新建事务,代理对象会通过配置的事务管理器(如 DataSourceTransactionManager)开启一个新的事务。

  4. ​方法执行​​:代理对象调用目标方法,执行实际的业务逻辑。

  5. ​事务提交或回滚​​:

    • 如果方法执行成功且没有抛出异常(或抛出的异常不在 noRollbackFor列表中),代理对象会提交事务。

    • 如果方法抛出异常(且该异常在 rollbackFor列表中或属于默认回滚的异常类型),代理对象会回滚事务。

  6. ​资源释放​​:事务提交或回滚后,代理对象会释放相关的事务资源(如数据库连接)。

​注意​ ​:由于 @Transactional基于 AOP 实现,因此它只能拦截 ​​public​ ​ 方法的事务。对于非 public 方法(如 private、protected 或包级私有方法),@Transactional注解将不会生效。


六、@Transactional的注意事项

在使用 @Transactional时,有一些常见的注意事项和常见问题需要了解,以避免潜在的错误和误解。

1. 自调用问题

由于 @Transactional基于 AOP 实现,它只能拦截通过代理对象调用的方法 。如果在同一个类中,一个方法调用另一个带有 @Transactional注解的方法(即自调用),事务注解将不会生效,因为调用是通过 this关键字直接进行的,而不是通过代理对象。

java 复制代码
@Service
public class UserService {

    @Transactional
    public void methodA() {
        // 业务逻辑
        methodB(); // 自调用,事务注解不生效
    }

    @Transactional
    public void methodB() {
        // 业务逻辑
    }
}

解决方案​​:

  • methodB移到另一个服务类中,通过依赖注入调用,以确保通过代理对象调用。

  • 使用 AopContext.currentProxy()获取当前代理对象进行调用(需要开启 exposeProxy配置)。

java 复制代码
@Service
public class UserService {

    @Autowired
    private UserService self; // 注入自身代理

    @Transactional
    public void methodA() {
        // 业务逻辑
        self.methodB(); // 通过代理对象调用
    }

    @Transactional
    public void methodB() {
        // 业务逻辑
    }
}
java 复制代码
@Service
public class UserService {

    @Transactional
    public void methodA() {
        // 业务逻辑
        ((UserService) AopContext.currentProxy()).methodB(); // 获取当前代理对象并调用
    }

    @Transactional
    public void methodB() {
        // 业务逻辑
    }
}

注意​ ​:使用 AopContext.currentProxy()需要在 Spring 配置中开启 exposeProxy ,例如在 XML 配置中添加 <aop:aspectj-autoproxy expose-proxy="true"/>,或在 Java 配置类中设置 @EnableAspectJAutoProxy(exposeProxy = true),需要引入依赖aspectjweaver

java 复制代码
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>

2. 异常处理

默认情况下,@Transactional只对未检查异常(继承自 RuntimeException)和 Error进行回滚。如果方法抛出已检查异常(继承自 Exception),事务不会回滚,除非通过 rollbackFor属性显式指定。

相关推荐
野蛮人6号10 分钟前
黑马点评系列问题之p63unlock.lua不知道怎么整
java·redis·黑马点评
深度混淆35 分钟前
C#,List<T> 与 Vector<T>
开发语言·c#·vector·list·simd
lixzest37 分钟前
C++ 中两个类之间的通信方式
开发语言·c++
Raners_1 小时前
【Java代码审计(2)】MyBatis XML 注入审计
xml·java·安全·网络安全·mybatis
BillKu1 小时前
Java读取Excel日期内容
java·开发语言·excel
ai小鬼头1 小时前
如何重装旁路由系统并优化AIStarter部署:一步步教程
java·css·github
come112341 小时前
Go 包管理工具详解:安装与使用指南
开发语言·后端·golang
笑衬人心。1 小时前
Hashtable 与 HashMap 的区别笔记
java·数据结构·笔记
金心靖晨1 小时前
消息中间件优化高手笔记
java·数据库·笔记
CV练习生Zzz2 小时前
MATLAB知识点总结
开发语言·matlab