本篇概述
在上一篇中,我们基本已经将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/...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。