事务使用
引入依赖:
<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.Driverspring.datasource.username=rootspring.datasource.password=KAG1823spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8logging.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@Repositorypublic interface UserMapper {@Insert("insert into user(username,age) values(#{username},#{age})")void save(User user);}
创建业务接口:
public interface UserService {void saveUser(User user);}
创建业务实现类:
@Servicepublic class UserServiceImpl implements UserService {private final UserMapper userMapper;public UserServiceImpl(UserMapper userMapper) {this.userMapper = userMapper;}@Transactional@Overridepublic void saveUser(User user) {userMapper.save(user);if (!user.getUsername().contains("K")) {throw new RuntimeException("用户名不含有K");}}}
创建启动类:
@SpringBootApplication@EnableTransactionManagementpublic class KHighnessApplication {public static void main(String[] args) {new SpringApplicationBuilder(KHighnessApplication.class).web(WebApplicationType.SERVLET).run(args);}}
创建测试:
@SpringBootTestclass KHighnessApplicationTest {@Resourceprivate UserService userService;private final User user1 = new User();private final User user2 = new User();@BeforeEachpublic void before() {user1.setUsername("KHighness");user2.setUsername("Flower");}@Testpublic 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如下:
@Servicepublic class UserServiceImpl implements UserService {private final UserMapper userMapper;public UserServiceImpl(UserMapper userMapper) {this.userMapper = userMapper;}@Transactional@Overridepublic void saveUser(User user) {userMapper.save(user);if (!user.getUsername().contains("K")) {throw new RuntimeException("用户名不含有K");}}@Overridepublic void saveUser2(User user) {this.saveUser(user);}}
测试:
@Testpublic void test2() {userService.saveUser2(user2);}
执行结果:
发现插入成功,说明事务不生效。
原因
这让我想起面试字节时一个AOP的面试题:
@logpublic void A() {}public void B() { A(); }
问直接通过this.B()调用A()方法能否打印日志。答案当然是不能的。
AOP的实现是通过目标类的JDK动态代理类或者CGlib动态代理类来实现的,直接调用方法调用的是目标类的方法而不是代理类的增强后方法,因此不能打印出日志。
Spring事务控制使用AOP代理实现,通过对目标对象的代理来增强目标方法,直接通过this调用本类的方法的时候,this的指向并非代理类,而是目标类本身。
解决
从IOC容器中获取UserService,然后调用saveUser方法:
@Servicepublic class UserServiceImpl implements UserService, ApplicationContextAware {private final UserMapper userMapper;private ApplicationContext applicationContext;public UserServiceImpl(UserMapper userMapper) {this.userMapper = userMapper;}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}@Transactional@Overridepublic void saveUser(User user) {userMapper.save(user);if (!user.getUsername().contains("K")) {throw new RuntimeException("用户名不含有K");}}@Overridepublic 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方法:通过事务管理器提交事务。
