本篇概述
在上一篇中,我們基本已經將SpringBoot對數據庫的操作,都介紹完了。在這一篇中,我們將介紹一下SpringBoot對事物的管理。我們知道在實際的開發中,保證數據的安全性是非常重要的,不能因爲異常,或者服務中斷等原因,導致髒數據的產生。所以掌握SpringBoot項目的事物管理,尤爲的重要。在SpringBoot中對事物的管理非常的方便。我們只需要添加一個註解就可以了,下面我們來詳細介紹一下有關SpringBoot事物的功能。
創建Service
因爲在上一篇中我們已經用測試用例的方式介紹了SpringBoot中的增刪改查功能。所以在這一篇的事物管理,我們還將已測試用例爲主。唯一不同之處,就是我們需要創建一個Service服務,然後將相關的業務邏輯封裝到Service中,來表示該操作是同一個操作。下面我們簡單的在Service中只添加一個方法,並且在方法中新增兩條數據,並驗證該Service是否成功將數據添加到數據庫中。下面爲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;
@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);
}
}
測試用例:
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() {
}
}
下面我們看一下數據庫中的數據是否插入成功。
拋出數據庫異常
我們看數據成功插入了。現在我們修改一下代碼,讓插入數據時,第二條數據的數據類型超出範圍來模擬程序運行時發生的異常。然後我們看,這樣是否影響第一條數據是否能正確的插入數據庫。下面爲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;
@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時,第二條就會拋出異常。下面爲執行日誌:
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
然後我們現在查一下數據庫中的數據,看看第二條數據的異常是否會影響第一條數據的插入。
添加@Transactional事物註解
我們看第一條數據成功的插入,但這明顯是錯誤的,因爲正常邏輯是不應該插入成功的。這樣會導致髒數據產生,也沒辦法保證數據的一致性。下面我們看一下在SpringBoot中怎麼通過添加事務的方式,解決上面的問題。上面提到過在SpringBoot中使Service支持事物很簡單,只要添加一個註解即可,下面我們添加完註解,然後在嘗試上面的方式,看看第一條數據還能否添加成功。然後我們在詳細介紹該註解的使用。下面爲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);
}
}
代碼和之前基本一樣,只是在方法上新增了一個@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
日誌還是和之前一樣拋出異常,現在我們在查一下數據庫中的數據。
發現數據庫中已經沒有第一條數據的內容了,這就說明了我們的事物添加成功了,在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
下面我們將上述的代碼添加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註解是自動檢測,是否拋出異常嗎?如果拋出了異常就回滾之前對數據庫的操作,那爲什麼我們拋出了異常,而數據沒有回滾呢?這是因爲@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註解中還包括很多個參數,下面我們詳細介紹一下這些參數的作用。
@Transactional註解參數說明:
參數 | 作用 |
---|---|
value | 指定使用的事務管理器 |
propagation | 可選的事務傳播行爲設置 |
isolation | 可選的事務隔離級別設置 |
readOnly | 讀寫或只讀事務,默認讀寫 |
timeout | 事務超時時間設置 |
rollbackFor | 導致事務回滾的異常類數組 |
rollbackForClassName | 導致事務回滾的異常類名字數組 |
noRollbackFor | 不會導致事務回滾的異常類數組 |
noRollbackForClassName | 不會導致事務回滾的異常類名字數組 |
下面我們只介紹一下部分參數,因爲大部分參數實際上是和Spring中的註解一樣的,有關Spring事物相關的內容,我們將在後續的文章中在做介紹,我們暫時介紹一下rollbackFor參數和noRollbackFor參數。(備註:rollbackForClassName和noRollbackForClassName與rollbackFor和noRollbackFor作用一致,唯一的區別就是前者指定的是異常的類名,後者指定的是類的Class名)。
- rollbackFor: 指定事物回滾的異常類。因爲在上面的測試中我們知道@Transactional事物類只會回滾RuntimeException類及其子類的異常,那麼實際的開發中,如果我們就想讓拋出Exception異常的類回滾,那應該怎麼辦呢?這時很簡單,只要在@Transactional註解中指定rollbackFor參數即可。該參數指定的是異常類的Class名。下面我們還是修改一下Servcie代碼,拋出Exception異常,但我們指定rollbackFor爲Exception.class,然後在看一下數據是否能回滾成功。下面爲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(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參數。
- noRollbackFor: 指定不回滾的異常類。看名字我們就知道該參數是和rollbackFor參數對應的。所以我們就不做過多介紹了,我們直接驗證該參數的作用。我們知道@Transactional註解會回滾RuntimeException類及其子類的異常。如果我們將noRollbackFor參數指定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(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/...