一,事务失效的12种场景
二,@Transactional注解的时效场景(附加代码)
一、正常情況
事务生效报错数据无变化
二、try/catch 导致事务失效
@Transactional 执行流程是: @Transactional 会在方法执行前,会自动开启事务;在方法成功执行完,会自动提交事务;如果方法在执行期间,出现了异常,那么它会自动回滚事务。
导致结果:
能看出来事务并没有生效
三、调用类内用的 @Transactional 方法
执行结果:
上述代码在添加用户之后即使遇到了异常,程序也没有执行回滚,这是因为 @Transactional 是基于 Spring AOP 实现的,而 Spring AOP 又是基于动态代理实现的,而当调用类内部的方法时,不是通过代理对象完成的,而是通过 this 对象实现的,这样就绕过了代理对象,从而事务就失效了。
解决办法:
1、自注入
@Service
public class UserInfoService {
@Autowired
private UserInfoService userInfoService;
public void justUpdate(){
userInfoService.updateUser2();
}
@Transactional(rollbackFor = Exception.class)
public void updateUser2() {
}
}
2、Spring上下文
@Service
public class UserInfoService {
ApplicationContext applicationContext;
public void justUpdate(){
UserInfoService userInfoService = (UserInfoService) applicationContext.getBean("userInfoService");
userInfoService.updateUser2();
}
@Transactional(rollbackFor = Exception.class)
public void updateUser2() {
}
}
3、获取他的代理类,直接调用代理类
@Service
public class UserInfoService {
public void justUpdate(){
((UserInfoService) AopContext.currentProxy()).updateUser2();
}
@Transactional(rollbackFor = Exception.class)
public void updateUser2() {
}
}
------------------------以下属于copy其他博客的场景这里我就不一一示范了---------------------------
四、多数据源事物配置问题
项目中没有配置事务管理器,需要在配置类或者配置文件中配置,因为项目是多数据源的,所以要区别配置不同数据源的事务管理器,如下:
@Primary
@Bean(name = "db1")
public DataSource getDataSource() {
return createDataSource();
}
@Bean(name = "db1TransactionManager")
public PlatformTransactionManager txManager(@Qualifier("db1") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "db2")
public DataSource getDataSource() {
return buildDataSource();
}
@Bean(name = "db2TransactionManager")
public PlatformTransactionManager txManager(@Qualifier("db2") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
可以看到,两个事务管理器配置了不同的beanName,接下来只需要 在需要事务控制的位置加上该事务管理器的name就可以完美解决!
@Override
@Transactional(value = "db1TransactionManager",rollbackFor = Exception.class)
public int updateOrInsert(BaseRequest<BankTemplateDto> param) {
...
}
五、新开启一个线程
如下的方式deleteUserA()也不会回滚,因为spring实现事务的原理是通过ThreadLocal把数据库连接绑定到当前线程中,新开启一个线程获取到的连接就不是同一个了,例如:
@Transactional
public void deleteUser() throws MyException{
userMapper.deleteUserA();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
int i = 1/0;
userMapper.deleteUserB();
}).start();
}
六、事务传播属性设置错误
注意传播属性的设置,一般情况下,propagation属性无需配置。会使用默认配置,即:PROPAGATION_REQUIRED,有些propagation属性会导致事务不会触发,一定要注意:
PROPAGATION_SUPPORTS: 如果存在事务,则进入事务;否则,以非事务方式运行。
PROPAGATION_NOT_SUPPORTED: 如果存在事务,则挂起事务,并以非事务方式运行。
PROPAGATION_NEVER: 以非事务形式运行,如果存在事务,则抛出异常。
七、注解的方法是否为public
@Transactional
private void deleteUser() throws MyException{
userMapper.deleteUserA();
int i = 1/0;
userMapper.deleteUserB();
}
idea直接会给出提示Methods annotated with ‘@Transactional’ must be overridable ,原理很简单,private修饰的方式,spring无法生成动态代理,AOP代理分别在intercept()和invoke()方法判断是否进行事务拦截,这两个方法都会间接调用AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法来获取事务控制的相关属性。这其中有以下一段代码
/**
* Same signature as {@link #getTransactionAttribute}, but doesn't cache the result.
* {@link #getTransactionAttribute} is effectively a caching decorator for this method.
* <p>As of 4.1.8, this method can be overridden.
* @since 4.1.8
* @see #getTransactionAttribute
*/
protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
}
这段代码会导致no-public的方法无法进入事务控制,所以一定要确保自己需要进行事务控制的方法包含public修饰符。