在上一篇中,我们基本已经将SpringBoot对数据库的操作,都介绍完了。在这一篇中,我们将介绍一下SpringBoot对事物的管理。我们知道在实际的开发中,保证数据的安全性是非常重要的,不能因为异常,或者服务中断等原因,导致脏数据的产生。所以掌握SpringBoot项目的事物管理,尤为的重要。在SpringBoot中对事物的管理非常的方便。我们只需要添加一个注解就可以了,下面我们来详细介绍一下有关SpringBoot事物的功能。
因为在上一篇中我们已经用测试用例的方式介绍了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
然后我们现在查一下数据库中的数据,看看第二条数据的异常是否会影响第一条数据的插入。
我们看第一条数据成功的插入,但这明显是错误的,因为正常逻辑是不应该插入成功的。这样会导致脏数据产生,也没办法保证数据的一致性。下面我们看一下在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,然后在执行上面的测试用例,查一下结果。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/...
http://jilinwula.com/article/...