Spring事务管理指南:从理论到实践

事务的概念

事务说白了就是一序列业务操作的集合,而且这个集合要求ACID,所谓ACID就是:

1. 原子性(Atomicity)
  • 含义:事务中的所有操作要么全部完成,要么全部不发生。
  • 通俗理解:想象你在银行转账给朋友。如果你从自己的账户中扣除了钱,但是没有成功把钱加到朋友的账户里,那这笔交易就不应该发生。原子性确保了转账要么完全成功,要么完全失败,不会留下半途而废的状态。
2. 一致性(Consistency)
  • 含义:事务执行后,数据必须处于一致的状态。
  • 通俗理解:还是拿银行转账为例,如果你从账户中转走了100元,那么你的账户余额应该减少100元,而朋友的账户余额应该增加100元。一致性保证了转账前后的数据是正确的,不会有莫名其妙的钱凭空消失或出现。
3. 隔离性(Isolation)
  • 含义:事务之间互不影响,就好像它们单独执行一样。
  • 通俗理解:假设你和你的朋友在同一时间给同一个朋友转账,你们的转账不应该互相干扰。隔离性确保了你们的转账操作各自独立完成,就像你们是唯一在操作的人一样。
4. 持久性(Durability)
  • 含义:一旦事务成功提交,它的结果就是永久的,即使系统崩溃也不会丢失。
  • 通俗理解:想象你已经成功转账给了朋友,这时候突然停电了。持久性保证了即使在这种情况下,转账的结果仍然是有效的,你的账户余额和朋友的账户余额仍然会被正确更新。

Spring操作事务的方式(实操)

Spring操作事务的方式主要有以下两种:

  • 声明式事务
  • 编程式事务

编程式事务就是自己通过适用Spring提供的工具来建立事务,适合需要自定义事务的场景,不过现实开发大多情况下用不到,而且编程式事务编写的事务和业务代码高度耦合,不利于业务扩展,同时维护起来也不容易,如果有多个事务发生异常,那岂不是要挨个改一遍,于是我们先学习一下声明式事务

声明式事务管理

声明式事务管理允许你通过配置(如XML或注解)来指定哪些方法需要进行事务管理,而不需要在业务代码中显式地编写事务控制代码。这种方式可以减少事务相关代码的侵入性,并且易于维护

在Spring中,声明式事务管理最常见的实现方式是使用@Transactional注解。你可以在类级别或方法级别应用此注解,以声明该类或方法需要被事务管理。

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

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

    @Transactional(readOnly = true)
    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}

在这个例子中,createUser 方法使用了@Transactional注解,表示这个方法需要在一个事务中执行。如果方法中发生了异常,则事务将自动回滚,否则事务会在方法正常结束时提交。getUserById 方法使用了readOnly = true 属性,表明这是一个只读操作,不需要创建事务,但如果当前存在事务,则该方法将继续在当前事务中执行。

配置事务管理器

要启用声明式事务管理,你需要配置一个事务管理器。对于JPA或Hibernate,你可以使用JpaTransactionManager;如果是普通的JDBC,则可以使用DataSourceTransactionManager

如果你使用Spring Boot,通常不需要手动配置事务管理器,因为它会自动为你配置好。但如果你使用XML配置或者自定义配置类,可以像下面这样配置:

@Configuration
@EnableTransactionManagement
public class AppConfig {

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
事务传播行为

在使用@Transactional注解时,还可以指定事务的传播行为,以控制不同事务方法之间的交互。Spring支持七种不同的传播行为,包括PROPAGATION_REQUIRED(默认)、PROPAGATION_SUPPORTSPROPAGATION_MANDATORY等。

@Transactional(propagation = Propagation.REQUIRED)
public void createUser(User user) {
    userRepository.save(user);
}

@Transactional(propagation = Propagation.SUPPORTS)
public User getUserById(Long id) {
    return userRepository.findById(id).orElse(null);
}
1. PROPAGATION_REQUIRED (默认)
  • 描述: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • 场景: 假设你正在开发一个电商系统,其中有一个transferMoney方法用于从一个账户转账到另一个账户。这个方法应该在一个事务中执行,以确保转账操作要么全部成功,要么全部失败。
    @Service
    public class AccountService {
    
        @Autowired
        private AccountRepository accountRepository;
    
        @Transactional
        public void transferMoney(Account fromAccount, Account toAccount, BigDecimal amount) {
            fromAccount.debit(amount);
            toAccount.credit(amount);
            accountRepository.save(fromAccount);
            accountRepository.save(toAccount);
        }
    }
    在这个场景中,如果transferMoney方法被另一个事务内的方法调用,它将加入那个事务;如果没有事务,则创建一个新的事务。
2. PROPAGATION_SUPPORTS
  • 描述: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
  • 场景: 假设你有一个getAccountBalance方法用于获取账户余额。这个方法不需要在事务中执行,但如果它被事务中的方法调用,它可以加入事务。
    @Service
    public class AccountService {
    
        @Autowired
        private AccountRepository accountRepository;
    
        @Transactional(readOnly = true)
        public BigDecimal getAccountBalance(Account account) {
            return accountRepository.findBalanceByAccountId(account.getId());
        }
    }
    在这个场景中,getAccountBalance方法通常是非事务性的,但如果它被事务中的方法调用,它将加入那个事务。
3. PROPAGATION_MANDATORY
  • 描述: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • 场景: 假设你有一个日志记录服务,它需要确保所有的日志记录都在事务中进行,以保证数据的一致性。
    @Service
    public class LoggingService {
    
        @Transactional(propagation = Propagation.MANDATORY)
        public void log(String message) {
            // 记录日志
        }
    }
    在这个场景中,如果log方法被非事务性的方法调用,它将抛出异常。
4. PROPAGATION_REQUIRES_NEW
  • 描述: 总是创建一个新的事务,并且如果当前存在事务,则挂起当前事务。
  • 场景: 假设你有一个sendEmail方法用于发送电子邮件确认。这个操作需要在一个独立的事务中执行,即使它被事务内的方法调用。
    @Service
    public class EmailService {
    
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void sendEmail(String email, String message) {
            // 发送邮件
        }
    }
    在这个场景中,sendEmail方法总是创建一个新的事务,即使它被事务内的方法调用。
5. PROPAGATION_NOT_SUPPORTED
  • 描述: 以非事务方式执行操作,并挂起当前事务(如果存在)。
  • 场景: 假设你有一个性能敏感的报表生成服务,它需要从数据库中读取大量数据。为了提高性能,你希望这个操作不在事务中执行。
    @Service
    public class ReportService {
    
        @Transactional(propagation = Propagation.NOT_SUPPORTED)
        public List<Report> generateReport() {
            // 生成报表
            return reportRepository.findAll();
        }
    }
    在这个场景中,generateReport方法将以非事务方式执行,即使它被事务内的方法调用。
6. PROPAGATION_NEVER
  • 描述: 以非事务方式执行操作,如果存在事务则抛出异常。
  • 场景: 假设你有一个pingDatabase方法用于检查数据库连接是否可用。这个方法不能在事务中执行,因为它可能影响事务的一致性。
    @Service
    public class DatabaseHealthCheck {
    
        @Transactional(propagation = Propagation.NEVER)
        public boolean pingDatabase() {
            // 检查数据库连接
            return databaseRepository.ping();
        }
    }
    在这个场景中,如果pingDatabase方法被事务内的方法调用,它将抛出异常。
7. PROPAGATION_NESTED
  • 描述: 如果当前存在事务,则在嵌套事务内执行;如果当前不存在事务,则行为类似于PROPAGATION_REQUIRED
  • 场景: 假设你有一个createUserAndProfile方法用于创建用户并关联一个个人资料。为了确保用户创建成功后才能创建个人资料,你希望在用户创建成功后创建个人资料时使用嵌套事务。
    @Service
    public class UserService {
    
        @Autowired
        private UserRepository userRepository;
        @Autowired
        private ProfileRepository profileRepository;
    
        @Transactional
        public void createUserAndProfile(User user, Profile profile) {
            userRepository.save(user);
            createProfileForUser(user, profile);
        }
    
        @Transactional(propagation = Propagation.NESTED)
        public void createProfileForUser(User user, Profile profile) {
            profile.setUser(user);
            profileRepository.save(profile);
        }
    }
    在这个场景中,如果createUserAndProfile方法在事务中被调用,createProfileForUser方法将在嵌套事务中执行。如果createUserAndProfile方法不在事务中被调用,则createProfileForUser方法将创建一个新的事务。
事务隔离级别
1.ISOLATION_DEFAULT (默认)
  • 描述: 使用底层数据库系统的默认隔离级别。
  • 场景: 如果你的应用不特别指定隔离级别,那么它将采用数据库的默认设置。例如,在MySQL中,默认的隔离级别通常是READ_COMMITTED
2. ISOLATION_READ_UNCOMMITTED
  • 描述: 允许脏读、不可重复读和幻读。
  • 场景: 这个级别通常不推荐使用,因为它允许事务读取未提交的数据(脏读)。但在某些情况下,如果读取操作对实时性要求非常高且可以接受不一致的数据,可能会考虑使用此级别。例如,在一个实时监控系统中,为了减少延迟,可能会牺牲数据的一致性来换取更快的响应时间。
3. ISOLATION_READ_COMMITTED (大多数数据库的默认值)
  • 描述: 不允许脏读,但允许不可重复读和幻读。
  • 场景: 这是大多数数据库系统的默认设置,适用于大多数情况。例如,在一个库存管理系统中,查询最新的库存量时,只需要读取已经提交的数据即可。
4. ISOLATION_REPEATABLE_READ
  • 描述: 不允许脏读和不可重复读,但允许幻读。
  • 场景: 这个级别确保事务能够重复读取相同的数据集。例如,在一个财务系统中,当你需要多次查询某个账户的余额时,希望每次查询的结果都是一样的(假设没有其他事务修改数据)。
5. ISOLATION_SERIALIZABLE
  • 描述: 提供最高级别的隔离,不允许脏读、不可重复读和幻读。
  • 场景: 这是最严格的隔离级别,确保事务完全独立地执行,就像它们是串行化的一样。虽然这能保证最高的数据一致性,但也可能导致较高的锁竞争和性能开销。例如,在一个银行转账系统中,为了保证资金转移的绝对安全性,可能会选择这个隔离级别。
按异常回滚

按指定异常回滚

@Transactional(rollbackFor = {异常类名})

不回滚哪些异常 

@Transactional(noRollbackFor ={异常类名})

总结: 

事务是具有ACID特性的一序列操作集合,Spring中管理事务的方法有编程事务和声明式事务,其中编程式事务由于其维护性低,可扩展性低,耦合度高的特点不常被使用,而声明式事务由于其简洁高效,低耦合的特性而被广泛使用,声明式事务可以使用xml方式配置也可以使用注解方式,其中注解方式最为简洁(尤其是在SpringBoot项目中),在以注解的方式中,总结了七种不同的事务传播方式以及五种事务隔离的方式,帮助大家在开发中使用事务得心应手。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值