SpringBoot事物管理

本篇概述

在上一篇中,我們基本已經將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名)。

  • 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/...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章