事务使用
引入依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <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>
|
包结构如下:
1 2 3 4 5 6 7 8 9 10 11
| top └─parak ├─entity │ └─User ├─mapper │ └─UserMapper ├─service │ │ └─UserService │ └─impl │ └─UserServiceImpl └─KHighnessApplication
|
创建数据库:
1 2 3 4 5 6
| 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;
|
配置文件:
1 2 3 4 5
| 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
|
实体类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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; } }
|
创建持久接口:
1 2 3 4 5 6 7 8
| @Mapper @Repository public interface UserMapper {
@Insert("insert into user(username,age) values(#{username},#{age})") void save(User user);
}
|
创建业务接口:
1 2 3 4 5
| public interface UserService {
void saveUser(User user);
}
|
创建业务实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @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"); } } }
|
创建启动类:
1 2 3 4 5 6 7 8
| @SpringBootApplication @EnableTransactionManagement public class KHighnessApplication { public static void main(String[] args) { new SpringApplicationBuilder(KHighnessApplication.class) .web(WebApplicationType.SERVLET).run(args); } }
|
创建测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @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
的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @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
组件,查看其源码:

AutoRegistrar
查看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如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @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); }
}
|
测试:
1 2 3 4
| @Test public void test2() { userService.saveUser2(user2); }
|
执行结果:

发现插入成功,说明事务不生效。
原因
这让我想起面试字节时一个AOP的面试题:
1 2 3 4
| @log public void A() {}
public void B() { A(); }
|
问直接通过this.B()
调用A()
方法能否打印日志。答案当然是不能的。
AOP的实现是通过目标类的JDK动态代理类或者CGlib动态代理类来实现的,直接调用方法调用的是目标类的方法而不是代理类的增强后方法,因此不能打印出日志。
Spring事务控制使用AOP代理实现,通过对目标对象的代理来增强目标方法,直接通过this
调用本类的方法的时候,this
的指向并非代理类,而是目标类本身。
解决
从IOC容器中获取UserService
,然后调用saveUser
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @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
方法:通过事务管理器提交事务。