Spring Boot中的事务

介绍

什么是事务

在计算机系统中,事务通常指一系列操作,这些操作作为一个单一的工作单元执行,要么全部成功,要么全部失败。事务具有以下四个基本特性,通常被称为 ACID 属性:

  • 原子性(Atomicity):事务中的所有操作都是作为一个整体执行的,它们要么全部完成,要么全部不执行。
  • 一致性(Consistency):事务必须使数据库从一个一致性状态转换到另一个一致性状态。
  • 隔离性(Isolation):并发执行的事务之间不会互相影响。
  • 持久性(Durability):一旦事务提交,其所做的更改就会永久保存在数据库中。
为什么在程序中使用事务

事务在应用程序中的使用主要是为了确保数据的完整性和一致性。在多用户环境中,尤其是在并发操作频繁的情况下,事务可以防止数据损坏和冲突。例如,转账操作就是一个典型的事务处理场景,它需要确保转账的原子性和一致性。此外,事务还可以提供以下好处:

  • 错误恢复:如果在事务执行过程中发生错误或系统崩溃,事务可以被回滚到开始状态,保证数据不会被错误地修改。
  • 安全性:事务可以保护数据不受未授权的操作或者系统故障的影响。
  • 同步控制:事务可以管理对数据库资源的并发访问,避免数据不一致和死锁。

事务的管理是任何需要确保数据安全和一致性的应用程序的关键组成部分。

Spring Boot 的事务管理

事务管理器

事务管理器是 Spring 框架中用于处理事务的核心组件。它负责协调数据源以确保事务的 ACID 属性得到满足。事务管理器是一个关键组件,它负责协调事务的整个生命周期,确保数据的一致性和完整性。 事务管理器的核心功能包括

  • 资源管理:事务管理器负责管理数据库连接和其他事务资源。
  • 事务同步:事务管理器确保事务的正确开始、提交或回滚。
  • 事务边界管理:事务管理器定义了事务的开始和结束点。
  • 异常处理:事务管理器决定在遇到异常时是否回滚事务。

Spring Boot 支持多种类型的事务管理器,最常见的是 DataSourceTransactionManager (适用于 JDBC 操作)和 JpaTransactionManager(适用于 JPA 操作),也可以自定义事务管理器

//配置事务管理器(创建了一个 DataSourceTransactionManager,它将管理 dataSource 的事务)
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}
}
事务的传播行为

事务的传播行为定义了一个事务方法是如何关联到其他事务的。Spring 定义了多种传播行为:

  • REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • REQUIRES_NEW:创建一个新的事务,并暂停当前的事务。
  • SUPPORTS:如果当前存在事务,则加入该事务;如果没有,则以非事务方式执行。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则暂停当前事务。
事务的隔离级别

事务的隔离级别是指事务在并发环境中保护数据不受其他事务影响的能力。Spring 支持以下隔离级别:

  • DEFAULT:使用底层数据存储的默认隔离级别。
  • READ_UNCOMMITTED:允许读取未提交的更改,可能会导致脏读。
  • READ_COMMITTED:允许读取并提交的更改,可以防止脏读。
  • REPEATABLE_READ:确保在事务内读取字段的多次读取结果一致。
  • SERIALIZABLE:确保事务串行执行,以避免并发问题。

通过合理配置这些属性,可以确保 Spring Boot 应用程序中的事务处理既安全又高效。

Spring Boot 的事务实现方式

声明式事务

声明式事务管理是通过使用注解或 XML 配置来实现的,它是 Spring Boot 推荐的事务实现方式。最常用的注解是 @Transactional

@Transactional 注解的使用

类或方法上添加 @Transactional 注解,可以将一个方法或整个类中的所有公共方法定义为事务性的。Spring 框架会自动为这些方法创建代理,并在方法执行前后添加事务管理逻辑。

@Transactional//在类上添加
public class TransactionalService {

    @Transactional//在方法上添加
    public void performTransactionalOperation() {
        // 业务逻辑
    }
}
@Transactional 注解的参数说明
  • propagation:定义了事务的传播行为。这决定了方法是如何加入事务的

    • REQUIRED(默认值):如果当前没有事务,就新建一个事务;如果已经存在一个事务中,就加入到这个事务中。这是最常用的

    • SUPPORTS:如果当前存在事务,就加入到这个事务中;如果没有事务,就以非事务的方式执行。

    • MANDATORY:如果已经存在一个事务,就加入到这个事务中;如果没有一个活动的事务,则抛出异常。

    • REQUIRES_NEW:新建事务,如果当前存在事务,就把当前事务挂起。

    • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

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

    • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则表现和 REQUIRED 一样。

  • isolation:定义了事务的隔离级别,它可以帮助防止事务间的干扰。

    • DEFAULT(默认值):这是默认的隔离级别。使用底层数据存储的默认隔离级别,通常是数据库设置的隔离级别。

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

    • READ_COMMITTED:允许事务读取并提交的数据变更。可以防止脏读,但不可重复读和幻读仍然可能发生。

    • REPEATABLE_READ:对同一字段的多次读取结果在同一事务中是一致的,除非数据被事务本身改变。可以防止脏读和不可重复读,但幻读仍然可能发生。

    • SERIALIZABLE:确保事务串行执行,以避免脏读、不可重复读和幻读。这是最高的隔离级别,也是最能保证数据完整性的级别,但同时可能会降低并发性能。

  • timeout:指定了事务的超时时间,默认值为 -1(表示无超时限制)。如果事务超过指定时间未完成,则自动回滚。

  • readOnly:表示这个事务只读取数据但不更新数据,这可以帮助数据库引擎优化事务。

  • rollbackFor:定义了哪些异常会触发事务回滚。默认情况下,事务只会在遇到运行时异常和错误时回滚。

  • noRollbackFor:定义了哪些异常不会触发事务回滚。

  • transactionManager:指定要使用的事务管理器。当你有多个事务管理器时,可以用这个属性指定一个特定的事务管理器。

实例:

public class TransactionalService {

    @Transactional(propagation = Propagation.REQUIRED,
               isolation = Isolation.DEFAULT,
               timeout = 5,
               readOnly = false,
               rollbackFor = Exception.class,
               noRollbackFor = CustomException.class)
    public void performTransactionalOperation() {
        // 业务逻辑
    }
}
@Transactional 的失效场景

因为 @Transactional 是基于代理的,所以在使用这个注解的时候要避免注解失效的情况。这些场景通常涉及到 Spring AOP 代理的工作方式。

  1. 非公共方法:如果 @Transactional 注解被应用在非公共(private、protected 或默认访问级别)的方法上,事务管理将不会被触发
  2. 同一个类内部的方法调用:当一个事务方法在同一个类内部被另一个方法调用时,由于代理方式的限制,事务可能不会正常工作(原因在第四条)
  3. 异常处理:如果在事务方法内部捕获了异常并没有重新抛出,事务管理器可能无法感知到异常,从而不会触发回滚
  4. 事务方法未通过代理调用:如果事务方法是直接调用的,而不是通过 Spring 生成的代理对象调用的,那么事务管理也不会被触发。
  5. 数据库不支持事务:如果使用的数据库不支持事务,或者使用了不支持事务的数据库引擎(例如,MySQL 的 MyISAM),则 @Transactional 注解不会有效
  6. 类没有被 Spring 管理:如果 @Transactional 注解所在的类没有被 Spring 容器管理,即它不是一个 Spring Bean,那么事务注解也不会生效
  7. 错误的事务管理器配置:如果在多事务管理器的环境中,没有正确指定 @Transactional 注解应该使用的事务管理器,事务也可能不会被正确管理。
  8. 传播行为配置错误:某些特定的传播行为配置可能导致事务不按预期执行,例如 PROPAGATION_NEVERPROPAGATION_NOT_SUPPORTED

编程式事务

编程式事务管理是通过编码来显式管理事务的边界。在 Spring Boot 中,可以使用 TransactionTemplatePlatformTransactionManager 直接控制事务。

TransactionTemplate
  • 简化事务代码TransactionTemplate 提供了一个模板化的方法来管理事务,它封装了事务的开始、提交和回滚的过程,使得开发者可以专注于业务逻辑而不是事务管理的细节。
  • 编程式事务管理:它属于编程式事务管理的一部分,允许在代码中显式控制事务的边界。
  • 回调接口TransactionTemplate 使用 TransactionCallback 接口或其实现类来执行事务操作,提供了一个回调方法 doInTransaction,在这个方法中编写需要执行的事务性业务逻辑。
@Autowired
private TransactionTemplate transactionTemplate;

public void executeOperationWithTransaction() {
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            // 业务逻辑
        }
    });
}
PlatformTransactionManager
  • 底层事务管理器PlatformTransactionManager 是 Spring 事务抽象的核心接口,它定义了事务管理的基本方法,如 getTransactioncommitrollback
  • 更细粒度的控制:使用 PlatformTransactionManager 可以获得更细粒度的事务控制,例如可以在事务中设置保存点,手动回滚到特定的保存点。
  • 直接与事务状态交互:开发者可以直接与事务状态进行交互,手动开始和结束事务,这对于需要精确控制事务行为的场景非常有用。
// 代码片段
// 手动回滚到特定的保存点
@Autowired
private PlatformTransactionManager transactionManager;

public void executeOperationWithSavepoint() {
    // 定义事务属性
    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

    // 开始事务
    TransactionStatus status = transactionManager.getTransaction(def);
    Object savepoint = null;
    try {
        // 业务逻辑 A
        // ...

        // 在此处创建一个保存点
        savepoint = status.createSavepoint();

        // 业务逻辑 B
        // ...

        // 提交事务
        transactionManager.commit(status);
    } catch (SomeSpecificException ex) {
        // 如果发生特定异常,则回滚到保存点
        if (savepoint != null) {
            status.rollbackToSavepoint(savepoint);
        }
        // 可以继续其他操作或重新抛出异常
    } catch (Exception ex) {
        // 如果发生其他异常,则回滚整个事务
        transactionManager.rollback(status);
        throw ex;
    }
}

// 代码片段
// 手动开始和结束事务 

	@Autowired
    private PlatformTransactionManager transactionManager;

    public void performServiceOperation() {
        // 创建事务定义
        TransactionDefinition def = new DefaultTransactionDefinition();
        
        // 开始事务
        TransactionStatus status = transactionManager.getTransaction(def);
        
        try {
            // 执行业务逻辑
            // ...

            // 业务逻辑执行成功,提交事务
            transactionManager.commit(status);
        } catch (Exception e) {
            // 业务逻辑执行出错,回滚事务
            transactionManager.rollback(status);
            throw e; // 可以选择重新抛出异常或进行其他错误处理
        }
    }

扩展(测试事务)

在工作中如果想让测试产生的数据不会污染测试数据库或生产数据库,可以通过测试事务来实现,测试事务通常涉及到确保在测试执行后,对数据库所做的更改不会持久化。

  1. 使用 @Transactional 注解:在测试类或测试方法上使用 @Transactional 注解。这样,每个测试方法执行完毕后,事务都会自动回滚。
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class TransactionalTest {

    @Test
    public void testMethod() {
        // 测试逻辑
    }
}
  1. 使用 @Rollback 注解:如果只想让特定的测试方法回滚事务,可以在该测试方法上使用 @Rollback 注解,并设置为 true
@Test
@Rollback(true)
public void testMethod() {
    // 测试逻辑
}
  1. 在不使用@Transactional 注解和 @Rollback 注解的情况也有办法做到同样的效果,不过这种方式并不是通过事务的回滚,而是使用@Before和@After自动的去清理产生的数据。

    @Before
    public void setUp() {
        // 初始化测试数据
    }
    
    @After
    public void tearDown() {
        // 清理测试数据
    }
    
  • 35
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Boot分布式事务的解决方案是JTA,即Java Transaction API。Spring Boot官方提供了Atomikos的解决思路。在项目引入Atomikos的依赖,然后在配置文件配置数据源和事务管理器即可实现分布式事务。具体步骤如下: 1.在pom.xml文件引入Atomikos的依赖: ```xml <dependency> <groupId>com.atomikos</groupId> <artifactId>transactions-jta</artifactId> <version>4.0.6</version> </dependency> ``` 2.在application.properties文件配置数据源和事务管理器: ```properties spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.jta.atomikos.datasource.ds1.unique-resource-name=ds1 spring.jta.atomikos.datasource.ds1.xa-data-source-class-name=com.mysql.jdbc.jdbc2.optional.MysqlXADataSource spring.jta.atomikos.datasource.ds1.xa-properties.url=jdbc:mysql://localhost:3306/test spring.jta.atomikos.datasource.ds1.xa-properties.user=root spring.jta.atomikos.datasource.ds1.xa-properties.password=root spring.jta.atomikos.transaction-manager-id=tm ``` 3.在代码使用@Transactional注解开启事务: ```java @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Transactional @Override public void transfer(int fromUserId, int toUserId, double amount) { userDao.decreaseBalance(fromUserId, amount); userDao.increaseBalance(toUserId, amount); } } ``` 以上就是Spring Boot分布式事务的解决方案。需要注意的是,使用JTA需要在应用服务器配置JTA事务管理器,例如Tomcat需要配置Bitronix或Atomikos。同时,JTA也有一些缺点,例如性能较差,配置较为复杂等。因此,在实际项目需要根据具体情况选择合适的事务解决方案。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值