前言
在日常的开发工作里,数据库操作是绕不开的步骤。事务能保证我们的数据操作,要么全部成功,要么全部失败。在 Spring 框架里,事务管理功能更是强大且便捷。
什么是事务
在数据库操作中,事务是由一组操作构成的逻辑单元,这组操作要么全部成功执行,要么全部不执行。事务有四个重要的特性,简称 ACID:
- 原子性(Atomicity) :事务就像一个不可分割的原子,里面包含的操作要么全部完成,要么全部不做。比如你去银行转账,从 A 账户转 100 元到 B 账户,这个操作包含从 A 账户扣款和给 B 账户加款两个步骤,这两个步骤必须作为一个整体,要么都成功,要么都失败。
- 一致性(Consistency) :事务执行前后,数据库的数据要保持一致性状态。还是拿转账来说,转账前 A 和 B 账户的总金额是一定的,转账后这个总金额也不能变。
- 隔离性(Isolation) :多个事务并发执行时,相互之间不能干扰。比如有两个用户同时给同一个账户转账,这两个事务应该相互隔离,不能出现数据混乱。
- 持久性(Durability) :一旦事务提交成功,它对数据库的改变就是永久性的,即使系统崩溃也不会丢失。
Spring 事务管理概述
Spring 为我们提供了方便的事务管理机制,主要有两种实现方式:编程式事务管理和声明式事务管理。
编程式事务管理
编程式事务管理就是在代码里手动管理事务的开启、提交和回滚。虽然这种方式比较灵活,但代码会显得比较繁琐(基本不用这个)下面是一个简单的示例:
@Service
public class UserService {
@Autowired
private PlatformTransactionManager transactionManager;
public void transferMoney() {
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 执行数据库操作
// ...
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
在这个例子中,我们手动获取事务状态,执行操作,成功就提交,失败则回滚。
声明式事务管理
声明式事务管理是 Spring 推荐的方式,它通过 AOP(面向切面编程)实现,将事务管理代码和业务逻辑代码分离,让代码更加简洁。声明式事务管理又分为基于 XML 配置和基于注解两种方式,下面重点介绍基于注解的方式。
基于注解的声明式事务管理
开启事务支持
要使用基于注解的事务管理,首先需要在 Spring 配置类上添加 @EnableTransactionManagement 注解:
@Configuration
@EnableTransactionManagement
public class AppConfig {
// 数据源、事务管理器等配置
}
使用 @Transactional 注解
@Transactional 注解可以用在类或者方法上。用在类上时,表示这个类的所有公共方法都开启事务;用在方法上时,只对该方法开启事务。示例如下:
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Transactional
public void createOrder(Order order) {
// 执行数据库操作
orderRepository.save(order);
// 模拟异常
if (true) {
throw new RuntimeException("模拟异常");
}
}
}
在这个例子中,createOrder 方法被 @Transactional 注解标记,当方法正常执行时,事务会自动提交;如果抛出异常,事务会自动回滚。
@Transactional 注解的属性
@Transactional 注解有很多属性,下面介绍几个常用的:
- propagation:事务传播行为,定义了事务方法和调用它的方法之间事务的关系。常见的传播行为有
REQUIRED(默认值,如果当前没有事务,就新建一个事务;如果有,就加入到当前事务中)、REQUIRES_NEW(总是新建一个新事务,挂起当前事务)等。 - isolation:事务隔离级别,用来解决多个事务并发访问时可能出现的问题,如脏读、不可重复读、幻读等。常见的隔离级别有
READ_COMMITTED(读已提交,避免脏读)、REPEATABLE_READ(可重复读,避免脏读和不可重复读)等。 - rollbackFor:指定哪些异常会触发事务回滚,默认情况下,只有
RuntimeException和Error会触发回滚。 - noRollbackFor:指定哪些异常不会触发事务回滚。
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
public void updateOrder(Order order) {
// 执行数据库操作
}
事务的传播行为
事务的传播行为定义了在一个事务方法调用另一个事务方法时,事务该如何处理。Spring 定义了 7 种传播行为,下面介绍几种常用的:
- REQUIRED:如果当前没有事务,就创建一个新事务;如果有,就加入到当前事务中。这是最常用的传播行为。
- REQUIRES_NEW:总是创建一个新事务,挂起当前事务,直到新事务执行完毕。
- SUPPORTS:如果当前有事务,就加入到当前事务中;如果没有,就以非事务方式执行。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- MANDATORY:如果当前存在事务,就加入该事务;如果当前没有事务,就抛出异常。
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional
public void methodA() {
// 执行操作
serviceB.methodB();
// 继续执行操作
}
}
@Service
public class ServiceB {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 执行操作
}
}
methodA 开启了一个事务,调用 methodB 时,methodB 会创建一个新的事务,与 methodA 的事务相互独立。
事务的隔离级别
事务的隔离级别用来解决多个事务并发访问时可能出现的问题,主要有以下几种:
- READ_UNCOMMITTED:允许读取未提交的数据,可能会出现脏读、不可重复读和幻读问题。
- READ_COMMITTED:只能读取已经提交的数据,避免了脏读,但可能会出现不可重复读和幻读问题。
- REPEATABLE_READ:确保在同一个事务中多次读取同一数据的结果是一样的,避免了脏读和不可重复读,但可能会出现幻读问题。
- SERIALIZABLE:最高的隔离级别,通过强制事务串行执行,避免了所有并发问题,但会影响性能。
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void readData() {
// 执行读取操作
}
总结
Spring 事务管理为我们提供了强大而灵活的功能,通过编程式和声明式两种方式,满足不同场景的需求。声明式事务管理使用起来更加方便,结合 @Transactional 注解的属性,可以灵活控制事务的传播行为和隔离级别。在实际开发中,我们要根据业务需求合理选择事务的传播行为和隔离级别,确保数据的一致性和完整性。
希望通过这篇文章,大家对 Spring 事务能有更深入的理解,能够在开发中熟练运用 Spring 事务管理功能。