前言
最近在开发业务代码的时候,犯了一个事务注解的错误:在同一个类的非事务方法中调用了另一个事务方法,导致事务没有生效,如下所示:
public ConfirmOrderResultVO batchConfirmPurchaseOrders(Long taobaoUserId, List<String> bizOrderIds) throws TCException {
………………………………………………………………
for (String bizOrderId : bizOrderIds) {
// 推单成功进入successList,否则进入failedList
if (confirmPurchaseOrder(taobaoUserId, bizOrderId)){
successList.add(Long.valueOf(bizOrderId));
}else {
failedList.add(Long.valueOf(bizOrderId));
}
}
………………………………………………………………
}
其中confirmPurchaseOrder()是一个事务方法:
@Transactional
public Boolean confirmPurchaseOrder(Long taobaoUserId, String bizOrderId) throws TCException {
logger.warn("|ConfirmPurchaseOrder|UserId:"+taobaoUserId+",orderId:"+bizOrderId);
…………………………
return ture;
}
这样在直接调用batchConfirmPurchaseOrders()方法时,如果confirmPurchaseOrder()方法发生了异常,是不会回滚的。原理在于Spring的事务管理依靠的动态代理模式,当在同一个类中调用一个非事务方法,是不会生成代理对象的,自然也不会触发事务。借此机会回顾一下代理模式和Spring事务管理的原理。
代理模式
网上讲解代理模式的文章千奇百怪,很多时候看完了也不明白讲的重点是什么。
事实上在生活中我们常常会遇到各种各样的代理模式,例如火车票代售点代理出售火车票,他在“帮忙”出售火车票的同时,收取了额外的手续费,记录了自己店里的流水等。
又比如班长代理老师来上交班费,他在“上交班费”的动作之前,还进行了检查班级同学是否到齐、向每一位同学收班费、核对班费金额,最后再上交班费。
总而言之,代理模式就是代理其他对象,在完成原动作的基础上(前后)做一些额外的自定义的工作。
聪明的朋友看到这里一定想到了:那我在一个方法中调用另一个方法不就是代理模式了吗?啊对对对,从功能上来讲是一样的,但是“模式”之所以为“模式”,以我拙见,其本质目的在于形成规范,以减少重复代码的编写。因此如果不能达到减少代码重复这一本质目的,便不能称之为“模式”。
按照代理模式的实现方式,分为两种:静态代理和动态代理。那我们就拿刚刚说过的“写作业”这件事来做例子,讲解一下两种实现方式的区别。
静态代理
首先定义一个“作业”接口,里面有一个方法“做作业”
public interface Homework {
void doHomework();
}
小红实现了这个接口。但是小红是一个不爱学习的小学生,又因为师从马掌门成为了学校的扛把子,所以为了不让老师发现,他决定自己随便做做,把剩下的部分交给小弟们来做。
public class XiaoHong implements Homework{
@Override
public void doHomework() {
System.out.println("XiaoHong did homework casually");
}
}
其中小王、小张两个小弟成绩优异,一个负责数学作业,一个负责英语作业,于是他们两个自告奋勇实现了Homework接口,在代理完成作业的基础上,还不断学习提高自己的能力,并且对作业答案进行了校验。
小王:
public class XiaoWang implements Homework{
// 持有Homework属性
private Homework homework;
// 通过构造函数初始化Homework
public XiaoWang(Homework homework){
this.homework=homework;
}
//代理实现
@Override
public void doHomework() {
doStudy();
homework.doHomework();
System.out.println("XiaoWang helps with MathHomework");
doCheck();
}
// 额外方法
private void doCheck() {
System.out.println("XiaoWang is checking-----------------");
}
// 额外方法
private void doStudy() {
System.out.println("XiaoWang is studying---------------");
}
}
小张:
public class XiaoZhang implements Homework{
// 持有Homework属性
private Homework homework;
// 通过构造函数初始化Homework
public XiaoZhang(Homework homework){
this.homework=homework;
}
//代理实现
@Override
public void doHomework() {
doStudy();
homework.doHomework();
System.out.println("XiaoZhang helps with EngHomework");
doCheck();
}
// 额外方法
private void doCheck() {
System.out.println("XiaoZhang is checking-----------------");
}
// 额外方法
private void doStudy() {
System.out.println("XiaoZhang is studying---------------");
}
}
于是,小红可以放心的把作业交给小王和小张了:
public static void main(String[] args) {
// 实例化一个目标对象
XiaoHong xiaoHong = new XiaoHong();
// 把目标对象通过构造函数传递给代理对象
XiaoZhang xiaoZhang =new XiaoZhang(xiaoHong);
XiaoWang xiaoWang =new XiaoWang(xiaoHong);
// 调用代理对象的方法
xiaoZhang.doHomework();
xiaoWang.doHomework();
}
输出:
XiaoZhang is studying---------------
XiaoHong did homework casually
XiaoZhang helps with EngHomework
XiaoZhang is checking-----------------
XiaoWang is studying---------------
XiaoHong did homework casually
XiaoWang helps with MathHomework
XiaoWang is checking-----------------
问题来了,如果老师又布置了一个Book接口和readBook方法,但是readBook前后的动作是一样的(都需要study和check),如何通过代理来实现呢?
一个方案是让小王和小张都实现Book接口和readBook方法,并持有新的Book对象;另一个方案是再找一个小赵实现Book接口和readBook方法。
这样两种方案实际上都能达到效果,但是如果接口和方法增多起来呢?如果让一个代理类实现一堆方法,并持有一堆不同的对象,这个类势必会变得臃肿不堪;如果每一个新的方法都创建一个新的代理类,引用不同的对象,又会造成代码的大量重复。因此,动态代理就出现了。
动态代理
前文所讲的静态代理之所以为“静态”,是因为每个接口的每个方法,我们都要显式的去实现、创建、调用,所有的操作都是写死的、是静