最近在開發過程當中遇到了一個問題,當在Controller中調用Service中A()方法,A方法內部又調用Service中B()方法,因爲A方法中只有查詢操做因此沒有加事務控制,B方法中含有屢次修改操做因此增長了@Transactional註解,結果在A方法調用完B方法後,程序報錯了,可是B方法中修改操做的數據居然成功了,我擦~什麼鬼,因而開啓了探索Spring事務之路,直接上示例。java
示例1:A方法無事務,B方法加事務spring
@RestController public class Controller{ @Autowired private StudentcardService studentcardService; @RequestMapping(value = "/test/{id}}", method = RequestMethod.GET) public Response queryStudentCard(@PathVariable("id") String id) { studentcardService.updateA(id); } }
@Service public class StudentcardServiceImpl implements StudentcardService { @Resource private StudentCardMapper studentCardMapper; @Override public void updateA(String id) { //先去調用內部方法B this.updateB(id); StudentCard sc =new StudentCard(); sc.setScId(id); //修改問題字段 sc.setQuestion("AAAAA"); studentCardMapper.update(sc); } @Override @Transactional public void updateB(String id) { StudentCard sc =new StudentCard(); sc.setScId(id); //修改答案字段 sc.setAnswer("BBBBB"); studentCardMapper.update(sc); //修改完數據後報錯 double i=1/0; } }
訪問後執行結果以下:app
然而事務並無起做用~接着進行測試ide
示例2:將A方法加事務,B方法不加事務測試
@Service public class StudentcardServiceImpl implements StudentcardService { @Resource private StudentCardMapper studentCardMapper; @Override @Transactional public void updateA(String id) { //先去調用內部方法B this.updateB(id); StudentCard sc =new StudentCard(); sc.setScId(id); //修改問題字段 sc.setQuestion("AAAAA"); studentCardMapper.update(sc); } @Override public void updateB(String id) { StudentCard sc =new StudentCard(); sc.setScId(id); //修改答案字段 sc.setAnswer("BBBBB"); studentCardMapper.update(sc); //修改完數據後報錯 double i=1/0; } }
訪問後執行結果以下:this
事務起做用了,都沒有修改爲功!接下來咱們來個加強版,加上try後看下會有怎樣的效果spa
示例3:A方法加事務,B方法沒有事務,可是在A調用B方法時用try進行包裹,B方法中有錯誤.net
@Service public class StudentcardServiceImpl implements StudentcardService { @Resource private StudentCardMapper studentCardMapper; @Override @Transactional public void updateA(String id) { //先去調用內部方法B try { this.updateB(id); }catch (Exception e){} StudentCard sc =new StudentCard(); sc.setScId(id); //修改問題字段 sc.setQuestion("AAAAA"); studentCardMapper.update(sc); } @Override public void updateB(String id) { StudentCard sc =new StudentCard(); sc.setScId(id); //修改答案字段 sc.setAnswer("BBBBB"); studentCardMapper.update(sc); //修改完數據後報錯 double i=1/0; } }
訪問後執行結果以下:線程
因爲報錯被try包起來了,因此數據都插入了!那若是將報錯信息放到執行完方法B後呢,會怎樣呢?3d
示例4:A方法加事務,B方法沒有事務,可是在A調用B方法時用try進行包裹,A方法中有錯誤
@Service public class StudentcardServiceImpl implements StudentcardService { @Resource private StudentCardMapper studentCardMapper; @Override @Transactional public void updateA(String id) { //先去調用內部方法B try { this.updateB(id); }catch (Exception e){} StudentCard sc =new StudentCard(); sc.setScId(id); //修改問題字段 sc.setQuestion("AAAAA"); studentCardMapper.update(sc); //修改完數據後報錯 double i=1/0; } @Override public void updateB(String id) { StudentCard sc =new StudentCard(); sc.setScId(id); //修改答案字段 sc.setAnswer("BBBBB"); studentCardMapper.update(sc); } }
訪問後執行結果以下:
哇塞,數據都沒有插入呢!這是由於在事務提交前報錯了,事務所有rollback了,下面言歸正傳,示例1爲什麼不能成功呢?因而查詢各類資料終於找到了原因,並對示例1進行改造
示例5:A方法無事務,B方法加事務
@Service @EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) public class StudentcardServiceImpl implements StudentcardService { @Resource private StudentCardMapper studentCardMapper; @Override public void updateA(String id) { //先去調用內部方法B StudentcardService studentcardService = (StudentcardService) AopContext.currentProxy(); studentcardService.updateB(id); //this.updateB(id); StudentCard sc =new StudentCard(); sc.setScId(id); //修改問題字段 sc.setQuestion("AAAAA"); studentCardMapper.update(sc); } @Override @Transactional public void updateB(String id) { StudentCard sc =new StudentCard(); sc.setScId(id); //修改答案字段 sc.setAnswer("BBBBB"); studentCardMapper.update(sc); //修改完數據後報錯 double i=1/0; } }
訪問後執行結果以下:
哈哈,數據沒有進行修改,事務起做用了!
下面說下具體對緣由:
示例1 事務沒有起做用,是因爲Spring事務本質是基於AOP代理來實現的,當Controller調用Service的方法A是基於proxy的,因此會切入,可是方法A在調用方法B時,屬於類內部調用,即便方法B上加上了@Transactional註解,但沒有Spring代理了,因此不受事務控制,天然事務不會生效。
示例2 事務能夠起做用是因爲事務的傳播行爲致使的,默認事務的傳播行爲爲:PROPAGATION_REQUIRED 。方法A標註了註解@Transactional ,執行的時候傳播給方法B,由於方法A開啓了事務,線程內的connection的屬性autoCommit=false,而且執行到方法B時,事務傳播依然是生效的,獲得的仍是方法A的connection,autoCommit仍是爲false,因此事務生效;反之,若是方法A沒有註解@Transactional 時,是不受事務管理的,autoCommit=true,那麼傳播給方法B的也爲true,執行完自動提交,即便B標註了@Transactional 事務也是不起做用的。
示例5 事務又能夠起做用的,是因爲咱們在方法A調用方法B時,先獲取到了Service的當前代理,而後用當前代理去調用方法B,因此事務固然會生效了~
順便補充下事務的傳播行爲,事務的傳播行爲是爲了解決業務層方法之間相互調用,產生的事務應該如何進行傳遞的問題。spring有以下7種傳播行爲:
一、PROPAGATION_REQUIRED:支持當前事務,若是當前不存在事務則新建一個。
二、PROPAGATION_SUPPORTS:支持當前事務,若是不存在,就不使用事務。
三、PROPAGATION_MANDATORY:支持當前事務,若是不存在,則拋出異常。
四、PROPAGATION_REQUIRES_NEW:若是當前有事務存在,掛起當前事務,建立一個新的事務。
五、PROPAGATION_NOT_SUPPORTED:以非事務方式運行,若是當前有事務存在,掛起當前事務。
六、PROPAGATION_NEVER:以非事務方式運行,若是當前有事務存在,拋出異常。
七、PROPAGATION_NESTED:若是當前存在一個事務,則該方法運行在一個嵌套的事務中。被嵌套的事務能夠從當前事務中單獨的提交和回滾。若是當前不存在事務,則開始一個新的事務。各廠商對這種傳播行爲的支持良莠不齊,使用時需注意。