SpringBoot事物管理

/ SpringBoot / 没有评论 / 97浏览

本篇概述

在上一篇中,我们基本已经将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() {
    }

}

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

  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;

@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

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

  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;

@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

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

  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;

@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)

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

  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;

@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)

  数据库中数据:

  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
@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]

  查看数据库中的数据:

  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
@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)

  查看数据库结果:

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

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

  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名)。

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参数,那么结果如何呢?我们看一下数据库中的结果。

  title

  我们看数据库中没有任何数据,也就证明了事物添加成功了,数据已经的回滚了。这也就是@Transactional注解中rollbackFor参数的作用,可以指定想要回滚的异常。rollbackForClassName参数和rollbackFor的作用一样,只不过该参数指定的是类的名字,而不是class名。在实际的开发中推荐使用rollbackFor参数,而不是rollbackForClassName参数。因为rollbackFor的参数是类型是Class类型,如果写错了,可以在编译期发现。而rollbackForClassName参数类型是字符串类型,如果写错了,在编译期间是发现不了的。所以推荐使用rollbackFor参数。

package com.jilinwula.springboot.helloworld.service;

import com.jilinwula.springboot.helloworld.Repository.UserInfoRepository;
import com.jilinwula.springboot.helloworld.entity.UserInfoEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
public class UserInfoService {
    @Autowired
    private UserInfoRepository userInfoRepository;

    /**
     * 保存用户信息
     */
    @Transactional(noRollbackFor = RuntimeException.class)
    public void save() throws Exception {
        UserInfoEntity userInfoEntity = new UserInfoEntity();
        userInfoEntity.setUsername("小米");
        userInfoEntity.setPassword("xiaomi");
        userInfoEntity.setNickname("小米");
        userInfoEntity.setRoleId(0L);
        userInfoRepository.save(userInfoEntity);

        UserInfoEntity userInfoEntity2 = new UserInfoEntity();
        userInfoEntity2.setUsername("京东");
        userInfoEntity2.setPassword("jingdong");
        userInfoEntity2.setNickname("京东");
        userInfoEntity2.setRoleId(0L);
        userInfoRepository.save(userInfoEntity2);

        throw new RuntimeException();
    }
}

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

  title

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


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

项目源码

  https://github.com/jilinwula/jilinwula-springboot-helloworld4