寫作時間: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.
一致的事務抽象
- JDBC/Hibernate/myBatis
- DataSource/JTA
PlatformTransactionManager
- DataSourceTransactionManager
- HibernateTransactionManager
- JtaTransactionManger
TransactionDefinition
- Propagation
- Isolation
- Timeout
- 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, 只改變過程,達到預期。
聲明式事務
圖解可以參考文章開頭的圖片
基於註解的配置方式
開啓事務註解的方式
- @EnableTransactionMangement
<tx:annotation-driven/>
一些配置
- proxyTargetClass
- mode
- order
@Transactional
- transactionManager
當配置了多個事務管理器時,可以使用該屬性指定選擇哪個事務管理器。 - propagation
事務的傳播行爲,默認值爲 Propagation.REQUIRED。 - isolation
事務的隔離級別,默認值爲 Isolation.DEFAULT。
可選的值有:
Isolation.DEFAULT 使用底層數據庫默認的隔離級別。
Isolation.READ_UNCOMMITTED
Isolation.READ_COMMITTED
Isolation.REPEATABLE_READ
Isolation.SERIALIZABLE
- timeout
事務的超時時間,默認值爲-1。如果超過該時間限制但事務還沒有完成,則自動回滾事務。 - readOnly
指定事務是否爲只讀事務,默認值爲 false;爲了忽略那些不需要事務的方法,比如讀取數據,可以設置 read-only 爲 true。 - rollbackFor 屬性
用於指定能夠觸發事務回滾的異常類型,可以指定多個異常類型。 - 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
事務的本質
- Spring的聲明式事務本質上是通過AOP來增強了類的功能
- Spring的AOP本質上就是爲類做了一個代理
看似在調用自己寫的類,實際用的是增強後的代理類。 - 問題的解法
訪問增強後的代理類的方法,而非直接訪問自身的方法。
在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