事務的傳播無效,required_new無效,動態代理給spring事務傳播留下的坑
事務的傳播級別
七種傳播級別
propagation_requierd:如果當前沒有事務,就新建一個事務,如果已存在一個事務中,加入到這個事務中,這是最常見的選擇。(預設)
propagation_supports:支援當前事務,如果沒有當前事務,就以非事務方法執行。
propagation_mandatory:使用當前事務,如果沒有當前事務,就丟擲異常。
propagation_required_new:新建事務,如果當前存在事務,把當前事務掛起。
propagation_not_supported:以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
propagation_never:以非事務方式執行操作,如果當前事務存在則丟擲異常。
propagation_nested:如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行與propagation_required類似的操作
情況A
有事務的A方法呼叫沒事務的B方法,都能成功插入,
B有異常都回滾。
A使用REQUIRED
,B會加入到A的事務當中。
@Transactional(propagation=Propagation.REQUIRED)
public void A() {
User user=new User();
user.setUserName("張三");
mapper.save(user);
B();
}
public void B() {
User user=new User();
user.setUserName("李四" );
mapper.save(user);
}
情況B
B單獨加上事務REQUIRES_NEW
:建立一個新的事務,掛起原來的事務。
無異常時都能新增。
B有異常,都不能新增。
問題就來了,為什麼都不能新增呢?按道理應該B有異常B回滾,A正常新增才對。這裡就涉及了JDK動態代理和spring事務的一個坑點。在Spring事務中巢狀呼叫其他的事務方法,事務都不能生效。
@Transactional(propagation=Propagation.REQUIRED)
public void A() {
User user=new User();
user.setUserName("張三" );
mapper.save(user);
B();
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void B() {
User user=new User();
user.setUserName("李四");
mapper.save(user);
int i=1/0;
}
動態代理的特性1
首先我們看一下一個簡單的動態代理實現方式:
介面
public interface OrderService {
void test1();
void test2();
}
實現類
public class OrderServiceImpl implements OrderService {
@Override
public void test1() {
System.out.println("--執行test1--");
}
@Override
public void test2() {
System.out.println("--執行test2--");
}
}
代理類
public class ObjectProxy implements InvocationHandler {
private static final String METHOD_PREFIX = "test";
private Object target;
public ObjectProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 我們使用這個標誌來識別是否使用代理還是使用方法本體
if (method.getName().startsWith(METHOD_PREFIX)) {
System.out.println("========分隔符========");
}
return method.invoke(target, args);
}
public Object getProxy() {
return Proxy.newProxyInstance(Thread.currentThread()
.getContextClassLoader(), target.getClass().getInterfaces(),
this);
}
}
測試類
public class Test {
public static void main(String[] args) {
OrderService orderService = new OrderServiceImpl();
ObjectProxy proxy = new ObjectProxy(orderService);
orderService = (OrderService) proxy.getProxy();
orderService.test1();
orderService.test2();
}
}
獲取的代理分別代理執行test1方法和test2方法。
========分隔符========
–執行test1–
========分隔符========
–執行test2–
代理執行了兩次
修改一下實現類巢狀執行test2
public class OrderServiceImpl implements OrderService {
@Override
public void test1() {
System.out.println("--執行test1--");
test2();
}
@Override
public void test2() {
System.out.println("--執行test2--");
}
}
測試類
public class Test {
public static void main(String[] args) {
OrderService orderService = new OrderServiceImpl();
ObjectProxy proxy = new ObjectProxy(orderService);
orderService = (OrderService) proxy.getProxy();
orderService.test1();
// orderService.test2();
}
}
結果:
========分隔符========
–執行test1–
–執行test2–
代理只了一次
總結:
只有代理物件proxy直接呼叫的那個方法才是真正的走代理的,巢狀的方法實際上就是 直接把巢狀的程式碼移動到代理的方法裡面。 所以,巢狀的事務都不能生效。
解決方案:
- 傳統mvc專案spring配置裡面增加:
<!-- 開啟暴露Aop代理到ThreadLocal支援,解決事務巢狀不生效問題。 -->
<aop:aspectj-autoproxy expose-proxy="true"/><!—註解風格支援-->
<aop:config expose-proxy="true"><!—xml風格支援-->
配置約束要加上以下配置, 同時加入aop類的jar。
使用代理物件呼叫B方法。
@Transactional(propagation=Propagation.REQUIRED)
public void A() {
User user=new User();
user.setUserName("張三");
mapper.save(user);
try {
((UserService) AopContext.currentProxy()).B();
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void B() {
User user=new User();
user.setUserName("李四");
mapper.save(user);
throw new RuntimeException("child Exception....................");
}
注意
:呼叫巢狀的方法需要用try-catch處理,不然異常繼續向上拋,導致A也異常回滾了。
- SpringBoot配置方式
新增依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
添加註解:
修改原有程式碼的執行方式為:
- 通過ApplicationContext上下文進行解決
獲取到當前類的例項物件直接呼叫。
/**
* Spring應用上下文
*/
@Autowired
private ApplicationContext context;
private UserService proxy;
@PostConstruct
public void init() {
//從Spring上下文中獲取AOP代理物件
proxy = context.getBean(UserService.class);
}
@Transactional(propagation=Propagation.REQUIRED)
public void A() {
User user=new User();
user.setUserName("張三");
mapper.save(user);
try {
proxy.B();
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void B() {
User user=new User();
user.setUserName("李四");
mapper.save(user);
throw new RuntimeException("child Exception....................");
}
無論是 獲取代理物件呼叫內嵌方法,還是從上下文物件獲取物件呼叫。本質就是重新的呼叫一次內嵌的方法,避免了動態代理 只代理外面方法的坑。重新呼叫就重新產生事務了。