共计 32562 个字符,预计需要花费 82 分钟才能阅读完成。
本篇概述
在上一篇中,我们基本已经将 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://jilinwula.com/article/…