首先rollback-only出现的原因先简单带过一下吧:在使用了Propagation.REQUIRED的事务传递中,若本层的service捕获了下层service的异常,则本层中的事务也无法提交,在方法结束,事务尝试提交时会报出Transaction rolled back because it has been marked as rollback-only错误。这是因为REQUIRED是同一个事务,具有原子性。当内层的方法出现异常的时候,会标记一个rollback-only,这样外层方法提交的时候,判断rollback-only为true,那么整个事务都不允许提交。参考代码如下:(以下代码全部基于service层方法级别注解,且事务传播机制为REQUIRED)
service1
public String getName(String name) {
try {
demoService2.insertUser();
}catch (Exception e) {
e.printStackTrace();
}
demoService3.insertUser();
return "1";
}
service2
public void insertUser() {
userDao.insertUserError();//这里报错,字段超长
}
service3
public void insertUser() {
userDao.insertUser1(); //这里正常插入一条数据
}
这样执行service1.getName后,service3的insertUser也是无法提交的。原因就是上边说的。
接下来对service2进行一下修改
service2
public void insertUser() {
try {
userDao.insertUserError();//这里报错,字段超长
}catch (Exception e) {
e.printStackTrace();
}
}
这样执行一下,可以看到service3提交了。这里就不再分析源代码了。研究一下原因是:DAO没有标记事务,只是使用了当前的连接,因此当DAO异常,没有标记当前事务的roll-back。而异常在service2里边进行catch了,因此在service1中没有异常,因此对于整个事务来说,spring认为这个事务没有出现异常,因此事务正常提交。
在修改一下
service2
public void insertUser() {
try {
demoService4.insertUser();
userDao.insertUserError();//这里报错,字段超长
}catch (Exception e) {
e.printStackTrace();
}
}
service4
public void insertUser() {
userDao.insertUserError();//这里报错,字段超长
}
这样再执行service1.getName还是和第一种情况一样,roll-back only了。
考虑到spring的事务传播机制,我们再对代码进行try catch时,应该多一些思考,事务传播往往和异常有着千丝万缕的关系。check exception不用多说了,主要是对于runtimeException,一定要慎重。 比如,在最内层catch异常,会对整个事务的原子性造成污染,即try的DAO可能执行失败了,但是外层方法的事务还是提交了。 当存在需要单独开辟事务的场景时,需要考虑其他的事务传播属性。
实际开发中,对于异常的处理更是一门学问,另找时间研究。