事务使用
引入依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
</dependencies>
包结构如下:
top
└─parak
├─entity
│ └─User
├─mapper
│ └─UserMapper
├─service
│ │ └─UserService
│ └─impl
│ └─UserServiceImpl
└─KHighnessApplication
创建数据库:
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '用户名',
`age` int DEFAULT NULL COMMENT '用户年龄',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
配置文件:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=KAG1823
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8
logging.level.top.parak.mapper=debug
实体类:
public class User implements Serializable {
private Integer id;
private String username;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
创建持久接口:
@Mapper
@Repository
public interface UserMapper {
@Insert("insert into user(username,age) values(#{username},#{age})")
void save(User user);
}
创建业务接口:
public interface UserService {
void saveUser(User user);
}
创建业务实现类:
@Service
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
public UserServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Transactional
@Override
public void saveUser(User user) {
userMapper.save(user);
if (!user.getUsername().contains("K")) {
throw new RuntimeException("用户名不含有K");
}
}
}
创建启动类:
@SpringBootApplication
@EnableTransactionManagement
public class KHighnessApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(KHighnessApplication.class)
.web(WebApplicationType.SERVLET).run(args);
}
}
创建测试:
@SpringBootTest
class KHighnessApplicationTest {
@Resource
private UserService userService;
private final User user1 = new User();
private final User user2 = new User();
@BeforeEach
public void before() {
user1.setUsername("KHighness");
user2.setUsername("Flower");
}
@Test
public void test1() {
userService.saveUser(user1);
userService.saveUser(user2);
}
}
测试结果如下:
说明user1插入,user2回滚。
事务原理
@EnableTransactionManagement
直接查看模块驱动注解@EnableTransactionManagement
的源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
}
可以看到这个注解通过@Import
向容器中导入了一个TransactionManagementConfigurationSelector
组件,查看其源码:
AutoProxyRegistrar
查看AutoProxyRegistrar
的源码:
查看AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry)
源码:
查看InfrastructureAdvisorAutoProxyCreator
的层级关系图:
这个和AOP中的AnnotationAwareAspectJAutoProxyCreator
的层级关系图一致,所以我们可以推断出InfrastructureAdvisorAutoProxyCreator
的作用为:为目标Service创建代理对象,增强目标Service方法,用于事务控制。
ProxyTransactionManagementConfiguration
- 注册
BeanFactoryTransactionAttributeSourceAdvisor
增强器,该增强器需要如下两个Bean- TransactionAttributeSource
- TransactionInterceptor
- 注册TransactionAttributeSource
查看AnnotationTransactionAttributeSource
源码:
查看SpringTransactionAnnotationParser
源码:
- 注册
TransactionInterceptor
事务拦截器
查看TransactionInterceptor
源码,其实现了MethodInterceptor
方法拦截器接口,目标方法执行的时候,对应拦截器的invoke
方法会被执行,所以重点关注TransactionInterceptor
实现的invoke
方法:
查看invokeWithinTransaction
方法源码:
查看completeTransactionAfterThrowing
方法源码:
这里,如果没有在@Transaction
注解上指定回滚的异常类型的话,默认只对RuntimeException
和Error
类型的异常进行回滚:
再看commitTransactionAfterReturning
方法源码:
DEBUG验证
在测试代码上打上断点:
以DEBUG方式运行test2:
可以看到目标对象已经被JDK代理(因为UserServiceImpl实现了接口,所以采用JDK动态代理)。
在断点处执行Step Into,程序跳转到JdkDynamicAopProxy
的invoke
方法:
在invocation.proceed()
处继续Step Into,查看内部调用过程:
点击Step Into,程序跳转到TransactionInterceptor
的invoke
方法:
继续Step Into,程序跳转到TransactionAspectSupport
的invokeWithinTransaction
方法:
不生效场景
场景一
Service方法抛出的异常不是RuntimeException或者Error类型,并且@Transactional
注解上没有指定回滚异常类型。
原因
默认情况下,Spring事务只对RuntimeException或者Error类型异常进行回滚,检查异常(通常为业务类异常)不会导致事务回滚。
解决
- 手动在
@Transactional
注解上声明回滚的异常类型rollbackFor
(方法抛出该异常及其所有子类型异常都能出发事务回滚): - 将方法内抛出的异常继承RuntimeException。
场景二
非事务方法直接通过this
调用本类事务方法。
比如,修改UserServiceImpl如下:
@Service
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
public UserServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Transactional
@Override
public void saveUser(User user) {
userMapper.save(user);
if (!user.getUsername().contains("K")) {
throw new RuntimeException("用户名不含有K");
}
}
@Override
public void saveUser2(User user) {
this.saveUser(user);
}
}
测试:
@Test
public void test2() {
userService.saveUser2(user2);
}
执行结果:
发现插入成功,说明事务不生效。
原因
这让我想起面试字节时一个AOP的面试题:
@log
public void A() {}
public void B() { A(); }
问直接通过this.B()
调用A()
方法能否打印日志。答案当然是不能的。
AOP的实现是通过目标类的JDK动态代理类或者CGlib动态代理类来实现的,直接调用方法调用的是目标类的方法而不是代理类的增强后方法,因此不能打印出日志。
Spring事务控制使用AOP代理实现,通过对目标对象的代理来增强目标方法,直接通过this
调用本类的方法的时候,this
的指向并非代理类,而是目标类本身。
解决
从IOC容器中获取UserService
,然后调用saveUser
方法:
@Service
public class UserServiceImpl implements UserService, ApplicationContextAware {
private final UserMapper userMapper;
private ApplicationContext applicationContext;
public UserServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Transactional
@Override
public void saveUser(User user) {
userMapper.save(user);
if (!user.getUsername().contains("K")) {
throw new RuntimeException("用户名不含有K");
}
}
@Override
public void saveUser2(User user) {
UserService userService = applicationContext.getBean(UserService.class);
userService.saveUser(user);
}
}
最终总结
模块驱动注解@EnableTransactionManagement
通过@Import
导入了一个@TransactionManagementConfigurationSelector组件
,它通过selectImports
方法批量注册两个类:
- 注册器:
AutoProxyRegistrar
- 配置类:
ProxyTransactionManagementConfiguration
(1)AutoProxyRegistrar
通过AopConfigUtils
向容器中注册InfrastructureAdvisorAutoProxyCreator
,作用是为目标Service创建代理对象,增强Service方法,用于事务控制。
(2)ProxyTransactionManagementConfiguration
主要向容器中注册两个bean:
① BeanFactoryTransactionAttributeSourceAdvisor
② TransactionInterceptor
①创建SpringTransactionAnnotationParser
,解析@Transaction
注解上的各个属性值,包装为Transaction对象
② 实现了MethodInterceptor
方法拦截器,在invoke
方法中先处理和Kotlin相关内容,再执行invokeWithTransaction
方法。
这个方法是事务核心,先获取目标方法的@Transaction
注解的属性,再获取事务管理器JdbcTransactionManager
,在一个try-catch块中通过反射执行目标方法proceedWithInvocation
- 出现异常则执行
completeTransactionThrowing
方法:需要判断方法抛出的异常是否在指定的范围内,是则通过事务管理器回滚事务,否则提交事务。 - 成功则执行
commitTransactionAfterReturning
方法:通过事务管理器提交事务。