最后这篇文章我们来讨论开发中最常用的剩下三种事务传播机制:REQUIRED、REQUIRES_NEW和NESTED
5. REQUIRED
REQUIRED是我们最常用的传播机制。如果当前有存在的事务则加入该事务,如果没有则新开一个事务。
先修改配置文件:
<tx:attributes> <tx:method name="insertSuperTable" propagation="REQUIRED"/> <tx:method name="insertSubTable" propagation="REQUIRED"/> </tx:attributes>
父事务类:
@Component
public class TransactionSuper {
@Autowired
TransactionSub transactionSub;
String insertSuperTable1 = "insert into super_table values (1, 'super1')";
String insertSuperTable2 = "insert into super_table values (2, 'super2')";
public void insertSuperTable(AbstractApplicationContext ctx) throws Exception{
System.out.println("========insertSuperTable start========");
JdbcTemplate jt = (JdbcTemplate)ctx.getBean("jdbcTemplate");
jt.execute(insertSuperTable1);
transactionSub.insertSubTable(ctx);
jt.execute(insertSuperTable2);
System.out.println("========insertSuperTable end========");
}
}
子事务类:
@Component
public class TransactionSub {
String insertSubTable1 = "insert into sub_table values (1, 'sub1')";
String insertSubTable2 = "insert into sub_table values (2, 'sub2')";
public void insertSubTable(AbstractApplicationContext ctx) throws Exception {
System.out.println("========insertSubTable start========");
JdbcTemplate jt = (JdbcTemplate) ctx.getBean("jdbcTemplate");
jt.execute(insertSubTable1);
jt.execute(insertSubTable2);
System.out.println("========insertSubTable end========");
}
}
然后运行测试方法
如果有存在的事务,跟MANDATORY效果一样,也是加入该事务,并且共用一个connection,所以父子方法对数据库的修改都是相互可见的。没有事务的情况这里就不再演示了,也就是insertSubTable方法自己新开一个事务。
我们来看看发生异常时的回滚,其实基本跟MANDATORY一样,只是子方法可以自己开启事务,而不一定要加入其它事务之中。
①RuntimeException
如果有已经存在的事务,跟MANDATORY一样,REQUIRED标注的方法是直接加入父事务,成为父事务的一部分,他们共享一个connection。所以不管是子事务还是父事务抛出RuntimeException的时候,父子事务都会回滚。如果没有事务,就只回滚子方法新开事务中的操作。
②Throwable和Exception
跟MANDATORY一样,如果不配置rollback-for属性,抛出Throwable和Exception都不会导致父子事务回滚,而是在哪儿出异常就在哪儿提交,就有可能出现部分提交的现象。
我们可以用rollback-for属性来让抛Throwable和Exception时也回滚。由于REQUIRED标注的方法是直接加入父事务,所以子类发生Throwable或Exception,就会父子一起回滚。
6. REQUIRES_NEW
被REQUIRES_NEW标记的方法会新建事务,如果当前存在事务,把当前事务挂起,等待新开事务完成后,被挂起的事务再恢复执行。
先修改配置文件:
<tx:attributes> <tx:method name="insertSuperTable" propagation="REQUIRED"/> <tx:method name="insertSubTable" propagation="REQUIRES_NEW"/> </tx:attributes>
直接执行测试方法:
从上面可以很明显的看出insertSubTable方法新开了一个connection并重启了一个新事务,insertSuperTable和insertSubTable是两个不同的connection中的两个不同的事务。所以父方法对数据库的修改是否对子方法可见,取决于数据库的事务隔离级别。
REQUIRES_NEW由于开了新连接和新事务,所以异常回滚跟其他事务传播机制有很大区别,下面我们结合具体实例来说明。
①RuntimeException
我们先让子方法insertSubTable抛出RuntimeException。
子事务类:
@Component
public class TransactionSub {
String insertSubTable1 = "insert into sub_table values (1, 'sub1')";
String insertSubTable2 = "insert into sub_table values (2, 'sub2')";
public void insertSubTable(AbstractApplicationContext ctx) throws Exception {
System.out.println("========insertSubTable start========");
JdbcTemplate jt = (JdbcTemplate) ctx.getBean("jdbcTemplate");
jt.execute(insertSubTable1);
ExceptionUtils.throwRuntimeException(); //抛出RuntimeException
jt.execute(insertSubTable2);
System.out.println("========insertSubTable end========");
}
}
运行测试类:
可以看到虽然两个方法运行在不同connection的事务里,但还是都发生了回滚。如果我们在父方法把子方法抛出的RuntimeException捕获住呢?修改父方法为:
父事务类:
@Component
public class TransactionSuper {
@Autowired
TransactionSub transactionSub;
String insertSuperTable1 = "insert into super_table values (1, 'super1')";
String insertSuperTable2 = "insert into super_table values (2, 'super2')";
public void insertSuperTable(AbstractApplicationContext ctx) throws Exception{
System.out.println("========insertSuperTable start========");
JdbcTemplate jt = (JdbcTemplate)ctx.getBean("jdbcTemplate");
jt.execute(insertSuperTable1);
//捕获RuntimeException
try{
transactionSub.insertSubTable(ctx);
}catch(RuntimeException re){
re.printStackTrace();
}
jt.execute(insertSuperTable2);
System.out.println("========insertSuperTable end========");
}
}
运行测试方法:
可以很明显的看出insertSubTable方法回滚了,而insertSuperTable方法却被成功执行了。同样的,如果在子事务方法执行完毕之后,父事务方法再抛出RuntimeException,那子事务也不会再回滚,因为子事务已经提交。这点跟采用加入当前事务的传播机制不一样,如果采用加入当前事务的方式,子事务的RuntimeException就算被父事务方法捕获父事务也一样被回滚,因为他们都在同一个connection的同一个事务之中。同理,由于他们在一个事务之中,就算子事务方法执行完毕后,父事务方法再抛出RuntimeException,子事务也一样会跟父事务一起回滚。
②Throwable和Exception
在没有配置rollback-for属性的时候跟其他事务传播机制是一样的效果,都是在哪儿出异常就在哪儿提交。唯一不同的是:如果是加入当前事务的方式,只要整个事务中其中一个方法发生Exception并且其也配置了rollback-for属性,那整个事务都会回滚。而被REQUIRES_NEW标注的方法发生Exception并且配置了rollback-for属性只会回滚本方法,其他方法则在发生异常的地方提交(如果其他方法没配置rollback-for属性的前提下)。
7. NESTED
最后一个事务传播机制是NESTED,如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与REQUIRED类似的操作,也就是新开事务。从字面上看好像跟REQUIRED没有什么区别,不就是有就加入,没有就新开吗?从这点来说他们确实大同小异,但他们最大的区别就是在发生Exception的处理上,NESTED引入了JDBC3.0的savepoint机制,我们在后面会详细说明,我们先来看看它的正常使用效果。
先修改配置文件:
<tx:attributes> <tx:method name="insertSuperTable" propagation="REQUIRED"/> <tx:method name="insertSubTable" propagation="NESTED"/> </tx:attributes>
父事务类:
@Component
public class TransactionSuper {
@Autowired
TransactionSub transactionSub;
String insertSuperTable1 = "insert into super_table values (1, 'super1')";
String insertSuperTable2 = "insert into super_table values (2, 'super2')";
public void insertSuperTable(AbstractApplicationContext ctx) throws Exception{
System.out.println("========insertSuperTable start========");
JdbcTemplate jt = (JdbcTemplate)ctx.getBean("jdbcTemplate");
jt.execute(insertSuperTable1);
transactionSub.insertSubTable(ctx);
jt.execute(insertSuperTable2);
System.out.println("========insertSuperTable end========");
}
}
子事务类:
@Component
public class TransactionSub {
String insertSubTable1 = "insert into sub_table values (1, 'sub1')";
String insertSubTable2 = "insert into sub_table values (2, 'sub2')";
public void insertSubTable(AbstractApplicationContext ctx) throws Exception {
System.out.println("========insertSubTable start========");
JdbcTemplate jt = (JdbcTemplate) ctx.getBean("jdbcTemplate");
jt.execute(insertSubTable1);
jt.execute(insertSubTable2);
System.out.println("========insertSubTable end========");
}
}
运行测试方法:
上面说到insertSubTable方法虽然表面上是开启了一个nested的事务,但实际上还是加入了insertSuperTable方法开启的事务。下面我们就用异常回滚来看看是否如此。
①RuntimeException
被NESTED标注的方法如果产生RuntimeException会导致事务回滚,但跟REQUIRED不同的是如果我们在父方法中捕获了这个异常,让父方法不在抛出RuntimeException,它就不会导致父事务的回滚,而REQUIRED就算我们进行捕获,父事务还是会回滚,因为他们都在一个事务里。那NESTED是怎么做到父子都在一个事务里,而不让父事务回滚的呢。答案就是:savepoint,如果子方法出现RuntimeException就会回滚到开始子事务的那个savepoint,如果父方法不再抛出,那么父方法就不会回滚,并可以继续执行。
我们这里让insertSubTable方法抛出RuntimeException异常,然后在insertSuperTable方法里进行捕获。我们来看看效果。
父事务类:
@Component
public class TransactionSuper {
@Autowired
TransactionSub transactionSub;
String insertSuperTable1 = "insert into super_table values (1, 'super1')";
String insertSuperTable2 = "insert into super_table values (2, 'super2')";
public void insertSuperTable(AbstractApplicationContext ctx) throws Exception{
System.out.println("========insertSuperTable start========");
JdbcTemplate jt = (JdbcTemplate)ctx.getBean("jdbcTemplate");
jt.execute(insertSuperTable1);
//捕获RuntimeException
try{
transactionSub.insertSubTable(ctx);
}catch(RuntimeException re){
re.printStackTrace();
}
jt.execute(insertSuperTable2);
System.out.println("========insertSuperTable end========");
}
}
子事务类:
@Component
public class TransactionSub {
String insertSubTable1 = "insert into sub_table values (1, 'sub1')";
String insertSubTable2 = "insert into sub_table values (2, 'sub2')";
public void insertSubTable(AbstractApplicationContext ctx) throws Exception {
System.out.println("========insertSubTable start========");
JdbcTemplate jt = (JdbcTemplate) ctx.getBean("jdbcTemplate");
jt.execute(insertSubTable1);
ExceptionUtils.throwRuntimeException(); //抛出RuntimeException
jt.execute(insertSubTable2);
System.out.println("========insertSubTable end========");
}
}
运行测试方法:
我们这次不在insertSubTable方法里面抛RuntimeException,而是在insertSuperTable里面抛RuntimeException,看看会有什么效果:
父事务类:
@Component
public class TransactionSuper {
@Autowired
TransactionSub transactionSub;
String insertSuperTable1 = "insert into super_table values (1, 'super1')";
String insertSuperTable2 = "insert into super_table values (2, 'super2')";
public void insertSuperTable(AbstractApplicationContext ctx) throws Exception{
System.out.println("========insertSuperTable start========");
JdbcTemplate jt = (JdbcTemplate)ctx.getBean("jdbcTemplate");
jt.execute(insertSuperTable1);
transactionSub.insertSubTable(ctx);
jt.execute(insertSuperTable2);
ExceptionUtils.throwRuntimeException(); //抛出RuntimeException
System.out.println("========insertSuperTable end========");
}
}
子事务类:
@Component
public class TransactionSub {
String insertSubTable1 = "insert into sub_table values (1, 'sub1')";
String insertSubTable2 = "insert into sub_table values (2, 'sub2')";
public void insertSubTable(AbstractApplicationContext ctx) throws Exception {
System.out.println("========insertSubTable start========");
JdbcTemplate jt = (JdbcTemplate) ctx.getBean("jdbcTemplate");
jt.execute(insertSubTable1);
jt.execute(insertSubTable2);
System.out.println("========insertSubTable end========");
}
}
执行测试方法:
可以看到在insertSuperTable方法抛出RuntimeException后,insertSubTable方法里的操作也回滚了。这就说明这两个方法的操作是在同一个事务里。NESTED开启的事务只不过是一个savepoint。
②Throwable和Exception
在没有配置rollback-for属性的时候跟其他事务传播机制是一样的效果,都是在哪儿出异常就在哪儿提交。唯一不同的是:如果是加入当前事务的方式,只要整个事务中其中一个方法发生Exception并且其也配置了rollback-for属性,那整个事务都会回滚。而被NESTED标注的方法发生Exception并且配置了rollback-for属性只会回滚到开始这个方法事务的savepoint,其他方法则在发生异常的地方提交(如果其他方法没配置rollback-for属性的前提下)。
转载于:https://blog.51cto.com/jaeger/1762039