SpringBoot事物管理

本篇概述

在上一篇中,咱們基本已經將SpringBoot對數據庫的操做,都介紹完了。在這一篇中,咱們將介紹一下SpringBoot對事物的管理。咱們知道在實際的開發中,保證數據的安全性是很是重要的,不能由於異常,或者服務中斷等緣由,致使髒數據的產生。因此掌握SpringBoot項目的事物管理,尤其的重要。在SpringBoot中對事物的管理很是的方便。咱們只須要添加一個註解就能夠了,下面咱們來詳細介紹一下有關SpringBoot事物的功能。java


建立Service

  由於在上一篇中咱們已經用測試用例的方式介紹了SpringBoot中的增刪改查功能。因此在這一篇的事物管理,咱們還將已測試用例爲主。惟一不一樣之處,就是咱們須要建立一個Service服務,而後將相關的業務邏輯封裝到Service中,來表示該操做是同一個操做。下面咱們簡單的在Service中只添加一個方法,而且在方法中新增兩條數據,並驗證該Service是否成功將數據添加到數據庫中。下面爲Service源碼:mysql

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

  測試用例:git

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() {
    }

}

  下面咱們看一下數據庫中的數據是否插入成功。github

  title

拋出數據庫異常

  咱們看數據成功插入了。如今咱們修改一下代碼,讓插入數據時,第二條數據的數據類型超出範圍來模擬程序運行時發生的異常。而後咱們看,這樣是否影響第一條數據是否能正確的插入數據庫。下面爲Service源碼:spring

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時,第二條就會拋出異常。下面爲執行日誌:sql

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源碼:apache

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

  日誌仍是和以前同樣拋出異常,如今咱們在查一下數據庫中的數據。tomcat

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

  • 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參數,那麼結果如何呢?咱們看一下數據庫中的結果。

  title

  咱們看數據庫中沒有任何數據,也就證實了事物添加成功了,數據已經的回滾了。這也就是@Transactional註解中rollbackFor參數的做用,能夠指定想要回滾的異常。rollbackForClassName參數和rollbackFor的做用同樣,只不過該參數指定的是類的名字,而不是class名。在實際的開發中推薦使用rollbackFor參數,而不是rollbackForClassName參數。由於rollbackFor的參數是類型是Class類型,若是寫錯了,能夠在編譯期發現。而rollbackForClassName參數類型是字符串類型,若是寫錯了,在編譯期間是發現不了的。因此推薦使用rollbackFor參數。

  • noRollbackFor: 指定不回滾的異常類。看名字咱們就知道該參數是和rollbackFor參數對應的。因此咱們就不作過多介紹了,咱們直接驗證該參數的做用。咱們知道@Transactional註解會回滾RuntimeException類及其子類的異常。若是咱們將noRollbackFor參數指定RuntimeException類。那麼此時事物應該就不會回滾了。下面咱們驗證一下。下面爲Service代碼:
package com.jilinwula.springboot.helloworld.service;

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

@Slf4j
@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/...


原文連接

  http://jilinwula.com/article/...

相關文章
相關標籤/搜索