在上一篇中,咱們基本已經將SpringBoot對數據庫的操做,都介紹完了。在這一篇中,咱們將介紹一下SpringBoot對事物的管理。咱們知道在實際的開發中,保證數據的安全性是很是重要的,不能由於異常,或者服務中斷等緣由,致使髒數據的產生。因此掌握SpringBoot項目的事物管理,尤其的重要。在SpringBoot中對事物的管理很是的方便。咱們只須要添加一個註解就能夠了,下面咱們來詳細介紹一下有關SpringBoot事物的功能。java
由於在上一篇中咱們已經用測試用例的方式介紹了SpringBoot中的增刪改查功能。因此在這一篇的事物管理,咱們還將已測試用例爲主。惟一不一樣之處,就是咱們須要建立一個Service服務,而後將相關的業務邏輯封裝到Service中,來表示該操做是同一個操做。下面咱們簡單的在Service中只添加一個方法,而且在方法中新增兩條數據,並驗證該Service是否成功將數據添加到數據庫中。下面爲Service源碼:mysql
package com.jilinwula.springboot.helloworld.service; import com.jilinwula.springboot.helloworld.Repository.UserInfoRepository; import com.jilinwula.springboot.helloworld.entity.UserInfoEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserInfoService { @Autowired private UserInfoRepository userInfoRepository; /** * 保存用戶信息 */ public void save() { UserInfoEntity userInfoEntity = new UserInfoEntity(); userInfoEntity.setUsername("小米"); userInfoEntity.setPassword("xiaomi"); userInfoEntity.setNickname("小米"); userInfoEntity.setRoleId(0L); userInfoRepository.save(userInfoEntity); UserInfoEntity userInfoEntity2 = new UserInfoEntity(); userInfoEntity2.setUsername("京東"); userInfoEntity2.setPassword("jingdong"); userInfoEntity2.setNickname("京東"); userInfoEntity2.setRoleId(0L); userInfoRepository.save(userInfoEntity2); } }
測試用例:git
package com.jilinwula.springboot.helloworld; import com.jilinwula.springboot.helloworld.service.UserInfoService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class JilinwulaSpringbootHelloworldApplicationTests { @Autowired private UserInfoService userInfoService; @Test public void save() { userInfoService.save(); } @Test public void contextLoads() { } }
下面咱們看一下數據庫中的數據是否插入成功。github
咱們看數據成功插入了。如今咱們修改一下代碼,讓插入數據時,第二條數據的數據類型超出範圍來模擬程序運行時發生的異常。而後咱們看,這樣是否影響第一條數據是否能正確的插入數據庫。下面爲Service源碼:spring
package com.jilinwula.springboot.helloworld.service; import com.jilinwula.springboot.helloworld.Repository.UserInfoRepository; import com.jilinwula.springboot.helloworld.entity.UserInfoEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserInfoService { @Autowired private UserInfoRepository userInfoRepository; /** * 保存用戶信息 */ public void save() { UserInfoEntity userInfoEntity = new UserInfoEntity(); userInfoEntity.setUsername("小米"); userInfoEntity.setPassword("xiaomi"); userInfoEntity.setNickname("小米"); userInfoEntity.setRoleId(0L); userInfoRepository.save(userInfoEntity); UserInfoEntity userInfoEntity2 = new UserInfoEntity(); userInfoEntity2.setUsername("京東京東京東京東京東京東京東京東京東"); userInfoEntity2.setPassword("jingdong"); userInfoEntity2.setNickname("京東"); userInfoEntity2.setRoleId(0L); userInfoRepository.save(userInfoEntity2); } }
爲了方便咱們測試,咱們已經將數據庫中的username字段的長度設置爲了10。這樣當username內容超過10時,第二條就會拋出異常。下面爲執行日誌:sql
aused by: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'username' at row 1 at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3976) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3914) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2530) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2683) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2495) at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1903) at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2124) at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2058) at com.mysql.jdbc.PreparedStatement.executeLargeUpdate(PreparedStatement.java:5158) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2043) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.apache.tomcat.jdbc.pool.StatementFacade$StatementProxy.invoke(StatementFacade.java:114) at com.sun.proxy.$Proxy90.executeUpdate(Unknown Source) at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204) ... 82 more
而後咱們如今查一下數據庫中的數據,看看第二條數據的異常是否會影響第一條數據的插入。數據庫
咱們看第一條數據成功的插入,但這明顯是錯誤的,由於正常邏輯是不該該插入成功的。這樣會致使髒數據產生,也沒辦法保證數據的一致性。下面咱們看一下在SpringBoot中怎麼經過添加事務的方式,解決上面的問題。上面提到過在SpringBoot中使Service支持事物很簡單,只要添加一個註解便可,下面咱們添加完註解,而後在嘗試上面的方式,看看第一條數據還可否添加成功。而後咱們在詳細介紹該註解的使用。下面爲Service源碼:apache
package com.jilinwula.springboot.helloworld.service; import com.jilinwula.springboot.helloworld.Repository.UserInfoRepository; import com.jilinwula.springboot.helloworld.entity.UserInfoEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class UserInfoService { @Autowired private UserInfoRepository userInfoRepository; /** * 保存用戶信息 */ @Transactional public void save() { UserInfoEntity userInfoEntity = new UserInfoEntity(); userInfoEntity.setUsername("小米"); userInfoEntity.setPassword("xiaomi"); userInfoEntity.setNickname("小米"); userInfoEntity.setRoleId(0L); userInfoRepository.save(userInfoEntity); UserInfoEntity userInfoEntity2 = new UserInfoEntity(); userInfoEntity2.setUsername("京東京東京東京東京東京東京東京東京東"); userInfoEntity2.setPassword("jingdong"); userInfoEntity2.setNickname("京東"); userInfoEntity2.setRoleId(0L); userInfoRepository.save(userInfoEntity2); } }
代碼和以前基本同樣,只是在方法上新增了一個@Transactional註解,下面咱們繼續執行測試用例。執行日誌:數組
Caused by: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'username' at row 1 at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3976) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3914) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2530) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2683) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2495) at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1903) at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2124) at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2058) at com.mysql.jdbc.PreparedStatement.executeLargeUpdate(PreparedStatement.java:5158) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2043) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.apache.tomcat.jdbc.pool.StatementFacade$StatementProxy.invoke(StatementFacade.java:114) at com.sun.proxy.$Proxy90.executeUpdate(Unknown Source) at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204) ... 92 more
日誌仍是和以前同樣拋出異常,如今咱們在查一下數據庫中的數據。tomcat
發現數據庫中已經沒有第一條數據的內容了,這就說明了咱們的事物添加成功了,在SpringBoot項目中添加事物就是這麼簡單。
下面咱們來測試一下,手動拋出異常,看看若是不添加@Transactional註解,數據是否能成功插入到數據庫中。Service源碼:
package com.jilinwula.springboot.helloworld.service; import com.jilinwula.springboot.helloworld.Repository.UserInfoRepository; import com.jilinwula.springboot.helloworld.entity.UserInfoEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class UserInfoService { @Autowired private UserInfoRepository userInfoRepository; /** * 保存用戶信息 */ public void save() { UserInfoEntity userInfoEntity = new UserInfoEntity(); userInfoEntity.setUsername("小米"); userInfoEntity.setPassword("xiaomi"); userInfoEntity.setNickname("小米"); userInfoEntity.setRoleId(0L); userInfoRepository.save(userInfoEntity); UserInfoEntity userInfoEntity2 = new UserInfoEntity(); userInfoEntity2.setUsername("京東"); userInfoEntity2.setPassword("jingdong"); userInfoEntity2.setNickname("京東"); userInfoEntity2.setRoleId(0L); userInfoRepository.save(userInfoEntity2); System.out.println(1 / 0); } }
咱們在代碼最後寫了一個除以0操做,因此執行時必定會發生異常,而後咱們看數據可否添加成功。執行日誌:
java.lang.ArithmeticException: / by zero at com.jilinwula.springboot.helloworld.service.UserInfoService.save(UserInfoService.java:32) at com.jilinwula.springboot.helloworld.JilinwulaSpringbootHelloworldApplicationTests.save(JilinwulaSpringbootHelloworldApplicationTests.java:19) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
繼續查看數據庫中的數據。
咱們發現這兩條數據都插入成功了。咱們一樣,在方法中添加@Transactional註解,而後繼續執行上面的代碼在執行一下。Service源碼:
package com.jilinwula.springboot.helloworld.service; import com.jilinwula.springboot.helloworld.Repository.UserInfoRepository; import com.jilinwula.springboot.helloworld.entity.UserInfoEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class UserInfoService { @Autowired private UserInfoRepository userInfoRepository; /** * 保存用戶信息 */ @Transactional public void save() { UserInfoEntity userInfoEntity = new UserInfoEntity(); userInfoEntity.setUsername("小米"); userInfoEntity.setPassword("xiaomi"); userInfoEntity.setNickname("小米"); userInfoEntity.setRoleId(0L); userInfoRepository.save(userInfoEntity); UserInfoEntity userInfoEntity2 = new UserInfoEntity(); userInfoEntity2.setUsername("京東"); userInfoEntity2.setPassword("jingdong"); userInfoEntity2.setNickname("京東"); userInfoEntity2.setRoleId(0L); userInfoRepository.save(userInfoEntity2); System.out.println(1 / 0); } }
執行日誌:
java.lang.ArithmeticException: / by zero at com.jilinwula.springboot.helloworld.service.UserInfoService.save(UserInfoService.java:33) at com.jilinwula.springboot.helloworld.service.UserInfoService$$FastClassBySpringCGLIB$$230fe90e.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:736) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:671) at com.jilinwula.springboot.helloworld.service.UserInfoService$$EnhancerBySpringCGLIB$$33f70012.save(<generated>) at com.jilinwula.springboot.helloworld.JilinwulaSpringbootHelloworldApplicationTests.save(JilinwulaSpringbootHelloworldApplicationTests.java:19) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
數據庫中數據:
咱們看數據又沒有插入成功,這樣就保證了咱們事物的一致性。
下面咱們將上述的代碼添加try catch,而後在執行上面的測試用例,查一下結果。Service源碼:
package com.jilinwula.springboot.helloworld.service; import com.jilinwula.springboot.helloworld.Repository.UserInfoRepository; import com.jilinwula.springboot.helloworld.entity.UserInfoEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Slf4j @Service public class UserInfoService { @Autowired private UserInfoRepository userInfoRepository; /** * 保存用戶信息 */ @Transactional public void save() { try { UserInfoEntity userInfoEntity = new UserInfoEntity(); userInfoEntity.setUsername("小米"); userInfoEntity.setPassword("xiaomi"); userInfoEntity.setNickname("小米"); userInfoEntity.setRoleId(0L); userInfoRepository.save(userInfoEntity); UserInfoEntity userInfoEntity2 = new UserInfoEntity(); userInfoEntity2.setUsername("京東"); userInfoEntity2.setPassword("jingdong"); userInfoEntity2.setNickname("京東"); userInfoEntity2.setRoleId(0L); userInfoRepository.save(userInfoEntity2); System.out.println(1 / 0); } catch (Exception e) { log.info("保存用戶信息異常", e); } } }
執行日誌:
2019-01-25 11:21:45.421 INFO 8654 --- [ main] c.j.s.h.service.UserInfoService : 保存用戶信息異常 java.lang.ArithmeticException: / by zero at com.jilinwula.springboot.helloworld.service.UserInfoService.save(UserInfoService.java:36) ~[classes/:na] at com.jilinwula.springboot.helloworld.service.UserInfoService$$FastClassBySpringCGLIB$$230fe90e.invoke(<generated>) [classes/:na] at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) [spring-core-4.3.21.RELEASE.jar:4.3.21.RELEASE] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:736) [spring-aop-4.3.21.RELEASE.jar:4.3.21.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) [spring-aop-4.3.21.RELEASE.jar:4.3.21.RELEASE] at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) [spring-tx-4.3.21.RELEASE.jar:4.3.21.RELEASE] at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282) [spring-tx-4.3.21.RELEASE.jar:4.3.21.RELEASE] at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) [spring-tx-4.3.21.RELEASE.jar:4.3.21.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) [spring-aop-4.3.21.RELEASE.jar:4.3.21.RELEASE] at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:671) [spring-aop-4.3.21.RELEASE.jar:4.3.21.RELEASE] at com.jilinwula.springboot.helloworld.service.UserInfoService$$EnhancerBySpringCGLIB$$5284ede6.save(<generated>) [classes/:na] at com.jilinwula.springboot.helloworld.JilinwulaSpringbootHelloworldApplicationTests.save(JilinwulaSpringbootHelloworldApplicationTests.java:19) [test-classes/:na] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_191] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_191] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_191] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_191] at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) [junit-4.12.jar:4.12] at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.12.jar:4.12] at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) [junit-4.12.jar:4.12] at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) [junit-4.12.jar:4.12] at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) [spring-test-4.3.21.RELEASE.jar:4.3.21.RELEASE] at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) [spring-test-4.3.21.RELEASE.jar:4.3.21.RELEASE] at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) [spring-test-4.3.21.RELEASE.jar:4.3.21.RELEASE] at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) [junit-4.12.jar:4.12] at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) [spring-test-4.3.21.RELEASE.jar:4.3.21.RELEASE] at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) [spring-test-4.3.21.RELEASE.jar:4.3.21.RELEASE] at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12] at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12] at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12] at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12] at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12] at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-4.3.21.RELEASE.jar:4.3.21.RELEASE] at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) [spring-test-4.3.21.RELEASE.jar:4.3.21.RELEASE] at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12] at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) [spring-test-4.3.21.RELEASE.jar:4.3.21.RELEASE] at org.junit.runner.JUnitCore.run(JUnitCore.java:137) [junit-4.12.jar:4.12] at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) [junit-rt.jar:na] at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) [junit-rt.jar:na] at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) [junit-rt.jar:na] at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) [junit-rt.jar:na]
查看數據庫中的數據:
咱們發現數據成功的插入了,雖然咱們添加了@Transactional事物註解,但數據仍是添加成功了。這是由於@Transactional註解的處理方式是,檢測Service是否發生異常,若是發生異常,則將以前對數據庫的操做回滾。上述代碼中,咱們對異常try catch了,也就是@Transactional註解檢測不到異常了,因此該事物也就不會回滾了,因此在Service中添加try catch時要注意,以避免事物失效。下面咱們手動拋出異常,來驗證上面的說法是否正確,也就是看看數據還可否回滾。Service源碼:
package com.jilinwula.springboot.helloworld.service; import com.jilinwula.springboot.helloworld.Repository.UserInfoRepository; import com.jilinwula.springboot.helloworld.entity.UserInfoEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Slf4j @Service public class UserInfoService { @Autowired private UserInfoRepository userInfoRepository; /** * 保存用戶信息 */ @Transactional public void save() throws Exception { try { UserInfoEntity userInfoEntity = new UserInfoEntity(); userInfoEntity.setUsername("小米"); userInfoEntity.setPassword("xiaomi"); userInfoEntity.setNickname("小米"); userInfoEntity.setRoleId(0L); userInfoRepository.save(userInfoEntity); UserInfoEntity userInfoEntity2 = new UserInfoEntity(); userInfoEntity2.setUsername("京東"); userInfoEntity2.setPassword("jingdong"); userInfoEntity2.setNickname("京東"); userInfoEntity2.setRoleId(0L); userInfoRepository.save(userInfoEntity2); System.out.println(1 / 0); } catch (Exception e) { log.info("保存用戶信息異常", e); } throw new Exception(); } }
執行日誌:
java.lang.Exception at com.jilinwula.springboot.helloworld.service.UserInfoService.save(UserInfoService.java:40) at com.jilinwula.springboot.helloworld.service.UserInfoService$$FastClassBySpringCGLIB$$230fe90e.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:736) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:671) at com.jilinwula.springboot.helloworld.service.UserInfoService$$EnhancerBySpringCGLIB$$43d47421.save(<generated>) at com.jilinwula.springboot.helloworld.JilinwulaSpringbootHelloworldApplicationTests.save(JilinwulaSpringbootHelloworldApplicationTests.java:20) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
查看數據庫結果:
咱們發現,數據庫中竟然成功的插入值了,這是爲何呢?上面不是說,在拋出異常時,@Transactional註解是自動檢測,是否拋出異常嗎?若是拋出了異常就回滾以前對數據庫的操做,那爲何咱們拋出了異常,而數據沒有回滾呢?這是由於@Transactional註解的確會檢測是否拋出異常,但並非檢測全部的異常類型,而是指定的異常類型。這裏說的指定的異常類型是指RuntimeException類及其它的子類。由於RuntimeException類繼承了Exception類,致使Exception類成爲了RuntimeException類的父類,因此@Transactional註解並不會檢測拋出的異常,因此,上述代碼中雖然拋出了異常,可是數據並無回滾。下面咱們繼續修改一下Service中的代碼,將代碼中的異常類修改成RuntimeException,而後在看一下運行結果。下面爲Service源碼:
package com.jilinwula.springboot.helloworld.service; import com.jilinwula.springboot.helloworld.Repository.UserInfoRepository; import com.jilinwula.springboot.helloworld.entity.UserInfoEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Slf4j @Service public class UserInfoService { @Autowired private UserInfoRepository userInfoRepository; /** * 保存用戶信息 */ @Transactional public void save() throws RuntimeException { try { UserInfoEntity userInfoEntity = new UserInfoEntity(); userInfoEntity.setUsername("小米"); userInfoEntity.setPassword("xiaomi"); userInfoEntity.setNickname("小米"); userInfoEntity.setRoleId(0L); userInfoRepository.save(userInfoEntity); UserInfoEntity userInfoEntity2 = new UserInfoEntity(); userInfoEntity2.setUsername("京東"); userInfoEntity2.setPassword("jingdong"); userInfoEntity2.setNickname("京東"); userInfoEntity2.setRoleId(0L); userInfoRepository.save(userInfoEntity2); System.out.println(1 / 0); } catch (Exception e) { log.info("保存用戶信息異常", e); } throw new RuntimeException(); } }
咱們就不看執行的日誌了,而是直接查數據庫中的結果。
咱們看數據沒有插入到數據庫中,這就說明了,事物添加成功了,數據已經成功的回滾了。在實際的開發中,咱們經常須要自定義異常類,來知足咱們開發的需求。這時要特別注意,自定義的異常類,必定要繼承RuntimeException類,而不能繼承Exception類。由於剛剛咱們已經驗證了,只有繼承RuntimeException類,當發生異常時,事物纔會回滾。繼承Exception類,是不會回滾的。這一點要特別注意。
下面咱們介紹一下@Transactional註解的參數。由於剛剛咱們只是添加了一個@Transactional註解,實際上在@Transactional註解中還包括不少個參數,下面咱們詳細介紹一下這些參數的做用。
@Transactional註解參數說明:
參數 | 做用 |
---|---|
value | 指定使用的事務管理器 |
propagation | 可選的事務傳播行爲設置 |
isolation | 可選的事務隔離級別設置 |
readOnly | 讀寫或只讀事務,默認讀寫 |
timeout | 事務超時時間設置 |
rollbackFor | 致使事務回滾的異常類數組 |
rollbackForClassName | 致使事務回滾的異常類名字數組 |
noRollbackFor | 不會致使事務回滾的異常類數組 |
noRollbackForClassName | 不會致使事務回滾的異常類名字數組 |
下面咱們只介紹一下部分參數,由於大部分參數其實是和Spring中的註解同樣的,有關Spring事物相關的內容,咱們將在後續的文章中在作介紹,咱們暫時介紹一下rollbackFor參數和noRollbackFor參數。(備註:rollbackForClassName和noRollbackForClassName與rollbackFor和noRollbackFor做用一致,惟一的區別就是前者指定的是異常的類名,後者指定的是類的Class名)。
package com.jilinwula.springboot.helloworld.service; import com.jilinwula.springboot.helloworld.Repository.UserInfoRepository; import com.jilinwula.springboot.helloworld.entity.UserInfoEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Slf4j @Service public class UserInfoService { @Autowired private UserInfoRepository userInfoRepository; /** * 保存用戶信息 */ @Transactional(rollbackFor = Exception.class) public void save() throws Exception { UserInfoEntity userInfoEntity = new UserInfoEntity(); userInfoEntity.setUsername("小米"); userInfoEntity.setPassword("xiaomi"); userInfoEntity.setNickname("小米"); userInfoEntity.setRoleId(0L); userInfoRepository.save(userInfoEntity); UserInfoEntity userInfoEntity2 = new UserInfoEntity(); userInfoEntity2.setUsername("京東"); userInfoEntity2.setPassword("jingdong"); userInfoEntity2.setNickname("京東"); userInfoEntity2.setRoleId(0L); userInfoRepository.save(userInfoEntity2); throw new Exception(); } }
按照以前咱們的測試結果咱們知道,@Transactional註解是不會回滾Exception異常類的,那麼如今咱們指定了rollbackFor參數,那麼結果如何呢?咱們看一下數據庫中的結果。
咱們看數據庫中沒有任何數據,也就證實了事物添加成功了,數據已經的回滾了。這也就是@Transactional註解中rollbackFor參數的做用,能夠指定想要回滾的異常。rollbackForClassName參數和rollbackFor的做用同樣,只不過該參數指定的是類的名字,而不是class名。在實際的開發中推薦使用rollbackFor參數,而不是rollbackForClassName參數。由於rollbackFor的參數是類型是Class類型,若是寫錯了,能夠在編譯期發現。而rollbackForClassName參數類型是字符串類型,若是寫錯了,在編譯期間是發現不了的。因此推薦使用rollbackFor參數。
package com.jilinwula.springboot.helloworld.service; import com.jilinwula.springboot.helloworld.Repository.UserInfoRepository; import com.jilinwula.springboot.helloworld.entity.UserInfoEntity; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Slf4j @Service public class UserInfoService { @Autowired private UserInfoRepository userInfoRepository; /** * 保存用戶信息 */ @Transactional(noRollbackFor = RuntimeException.class) public void save() throws Exception { UserInfoEntity userInfoEntity = new UserInfoEntity(); userInfoEntity.setUsername("小米"); userInfoEntity.setPassword("xiaomi"); userInfoEntity.setNickname("小米"); userInfoEntity.setRoleId(0L); userInfoRepository.save(userInfoEntity); UserInfoEntity userInfoEntity2 = new UserInfoEntity(); userInfoEntity2.setUsername("京東"); userInfoEntity2.setPassword("jingdong"); userInfoEntity2.setNickname("京東"); userInfoEntity2.setRoleId(0L); userInfoRepository.save(userInfoEntity2); throw new RuntimeException(); } }
咱們查看一下數據庫中是否成功的插入了數據。
咱們看數據庫中成功的插入數據了,也就證實了@Transactional註解的noRollbackFor參數成功了,由於正常來講,數據是會回滾的,由於咱們拋出的是RuntimeException異常。數據沒有回滾也就說明了,參數成功。noRollbackForClassName參數和noRollbackFor參數同樣,只是一個指定的是class類型,一個指定的是字符串類型。因此,爲了在編譯期間發現問題,仍是推薦使用noRollbackFor參數。
上述內容就是SpringBoot中的事物管理,若有不正確的歡迎留言,謝謝。
https://github.com/jilinwula/...