博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringBoot事物管理
阅读量:7221 次
发布时间:2019-06-29

本文共 35633 字,大约阅读时间需要 118 分钟。

本篇概述

在上一篇中,我们基本已经将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;@Servicepublic 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)@SpringBootTestpublic class JilinwulaSpringbootHelloworldApplicationTests {    @Autowired    private UserInfoService userInfoService;    @Test    public void save() {        userInfoService.save();    }    @Test    public void contextLoads() {    }}

  下面我们看一下数据库中的数据是否插入成功。

  title

抛出数据库异常

  我们看数据成功插入了。现在我们修改一下代码,让插入数据时,第二条数据的数据类型超出范围来模拟程序运行时发生的异常。然后我们看,这样是否影响第一条数据是否能正确的插入数据库。下面为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;@Servicepublic 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

  然后我们现在查一下数据库中的数据,看看第二条数据的异常是否会影响第一条数据的插入。

  title

添加@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;@Servicepublic 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

  日志还是和之前一样抛出异常,现在我们在查一下数据库中的数据。

  title

  发现数据库中已经没有第一条数据的内容了,这就说明了我们的事物添加成功了,在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;@Servicepublic 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)

  继续查看数据库中的数据。

  title

  我们发现这两条数据都插入成功了。我们同样,在方法中添加@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;@Servicepublic 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(
) 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(
) 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)

  数据库中数据:

  title

  我们看数据又没有插入成功,这样就保证了我们事物的一致性。


添加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@Servicepublic 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(
) [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(
) [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]

  查看数据库中的数据:

  title

  我们发现数据成功的插入了,虽然我们添加了@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@Servicepublic 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(
) 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(
) 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)

  查看数据库结果:

  title

@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@Servicepublic 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();    }}

  我们就不看执行的日志了,而是直接查数据库中的结果。

  title

  我们看数据没有插入到数据库中,这就说明了,事物添加成功了,数据已经成功的回滚了。在实际的开发中,我们常常需要自定义异常类,来满足我们开发的需求。这时要特别注意,自定义的异常类,一定要继承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@Servicepublic 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参数,那么结果如何呢?我们看一下数据库中的结果。

  title

  我们看数据库中没有任何数据,也就证明了事物添加成功了,数据已经的回滚了。这也就是@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@Servicepublic 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();    }}

  我们查看一下数据库中是否成功的插入了数据。

  title

  我们看数据库中成功的插入数据了,也就证明了@Transactional注解的noRollbackFor参数成功了,因为正常来说,数据是会回滚的,因为我们抛出的是RuntimeException异常。数据没有回滚也就说明了,参数成功。noRollbackForClassName参数和noRollbackFor参数一样,只是一个指定的是class类型,一个指定的是字符串类型。所以,为了在编译期间发现问题,还是推荐使用noRollbackFor参数。


  上述内容就是SpringBoot中的事物管理,如有不正确的欢迎留言,谢谢。


项目源码

  


原文链接

  

转载地址:http://xshym.baihongyu.com/

你可能感兴趣的文章
啥样的超级计算机能给海洋做“CT”?
查看>>
ARM公司收购Apical,欲致力推进“目联网”技术
查看>>
各地纷纷抢建互联网数据中心
查看>>
永信至诚助“海南省首届网络安全大赛”决赛圆满收官
查看>>
科普知识:什么是攻击隐写术
查看>>
趋势所需 统一存储逐渐走向成熟
查看>>
聚合、增值和生态:神州数码云科服务再拓新局
查看>>
"运营"与"增长黑客"之间差一个数据驱动
查看>>
勒索软件从未停止
查看>>
《VMware Virtual SAN权威指南》一2.3.4 VMkernel网络
查看>>
AMD:将在机器学习GPU领域“引发从来没有过的竞争”
查看>>
《计算机视觉:模型、学习和推理》一2.4 条件概率
查看>>
Riverbed将SD-WAN融入WAN优化
查看>>
信息管税邂逅大数据,加速破解新常态下税收剪刀差
查看>>
从应用角度谈谈初创企业服务器采购建议与解决方案
查看>>
修改CPU 对抗计算机病毒
查看>>
8个方法让你成为更优秀的程序员
查看>>
城市之眼视觉计算技术
查看>>
bd:快速返回某级父目录而不用冗余地输入 “cd ../../..”
查看>>
Wi-FM:借调频无线电信号可大幅提高无线网速
查看>>