Spring Boot中的事务处理
数据库事务介绍
数据库事务是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。
通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的 ACID(原子性、一致性、隔离性和持久性)属性,事务是数据库运行中的逻辑工作单位,由数据库中的事务管理子系统负责事务的处理。
首先需要明确的一点是 Spring Boot 事务机制实质上就是 Spring 的事务机制,是采用统一的机制处理来自不同数据访问技术的事务处理,只不过 Spring Boot 基于自动配置的特性作了部分处理来节省开发者的配置工作,这一知识点我们会结合部分源码进行讲解。
Spring 事务管理分两种方式:
- 编程式事务,指的是通过编码方式实现事务;
- 声明式事务,基于 AOP,将具体业务逻辑与事务处理解耦。
声明式事务是建立在 AOP 机制之上的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务最大的优点,就是通过 AOP 机制将具体业务逻辑与事务处理解耦,不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,因此在实际使用中声明式事务用的比较多。
声明式事务有两种方式:一种是在 XML 配置文件中做相关的事务规则声明;另一种是基于 @Transactional 注解的方式(@Transactional 注解是来自 org.springframework.transaction.annotation 包),便可以将事务规则应用到业务逻辑中。
未使用SpringBoot时的事务配置
<!-- 事务管理 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务通知属性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 定义事务传播属性 -->
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="import*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="upd*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="set*" propagation="REQUIRED"/>
<tx:method name="remove*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="get*" propagation="REQUIRED" read-only="true"/>
<tx:method name="*" propagation="REQUIRED" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 配置事务切面 -->
<aop:config>
<aop:pointcut id="serviceOperation"
expression="(execution(* com.ssm.demo.service.*.*(..)))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation"/>
</aop:config>
SpringBoot中的事务控制
package com.lou.springboot.service;
import com.lou.springboot.dao.UserDao;
import com.lou.springboot.entity.User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
@Service
public class TransactionTestService {
@Resource
UserDao userDao;
public Boolean test1() {
User user = new User();
user.setPassword("test1-password");
user.setName("test1");
// 在数据库表中新增一条记录
userDao.insertUser(user);
// 发生异常
System.out.println(1 / 0);
return true;
}
@Transactional
public Boolean test2() {
User user = new User();
user.setPassword("test2-password");
user.setName("test2");
// 在数据库表中新增一条记录
userDao.insertUser(user);
// 发生异常
System.out.println(1 / 0);
return true;
}
}
首先在类上添加 @Service 将其注册到 IOC 容器中管理,之后注入 UserDao 对象以实现后续的数据层操作,最后实现两个业务方法 test1() 和 test2(),二者实现类似,只是两个方法添加的用户对象名称和密码字符串不同,且 test2() 方法上添加了 @Transactional 注解,而 test1() 方法上并没有添加,在方法中我们都添加了一句代码,让数字 1 去除以 数字 0,这段代码一定会出现异常,我们用这个来模拟在发生异常时事务处理能否成功。
按照正常理解,在执行 SQL 语句后,一旦发生异常,这次数据库更改一定会被事务进行回滚,正常情况下数据库中会有 test1 的数据而没有 test2 的数据,因为 test1() 方法并没有纳入事务管理中,而 test2() 方法由于加上了 @Transactional 注解是会被事务管理器处理的,那么接下来我们就来执行两个业务层方法,看看数据库中的数据变化。
SpringBoot事务管理器自动配置
在讲解声明式事务时,我们提到了声明式事务的配置过程,首先需要配置事务管理器,但是我们在开发时并没有进行该管理器的配置但是事务管理却起到了作用,这是为什么呢?
答案是 Spring Boot 在启动过程中已经将该对象自动配置完成了,所以我们在 Spring Boot 项目中可以直接使用 @Transactional 注解来处理事务,感兴趣的同学可以查看源码进行学习,事务管理器的自动配置类如下:
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
可以在你本地开发工具 (IDEA 或者 Eclipse) 中搜索这两个类的源码来看。
Transactional 事务实现机制
@Transactional 不仅可以注解在方法上,也可以注解在类上。当注解在类上时,意味着此类的所有 public 方法都是开启事务的。如果类级别和方法级别同时使用了 @Transactional 注解,则使用在类级别的注解会重载方法级别的注解。
在应用系统调用声明了 @Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据 @Transactional 的属性配置信息,这个代理对象决定该声明 @Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor 拦截时,会在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器 AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务。
总结
由于 Spring Boot 的自动配置机制,我们在使用 Spring Boot 开发项目时如果需要进行事务处理,直接在对应方法上添加 @Transactional 即可,Spring Boot 事务机制实质上就是 Spring 的事务机制,只不过在配置方式上有所不同,Spring Boot 更简便一些,所以只要你对事务处理有一定的理解,那么在使用 Spring Boot 处理事务时也不会遇到问题。