易筋SpringBoot 2.1 | 第十七篇:SpringBoot的事務Transaction

寫作時間:2019-08-15
Spring Boot: 2.1 ,JDK: 1.8, IDE: IntelliJ IDEA

說明

事務管理是應用系統開發中必不可少的一部分。Spring 爲事務管理提供了豐富的功能支持。Spring 事務管理分爲編程式和聲明式的兩種方式。編程式事務指的是通過編碼方式實現事務;聲明式事務基於 AOP,將具體業務邏輯與事務處理解耦。聲明式事務管理使業務代碼邏輯不受污染, 因此在實際使用中聲明式事務用的比較多。聲明式事務有兩種方式,一種是在配置文件(xml)中做相關的事務規則聲明,另一種是基於 @Transactional 註解的方式。

聲明式事務圖解


需要明確幾點:

默認配置下 Spring 只會回滾運行時、未檢查異常(繼承自 RuntimeException 的異常)或者 Error。參考#transaction-declarative-rolling-back
@Transactional 註解只能應用到 public 方法纔有效。參考這裏 Method visibility and @Transactional.

Method visibility and @Transactional

When using proxies, you should apply the @Transactional annotation only to methods with public visibility. 
If you do annotate protected, private or package-visible methods with the @Transactional annotation,
no error is raised, but the annotated method does not exhibit the configured transactional settings. 
Consider the use of AspectJ (see below) if you need to annotate non-public methods.

一致的事務抽象

  1. JDBC/Hibernate/myBatis
  2. DataSource/JTA

PlatformTransactionManager

  1. DataSourceTransactionManager
  2. HibernateTransactionManager
  3. JtaTransactionManger

TransactionDefinition

  1. Propagation
  2. Isolation
  3. Timeout
  4. Read-only status
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

事務的傳播特性

事務傳播行爲類型 說明
PROPAGATION_REQUIRED 0 如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是最常見的選擇。
PROPAGATION_SUPPORTS 1 支持當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY 2 使用當前的事務,如果當前沒有事務,就拋出異常。
PROPAGATION_REQUIRES_NEW 3 新建事務,如果當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED 4 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER 5 以非事務方式執行,如果當前存在事務,則拋出異常。
PROPAGATION_NESTED 6 如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類 似的操作。

Required 圖解

Required New 圖解

事務隔離級別

隔離級別是指若干個併發的事務之間的隔離程度。TransactionDefinition 接口中定義了四個表示隔離級別的常量:

隔離性 髒讀 不可重複讀 幻讀
ISOLATION_READ_UNCOMMITTED 1 ✔️ ✔️ ✔️
ISOLATION_READ_COMMITTED 2 ✔️ ✔️
ISOLATION_REPEATABLE_READ 3 ✔️
ISOLATION_REPEATABLE_SERIALIZABLE 4

工程建立

參照教程【SpringBoot 2.1 | 第一篇:構建第一個SpringBoot工程】新建一個Spring Boot項目,名字叫demodbtransaction, 在目錄src/main/java/resources 下找到配置文件application.properties,重命名爲application.yml

在Dependency中選擇
Developer Tools > Lombok
Web > Spring Web Starter
SQL > H2 DataBase / JDBC API
Ops > Spring Boot Actuator。

在這裏插入圖片描述

設置日誌打印格式化ANSI

ANSI - American National Standards Institute
Support classes to provide ANSI color output.

src > main > resources > application.yml

spring:
  output:
    ansi:
      enabled: always

創建表Foo

路徑 src > main > resources > schema.sql

CREATE TABLE FOO (ID INT IDENTITY, BAR VARCHAR(64));

編程式事務

TransactionTemplate

  • TransactionCallback
  • TransactionCallbackWithoutResult

PlatforomTransactionManager

  • 可以傳入TransactionDefinition進行定義

Controller 實現編程式事務

com.zgpeace.demodbtransaction.DemodbtransactionApplication

package com.zgpeace.demodbtransaction;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

@SpringBootApplication
@Slf4j
public class DemodbtransactionApplication implements CommandLineRunner {

  @Autowired
  private TransactionTemplate transactionTemplate;
  @Autowired
  private JdbcTemplate jdbcTemplate;

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

  @Override
  public void run(String... args) throws Exception {
    programTransaction();
  }

  private void programTransaction() {
    log.info("COUNT BEFORE TRANSACTION: {}", getCount());
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
      @Override
      protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
        jdbcTemplate.execute("INSERT INTO FOO (ID, BAR) VALUES (1, 'zgpeace')");
        log.info("COUNT IN TRANSACTION: {}", getCount());
        transactionStatus.setRollbackOnly();
      }
    });
    log.info("COUNT AFTER TRANSACTION: {}", getCount());
  }

  private long getCount() {
    return (long) jdbcTemplate.queryForList("SELECT COUNT(*) AS CNT FROM FOO").get(0).get("CNT");
  }
}

註釋:這裏在執行插入數據之前,當中,之後分別打印數據表中數據的count的變化。因爲執行的setRollbackOnly(), 所以之前跟之後的數據是一樣的。

啓動成功,查看setRollbackOnly的日誌

COUNT BEFORE TRANSACTION: 0
COUNT IN TRANSACTION: 1
COUNT AFTER TRANSACTION: 0

註釋: RollbackOnly, 只改變過程,達到預期。

聲明式事務

圖解可以參考文章開頭的圖片

基於註解的配置方式

開啓事務註解的方式

  1. @EnableTransactionMangement
  2. <tx:annotation-driven/>

一些配置

  1. proxyTargetClass
  2. mode
  3. order

@Transactional

  1. transactionManager
    當配置了多個事務管理器時,可以使用該屬性指定選擇哪個事務管理器。
  2. propagation
    事務的傳播行爲,默認值爲 Propagation.REQUIRED。
  3. isolation
    事務的隔離級別,默認值爲 Isolation.DEFAULT。
可選的值有:
Isolation.DEFAULT 使用底層數據庫默認的隔離級別。
Isolation.READ_UNCOMMITTED
Isolation.READ_COMMITTED
Isolation.REPEATABLE_READ
Isolation.SERIALIZABLE
  1. timeout
    事務的超時時間,默認值爲-1。如果超過該時間限制但事務還沒有完成,則自動回滾事務。
  2. readOnly
    指定事務是否爲只讀事務,默認值爲 false;爲了忽略那些不需要事務的方法,比如讀取數據,可以設置 read-only 爲 true。
  3. rollbackFor 屬性
    用於指定能夠觸發事務回滾的異常類型,可以指定多個異常類型。
  4. noRollbackFor 屬性
    拋出指定的異常類型,不回滾事務,也可以指定多個異常類型。

創建自定義Exception類

com.zgpeace.demodbtransaction.RollbackException

package com.zgpeace.demodbtransaction;

public class RollbackException extends Exception {
}

數據操作Service

接口 com.zgpeace.demodbtransaction.FooService

package com.zgpeace.demodbtransaction;

public interface FooService {
  void insertRecord();

  void insertThenRollback() throws RollbackException;

  void invokeInsertThenRollback() throws RollbackException;
}

實現類 com.zgpeace.demodbtransaction.FooServiceImpl

package com.zgpeace.demodbtransaction;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class FooServiceImpl implements FooService {
  @Autowired
  private JdbcTemplate jdbcTemplate;

  @Override
  @Transactional
  public void insertRecord() {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('AAA')");
  }

  @Override
  @Transactional(rollbackFor = RollbackException.class)
  public void insertThenRollback() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('BBB')");
    throw new RollbackException();
  }

  @Override
  public void invokeInsertThenRollback() throws RollbackException {
    insertThenRollback();
  }
}


Controller 聲明式事務

com.zgpeace.demodbtransaction.DemodbtransactionApplication 更新類如下內容

package com.zgpeace.demodbtransaction;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

@SpringBootApplication
@Slf4j
@EnableTransactionManagement(mode = AdviceMode.PROXY)
public class DemodbtransactionApplication implements CommandLineRunner {
  @Autowired
  private FooService fooService;

  @Autowired
  private TransactionTemplate transactionTemplate;
  @Autowired
  private JdbcTemplate jdbcTemplate;

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

  @Override
  public void run(String... args) throws Exception {
    //programTransaction();
    anotateTransaction();
  }

  private void anotateTransaction() {
    fooService.insertRecord();
    log.info("AAA {}", jdbcTemplate.queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='AAA'", Long.class));

    try {
      fooService.insertThenRollback();
    } catch (Exception e) {
      log.info("BBB {}", jdbcTemplate.queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='BBB'", Long.class));
    }

    try {
      fooService.invokeInsertThenRollback();
    } catch (Exception e) {
      log.info("BBB {}", jdbcTemplate.queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='BBB'", Long.class));
    }
  }

註釋: invokeInsertThenRollback() 方法事務不起作用,是因爲通過方法去調用字方法,沒有作用到proxy類的調用。

啓動成功,查看聲明式事務結果

AAA
BBB 0
BBB 1

事務的本質

  1. Spring的聲明式事務本質上是通過AOP來增強了類的功能
  2. Spring的AOP本質上就是爲類做了一個代理
    看似在調用自己寫的類,實際用的是增強後的代理類。
  3. 問題的解法
    訪問增強後的代理類的方法,而非直接訪問自身的方法。

在ServiceImp類中,聲明父類的對象,調用父類對象來調用方法即可。

@Component
public class FooServiceImpl implements FooService {
  @Autowired
  private JdbcTemplate jdbcTemplate;
  @Autowired
  private FooService fooService;

  @Override
  @Transactional(rollbackFor = RollbackException.class)
  public void insertThenRollback() throws RollbackException {
    jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('BBB')");
    throw new RollbackException();
  }

  @Override
  public void invokeInsertThenSuperRollback() throws RollbackException {
    fooService.insertThenRollback();
  }
}

總結

恭喜你,學會了SpringBoot的事務Transaction編程實現和聲明式實現。
代碼下載:

https://github.com/zgpeace/Spring-Boot2.1/tree/master/demodbtransaction

參考

https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-declarative-annotations

https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-declarative-rolling-back

https://blog.csdn.net/nextyu/article/details/78669997

https://github.com/geektime-geekbang/geektime-spring-family/tree/master/Chapter%202/programmatic-transaction-demo

https://github.com/geektime-geekbang/geektime-spring-family/tree/master/Chapter%202/declarative-transaction-demo

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