SpringBoot中的聲明式事務(wù)詳解
事務(wù)
所有數(shù)據(jù)訪問技術(shù)都有事務(wù)機(jī)制,這些技術(shù)提供了API來開啟事務(wù)、提交事務(wù)完成數(shù)據(jù)操作,或者在發(fā)生錯誤的時候回滾數(shù)據(jù)。
Spring采用統(tǒng)一的機(jī)制來處理不同的數(shù)據(jù)訪問技術(shù)的事務(wù), Spring的事務(wù)提供一個 PlatformTransactionManager 的接口,不同的數(shù)據(jù)訪問技術(shù)使用不同的接口實(shí)現(xiàn)。
| Data Tech | 實(shí)現(xiàn) |
| JDBC | DataSourceTransactionManager |
| JPA | JPATransactionManager |
| Hibernate | HibernateTransactionManager |
| JDO | JDOTransactionManager |
| 分布式事務(wù) | JtaTransactionManager |
Mybatis-Spring依賴于 DataSourceTransactionManager ,沒有自己實(shí)現(xiàn) PlatformTransactionManager。
而得益于SpringBoot的自動配置機(jī)制,為我們自動開啟了聲明式事務(wù)支持, 我們無需添加注解 @EnableTransactionManagement 。
事務(wù)基礎(chǔ)
Spring提供一個 @EnableTransactionManagement 注解在配置類上開啟聲明式事務(wù)支持, 自動掃描加了 @Transactional 注解的類和方法,加入事務(wù)支持。
@Transactional 的配置項(xiàng):
| 配置項(xiàng) | 含義 | 備注 |
| value | 定義事務(wù)管理器 | 它是 SpringIOC 容器的一個Bean id,這個Bean需要實(shí)現(xiàn)接口 PlatformTransactionManager |
| transactionManager | 定義事務(wù)管理器 | 它是 SpringIOC 容器的一個Bean id,這個Bean需要實(shí)現(xiàn)接口 PlatformTransactionManager |
| isolation | 隔離級別 | 這是一個數(shù)據(jù)庫在多個事務(wù)同時存在時的概念。默認(rèn)值是數(shù)據(jù)庫默認(rèn)隔離級別 |
| propagation | 傳播行為 | 傳播行為是方法之間調(diào)用的問題。默認(rèn)值為Progation.REQUIRED |
| timeout | 超時時間 | 單位為秒,當(dāng)超時時,會引發(fā)異常,默認(rèn)會導(dǎo)致事務(wù)回滾 |
| readOnly | 是否開啟只讀事務(wù) | 默認(rèn) false |
| rollbackFor | 回滾事務(wù)的異常類定義 | 只有當(dāng)方法產(chǎn)生所定義的異常時,才會回滾事務(wù),否則提交事務(wù) |
| rollbackForClassName | 回滾事務(wù)的異常類名定義 | 同 rollbackFor,只是使用類名稱定義 |
| noRollbackFor | 當(dāng)產(chǎn)生哪些異常不回滾事務(wù) | 當(dāng)產(chǎn)生所定義異常時,Spring將繼續(xù)提交事務(wù) |
| noRollbackForClassName | 當(dāng)產(chǎn)生哪些異常不回滾事務(wù) | 同 noRollbackFor,只是使用類名稱定義 |
propagation
事務(wù)的傳播機(jī)制,主要有以下幾種,默認(rèn)是 REQUIRED:
- REQUIRED - 方法A調(diào)用時候沒有事務(wù)新建一個事務(wù),在方法A中調(diào)用方法B,將使用相同的事務(wù),如果方法B發(fā)生異常需要回滾,整個事務(wù)回滾。
- REQUIRES_NEW - 方法A調(diào)用方法B時,無論是否存在事務(wù)都開啟一個新事務(wù),這樣B方法異常不會導(dǎo)致A的數(shù)據(jù)回滾。
- NESTED - 和REQUIRES_NEW類似,但是只支持JDBC,不支持JPA或Hibernate
- SUPPORTS - 方法調(diào)用時有事務(wù)就用事務(wù),沒事務(wù)就不用事務(wù)
- NOT_SUPPORTED - 強(qiáng)制方法不在事務(wù)中執(zhí)行,若有事務(wù),在方法調(diào)用到結(jié)束階段先掛起事務(wù)。
- NEVER - 強(qiáng)制不能有事務(wù),若有事務(wù)就拋出異常
- MANDATORY - 強(qiáng)制必須有事務(wù),如果沒有事務(wù)就拋出異常
isolation
事務(wù)的隔離級別,決定了事務(wù)的完整性,主要一下幾種,默認(rèn)是DEFAULT:
- READ_UNCOMMITTED - A事務(wù)修改記錄但沒提交,B事務(wù)可讀取到修改后的值??蓪?dǎo)致臟讀、不可重復(fù)讀、幻讀。
- READ_COMMITTED - A事務(wù)修改并提交后,B事務(wù)才能讀取到修改后的值,阻止了臟讀,但可能導(dǎo)致不可重復(fù)讀和幻讀。
- REPEATABLE_READ - A事務(wù)讀取了一條記錄,B事務(wù)將不能修改這條記錄,阻止臟讀和不可重復(fù)讀,但是可能出現(xiàn)幻讀。
- SERIALIZABLE - 事務(wù)是順序執(zhí)行的,可避免所有缺陷,但是開銷很大。
- DEFAULT - 使用當(dāng)前數(shù)據(jù)庫默認(rèn)隔離級別,入Oracle、SQL Server是READ_COMMITTED,MySQL是REPEATABLE_READ
timeout
事務(wù)過期時間,默認(rèn)是當(dāng)前數(shù)據(jù)庫默認(rèn)事務(wù)過期時間。
readOnly
指定是否為只讀事務(wù),默認(rèn)是false。
如果你一次執(zhí)行單條查詢語句,則沒有必要啟用事務(wù)支持,數(shù)據(jù)庫默認(rèn)支持SQL執(zhí)行期間的讀一致性。
如果你一次執(zhí)行多條查詢語句,例如統(tǒng)計查詢,報表查詢,在這種場景下,多條查詢SQL必須保證整體的讀一致性, 否則,在前條SQL查詢之后,后條SQL查詢之前,數(shù)據(jù)被其他用戶改變,則該次整體的統(tǒng)計查詢將會出現(xiàn)讀數(shù)據(jù)不一致的狀態(tài), 此時,應(yīng)該啟用只讀事務(wù)支持。
只讀事務(wù)與讀寫事務(wù)區(qū)別:
對于只讀查詢,可以指定事務(wù)類型為 readonly,即只讀事務(wù)。
由于只讀事務(wù)不存在數(shù)據(jù)的修改, 因此數(shù)據(jù)庫將會為只讀事務(wù)提供一些優(yōu)化手段,例如Oracle對于只讀事務(wù),不啟動回滾段,不記錄回滾log。
rollbackFor
指定哪些異??梢詫?dǎo)致事務(wù)回滾,默認(rèn)是 Throwable 的子類。
noRollbackFor
執(zhí)行哪些異常不可用引起事務(wù)回滾,默認(rèn)是 Throwable 的子類。
實(shí)戰(zhàn)篇
實(shí)際項(xiàng)目中,使用SpringBoot的默認(rèn)配置就已經(jīng)足夠滿足我們的需求了。 本篇將通過幾個例子來演示如何使用@Transactional注解,在出現(xiàn)異常時候回滾或不回滾數(shù)據(jù)。
使用的DAO技術(shù)是MyBatis進(jìn)行數(shù)據(jù)訪問,使用druid數(shù)據(jù)庫連接池, 另外配合mybatis-plus,實(shí)現(xiàn)數(shù)據(jù)訪問層。
引入依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- MyBatis plus增強(qiáng)和springboot的集成-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatisplus-spring-boot-starter</artifactId>
<version>${mybatisplus-spring-boot-starter.version}</version>
</dependency>配置數(shù)據(jù)庫連接:
################### spring配置 ###################
spring:
profiles:
active: dev
datasource:
url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&autoReconnect=true&tinyInt1isBit=false&useUnicode=true&characterEncoding=utf8
username: root
password: xxxxx然后增加mybatis個性化配置:
################### mybatis-plus配置 ###################
mybatis-plus:
mapper-locations: classpath*:com/xncoding/trans/dao/repository/mapping/*.xml
typeAliasesPackage: >
com.dao.entity
global-config:
id-type: 0 # 0:數(shù)據(jù)庫ID自增 1:用戶輸入id 2:全局唯一id(IdWorker) 3:全局唯一ID(uuid)
db-column-underline: false
refresh-mapper: true
configuration:
map-underscore-to-camel-case: true
cache-enabled: true #配置的緩存的全局開關(guān)
lazyLoadingEnabled: true #延時加載的開關(guān)
multipleResultSetsEnabled: true #開啟的話,延時加載一個屬性時會加載該對象全部屬性,否則按需加載屬性增加實(shí)體類User:
@TableName(value = "t_user")
public class User extends Model<User> {
/**
* 主鍵ID
*/
@TableId(value="id", type= IdType.AUTO)
private Integer id;
private String username;
private String password;
// 省略 get/set 方法
@Override
protected Serializable pkVal() {
return this.id;
}
}增加 UserMapper 類:
public interface UserMapper extends BaseMapper<User> {}增加 Mybatis 配置類:
@Configuration
@EnableTransactionManagement(order = 2)
@MapperScan(basePackages = {"com.dao.repository"})
public class MybatisPlusConfig {
@Resource
private DruidProperties druidProperties;
/**
* 單數(shù)據(jù)源連接池配置
*/
@Bean
public DruidDataSource singleDatasource() {
DruidDataSource dataSource = new DruidDataSource();
druidProperties.config(dataSource);
return dataSource;
}
}定義Service,并注入UserMapper:
@Service
public class UserService {
@Resource
private UserMapper userMapper;
}增加Controller,注入Service,定義幾個url來做測試用:
@RestController
public class UserController {
@Resource
private UserService userService;
}到此為止項(xiàng)目初始化完成,可以在Service中添加方法進(jìn)行聲明式事務(wù)測試了。
異?;貪L
@Transactional 注解可以放到類也可以放到方法上,如果放到類上面會對所有 public 方法添加注解, 不過你仍然可以在方法上面加這個注解,會覆蓋類上面聲明的事務(wù)注解。
先實(shí)驗(yàn)一個拋出異常會回滾的方法:
/**
* 增刪改要寫 ReadOnly=false 為可寫
* @param user 用戶
*/
@Transactional(readOnly = false)
public void updateUserError(User user) {
userMapper.updateById(user);
errMethod(); // 執(zhí)行一個會拋出異常的方法
}
private void errMethod() {
System.out.println("error");
throw new RuntimeException("runtime");
}然后在Controller里面添加一個url調(diào)用此方法:
@RequestMapping("/errorUpdate") public Object first() {<!-- --> User user = new User(); user.setId(1); user.setUsername("admin"); user.setPassword("admin"); userService.updateUserError(user); return "first controller"; }}
數(shù)據(jù)庫里面先插入一條數(shù)據(jù): 1|admin|123
啟動應(yīng)用后訪問地址: //localhost:8092/errorUpdate
控制臺打印異常:
@RequestMapping("/errorUpdate")
public Object first() {
User user = new User();
user.setId(1);
user.setUsername("admin");
user.setPassword("admin");
userService.updateUserError(user);
return "first controller";
}
}查看數(shù)據(jù)庫中記錄: 1|admin|123 ,沒有變動,說明回滾成功。
異常不回滾
你還可以指定特定異常不回滾,比如自定義一個MyException,拋出這個異常不回滾。
public class MyException extends RuntimeException {
public MyException() {
super();
}
public MyException(String runtime) {
super(runtime);
}
}然后通過指定這個異常不回滾:
@Transactional(readOnly = false, noRollbackFor = {MyException.class})
public void updateUserError2(User user) {
userMapper.updateById(user);
errMethod2(); // 執(zhí)行一個會拋出自定義異常的方法
}
private void errMethod2() {
System.out.println("error");
throw new MyException("runtime");
}然后再定義一個url來驗(yàn)證:
@RequestMapping("/errorUpdate2")
public Object second() {
User user = new User();
user.setId(1);
user.setUsername("admin");
user.setPassword("admin");
userService.updateUserError(user);
return "second controller";
}重啟服務(wù)器,訪問地址://localhost:8092/errorUpdate2
控制臺仍然報異常:
Caused by: com.xncoding.trans.exception.MyException: runtime
at com.xncoding.trans.service.UserService.errMethod2(UserService.java:43) ~[classes/:na]
at com.xncoding.trans.service.UserService.updateUserError2(UserService.java:34) ~[classes/:na]
看看數(shù)據(jù)庫中記錄:1|admin|admin,更改成功,說明拋出這個MyException異常后并不會回滾。
到此這篇關(guān)于SpringBoot中的聲明式事務(wù)詳解的文章就介紹到這了,更多相關(guān)SpringBoot聲明式事務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springmvc接口接收參數(shù)與請求參數(shù)格式的整理
這篇文章主要介紹了springmvc接口接收參數(shù)與請求參數(shù)格式的整理,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
Java內(nèi)部類應(yīng)用之靜態(tài)內(nèi)部類應(yīng)用示例
這篇文章主要介紹了Java內(nèi)部類應(yīng)用之靜態(tài)內(nèi)部類應(yīng)用,結(jié)合實(shí)例形式分析了Java靜態(tài)內(nèi)部類的原理、功能、用法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2019-09-09
解決elastic-job-ui在使用druid作為數(shù)據(jù)庫連接池時作業(yè)維度報錯問題
這篇文章主要介紹了解決elastic-job-ui在使用druid作為數(shù)據(jù)庫連接池時作業(yè)維度報錯問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04
java實(shí)現(xiàn)合并2個文件中的內(nèi)容到新文件中
這篇文章主要介紹了java實(shí)現(xiàn)合并2個文件中的內(nèi)容到新文件中,思路非常不錯,這里推薦給大家。2015-03-03

