[b]工作中正好碰到这个问题,于是学习以下两个文章。先介绍了一些知识。后加入了对知识的分析,方便理解。并对可能的原理进行推测,以后有空看源码。[/b]
[url=]文章一[/url]
[url=]文章二[/url]

做java的WEB开发,通常都是分层的,包括action(controller层),service层,dao层。这里重点说说service层,也就是业务逻辑层的事务问题。

1.首先说一说action(controller)层
这一层是与web应用密切相关的,主要是处理request与response。把请求中的东西拿出来,作为业务层的输入,再把业务层的结束包装(处理)后,产生响应对象。

代码要求:[b]功能分层来说,这个action层功能主要是处理web的数据的,通常是不应该含有事务逻辑的,如果有请另包装到到一个service中。[/b]我经常看到一些代码把request传递到service中处理,问过原因,说是参考其它人的代码也是这么写的。所以告诉原因很重要。业务通用性来讲,业务逻辑不一定都是给web用的,总不能让非web的应用,数据包装成request来用吧。

2.spring注解事务
我们现在都用注解方式,很多时候只是写一个@Transactional。实际上后面可以设置多种参数。
有表明传播性与隔离级别。比如:
@Transactional(propagation = Propagation.REQUIRES_NEW)。不写的情况下,默认是:PROPAGATION_REQUIRED。
也有表明引起事务回滚的异常类:
@Transaction(noRollbackFor=RuntimeException.class)
@Transaction(RollbackFor=Exception.class)
不写的情况下,默认是:RuntimeException,只回滚unchecked异常。
另外还有一个readOnly = true,最后会讲。
默认配置,能够满足正常的需要,没有特殊情况就只要一个@Transaction。但要注意后面提到的异常处理。

3.关于spring注解事务的异常
[b]代码要求:按默认情况配置,也就是不配置。自定义业务中异常的时候,让自定义的异常继承自RuntimeException,这样抛出的时候才会被Spring默认的事务处理准确处理。如果是一个servive中产生了checked异常,请在本service方法中处理掉或者另抛出unchecked一场,否则如果有外层service,将不知道要不要回滚整个事务。[/b]
[color=red][b]通常我们的项目中,注意异常的处理就可以了。[/b][/color]

以下讨论的回滚都指unchecked异常,先是主要的几种传播与隔离配置。
3.PROPAGATION_REQUIRED
一般文章介绍:默认事务类型,如果没有,就新建一个事务;如果有,就加入当前事务。适合绝大多数情况。

结论:
内外都无trycatch情况:
这样在方法中任何地方发生unchecked异常将触发整个方法的回滚。
无论内外层产生unchecked异常,都回滚。

如果有trycatch情况:
外层发生了unchecked异常,但被外层trycatch了,那是不会回滚的。
[b]内层发生了unchecked异常,但外层有trycatch,但还是回滚了。按说外层的AOP是感受不到抛出的异常的,怎么会回滚呢?我猜测是内层的AOP也抛出了一个不能被外层trycatch的异常,而被外层的AOP感受到了。[/b]
内层发生了unchecked异常,但内层有trycatch,内层不回滚,如果外层正常,也不回滚了。


4.PROPAGATION_NESTED
一些文章先测试再讲规则,有点不知其所以然的感觉,所以我先查查什么是嵌套事务及其原则?
嵌套事务是一个外部事务的一个子事务,是一个外部事务的一个组成部分,当嵌套事务发生异常而回滚,则会恢复到嵌套事务的执行前的状态,相当于嵌套事务未执行。
如果外部事务回滚,则嵌套事务也会回滚!!!外部事务提交的时候,嵌套它才会被提交。
定义明确了,无论是交给spring处理NESTED事务,还是数据库处理NESTED事务,或者你设计什么事务时,原则都应该一样。

当所调用的方法为NESTED事务,该事务的回滚可以不影响到调用者的事务
当然如果子事务没有trycatch,异常冒泡而出,就将触发调用者事务的回滚。子事务有了trycatch就不会影响到外部调用者事务了,不会触发整个方法的回滚。

而调用者出现unchecked异常,却能触发所调用的nested事务的回滚!

5.PROPAGATION_REQUIRES_NEW
更少用到了此情况,通常可以将此部分代码放在事务之外执行 实在剥离不开才会用到。当被调用时,就相当于暂停(挂起)当前事务,先开一个新的事务去执行REQUIRES_NEW的方法,如果REQUIRES_NEW中的异常得到了处理那么他将不影响调用者的事务,同时,调用者之后出现了异常,同样也不会影响之前调用的REQUIRES_NEW方法的事务.
[b]有个猜测:但是如果如果内部REQUIRES_NEW中的异常没有处理,异常冒泡会影响到调用者吧![/b]

6.REQUIRES_NEW与NESTED比较
PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, REQUIRES_NEW 完全是一个新的事务,而NESTED 则是外部事务的子事务,如果外部事务commit,嵌套事务也会被commit, 这个规则同样适用于rollback。
PROPAGATION_NESTED可以在外层rollback所有内层的事务。REQUIRES_NEW不行。

后面再介绍几个配置参数,简单介绍就明白了。
7.PROPAGATION_SUPPORTS:如果没有,就以非事务方式执行;如果有,就使用当前事务。

8.PROPAGATION_NOT_SUPPORTED:如果没有,就以非事务方式执行;如果有,就将当前事务挂起。即无论如何不支持事务。
例子如下面的文章:[url=https://mp.weixin.qq.com/s?__biz=MjM5NzMyMjAwMA==&mid=2651479156&idx=1&sn=a82439f13524427e2b63255f56d9317d&chksm=bd25300b8a52b91db99a5f07d602a2af9e3f46b25931a09625382c583df00ebfd2622dea91e6&mpshare=1&scene=1&srcid=0930XON5dbk36wXcZMdqVTfg&pass_ticket=XOfZzmMpH2pI8gMrC2g78HGg437DR6AbOImhzfbHJFOeuQ1IhmTQskqufRxhhgbG#rd]微信文章[/url]
上面的文章中,介绍了一个事务的业务中,突然要加一个非事务的方法。非事务的方法不能是本对象里的方法,不能是private方法,只能是public的,而且只有另写对象,以注入的方式,才能实现事务之间的关系处理。因为AOP代理了这些方法所在的业务对象。

9.PROPAGATION_NEVER:如果没有,就以非事务方式执行;如果有,就抛出异常。

10.PROPAGATION_MANDATORY:如果没有,就抛出异常;如果有,就使用当前事务。

11.引伸的配置@Transactional(readOnly = true)
从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!(查询中不会出现别人在时间点a之后提交的数据)。
应用场合:
如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。
【注意是一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用只读事务】
在将事务设置成只读后,相当于将数据库设置成只读数据库,此时若要进行写的操作,会出现错误。

[b]项目中的使用情况:目前的项目几乎没有实时的,多条相关的只读查询,不需要这个设置。[/b]

[b]注:以上的总结都没有时间亲测,只是看一些文章的总结。如有错误,欢迎指正。[/b] :oops:

-------------------------------------------------------------------------
另记录一个问题解决:之前部署的一个应用,平时正常,但经常早上不能访问,很是着急,上班前要花时间远程处理。发现是应用服务器与数据库服务器的网线直连不通了,时间紧就重启下。后来发现只要网络禁用再启动就行了。
后来几次后没找到原因,没条件换硬件或者不断修改配置来测试。突然想到定时任务,于是查到可以自动禁用启用网络的命令。至此,这个麻烦的问题再也没出现过了。