开发过程中遇到的问题
解决
Spring中配置隔离级别
在Spring项目中配置隔离级别只需要做如下操作
public int insertUser(User user){
return userDao.insertUser(user);
}
上面的代码中我们使用了串行化的隔离级别来包住数据的一致性,这使它将阻塞其他的事务进行并发,所以它只能运用在那些低并发而又需要保证数据一致性的场景下。
隔离级别字典:
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
传播行为
在Spring中,当一个方法调用另外一个方法时,可以让事务采取不同的策略工作,如新建事务或者挂起当前事务等,这便是事务的传播行为。
定义
在Spring的事务机制中对数据库存在7种传播行为,通过枚举类Propagation定义。
/**
* 需要事务,默认传播性行为。
* 如果当前存在事务,就沿用当前事务,否则新建一个事务运行子方法
*/
REQUIRED(0),
/**
* 支持事务,如果当前存在事务,就沿用当前事务,
* 如果不存在,则继续采用无事务的方式运行子方法
*/
SUPPORTS(1),
/**
* 必须使用事务,如果当前没有事务,抛出异常
* 如果存在当前事务,就沿用当前事务
*/
MANDATORY(2),
/**
* 无论当前事务是否存在,都会创建新事务允许方法
* 这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立
*/
REQUIRES_NEW(3),
/**
* 不支持事务,当前存在事务时,将挂起事务,运行方法
*/
NOT_SUPPORTED(4),
/**
* 不支持事务,如果当前方法存在事务,将抛出异常,否则继续使用无事务机制运行
*/
NEVER(5),
/**
* 在当前方法调用子方法时,如果子方法发生异常
* 只回滚子方法执行过的SQL,而不回滚当前方法的事务
*/
NESTED(6);
......
}
日常开发中基本只会使用到REQUIRED(0),REQUIRES_NEW(3),NESTED(6)三种。
NESTED和REQUIRES_NEW是有区别的。NESTED传播行为会沿用当前事务的隔离级别和锁等特性,而REQUIRES_NEW则可以拥有自己独立的隔离级别和锁等特性。
NESTED的实现主要依赖于数据库的保存点(SAVEPOINT)技术,SAVEPOINT记录了一个保存点,可以通过ROLLBACK TO SAVEPOINT来回滚到某个保存点。如果数据库支持保存点技术时就启用保存点技术;如果不支持就会新建一个事务去执行代码,也就相当于REQUIRES_NEW。
Transactional自调用失效
如果一个类中自身方法的调用,我们称之为自调用。如一个订单业务实现类OrderServiceImpl中有methodA方法调用了自身类的methodB方法就是自调用,如:
public void methodA(){
for (int i = 0; i < 10; i++) {
methodB();
}
}
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW)
public int methodB(){
......
}
在上面方法中不管methodB如何设置隔离级别和传播行为都是不生效的。即自调用失效。
这主要是由于@Transactional的底层实现原理是基于AOP实现,而AOP的原理是动态代理,在自调用的过程中是类自身的调用,而不是代理对象去调用,那么就不会产生AOP,于是就发生了自调用失败的现象。
要克服这个问题,有2种方法:
编写两个Service,用一个Service的methodA去调用另外一个Service的methodB方法,这样就是代理对象的调用,不会有问题;
在同一个Service中,methodA不直接调用methodB,而是先从Spring IOC容器中重新获取代理对象`OrderServiceImpl·,获取到后再去调用methodB。说起来有点乱,还是show you the code。
private ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Transactional
public void methodA(){
OrderService orderService = applicationContext.getBean(OrderService.class);
for (int i = 0; i < 10; i++) {
orderService.methodB();
}
}
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW)
public int methodB(){
......
}
}
上面代码中我们先实现了ApplicationContextAware接口,然后通过applicationContext.getBean()获取了OrderService的接口对象。这个时候获取到的是一个代理对象,也就能正常使用AOP的动态代理了。
按照这个方法写了, 但是最终遇到了下面的问题, 便没有再继续往下走了, 尝试对 进行了同步方法的重量级锁模式, 最终定位到可能是: Spring的事务提交了, 但是数据库的事务还没有提交;
最后另辟蹊径了.
关于在开发过程中, 使用默认隔离级别SUPPORTS, 子事务捕获异常,但是父事务依然 显示, 当前事务被标记为rollback的问题:
因为在开发的代码中只要存在 throw异常, 就会把当前事务标记为 rollback;