成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一篇文章徹底吃透 Spring 的事務實現

開發 前端
編程式的事務管理比較靈活,如果當前操作非常耗時,可以采用編程式的事務管理來提交事務,避免長事務影響數據庫性能;其次如果數據操作比較簡單時間短,可以采用聲明式事務管理,如果使用不當,可能會導致事務失效,因此在實際使用中要多加小心。

一、背景介紹

今天我們接著來聊聊 SpringBoot 事務相關的具體使用方式。

何謂事務?熟悉數據庫的同學可能比較熟悉,對于任意一組 SQL 語句,要么全部執行成功,要么全部執行失敗;如果中途因為某個原因其中一條語句執行失敗,那么先前執行過的語句將全部撤回。

事務控制的好處在于:可以保證數據操作的安全性。比如,當你給某個客戶 A 轉賬 100 塊,此時銀行需要從你的個人賬戶中扣除 100 塊,然后再給客戶 A 賬戶增加 100 塊,這其實是兩個動作,假如銀行在操作客戶 A 的賬戶時出現故障,此時你個人賬戶的錢已經被扣除,但對方的賬戶并沒有到賬,這將會給客戶產生重大損失。有了事務控制之后,當操作對方的賬戶發生異常時,可以將個人賬戶中扣除的錢進行撤回,從而保證用戶資金賬戶的安全性。

Java 作為一個高級開發語言,同樣支持數據庫的事務控制。在上文中,我們了解到所有涉及到數據庫的操作,都需要通過數據庫連接對象來完成。當我們在操作數據庫時,如果想要開啟手動事務控制(默認是自動提交),其實通過連接對象的autoCommit參數就可以完成,例如如下示例:

// 1.加載數據庫驅動包
Class.forName(DRIVER_CLASS);
// 2.創建一個數據庫連接實例
Connection conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
Statement statement = null;
try {
    // 3.設置手動事務提交,默認是true
    conn.setAutoCommit(false);
    // 4.執行多條SQL語句
    statement = conn.createStatement();
    statement.executeUpdate("insert into tb_user(id, name) values(1, 'tom') ");
    statement.executeUpdate("insert into tb_role(id, name) values(1, 'sys') ");
    ...
    // 5.提交事務
    conn.commit();
} catch (SQLException e) {
    // 如果SQL執行異常,回滾事務
    conn.rollback();
}

// 6.關閉連接
statement.close();
conn.close();

了解了 JDBC 的事務控制之后,再來學習 SpringBoot 事務控制就要容易的多,下面我們一起來看看相關的使用方式。

二、SpringBoot 事務

在 Spring 中事務有兩種實現方式,分別是編程式事務管理和聲明式事務管理。

  • 編程式事務管理:利用的是TransactionTemplate類或者更底層的PlatformTransactionManager事務管理器來控制事務操作,用戶可以手動提交或者回滾事務,編程上比較靈活
  • 聲明式事務管理:利用的是@Transactional注解對事務進行管理,本質是通過 AOP 對方法前后進行攔截,在目標方法開始之前創建或者加入一個事務,目標方法執行完成之后根據情況進行提交或者回滾事務,使用上比較簡單,易上手

當我們使用  SpringBoot 框架來開發項目的時候,SpringBoot 會自動將 Spring 對數據庫事務支持的依賴庫加載到工程中,無需再次添加相關依賴包。

下面我們以之前介紹的 SpringBoot 整合 mybatis 的工程為例子,利用事務控制來執行多表數據插入操作,一起來看看這兩種事務管理的應用方式。

2.1、編程式事務管理

編程式事務管理主要有兩種實現方式,第一種是利用TransactionTemplate類來提交事務,編程簡單靈活,也是常用的方式之一;另一種是采用PlatformTransactionManager事務管理器來控制事務的提交和回滾。

我們先看看更底層的PlatformTransactionManager接口應用方式。

2.1.1、PlatformTransactionManager 事務管理器

利用PlatformTransactionManager事務管理器來實現事務的操作,示例如下:

@Service
publicclass ApiService {

    privatestaticfinal Logger LOGGER = LoggerFactory.getLogger(ApiService.class);

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private PlatformTransactionManager transactionManager;

    public void insert(Role role, Menu menu){
        //手動開啟事務
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        try {
            // 新增角色信息
            roleMapper.insert(role);
            // 新增菜單信息
            menuMapper.insert(menu);
            // 提交事務
            transactionManager.commit(status);
        } catch (Exception e) {
            // 回滾事務
            transactionManager.rollback(status);
            LOGGER.error("提交數據異常",e);
        }
    }
}

在執行角色信息插入和菜單信息插入的時候,如果都成功,則提交事務;如果任意其中一個方法失敗,整個操作進行回滾。

關于事務管理器,無論采用的是 JPA 還是 JDBC 等,底層的事務管理器都實現自PlatformTransactionManager接口。如果采用的是spring-boot-starter-jdbc或者Mybatis操作數據庫,Spring Boot 框架會默認將DataSourceTransactionManager實例作為實現類;如果采用的是spring-boot-starter-data-jpa,框架會默認將JpaTransactionManager實例作為實現類。

關于這一點,我們可以寫一個測試方法來查看PlatformTransactionManager接口的實現類,具體如下:

@MapperScan("com.example.mybatis.mapper")
@SpringBootApplication
publicclass Application {

    @Bean
    public Object testBean(PlatformTransactionManager platformTransactionManager){
        System.out.println("transactionManager:" + platformTransactionManager.getClass().getName());
        returnnew Object();
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

啟動服務,輸出結果如下:

transactionManager:org.springframework.jdbc.datasource.DataSourceTransactionManager

2.1.2、TransactionTemplate 事務模板類

除了采用事務管理器來實現事務手動控制,Spring 事務框架還為用戶提供了TransactionTemplate事務模板類,通過它也可以實現事務的手動控制,并且操作更加簡單,示例如下:

@Service
publicclass ApiService {

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private TransactionTemplate transactionTemplate;

    /**
     * 方式一:帶返回值的事務提交
     * @param role
     * @param menu
     */
    public void insert1(Role role, Menu menu){
        Integer result =transactionTemplate.execute(status -> {
            // 新增角色信息
            roleMapper.insert(role);
            // 新增菜單信息
            menuMapper.insert(menu);
            return1;
        });
    }

    /**
     * 方式二:忽略返回值的事務提交
     * @param role
     * @param menu
     */
    public void insert2(Role role, Menu menu){
        transactionTemplate.execute(status -> {
            // 新增角色信息
            roleMapper.insert(role);
            // 新增菜單信息
            menuMapper.insert(menu);
            returnnull;
        });
    }

    /**
     * 方式三:不帶返回值的事務提交
     * @param role
     * @param menu
     */
    public void insert3(Role role, Menu menu){
        transactionTemplate.execute(new TransactionCallbackWithoutResult(){

            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                // 新增角色信息
                roleMapper.insert(role);
                // 新增菜單信息
                menuMapper.insert(menu);
            }
        });
    }
}

以上三種方式,都可以實現實現事務的手動控制,效果等同于采用事務管理器來實現事務手動控制。

如果仔細翻查TransactionTemplate類的execute()方法,你會發現它底層的實現邏輯,與上文介紹的利用事務管理器來控制事務的提交和回滾操作類似。

execute()方法的部分核心源碼如下!

圖片圖片

因此,在編程式事務管理方式下,推薦采用TransactionTemplate類來實現,編程上會更加靈活簡單。

2.2、聲明式事務管理

聲明式事務管理就更加簡單了,只需要在方法上增加注解@Transactional即可,無需任何配置。

2.2.1、Transactional 注解應用示例

@Service
publicclass ApiService {

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private MenuMapper menuMapper;

    @Transactional
    public void insert(Role role, Menu menu){
        // 新增角色信息
        roleMapper.insert(role);
        // 新增菜單信息
        menuMapper.insert(menu);
    }
}

聲明式事務管理方式,本質采用的是 AOP 動態代理的方式,對標注@Transactional注解的方法進行前后攔截,然后通過事務管理器來實現事務控制。

盡管@Transactional注解可以作用于接口、接口方法、類以及類方法上,但是 Spring 不推薦在接口或者接口方法上使用該注解,如果編程不當某些場景下可能會失效。當作用于類上,那么該類的所有public方法將都具有事務屬性。在實際使用過程中,推薦在類的方法上使用該注解,以便實現精準的事務控制。

2.2.2、Transactional 注解失效場景

在使用@Transactional注解時,有以下幾個場景,事務可能不會生效!

  • 場景一:@Transactional注解如果應用在非public方法,事務不會生效,并且不會拋異常,該注解只會代理public修飾的方法
  • 場景二:同一個類中的方法,調用標注@Transactional注解的方法,事務控制也不會生效
  • 場景三:內部異常如果被catch吃了,事務不會回滾
  • 場景四:@Transactional注解默認只對運行時異常或者 Error 才回滾事務,其它場景不會觸發事務回滾,如果異常不在范圍之內,事務不會回滾
  • 場景五:@Transactional注解上的配置參數使用不當,可能導致事務失效

下面我們每個場景下,錯誤的用法。

事務失效:場景一

@Transactional注解應該只被應用到public方法上,如果應用在非public方法,事務不會生效,并且不會拋異常,錯誤示例如下:

@Service
publicclass ApiService {

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private MenuMapper menuMapper;

    @Transactional
    protected void insert(Role role, Menu menu){
        // 新增角色信息
        roleMapper.insert(role);
        // 新增菜單信息
        menuMapper.insert(menu);
    }
}

此時,執行insert操作的時候會自動提交,Spring Boot 不會開啟事務控制。假如menuService.insert()方法執行異常,此時roleService.insert()提交的數據不會回滾。

原因在于:@Transactional注解只會代理public修飾的方法,由 Spring AOP 代理決定的。

事務失效:場景二

同一個類中的方法,如果調用標注@Transactional注解的方法,事務控制也不會生效,錯誤示例如下:

@Service
publicclass ApiService {

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private MenuMapper menuMapper;

    public void save(Role role, Menu menu){
        insert(role, menu);
    }

    @Transactional
    public void insert(Role role, Menu menu){
        // 新增角色信息
        roleMapper.insert(role);
        // 新增菜單信息
        menuMapper.insert(menu);
    }
}

當外部調用save()方法來保存數據的時候,此時 Spring Boot 不會開啟事務控制,會自動提交數據,如果執行過程中發生異常,之前執行過的數據操作不會回滾。

原因在于:被@Transactional標注的方法,只有被當前類以外的代碼調用時,才會由 Spring Aop 生成的代理對象來管理。

事務失效:場景三

被@Transactional標注的方法,內部異常如果被手動catch吃了,事務不會回滾,錯誤示例如下:

@Service
publicclass ApiService {

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private MenuMapper menuMapper;

    @Transactional
    public void insert(Role role, Menu menu){
        try {
            // 新增角色信息
            roleMapper.insert(role);
            // 新增菜單信息
            menuMapper.insert(menu);
        } catch (Exception e){
            e.printStackTrace();
            // todo..
        }
    }
}

此時,被@Transactional標注的方法具備事務控制,如果執行過程中發生異常,數據不會回滾,因為異常被捕獲了。當 Spring AOP 事務代理類沒有感知到異常時,會自動提交事務。

事務失效:場景四

@Transactional注解默認只對運行時異常或者 Error 才回滾事務,其它場景不會觸發事務回滾,如果異常不在范圍之內,事務不會回滾,錯誤示例如下:

@Service
publicclass ApiService {

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private MenuMapper menuMapper;

    @Transactional
    public void insert(Role role, Menu menu) throws Exception {
        try {
            // 新增角色信息
            roleMapper.insert(role);
            // 新增菜單信息
            menuMapper.insert(menu);
        } catch (Exception e){
            thrownew Exception("保存錯誤");
        }
    }
}

此時中途如果插入數據失敗,會拋Exception異常,但是之前執行成功的數據不會回滾。

如果想要支持其它類型的異常,可以在@Transactional注解類上配置rollbackFor參數,比如如下示例:

@Transactional(rollbackFor = Exception.class)

這個參數配置僅限于 Throwable 異常類及其子類。

事務失效:場景五

在@Transactional注解類上,其實隱含了很多的事務屬性參數,如果參數配置不當,可能也會導致事務失效,錯誤示例如下:

@Service
publicclass ApiService {

    @Autowired
    private RoleService roleService;

    @Autowired
    private MenuService menuService;

    @Transactional(readOnly = true)
    public void insert(Role role, Menu menu){
        // 新增角色信息
        roleService.insert(role);
        // 新增菜單信息
        menuService.insert(menu);
    }
}

此時提交數據會報錯,因為readOnly = true參數表示只讀模式,不能對數據庫的數據進行更改操作。

三、事務注解詳解

在上文中,我們介紹了@Transactional事務注解的基本用法,正如上文所說,在注解類上,其實隱含了很多的事務屬性參數,Transactional注解類源碼如下。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public@interface Transactional {

@AliasFor("transactionManager")
String value() default "";

@AliasFor("value")
String transactionManager() default "";

Propagation propagation() default Propagation.REQUIRED;

Isolation isolation() default Isolation.DEFAULT;

int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

boolean readOnly() default false;

 Class<? extends Throwable>[] rollbackFor() default {};

 String[] rollbackForClassName() default {};

 Class<? extends Throwable>[] noRollbackFor() default {};

 String[] noRollbackForClassName() default {};
}

下面我們一起來看看每個屬性的作用。

屬性

類型

默認值

說明

transactionManager

String

DEFAULT

事務管理器

propagation

Propagation枚舉

REQUIRED

事務傳播屬性

isolation

Isolation枚舉

DEFAULT

事務隔離級別

timeout

int

-1

超時(秒)

readOnly

boolean

false

是否只讀

rollbackFor

Class[]

{}

需要支持回滾的異常類

rollbackForClassName

String[]

{}

需要支持回滾的異常類名

noRollbackFor

Class[]

{}

不需要支持回滾的異常類

noRollbackForClassName

String[]

{}

不需要支持回滾的異常類名

我們重點看看transactionManager、propagation和isolation這三個參數屬性值配置,其它參數基本上見名之意,就不用介紹了。

3.1、事務管理器屬性

默認情況下,不需要我們手動配置事務管理器實例。如果 Spring 容器中有多個事務管理器實例,比如多數據源的情況下,某些場景下,就需要我們手動指定事務管理器實例。

具體應用示例如下:

@Configuration
publicclass TransactionManagerConfigBean {

    @Autowired
    private DataSource dataSource;

    /**
     * 自定義一個事務管理器1,同時作為默認事務管理器
     * @return
     */
    @Bean(name = "txManager1")
    @Primary
    public PlatformTransactionManager txManager1() {
        returnnew DataSourceTransactionManager(dataSource);
    }

    /**
     * 自定義一個事務管理器2
     * @return
     */
    @Bean(name = "txManager2")
    public PlatformTransactionManager txManager2() {
        returnnew DataSourceTransactionManager(dataSource);
    }
}

如果需要使用指定的事務管理器,只需要在@Transactional注解中配置相應的參數即可。

@Service
publicclass ApiService {

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private MenuMapper menuMapper;

    @Transactional(value = "txManager2")
    public void insert(Role role, Menu menu) throws Exception {
        // 新增角色信息
        roleMapper.insert(role);
        // 新增菜單信息
        menuMapper.insert(menu);
    }
}

3.2、事務傳播屬性

事務傳播屬性,指的是當一個方法內同時存在多個事務的時候,Spring 如何處理這些事務的行為。

Spring 支持 7 種事務傳播方式,Propagation枚舉類支持的屬性值如下:

  • REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務
  • SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行
  • MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常
  • REQUIRES_NEW:創建一個新的事務,如果當前存在事務,則把當前事務掛起
  • NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起
  • NEVER:以非事務方式運行,如果當前存在事務,則拋出異常
  • NESTED:如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價于REQUIRED

如果想要指定事務傳播行為,可以通過propagation屬性設置,例如:

@Transactional(propagation = Propagation.REQUIRED)

Spring 默認采用的是REQUIRED屬性值,也就是說,如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。

這樣設計的好處在于:當一個方法存在多個事務開啟的操作時,只會有一個有效的事務實例,可以實現數據的原子性操作。

比如如下示例:

@Service
publicclass ApiService {

    privatestaticfinal Logger LOGGER = LoggerFactory.getLogger(ApiService.class);

    @Autowired
    private RoleService roleService;

    @Autowired
    private MenuService menuService;

    @Autowired
    private PlatformTransactionManager transactionManager;

    public void save(Role role, Menu menu){
        //手動開啟事務
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        try {
            // 新增角色信息
            roleService.insert(role);
            // 新增菜單信息
            menuService.insert(menu);
            // 提交事務
            transactionManager.commit(status);
        } catch (Exception e) {
            // 回滾事務
            transactionManager.rollback(status);
            LOGGER.error("提交數據異常",e);
        }
    }
}
@Service
public class RoleService {

    @Autowired
    private RoleMapper roleMapper;

    @Transactional
    public void insert(Role role){
        roleMapper.insert(role);
    }
}
@Service
publicclass MenuService {

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void insert(Menu menu){
        transactionTemplate.execute(status -> {
            menuMapper.insert(menu);
            returnnull;
        });
    }
}

當調用ApiService.save()方法時,如果出現異常,所有的操作都會回滾;反之,提交事務。

3.3、事務隔離級別屬性

事務隔離級別,可以簡單的理解為數據庫的事務隔離級別。

從數據庫角度,為了解決多個事務操作同一條數據產生的并發問題,提出了事務隔離級別概念,由低到高依次為 Read uncommitted 、Read committed 、Repeatable read 、Serializable ,這四個級別可以逐個解決臟讀 、不可重復讀 、幻讀等這幾類問題,每個隔離級別作用如下:

  • read uncommitted:俗稱讀未提交,指的是一個事務還沒提交時,它做的變更就能被別的事務看到。
  • Read committed:俗稱讀提交,指的是一個事務提交之后,它做的變更才會被其他事務看到。
  • Repeatable read:俗稱可重復讀,指的是一個事務執行過程中看到的數據,總是跟這個事務在啟動時看到的數據是一致的,同時當其他事務在未提交時,變更是不可見的。
  • Serializable:俗稱串行化,顧名思義就是對于同一行記錄,“寫”會加“寫鎖”,“讀”會加“讀鎖”。當出現讀寫鎖沖突的時候,后訪問的事務必須等前一個事務執行完成,才能繼續執行。

在 Spring 中,事務隔離級別的設置可以通過Isolation枚舉類來指定,其支持的屬性值如下:

  • DEFAULT:默認值,表示使用底層數據庫的默認隔離級別。大部分數據庫,默認隔離級別為可重復讀;Mysql 有些例外,采用可重復讀隔離級別
  • READ_UNCOMMITTED:對應數據庫中讀未提交的隔離級別
  • READ_COMMITTED :對應數據庫中讀提交的隔離級別
  • REPEATABLE_READ :對應數據庫中可重復讀的隔離級別
  • SERIALIZABLE:對應數據庫中串行化的隔離級別

如果想要指定事務隔離級別,可以通過isolation屬性設置,例如:

@Transactional(isolation = Isolation.DEFAULT)

四、小結

最后總結一下,編程式的事務管理比較靈活,如果當前操作非常耗時,可以采用編程式的事務管理來提交事務,避免長事務影響數據庫性能;其次如果數據操作比較簡單時間短,可以采用聲明式事務管理,如果使用不當,可能會導致事務失效,因此在實際使用中要多加小心。

本文主要圍繞 Spring Boot 事務管理的使用方式,做了一次知識內容的總結,如果有描述不對的地方,歡迎留言指出。

五、參考

1.https://www.cnblogs.com/sharpest/p/7995203.html

2.https://blog.csdn.net/MinggeQingchun/article/details/119579941

責任編輯:武曉燕 來源: 潘志的技術筆記
相關推薦

2024-06-25 08:18:55

2013-04-15 10:59:08

iOS開發ARC版本說明

2022-08-03 08:17:00

Redis事務內存

2025-05-26 01:21:00

SpringIoC配置

2019-10-15 10:23:13

服務器MySQL 數據

2017-07-20 16:55:56

Android事件響應View源碼分析

2024-05-17 10:05:06

Java機制應用

2022-05-25 08:31:31

ArthasInstrument

2019-07-23 08:55:46

Base64編碼底層

2021-04-07 13:28:21

函數程序員異步

2020-10-09 08:15:11

JsBridge

2015-07-15 17:09:48

HiveHadoop分布式文件系統

2017-09-05 08:52:37

Git程序員命令

2024-05-10 08:19:59

arthasjava字節碼

2022-02-21 09:44:45

Git開源分布式

2023-05-12 08:19:12

Netty程序框架

2019-04-17 15:16:00

Sparkshuffle算法

2021-04-09 08:40:51

網絡保險網絡安全網絡風險

2021-06-30 00:20:12

Hangfire.NET平臺

2021-11-04 10:34:02

JavaScript繼承編程
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 在线观看第一页 | 九九久久在线看 | 美女天天干天天操 | 中国美女av | 国产精品视频在线免费观看 | 成人精品国产 | 国产精品综合网 | 欧美精品久久久久久 | 综合久| 夜夜爽99久久国产综合精品女不卡 | 99九色 | 久久久久久国产免费视网址 | 91精品国产一区二区三区蜜臀 | 国产精品久久久久久久久久免费看 | 亚洲成在线观看 | 中文字幕一区二区三区四区五区 | 精品一区二区三区在线观看 | 一区二区三区四区在线视频 | 视频在线观看一区 | 麻豆精品久久 | 一区日韩| 国产黄色大片网站 | 天天精品在线 | 成人在线免费观看视频 | www国产亚洲精品久久网站 | 国产精品免费看 | 丁香婷婷在线视频 | 久久黄色 | 欧美国产精品 | 91精品国产91久久久久久不卞 | 成人在线视频免费看 | 综合一区二区三区 | 二区中文字幕 | 91香蕉| 亚洲国产精品99久久久久久久久 | 国产在线网址 | 亚洲va在线va天堂va狼色在线 | 亚洲视频在线看 | 国产亚洲区| 狠狠久久 | 国产精品美女www |