基于 Spring 的本地事务管理

1. 什么是本地事务?

本地事务是指在单一数据库系统内执行的一组操作,这些操作要么全部完成,要么全部不执行,是一个不可分割的工作单元。本地事务具有ACID特性:

  • 原子性(Atomicity):事务中的所有操作都作为一个整体提交或回滚;如果事务的一部分失败,则整个事务都会被撤销。
  • 一致性(Consistency):事务将数据库从一个一致状态转换到另一个一致状态,确保数据的完整性和规则得到遵守。
  • 隔离性(Isolation):多个并发事务之间相互隔离,一个事务的中间状态对其他事务是不可见的。
  • 持久性(Durability):一旦事务成功提交,它对数据库的更改就是永久性的,即使系统出现故障。

2. 使用JDBC进行本地事务管理

2.1. 基本步骤

  1. 获取数据库连接:使用DriverManager.getConnection()方法获取数据库连接。
  2. 关闭自动提交:调用connection.setAutoCommit(false)关闭自动提交模式。
  3. 执行SQL操作:在同一个连接上执行多个SQL语句。
  4. 提交或回滚事务:根据操作结果调用connection.commit()或connection.rollback()。
  5. 关闭连接:释放数据库连接资源。

2.2. 示例代码

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class JdbcTransactionExample {
    private static final String DB_URL = "jdbc:mysql://localhost:3306/test";
    private static final String USER = "root";
    private static final String PASS = "root";

    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement stmt1 = null;
        PreparedStatement stmt2 = null;

        try {
            // 1. 获取数据库连接
            conn = DriverManager.getConnection(DB_URL, USER, PASS);
            // 2. 关闭自动提交
            conn.setAutoCommit(false);

            // 3. 执行SQL操作
            String sql1 = "INSERT INTO users (name, email) VALUES (?, ?)";
            stmt1 = conn.prepareStatement(sql1);
            stmt1.setString(1, "John Doe");
            stmt1.setString(2, "john.doe@example.com");
            stmt1.executeUpdate();

            // 模拟异常
            // int a = 0/0;

            String sql2 = "UPDATE accounts SET balance = balance - 100 WHERE user_id = ?";
            stmt2 = conn.prepareStatement(sql2);
            stmt2.setInt(1, 1);
            stmt2.executeUpdate();

            // 4. 提交事务
            conn.commit();
            System.out.println("Transaction committed successfully.");
        } catch (SQLException e) {
            if (conn != null) {
                try {
                    // 5. 回滚事务
                    conn.rollback();
                    System.out.println("Transaction rolled back.");
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            // 6. 关闭连接
            try {
                if (stmt1 != null) stmt1.close();
                if (stmt2 != null) stmt2.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

3. 使用Spring框架进行本地事务管理

3.1. 编程式事务管理

编程式事务管理是指在代码中显式地控制事务的开始、提交和回滚。这种方式提供了更大的灵活性,但也会增加代码的复杂性。

3.1.1. 使用TransactionTemplate进行编程式事务管理

TransactionTemplate是Spring提供的一个类,用于简化编程式事务管理。它封装了事务管理的逻辑,使得开发者可以更方便地控制事务。

3.1.1.1. 配置TransactionTemplate

首先,需要配置一个TransactionTemplate bean。通常在Spring Boot项目中,可以通过DataSourceTransactionManager来配置。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;

@Configuration
public class TransactionConfig {

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
        // 设置事务隔离级别
//        transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        // 设置事务只读
//        transactionTemplate.setReadOnly(true);
        // 设置传播行为
//        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        // 设置超时时间为30秒
//        transactionTemplate.setTimeout(30); 
        return transactionTemplate;
    }
}

注意: 在Spring

Boot中,PlatformTransactionManager、TransactionTemplate通常会被JPA、JDBC、MyBatis、Hibernate等常见框架自动配置和注入,所以通常不需要手动配置PlatformTransactionManager、TransactionTemplate。

3.1.2. 使用TransactionTemplate管理事务

在需要事务管理的方法中,注入TransactionTemplate并使用它来控制事务。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private AccountRepository accountRepository;

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void registerUser(User user) {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    // 1. 保存用户信息
                    userRepository.save(user);

                    // 设置保存点
//                    Object savepoint = status.createSavepoint();

                    // ... 其他操作

                    // 操作失败,回滚到保存点
//                     status.rollbackToSavepoint(savepoint);

                    // 2. 更新账户余额
                    Account account = accountRepository.getById(user.getId());
                    account.setBalance(account.getBalance() - 100);
                    accountRepository.updateById(account);

                    // 释放保存点
//                    status.releaseSavepoint(savepoint);
                } catch (Exception e) {
                    // 3. 设置事务回滚
                    status.setRollbackOnly();
                    e.printStackTrace();
                }
            }
        });
    }
}

3.1.2 使用PlatformTransactionManager进行编程式事务管理

除了TransactionTemplate,还可以直接使用PlatformTransactionManager来管理事务。

3.1.2.1 配置PlatformTransactionManager

与前面的配置相同,确保已经配置了PlatformTransactionManager。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

@Configuration
public class TransactionConfig {

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

注意: 在Spring
Boot中,PlatformTransactionManager通常会被JPA、JDBC、MyBatis、Hibernate等常见框架自动配置和注入,所以通常不需要手动配置PlatformTransactionManager。

3.1.2.2 使用PlatformTransactionManager管理事务

在需要事务管理的方法中,注入PlatformTransactionManager并使用它来控制事务。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private AccountRepository accountRepository;

    @Autowired
    private PlatformTransactionManager transactionManager;

    public void registerUser(User user) {
        // 1. 定义事务属性
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        // 设置传播行为
//        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        // 设置隔离级别
//        def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        // 设置是否只读
//        def.setReadOnly(false); 

        // 2. 获取事务状态
        TransactionStatus status = transactionManager.getTransaction(def);

        try {
            // 3. 保存用户信息
            userRepository.save(user);

            // 创建保存点
//            Object savepoint = status.createSavepoint();
            // 回滚到保存点
//            status.rollbackToSavepoint(savepoint);

            // 4. 更新账户余额
            Account account = accountRepository.getById(user.getId());
            account.setBalance(account.getBalance() - 100);
            accountRepository.updateById(account);

            // 释放保存点
//            status.releaseSavepoint(savepoint);

            // 5. 提交事务
            transactionManager.commit(status);
        } catch (Exception e) {
            // 6. 回滚事务
            transactionManager.rollback(status);
            e.printStackTrace();
        }
    }
}

3.1.3 TransactionTemplate 和 PlatformTransactionManager 区别和联系

特性/功能TransactionTemplatePlatformTransactionManager
用途提供模板化的编程方式来管理事务,简化事务管理代码。定义事务管理的基本接口,提供事务管理的核心功能。
编程模型编程式事务管理。可以用于编程式和声明式事务管理。
使用方式直接在代码中使用 TransactionTemplate 来管理事务。通常通过 TransactionTemplate@Transactional 注解使用。
配置属性可以设置隔离级别、传播行为、超时时间、只读属性等。通过 TransactionTemplate 配置,也可以通过其他方式配置。
回滚规则通过 TransactionCallback 手动处理异常来控制回滚。通过 TransactionAttribute 配置回滚规则。
保存点支持保存点(savepoints),可以部分回滚事务。支持保存点(savepoints),可以部分回滚事务。
事务状态管理使用 TransactionStatus 对象来管理事务状态。使用 TransactionStatus 对象来管理事务状态。
依赖依赖于 PlatformTransactionManager是事务管理的核心接口,TransactionTemplate 依赖于它。
灵活性提供更高的灵活性,适合复杂的事务逻辑。提供基本的事务管理功能,适合大多数情况。

3.2. 声明式事务管理

Spring框架提供了强大的声明式事务管理,简化了事务处理代码。通过使用@Transactional注解,可以方便地定义方法级别的事务边界。

3.2.1 声明式事务使用

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private AccountRepository accountRepository;

    @Transactional
    public void registerUser(User user) {
        // 1. 保存用户信息
        userRepository.save(user);

        // 2. 更新账户余额
        Account account = accountRepository.getById(user.getId());
        account.setBalance(account.getBalance() - 100);
        accountRepository.updateById(account);

        // 如果抛出运行时异常,事务将回滚
    }
}

常用的 @Transactional 参数及其说明

  1. propagation
  • 类型: Propagation
  • 默认值: Propagation.REQUIRED
  • 描述: 定义了事务的传播行为,即当前事务与新事务之间的关系。
  • 可选值:
    • REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
    • SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
    • MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
    • REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则挂起当前事务。
    • NOT_SUPPORTED: 以非事务方式执行操作,如果当前存在事务,则挂起当前事务。
    • NEVER: 以非事务方式执行操作,如果当前存在事务,则抛出异常。
    • NESTED: 如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新的事务。
  1. isolation
  • 类型: Isolation
  • 默认值: Isolation.DEFAULT
  • 描述: 定义了事务的隔离级别,决定了事务之间如何相互影响。
  • 可选值:
    • DEFAULT: 使用底层数据库的默认隔离级别。
    • READ_UNCOMMITTED: 最低的隔离级别,允许读取未提交的数据。
    • READ_COMMITTED: 只能读取已经提交的数据,不能读取未提交的数据。
    • REPEATABLE_READ: 在同一个事务中,多次读取同一数据的结果是一致的。
    • SERIALIZABLE: 最高的隔离级别,事务串行执行,完全隔离。
  1. timeout
  • 类型: int
  • 默认值: -1
  • 描述: 事务的超时时间,单位为秒。如果事务在这个时间内没有完成,则自动回滚。
  1. readOnly
  • 类型: boolean
  • 默认值: false
  • 描述: 标记当前事务是否为只读事务。对于某些数据库,标记为只读可以提高性能。
  1. rollbackFor
  • 类型: Class<? extends Throwable>[]
  • 默认值: {}
  • 描述: 指定哪些异常需要触发事务回滚。默认情况下,运行时异常(RuntimeException)和错误(Error)会触发回滚,而检查型异常(Exception)不会触发回滚。
  1. noRollbackFor
  • 类型: Class<? extends Throwable>[]
  • 默认值: {}
  • 描述: 指定哪些异常不需要触发事务回滚。即使这些异常被抛出,事务也不会回滚。

3.2.2 @Transactional失效场景

该部分转载于 一口气怼完12种@Transactional的失效场景

3.2.2.1 代理不生效

声明式事务是基于 AOP(面向切面编程)代理机制来拦截方法调用,并在方法执行前后进行事务管理。Spring 生成代理的方式主要有两种:JDK
动态代理和CGLIB 代理。

PlatformTransactionManager 实现
选择PlatformTransactionManager
管理JDBC事务
管理JPA事务
管理JTA事务
PlatformTransactionManager
获取PlatformTransactionManager
DataSourceTransactionManager
JpaTransactionManager
JtaTransactionManager
启动Spring应用
扫描带有@Transactional的类和方法
解析@Transactional注解
使用AnnotationTransactionAttributeSource解析注解属性
创建AOP代理
目标类是否实现了接口?
使用JDK动态代理
使用CGLIB代理
创建JdkDynamicAopProxy
创建ObjenesisCglibAopProxy
代理对象拦截方法调用
调用TransactionInterceptor
获取TransactionAttribute
获取TransactionStatus
开启事务
执行目标方法
方法是否正常返回?
提交事务
回滚事务
方法执行完成
  1. JDK 动态代理:
  • 基本概念:
    • 基于接口的代理:JDK 动态代理是基于 Java 的反射机制实现的,它只能代理实现了接口的类。
    • 生成代理类:在运行时动态生成一个实现了相同接口的代理类。
  • 适用场景:
    • 目标类实现了接口:只有当目标类实现了至少一个接口时,才能使用 JDK 动态代理。
  • 优点:
    • 性能较好:由于基于标准的 Java 反射机制,性能相对较高。
    • 符合面向接口编程:符合 Java 的面向接口编程原则。
  • 缺点:
    • 仅限接口:只能代理实现了接口的类,无法代理没有实现接口的类。
  1. CGLIB 代理:
  • 基本概念:
    • 基于类的代理:CGLIB(Code Generation Library)代理是通过生成目标类的子类来实现的,因此可以代理没有实现接口的类。
    • 生成代理类:在运行时动态生成一个继承自目标类的代理类。
  • 适用场景:
    • 目标类没有实现接口:当目标类没有实现任何接口时,只能使用 CGLIB 代理。
    • 需要代理类本身:某些情况下需要代理类本身而不是接口。
  • 优点:
    • 无需接口:可以代理没有实现接口的类。
    • 灵活性高:可以代理类本身。
  • 缺点:
    • 性能稍差:由于需要生成子类,性能相对 JDK 动态代理稍差。
    • 无法代理 final 类:CGLIB 无法代理 final 类和 final 方法。
  1. Spring 如何选择代理方式:
    Spring 会根据以下条件自动选择使用 JDK 动态代理还是 CGLIB 代理:
  • 默认行为:
    • 如果目标类实现了接口,默认使用 JDK 动态代理
    • 如果目标类没有实现接口,默认使用 CGLIB 代理
  • 配置选项:
    • proxy-target-class 属性:可以通过配置 proxy-target-class 属性来强制使用 CGLIB 代理。
      • proxy-target-class=true:强制使用 CGLIB 代理。
      • proxy-target-class=false:强制使用 JDK 动态代理(如果可能)。

proxy-target-class配置项

3.2.2.1.1 将注解标注在接口方法上

失效原因:

  • @Transactional是支持标注在方法与类上的。一旦标注在接口上,对应接口实现类的代理方式如果是CGLIB,将通过生成子类的方式生成目标类的代理,将无法解析到@Transactional,从而事务失效。

解决方案:

  • 将@Transactional标注在接口方法上,将接口实现类的代理方式改为JDK动态代理。
  • 将@Transactional标注在方法与类上的。
3.2.2.1.2 被final、static关键字修饰的类或方法

失效原因:

  • final 方法:对于 final 方法,由于它们不能被子类重写,JDK 动态代理和 CGLIB 代理都无法生成有效的代理逻辑来拦截这些方法。
  • static 方法:AOP 代理机制依赖于实例级别的拦截,而 static 方法属于类级别,不是实例的一部分。因此,Spring 的 AOP 代理无法拦截
    static 方法。

解决方案:

  • 将 final、static 修饰的类方法改为非final、static 修饰。
3.2.2.1.3 类方法内部调用

失效原因:

  • 当一个类中的方法直接调用另一个带有 @Transactional 注解的方法时,这个调用是通过 this 引用进行的,而不是通过代理对象。因此,AOP
    代理无法拦截到这个调用,事务管理逻辑也不会被触发。

解决方案:

  • 将业务逻辑移到非内部调用的方法中:将实际的业务逻辑移到另一个服务类或方法中,避免在同一类中自调用带有 @Transactional
    注解的方法。
  • 自我注入的方式:通过构造函数注入或字段注入自身实例,确保调用的是代理对象而不是直接通过 this 引用调用。
  • 使用代理对象调用:通过AopContext.currentProxy()获取代理对象

AspectJ 解决事务失效:

  1. 引入依赖
<dependency>
     <groupId>org.aspectj</groupId>
     <artifactId>aspectjweaver</artifactId>
</dependency>
  1. 暴露代理对象
    在启动类上添加注解@EnableAspectJAutoProxy(exposeProxy=true),暴露代理对象:
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
public class AppConfig {
}
  1. 使用代理对象
    内部调用失效示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TransactionalService {

    @Autowired
    private AnotherService anotherService;

    @Transactional
    public void methodA() {
        // 一些数据库操作
        System.out.println("Method A is running");
        methodB(); // 内部方法调用
    }

    @Transactional
    public void methodB() {
        // 一些数据库操作
        System.out.println("Method B is running");
    }
}

使用代理对象调用示例:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TransactionalService {

    @Autowired
    private AnotherService anotherService;

    @Transactional
    public void methodA() {
        // 一些数据库操作
        System.out.println("Method A is running");
         ((TransactionalService) AopContext.currentProxy()).methodB(); // 使用AopContext获取代理对象
    }

    @Transactional
    public void methodB() {
        // 一些数据库操作
        System.out.println("Method B is running");
    }
}
3.2.2.1.4 当前类没有被Spring管理

失效原因:

  • 如果类不是由 Spring 容器管理的(例如通过 new 关键字直接实例化),那么即使该类的方法上有 @Transactional 注解,事务管理也不会生效。

解决方案:

  • 将类注册到 Spring 容器中,使其成为 Spring 管理的 bean。
3.2.2.2 框架或底层不支持的功能

这类失效场景主要聚焦在框架本身在解析@Transactional时的内部支持。如果使用的场景本身就是框架不支持的,那事务也是无法生效的。

3.2.2.2.1 非public修饰的方法

失效原因:

  • 默认情况下,Spring 的 AOP 代理只拦截 public 方法。对于非 public 方法(如 private、protected
    或包级私有方法),由于方法不可见,代理机制无法拦截这些方法,因此事务管理不会生效。

解决方案:

  • 将方法设置为 public。
3.2.2.2.2 多线程调用

在 TransactionAspectSupport.prepareTransactionInfo 方法中
TransactionAspectSupport.prepareTransactionInfo 方法
txInfo.bindToThread() 无论是否创建了新的事务,都会将TransactionInfo对象绑定到当前线程。这是为了确保事务信息栈的完整性,即使没有创建新事务。

3.2.2.2.3 数据库本身不支持事务

某些NoSQL数据库或特定版本的关系型数据库,比如Mysql的Myisam存储引擎是不支持事务的。Spring
的声明式事务管理将无法正常工作,因为这些数据库缺乏必要的事务特性(如ACID属性)。

3.2.2.2.4 未开启事务

在MVC项目中还需要在applicationContext.xml文件中,手动配置事务相关参数。如果忘了配置,事务肯定是不会生效的。

3.2.2.3 错误使用@Transactional
3.2.2.3.1 错误的传播机制

失效原因:

  • 传播行为不匹配:如果使用了不合适的传播行为,例如 SUPPORTS 或 NOT_SUPPORTED,可能会导致事务管理失效。这些传播行为在某些情况下不会创建或加入事务。
  • 方法调用链中的传播行为冲突:在方法调用链中,不同方法可能有不同的传播行为,如果这些行为不兼容,可能会导致事务管理失效。

解决方案:

  • 使用合适的传播行为:确保在方法调用链中使用正确的传播行为,以匹配事务的预期行为。
3.2.2.3.2 rollbackFor属性设置错误

失效原因:

  • rollbackFor 属性用于指定哪些异常会触发事务回滚。默认情况下,Spring 只会在遇到未检查异常(即继承自 RuntimeException
    的异常)时回滚事务。对于已检查异常(即不继承自 RuntimeException 的异常),你需要显式地指定它们以触发回滚。

解决方案:

  • 使用 rollbackFor 属性:在方法上添加 rollbackFor 属性,并指定需要触发回滚的异常类型。
3.2.2.3.3 异常被内部 try-catch

失效原因:

  • 当异常在方法内部被捕获并处理时,Spring 的事务管理机制无法感知到异常的发生,因此不会触发回滚操作。这会导致事务管理失效,即使你正确配置了
    rollbackFor 属性。

解决方案:

  • 避免在方法内部处理异常:避免在方法内部处理异常,或者将异常抛出给上层调用者,让上层调用者处理异常。
3.2.2.3.4 嵌套事务

预期场景:

  • 外层事务不因内层事务的异常而回滚。

失效原因:

  • Spring 的事务管理不支持嵌套事务。当一个方法开始一个新的事务时,如果该方法内部再次调用另一个方法,则该方法将不会创建一个新的事务,而是将使用外层方法的事务。

解决方案:

  • 使用 Propagation.REQUIRES_NEW:在内层方法上使用 Propagation.REQUIRES_NEW
    传播行为,将创建一个新的事务,而不管外层方法是否已经创建了事务。这样,内层事务与外层事务相互独立,内层事务的异常不会影响外层事务。
  • 使用 try-catch:在外层方法调用内层方法时,在外层方法中捕获异常,并在异常处理中回滚事务。
3.2.2.4 总结

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

可儿·四系桜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值