概叙
事务是指数据库操作的一个执行单元,它由一个或多个数据库操作组成,这些操作必须作为一个整体来执行,要么完全执行,要么完全不执行。
事务的基本概念包括以下几点:
-
原子性(Atomicity):事务中的所有操作要么全部执行成功,要么全部不执行,事务是不可分割的操作单元。
-
一致性(Consistency):事务的执行使数据库从一个一致状态转变为另一个一致状态。执行事务的中间状态不能被其他用户/事务访问到。
-
隔离性(Isolation):并发执行的事务之间不能互相干扰,每个事务应该相互独立并且感知不到其他事务的存在。
-
持久性(Durability):事务一旦提交,对数据库中的数据所做的更改是永久的,即使在系统崩溃等异常情况下也能够恢复。
这四个属性,通常被称为 ACID(原子性、一致性、隔离性、持久性),它们保证了事务的可靠性和一致性,是数据库管理系统必须具备的特性。
事务的重要性
在业务处理中,事务处理是非常重要的,它确保了数据的一致性、可靠性和完整性。以下是事务处理的重要性:
-
数据的一致性:事务处理确保了在任何情况下,数据库中的数据都处于一致的状态。当多个操作需要同时执行时,事务可以保证数据的正确和有效的更新。如果中途出现错误或故障,事务可以回滚将数据库恢复到原始状态,避免了数据的不一致性。
-
数据的可靠性:事务处理保证了数据的可靠性,确保了每个操作都能成功执行,否则会被回滚到之前的状态。这个特性对于敏感的交易操作,如转账、支付,以及其他重要的数据操作非常关键。
-
数据的完整性:事务处理通过强制执行数据一致性约束(如唯一性约束、参照完整性约束),保证了数据的完整性。任何时候,只要一个操作违反了数据完整性规则,整个事务将会被回滚,使数据库保持一致和完整。
-
并发控制:同时有多个用户对数据库进行读写操作时,事务处理可防止数据不一致现象的发生。例如,当一个用户正在修改某一行数据时,其他用户必须等待该事务完成,保证了并发操作的正确性。
下面是一个提供的实例,证明没有事务处理可能产生的问题:
一个在线购物网站的库存管理系统由多个用户同时访问。用户 A 和用户 B 同时抢购同一商品,都查询了库存并发现有足够的数量可以购买。然而,在数据库中并没有对库存进行足够的实时更新。
用户 A 立即下单并完成支付,此时库存数量减少。然而,在用户 A 完成操作前,用户 B 也下单并支付。由于库存信息没有进行同步更新,系统未能检测到库存已经不足。
结果是用户 B 支付了商品,但实际上库存已经不足以供应。这就导致了订单和库存之间的不一致,可能引发更多的问题,如商品无法准时发货,造成客户投诉和商家信誉损失。
如果事务处理进行适当的处理,例如加锁和写入操作的同步等措施,可以避免这类问题发生。
SpringBoot 与事务
Spring 框架通过 AOP(面向切面编程)和 IoC(控制反转)原理,实现了事务的低耦合方式支持。具体实现步骤如下:
-
使用 @Transactional 注解:Spring 框架提供了 @Transactional 注解,用于标记一个方法或者类需要进行事务管理。通过在方法上加上 @Transactional 注解,Spring 将会自动为该方法开启一个事务。
-
配置事务管理器:Spring 框架提供了多种事务管理器,如 DataSourceTransactionManager、JtaTransactionManager 等,通过配置文件指定使用的事务管理器。
-
设置事务传播行为:Spring 框架支持多种事务传播行为,如 REQUIRED、REQUIRES_NEW 等,通过注解或者 XML 配置指定事务传播行为。
-
异常处理:Spring 框架通过捕获特定的异常类型来判断是否需要回滚事务。当抛出指定异常时,Spring 会回滚事务。
通过以上这些步骤,开发者可以快速简便地配置和管理事务。Spring Boot 的事务处理功能减少了开发工作量,并且支持灵活精细的事务控制配置。
Spring 事务管理器
详细参考:实战:spring项目中不推荐用自带的声明式事务@Transactional_spring 不加transactional-CSDN博客
Spring 事务管理器是 Spring 框架用于管理数据库事务的核心组件之一。它负责协调和控制事务的开始、提交和回滚等操作。Spring 提供了各种类型的事务管理器,方便开发者根据需求选择合适的管理器。其工作原理如下:
-
连接获取和绑定:在事务开始前,事务管理器负责从连接池中获取数据库连接,并将其绑定到当前线程上下文中。
-
事务资源管理:事务管理器通过提供额外的接口,如
TransactionDefinition
来管理事务的基本属性,如事务隔离级别、超时时间和事务传播特性等。 -
事务的本地化嵌套:事务管理器支持将多个事务组织成一个事务链,并将整个链作为一个本地事务进行管理和控制。这对于分布式事务非常有用。
-
事务的提交和回滚:在事务结束后,事务管理器根据事务的状态决定是提交事务还是回滚事务。如果一个事务执行成功,则会将事务中所做的所有更改一起提交到数据库。反之,如果事务失败,则会撤销所有更改,回滚到事务开始前的状态。
二、不同类型的事务管理器的适用场景及其区别:
-
JDBC 事务管理器:适用于纯 JDBC 事务。它使用 JDBC 的原生 API 来实现事务管理。这种管理器相对较低级,仅适用于单个数据库连接的事务。
-
JTA 事务管理器:适用于分布式事务,如跨多个数据库或 JMS 消息队列的事务。它使用 Java Transaction API(JTA)来实现事务协调和管理。JTA 事务管理器需要外部的事务管理器(如应用服务器)的支持。
-
JPA 事务管理器:适用于基于 Java Persistence API(JPA)的数据库访问。它允许在使用 JPA ORM 框架(如 Hibernate)时进行事务管理,与特定的 ORM 框架无关。
-
Atomikos 事务管理器:适用于高度分布式环境下的事务管理。它是一个 XA 事务管理器,支持跨多个数据库和消息队列的分布式事务。
这些不同类型的事务管理器适用于不同的应用场景和需求,开发者可以根据其具体情况选择合适的事务管理器来管理和控制事务。例如,如果应用需要支持跨多个数据库的分布式事务,则可以选择 JTA 事务管理器;而如果应用只涉及到一个数据库,则可以选择 JDBC 事务管理器。
事务的隔离级别
-
未提交读(Read Uncommitted):允许一个事务读取另一个事务未提交的数据,最低的隔离级别,存在脏读(Dirty Read)问题。
-
已提交读(Read Committed):一个事务只能读取到已经提交的数据,避免了脏读,但是可能存在不可重复读(Non-repeatable Read)问题。
-
重复读(Repeatable Read):一个事务在执行期间多次读取同一数据会保持一致,避免了不可重复读,但是可能存在幻读(Phantom Read)问题。
-
串行化(Serializable):最高的隔离级别,保证了完全的数据隔离性,通过对数据加锁实现,但是可能会导致大量的性能开销。
事务注解
事务注解是 Spring 框架中用于声明事务的注解,可以用于标注在方法或类上。它能够告诉 Spring 框架在执行被注解的方法时,要启动事务,并根据具体的配置进行事务管理。
在 SpringBoot 中,可以通过在方法或类上添加 @Transaction 注解来启用事务管理。@Transaction 注解有以下几种常用的属性:
-
propagation:指定事务的传播行为方式,默认为 REQUIRED。可选的取值包括 REQUIRED、REQUIRES_NEW、SUPPORTS、NOT_SUPPORTED、NEVER 等,用于定义多个事务之间的关系和依赖。
-
isolation:指定事务的隔离级别,默认为 DEFAULT。常用的隔离级别包括 READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE 等,用于解决并发问题。
-
rollbackFor/rollbackForClassName:指定哪些异常触发事务回滚,默认为空。可以通过指定异常类型或异常类名的方式来定义需要回滚的异常类型。
-
noRollbackFor/noRollbackForClassName:指定哪些异常不触发事务回滚,默认为空。可以通过指定异常类型或异常类名的方式来定义不需要回滚的异常类型
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Transactional
public void placeOrder(Order order) {
orderRepository.save(order);
}
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
public void updateOrderStatus(String orderId, int status) {
Order order = orderRepository.findById(orderId).orElse(null);
if (order != null) {
order.setStatus(status);
}
}
}
编程式事务
编程式事务管理是指通过编写代码来管理事务的一种方式。与声明式事务管理相对应,编程式事务管理要求开发人员显式地在代码中指定事务的开始、提交或回滚等操作。
在 SpringBoot 中实现编程式事务,可以借助于 Spring 框架提供的 TransactionTemplate 类。
首先,需要在项目的配置类中定义一个事务管理器(如 DataSourceTransactionManager),并将其注入到事务模板中:
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public TransactionTemplate transactionTemplate(DataSourceTransactionManager transactionManager) {
return new TransactionTemplate(transactionManager);
}
}
接下来,在需要进行事务管理的方法中,可以使用 TransactionTemplate 进行事务的执行:
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private UserDao userDao;
public void transferMoney(String fromAccount, String toAccount, BigDecimal amount) {
transactionTemplate.executeWithoutResult(status -> {
userDao.withdraw(fromAccount, amount);
userDao.deposit(toAccount, amount);
});
}
}
事务注解和编程式事务的比较
事务注解的优点:
-
代码简洁,易于理解和维护。
-
高度自动化,无需手动管理事务的开启、提交和回滚。
-
事务的划分更加细粒度,可以按照方法级别进行事务控制。
事务注解的缺点:
-
不够灵活,对于复杂的事务管理逻辑难以处理。
-
无法方便地嵌套多层事务。
编程式事务管理的优点:
-
灵活性高,可以根据具体的业务情况编写相应的事务控制逻辑。
-
可以嵌套多层事务,更加细粒度地控制事务的开启、提交和回滚。
编程式事务管理的缺点:
-
代码相对繁琐复杂,需要手动处理事务的开启、提交和回滚。
-
可读性差,在多层嵌套的情况下,事务控制过程不够直观。
根据具体的业务场景,可以推荐使用事务注解还是编程式事务。
推荐使用事务注解的情况:
-
事务逻辑相对简单,不需要自定义的事务控制逻辑。
-
想要快速、方便地进行事务控制,减少手动管理的麻烦。
-
特定场景下只需要简单事务的开启和提交。
推荐使用编程式事务管理的情况:
-
事务逻辑相对复杂,需要做一些特殊的操作,例如根据条件动态控制事务的提交和回滚。
-
需要嵌套多层事务,对事务的控制要求较为灵活。
-
对事务管理代码的可控性和可扩展性有较高的要求。