//@Service (注释了@Service)
public class xxxServiceImpl implements xxxService {
@Transactional
public void addxxx(TianLuo tianluo) {
//...
}
}
上面例子中,spring事务没有生效,因为Spring事务是由AOP机制实现的,也就是从Spring IOC 容器中获取bean 时,Spring 会为目标类创建代理,来支持事务的。但是@Service被注释后,你的service类都不是被spring 管理的,更不会创建代理来支持事务。
@Configuration
public class AppConfig {
// 没有配置事务管理器
}
@Service
public class MyService {
@Transactional
public void doSomething() {
// ...
}
}
没有在AppConfig中配置事务管理器,因为Spring无法创建事务代理对象,导致事务不生效。
如何解决呢?
@Configuration
public class AppConfig {
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
@Service
public class MyService {
@Transactional
public void doSomething() {
// ...
}
}
如果是SpringBoot项目,默认会自动配置事务管理器并开启事务支持的。
@Service
public class xxxServiceImpl {
@Autowired
private xxxMapper xxxMapper;
@Transactional
public final void xxx(xxx xxx) {
xxxMapper.save(xxx);
}
}
一个方法被final或者static修饰,则该方法不能被子类重写,也就是说在该方法上进行动态代理,这会导致Spring无法生成事务代理对象来管理事务。
事务方法不要被final或者static修饰。
@Service
public class xxxServiceImpl implements xxxService {
@Autowired
private xxxMapper xxxMapper;
@Autowired
private xxx1Mapper xxx1Mapper;
public void addxxx(xxx xxx){
// 调用内部的事务方法
this.executeAddxxx(xxx);
}
@Transactional
public void executeAddxxx(xxx xxx) {
xxxMapper.save(xxx);
xxx1Mapper.saveFlow(xxx);
}
}
事务是通过Spring AOP代理来实现的,而在同一个类中,一个方法调用另一个方法时,调用方法直接调用目标方法的代码,而不是通过代理类去进行调用的。
可以新建多一个类,让调用方法分开在不同类中。
也可以在该service类中注入自己,或者通过AopContext.currentProxy()获取代理对象。
@Service
public class xxxServiceImpl {
@Autowired
private xxxMapper xxxMapper;
@Transactional
private void xxx(xxx xxx) {
xxxMapper.save(xxx);
}
}
spring事务方法xxx不是public,事务不生效。因为spring事务是由AOP机制实现的,AOP机制的本质就是动态代理,而动态代理的事务方法不是public的话,computeTransactionAttribute()方法就会返回null,也就是这时事务属性不存在了。可以看下AbstractFallbackTransactionAttributeSource的源码:
事务方法的访问权限必须得是public
spring事务的底层,还是依赖于数据库本身的事务支持。在mysql中,MyISAM存储引擎是不支持事务的,InnoDB引擎才支持事务。因为在开发阶段设计表的时候,要确认你选择的存储引擎是支持事务的。
@Service
public class xxxServiceImpl {
@Transactional(readOnly = true)
public void updateUser(User user) {
userDao.updateUser(user);
}
}
上述方法中使用了@Transactional注解,但是注解中的readOnly = true属于指示这是一个只读事务,因为在进行更新User实体的时候会抛出异常。
如何解决呢?
将readOnly = true改为false,或者移除@Transactional注解中的readOnly属性。
@Transactional(timeout = 1)
public void doSomething() {
//...
}
上面方法中,timeout属性被设置为1秒,这意味着如果事务在1秒内无法完成,则报事务超时了。
@Transactional(rollbackFor = Error.class)
public void doSomething() {
//...
}
rollbackFor属性指定的异常必须是Throwable或者子类。默认情况下,RuntimeException和Error两种异常都是会自动回滚的。但是因为上述的代码指定的rollbackFor = Error.class,但是抛出的异常又是Exception,而Exception和Error没有任何什么继承关系,因此事务就不生效。
rollbackFor属性指定的异常与抛出的异常要匹配。
一般使用:@Transactional(rollbackFor=Exception.class)
@Service
public class xxxServiceInOutService {
@Autowired
private xxxService xxxService;
@Autowired
private xxxMapper xxxMapper;
@Transactional
public void addxxx(xxx xxx) throws Exception {
xxxMapper.save(xxx);
xxxService.saveFlow(xxx);
}
}
@Service
public class TianLuoFlowService {
@Autowired
private xxxMapper xxxMapper;
@Transactional(propagation = Propagation.NESTED)
public void saveFlow(xxx xxx) {
xxxMapper.save(xxx);
throw new RuntimeException();
}
}
以上代码使用了嵌套事务,如果saveFlow出现运行时异常,会继续往上抛,到外层addxxx的方法,导致xxxMapper.save也会回滚。如果不想因为被内部嵌套的事务影响,可以用try-catch包住。
@Transactional
public void addxxx(xxx xxx) throws Exception {
tianLuoMapper.save(xxx);
try {
xxxService.saveFlow(xxx);
} catch (Exception e) {
log.error("save xxx flow fail,message:{}",e.getMessage());
}
}
@Service
public class xxxService {
@Autowired
private xxxMapper xxxMapper;
@Autowired
private xxxService xxxService;
@Transactional
public void addxxx(xxx xxx) {
//保存xxx数据库记录
xxxMapper.save(xxx);
//多线程调用
new Thread(() -> {
xxxService.saveFlow(xxx);
}).start();
}
}
@Service
public class xxxService {
@Autowired
private xxxMapper xxxMapper;
@Transactional
public void saveFlow(xxx xxx) {
xxxMapper.saveFlow(xxx);
}
}
事务不生效原因:因为Spring事务是基于线程绑定的,每个线程都有自己的事务上下文,而多线程环境下可能会存在多个线程共享同一个事务上下文的情况,导致事务不生效。Spring事务管理器通过使用线程本地变量(ThreadLocal)来实现线程安全。
在Spring事务管理器中,通过TransactionSynchronizationManager类来管理事务上下文。TransactionSynchronizationManager内部维护了一个ThreadLocal对象,用来存储当前线程的事务上下文。在事务开始时,TransactionSynchronizationManager会将事务上下文绑定到当前线程的ThreadLocal对象中,当事务结束时,TransactionSynchronizationManager会将事务上下文从ThreadLocal对象中移除。
@Service
public class xxxServiceImpl implements xxxService {
@Autowired
private xxxMapper xxxMapper;
@Autowired
private xxxFlowMapper xxxFlowMapper;
@Transactional
public void addxxx(xxx xxx) {
try {
//保存tianluo数据库记录
xxxMapper.save(xxx);
//保存tianluo flow数据库记录
xxxFlowMapper.saveFlow(xxx);
} catch (Exception e) {
log.error("add TianLuo error,id:{},message:{}", xxx.getId(),e.getMessage());
}
}
}
事务不生效的原因:事务中的异常已经被业务捕获并处理,而没有被正确地被传播回事务管理器,事务将无法回滚。
解决方案:在spring事务方法中,当我们使用了try-catch,如果catch住异常,记录完异常日志什么的,一定要重新把异常抛出来。
@Service
public class xxxServiceImpl implements xxxService {
@Autowired
private xxxMapper xxxMapper;
@Autowired
private xxxFlowMapper xxxFlowMapper;
@Transactional
public void addxxx(xxx xxx) {
try {
//保存tianluo数据库记录
xxxMapper.save(xxx);
//保存tianluo flow数据库记录
xxxFlowMapper.saveFlow(xxx);
} catch (Exception e) {
log.error("add TianLuo error,id:{},message:{}", xxx.getId(),e.getMessage());
throw e;
}
}
}