先來說下該博文的創作背景,要從一道經典的面試(網傳是)說起下面貼上源碼
@Autowired
ITestMapper testMapper;
public void parent(){
child();
User user = new User();
user.setName("lgh").setMobile("18813140000");
testMapper.insert(user);
}
public void child(){
User user = new User();
user.setName("lgh-1").setMobile("18813140001");
testMapper.insert(user);
throw new ServiceException("主動拋出異常");
}
示例中兩個方法要求如何改造使得child() 方法不能影響parent() 方法的執行,換句話說就是正常情況下保證parent() 方法的正常執行,而child() 無關緊要。
然後就各顯神通廢話不多說,出現啦下面這個看似正確完美的版本,直接貼源碼:
@Transactional
public void parent(){
try {
child();
} catch (Exception e) {
log.error("child插入異常-{}"+e);
}
User user = new User();
user.setName("lgh").setMobile("18813140000");
testMapper.insert(user);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void child(){
User user = new User();
user.setName("lgh-1").setMobile("18813140001");
testMapper.insert(user);
throw new ServiceException("主動拋出異常");
}
事實證明執行完畢之後,parent 和 child 方法都成功啦,通過日誌發現child 的事務並沒有創建執行,也就是說 child 的事務沒有起作用,因爲spring的事務是通過AOP實現的,說到底也就是jdk 的動態代理來實現的。我們就模擬了一下上面的場景通過動態代理來說明這個問題所在,jdk 的動態代理是通過接口實現的源碼如下:
public class TestServiceImpl implements TestService {
@Override
public void parent() {
child();
System.out.println("parent method invoke start");
}
@Override
public void child() {
System.out.println("child method invoke start");
}
@Override
public void test() {
System.out.println("test method invoke start");
}
}
代理調用實現:
public class TestHandler implements InvocationHandler {
private Object target; // 目標對象 真實對象
public TestHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().startsWith("parent") || method.getName().startsWith("child")){
System.out.println("事務處理中*******************");
}
return method.invoke(target, args);
}
public static void main(String[] args) {
TestHandler handler = new TestHandler(new TestServiceImpl());
TestService service = (TestService) Proxy.newProxyInstance(TestHandler.class.getClassLoader(), new Class[]{TestService.class}, handler);
service.parent();
}
}
通過代理調用parent 方法,內部調用 child 方法,日誌打印如下:
事務處理中*******************
child method invoke start
parent method invoke start
也就是說調用parent 方法的時候是代理類去調用的進而打印啦事務處理的日誌,但是child 方法的調用並沒有走代理類,通過打印該對象 this 得出結論。再parent 方法被代理類調用的時候,內部調用child 方法的對象是目標源對象,而不是代理對象。這樣就明朗很多,之所以child 的事務註解失效是因爲 child 的調用不是 代理的調用而是源目標對象本身的調用。
如果想實現child 的事務註解生效,顯而易見需要使用代理類去調用child 方法,實現如下:
@Transactional
public void parent(){
try {
((TestService)AopContext.currentProxy()).child();
} catch (Exception e) {
log.error("child插入異常-{}"+e);
}
User user = new User();
user.setName("lgh").setMobile("18813140000");
testMapper.insert(user);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void child(){
User user = new User();
user.setName("lgh-1").setMobile("18813140001");
testMapper.insert(user);
throw new ServiceException("主動拋出異常");
}
說到這呢幫大家再回顧下事務的傳播屬性及隔離級別:請參考https://blog.csdn.net/qq_39470733/article/details/105217133
如有披露或問題歡迎留言或者入羣探討