【Spring Boot系列】- Spring Boot事務(wù)應(yīng)用詳解
一、事務(wù)簡(jiǎn)介
事務(wù)(Transaction)是數(shù)據(jù)庫(kù)操作最基本單元,邏輯上一組操作,要么都成功。如果有一個(gè)操作失敗。則事務(wù)操作都失敗(回滾(Rollback))。
事務(wù)的四個(gè)特性(ACID):
1. 原子性(Atomicity)
一個(gè)事務(wù)(Transaction)中的所有操作,要么全部完成,要么全部不完成,不會(huì)結(jié)束在中間某個(gè)環(huán)節(jié)。
2. 一致性(Consistency)
事務(wù)開(kāi)始之前和事務(wù)結(jié)束以后,數(shù)據(jù)庫(kù)的完整性沒(méi)有被破壞。這表示寫(xiě)入的資料必須完全符合所有的預(yù)設(shè)規(guī)則。
3. 隔離性(Isolation)
一個(gè)事務(wù)的執(zhí)行不能被其它事務(wù)干擾,即一個(gè)事務(wù)內(nèi)部的操作及使用的數(shù)據(jù)對(duì)并發(fā)的其它事務(wù)是隔離的,并發(fā)執(zhí)行的各個(gè)事務(wù)之間不能互相打擾。
4. 隔離性(Durability)
事務(wù)處理結(jié)束后,對(duì)數(shù)據(jù)的修改就是永久的,即便系統(tǒng)故障也不會(huì)丟失。
其中,事務(wù)隔離又分為以下 4 種不同的級(jí)別
- 未提交讀(Read uncommitted):最低的隔離級(jí)別,允許“臟讀”(dirty reads),事務(wù)可以看到其他事務(wù)“尚未提交”的修改。如果另一個(gè)事務(wù)回滾,那么當(dāng)前事務(wù)讀到的數(shù)據(jù)就是臟數(shù)據(jù)。
- 提交讀(read committed):一個(gè)事務(wù)可能會(huì)遇到不可重復(fù)讀(Non Repeatable Read)的問(wèn)題。不可重復(fù)讀是指,在一個(gè)事務(wù)內(nèi),多次讀同一數(shù)據(jù),在這個(gè)事務(wù)還沒(méi)有結(jié)束時(shí),如果另一個(gè)事務(wù)恰好修改了這個(gè)數(shù)據(jù),那么,在第一個(gè)事務(wù)中,兩次讀取的數(shù)據(jù)就可能不一致。
- 可重復(fù)讀(repeatable read): 一個(gè)事務(wù)可能會(huì)遇到幻讀(Phantom Read)的問(wèn)題。幻讀是指,在一個(gè)事務(wù)中,第一次查詢某條記錄,發(fā)現(xiàn)沒(méi)有,但是,當(dāng)試圖更新這條不存在的記錄時(shí),竟然能成功,并且,再次讀取同一條記錄,它就神奇地出現(xiàn)了
- 串行化(Serializable): 最嚴(yán)格的隔離級(jí)別,所有事務(wù)按照次序依次執(zhí)行,因此,臟讀、不可重復(fù)讀、幻讀都不會(huì)出現(xiàn)。雖然 Serializable 隔離級(jí)別下的事務(wù)具有最高的安全性,但是,由于事務(wù)是串行執(zhí)行,所以效率會(huì)大大下降,應(yīng)用程序的性能會(huì)急劇降低。如果沒(méi)有特別重要的情景,一般都不會(huì)使用 Serializable 隔離級(jí)別。
需要格外注意的是:事務(wù)能否生效,取決于數(shù)據(jù)庫(kù)引擎是否支持事務(wù)。如MySQL的InnoDB引擎是支持事務(wù)的,但是MyISAM就不支持事務(wù)。
二、Spring事務(wù)
Spring對(duì)事務(wù)提供了很好的支持。Spring借助IOC容器強(qiáng)大的配置能力,為事務(wù)提供豐富功能支持。
Spring支持以下2種事務(wù)管理方式:
- 聲明式事務(wù)管理:Spring 聲明式事務(wù)管理在底層采用了 AOP 技術(shù),其最大的優(yōu)點(diǎn)在于無(wú)須通過(guò)編程的方式管理事務(wù),只需要在配置文件中進(jìn)行相關(guān)的規(guī)則聲明,就可以將事務(wù)規(guī)則應(yīng)用到業(yè)務(wù)邏輯中。
- 編程式事務(wù)管理:編程式事務(wù)管理是通過(guò)編寫(xiě)代碼實(shí)現(xiàn)的事務(wù)管理。 這種方式能夠在代碼中精確地定義事務(wù)的邊界,我們可以根據(jù)需求規(guī)定事務(wù)從哪里開(kāi)始,到哪里結(jié)束。
選擇編程式事務(wù)還是聲明式事務(wù),很大程度上就是在控制權(quán)上顆粒度和易用性之間進(jìn)行權(quán)衡。
- 編程式對(duì)事物控制的細(xì)粒度更高,我們能夠精確的控制事務(wù)的邊界,事務(wù)的開(kāi)始和結(jié)束完全取決于我們的需求,但這種方式存在一個(gè)致命的缺點(diǎn),那就是事務(wù)規(guī)則與業(yè)務(wù)代碼耦合度高,難以維護(hù),因此我們很少使用這種方式對(duì)事務(wù)進(jìn)行管理。
- 聲明式事務(wù)易用性更高,對(duì)業(yè)務(wù)代碼沒(méi)有侵入性,耦合度低,易于維護(hù),因此這種方式也是我們最常用的事務(wù)管理方式。
三、Spring聲明式事務(wù)
Spring的聲明式事務(wù)管理在底層是建立在AOP的基礎(chǔ)上的,其本質(zhì)是對(duì)方法前后進(jìn)行攔截,然后在目標(biāo)方法開(kāi)始之前創(chuàng)建或者加入一個(gè)事務(wù),在執(zhí)行完目標(biāo)方法之后根據(jù)執(zhí)行情況提交或者回滾事務(wù)。
聲明式事務(wù)最大的優(yōu)點(diǎn)就是不需要通過(guò)編程的方式管理事務(wù),這樣就不需要在業(yè)務(wù)邏輯代碼中摻雜事務(wù)管理的代碼,只需在配置文件中做相關(guān)的事務(wù)規(guī)則聲明(或通過(guò)等價(jià)的基于標(biāo)注的方式),便可以將事務(wù)規(guī)則應(yīng)用到業(yè)務(wù)邏輯中。因?yàn)槭聞?wù)管理本身就是一個(gè)典型的橫切邏輯,正是 AOP 的用武之地。Spring 開(kāi)發(fā)團(tuán)隊(duì)也意識(shí)到了這一點(diǎn),為聲明式事務(wù)提供了簡(jiǎn)單而強(qiáng)大的支持。
在開(kāi)發(fā)中使用聲明式事務(wù),不僅因?yàn)槠浜?jiǎn)單,更主要是因?yàn)檫@樣使得純業(yè)務(wù)代碼不被污染,極大方便后期的代碼維護(hù)。
3.1 聲明式事務(wù)的2種實(shí)現(xiàn)方式
- 配置文件的方式: 即在spring xml文件中進(jìn)行統(tǒng)一配置,開(kāi)發(fā)者基本上就不用關(guān)注事務(wù)的事情了,代碼中無(wú)需關(guān)心任何和事務(wù)相關(guān)的代碼,一切交給spring處理。
- 注解的方式: 只需在需要spring來(lái)幫忙管理事務(wù)的方法上加上@Transaction注解就可以了,注解的方式相對(duì)來(lái)說(shuō)更簡(jiǎn)潔一些,都需要開(kāi)發(fā)者自己去進(jìn)行配置,可能有些同學(xué)對(duì)spring不是太熟悉,所以配置這個(gè)有一定的風(fēng)險(xiǎn),做好代碼review就可以了。
3.2 聲明式事務(wù)注解方式5個(gè)步驟
1. 啟用Spring的注釋驅(qū)動(dòng)事務(wù)管理功能
在spring配置類上加上@EnableTransactionManagement注解
@EnableTransactionManagement
public class MainConfig4 {
}
當(dāng)spring容器啟動(dòng)的時(shí)候,發(fā)現(xiàn)有@EnableTransactionManagement注解,此時(shí)會(huì)攔截所有bean的創(chuàng)建,掃描看一下bean上是否有@Transaction注解(類、或者父類、或者接口、或者方法中有這個(gè)注解都可以),如果有這個(gè)注解,spring會(huì)通過(guò)aop的方式給bean生成代理對(duì)象,代理對(duì)象中會(huì)增加一個(gè)攔截器,攔截器會(huì)攔截bean中public方法執(zhí)行,會(huì)在方法執(zhí)行之前啟動(dòng)事務(wù),方法執(zhí)行完畢之后提交或者回滾事務(wù)。
EnableTransactionManagement 的源碼
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
/**
* spring是通過(guò)aop的方式對(duì)bean創(chuàng)建代理對(duì)象來(lái)實(shí)現(xiàn)事務(wù)管理的
* 創(chuàng)建代理對(duì)象有2種方式,jdk動(dòng)態(tài)代理和cglib代理
* proxyTargetClass:為true的時(shí)候,就是強(qiáng)制使用cglib來(lái)創(chuàng)建代理
*/
boolean proxyTargetClass() default false;
/**
* 用來(lái)指定事務(wù)攔截器的順序
* 我們知道一個(gè)方法上可以添加很多攔截器,攔截器是可以指定順序的
* 比如你可以自定義一些攔截器,放在事務(wù)攔截器之前或者之后執(zhí)行,就可以通過(guò)order來(lái)控制
*/
int order() default Ordered.LOWEST_PRECEDENCE;
}
2. 定義事務(wù)管理器
事務(wù)交給spring管理,那么你肯定要?jiǎng)?chuàng)建一個(gè)或者多個(gè)事務(wù)管理者,有這些管理者來(lái)管理具體的事務(wù),比如啟動(dòng)事務(wù)、提交事務(wù)、回滾事務(wù),這些都是管理者來(lái)負(fù)責(zé)的。
spring中使用PlatformTransactionManager這個(gè)接口來(lái)表示事務(wù)管理者。
PlatformTransactionManager多個(gè)實(shí)現(xiàn)類,用來(lái)應(yīng)對(duì)不同的環(huán)境
JpaTransactionManager: 如果你用jpa來(lái)操作db,那么需要用這個(gè)管理器來(lái)幫你控制事務(wù)。
DataSourceTransactionManager: 如果你用是指定數(shù)據(jù)源的方式,比如操作數(shù)據(jù)庫(kù)用的是:JdbcTemplate、mybatis、ibatis,那么需要用這個(gè)管理器來(lái)幫你控制事務(wù)。
HibernateTransactionManager: 如果你用hibernate來(lái)操作db,那么需要用這個(gè)管理器來(lái)幫你控制事務(wù)。
JtaTransactionManager: 如果你用的是java中的jta來(lái)操作db,這種通常是分布式事務(wù),此時(shí)需要用這種管理器來(lái)控制事務(wù)。
3. 需使用事務(wù)的目標(biāo)上加@Transaction注解
- @Transaction放在接口上,那么接口的實(shí)現(xiàn)類中所有public都被spring自動(dòng)加上事務(wù)。
- @Transaction放在類上,那么當(dāng)前類以及其下無(wú)限級(jí)子類中所有pubilc方法將被spring自動(dòng)加上事務(wù)。
- @Transaction放在public方法上,那么該方法將被spring自動(dòng)加上事務(wù)。
Transaction參數(shù)介紹
參數(shù) | 描述 |
---|---|
value | 指定事務(wù)管理器的bean名稱,如果容器中有多事務(wù)管理器PlatformTransactionManager,那么你得告訴spring,當(dāng)前配置需要使用哪個(gè)事務(wù)管理器 |
transactionManager | 同value,value和transactionManager選配一個(gè)就行,也可以為空,如果為空,默認(rèn)會(huì)從容器中按照類型查找一個(gè)事務(wù)管理器bean |
propagation | 事務(wù)的傳播屬性,下篇文章詳細(xì)介紹 |
isolation | 事務(wù)的隔離級(jí)別,就是制定數(shù)據(jù)庫(kù)的隔離級(jí)別,數(shù)據(jù)庫(kù)隔離級(jí)別大家知道么?不知道的可以去補(bǔ)一下 |
timeout | 事務(wù)執(zhí)行的超時(shí)時(shí)間(秒),執(zhí)行一個(gè)方法,比如有問(wèn)題,那我不可能等你一天吧,可能最多我只能等你10秒 10秒后,還沒(méi)有執(zhí)行完畢,就彈出一個(gè)超時(shí)異常吧 |
readOnly | 是否是只讀事務(wù),比如某個(gè)方法中只有查詢操作,我們可以指定事務(wù)是只讀的 設(shè)置了這個(gè)參數(shù),可能數(shù)據(jù)庫(kù)會(huì)做一些性能優(yōu)化,提升查詢速度 |
rollbackFor | 定義零(0)個(gè)或更多異常類,這些異常類必須是Throwable的子類,當(dāng)方法拋出這些異常及其子類異常的時(shí)候,spring會(huì)讓事務(wù)回滾 如果不配做,那么默認(rèn)會(huì)在 RuntimeException 或者 Error 情況下,事務(wù)才會(huì)回滾 |
rollbackForClassName | 同 rollbackFor,只是這個(gè)地方使用的是類名 |
noRollbackFor | 定義零(0)個(gè)或更多異常類,這些異常類必須是Throwable的子類,當(dāng)方法拋出這些異常的時(shí)候,事務(wù)不會(huì)回滾 |
noRollbackForClassName | 同 noRollbackFor,只是這個(gè)地方使用的是類名 |
4. 執(zhí)行db業(yè)務(wù)操作
在@Transaction標(biāo)注類或者目標(biāo)方法上執(zhí)行業(yè)務(wù)操作,此時(shí)這些方法會(huì)自動(dòng)被spring進(jìn)行事務(wù)管理。
@Component
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void insertBatch(String names) {
jdbcTemplate.update("truncate table t_user");
for (String name : names) {
jdbcTemplate.update("INSERT INTO t_user(name) VALUES (?)", name);
}
}
}
5. 啟動(dòng)spring容器,使用bean執(zhí)行業(yè)務(wù)操作
@Test
public void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MainConfig4.class);
context.refresh();
UserService userService = context.getBean(UserService.class);
userService.insertBatch("java高并發(fā)系列", "mysql系列", "maven系列", "mybatis系列");
}
四、Spring編程式事務(wù)
通過(guò)硬編碼的方式使用spring中提供的事務(wù)相關(guān)的類來(lái)控制事務(wù)。
編程式事務(wù)主要的兩種用法:
- 通過(guò)PlatformTransactionManager控制事務(wù)。
- 通過(guò)TransactionTemplate控制事務(wù)。
4.1 PlatfornTransactionManager
這種是最原始的方式,代碼量較大,后面其他方式都是針對(duì)這種方式的封裝
@Test
public void test1() throws Exception {
//定義一個(gè)數(shù)據(jù)源
DataSource dataSource = new DataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/int?characterEncoding=UTF-8&serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setInitialSize(5);
//定義一個(gè)JdbcTemplate,用來(lái)方便執(zhí)行數(shù)據(jù)庫(kù)增刪改查
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//1.定義事務(wù)管理器,給其指定一個(gè)數(shù)據(jù)源(可以把事務(wù)管理器想象為一個(gè)人,這個(gè)人來(lái)負(fù)責(zé)事務(wù)的控制操作)
PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
//2.定義事務(wù)屬性:TransactionDefinition,
// TransactionDefinition可以用來(lái)配置事務(wù)的屬性信息,比如事務(wù)隔離級(jí)別、事務(wù)超時(shí)時(shí)間、事務(wù)傳播方式、是否是只讀事務(wù)等等。
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
//3.開(kāi)啟事務(wù):調(diào)用platformTransactionManager.getTransaction開(kāi)啟事務(wù)操作,得到事務(wù)狀態(tài)(TransactionStatus)對(duì)象
TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
//4.執(zhí)行業(yè)務(wù)操作,下面就執(zhí)行2個(gè)插入操作
try {
System.out.println("before:" + jdbcTemplate.queryForList("SELECT * from t_user"));
jdbcTemplate.update("insert into t_user (name) values (?)", "test1-1");
jdbcTemplate.update("insert into t_user (name) values (?)", "test1-2");
//5.提交事務(wù):platformTransactionManager.commit
platformTransactionManager.commit(transactionStatus);
} catch (Exception e) {
//6.回滾事務(wù):platformTransactionManager.rollback
platformTransactionManager.rollback(transactionStatus);
}
System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}
4.2 代碼分析
步驟一:定義事務(wù)管理器PlatformTransactionManager
事務(wù)管理器相當(dāng)于一個(gè)管理員,這個(gè)管理員就是用來(lái)幫你控制事務(wù)的,比如開(kāi)啟事務(wù),提交事務(wù),回滾事務(wù)等等。
spring中使用PlatformTransactionManager這個(gè)接口來(lái)表示事務(wù)管理器
public interface PlatformTransactionManager {
//獲取一個(gè)事務(wù)(開(kāi)啟事務(wù))
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
//提交事務(wù)
void commit(TransactionStatus status) throws TransactionException;
//回滾事務(wù)
void rollback(TransactionStatus status) throws TransactionException;
}
步驟二:定義事務(wù)屬性TransactionDefinition
定義事務(wù)屬性,比如事務(wù)隔離級(jí)別、事務(wù)超時(shí)時(shí)間、事務(wù)傳播方式、是否是只讀事務(wù)等等。
spring中使用TransactionDefinition接口來(lái)表示事務(wù)的定義信息,有個(gè)子類比較常用:DefaultTransactionDefinition。
步驟三:開(kāi)啟事務(wù)
調(diào)用事務(wù)管理器的getTransaction方法,即可以開(kāi)啟一個(gè)事務(wù)。這個(gè)方法會(huì)返回一個(gè)TransactionStatus表示事務(wù)狀態(tài)的一個(gè)對(duì)象,通過(guò)TransactionStatus提供的一些方法可以用來(lái)控制事務(wù)的一些狀態(tài),比如事務(wù)最終是需要回滾還是需要提交。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-715686.html
步驟四:執(zhí)行業(yè)務(wù)操作
事務(wù)管理器開(kāi)啟事務(wù)的時(shí)候,會(huì)創(chuàng)建一個(gè)連接,將datasource和connection映射之后丟在了ThreadLocal中,而JdbcTemplate內(nèi)部執(zhí)行db操作的時(shí)候,也需要獲取連接,JdbcTemplate會(huì)以自己內(nèi)部的datasource去上面的threadlocal中找有沒(méi)有關(guān)聯(lián)的連接,如果有直接拿來(lái)用,若沒(méi)找到將重新創(chuàng)建一個(gè)連接,而此時(shí)是可以找到的,那么JdbcTemplate就參與到spring的事務(wù)中了。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-715686.html
4.2 TransactionTemplate
@Test
public void test2() throws Exception {
//定義一個(gè)數(shù)據(jù)源
DataSource dataSource = new DataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/int?characterEncoding=UTF-8&serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setInitialSize(5);
//定義一個(gè)JdbcTemplate,用來(lái)方便執(zhí)行數(shù)據(jù)庫(kù)增刪改查
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//1.定義事務(wù)管理器,給其指定一個(gè)數(shù)據(jù)源(可以把事務(wù)管理器想象為一個(gè)人,這個(gè)人來(lái)負(fù)責(zé)事務(wù)的控制操作)
PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
//2.定義事務(wù)屬性:TransactionDefinition,TransactionDefinition可以用來(lái)配置事務(wù)的屬性信息,比如事務(wù)隔離級(jí)別、事務(wù)超時(shí)時(shí)間、事務(wù)傳播方式、是否是只讀事務(wù)等等。
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
transactionDefinition.setTimeout(10);
//3.創(chuàng)建TransactionTemplate對(duì)象
TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager,transactionDefinition);/**
* 4.通過(guò)TransactionTemplate提供的方法執(zhí)行業(yè)務(wù)操作
* 主要有2個(gè)方法:
* (1).executeWithoutResult(Consumer<TransactionStatus> action):沒(méi)有返回值的,需傳遞一個(gè)Consumer對(duì)象,在accept方法中做業(yè)務(wù)操作
* (2).<T> T execute(TransactionCallback<T> action):有返回值的,需要傳遞一個(gè)TransactionCallback對(duì)象,在doInTransaction方法中做業(yè)務(wù)操作
* 調(diào)用execute方法或者executeWithoutResult方法執(zhí)行完畢之后,事務(wù)管理器會(huì)自動(dòng)提交事務(wù)或者回滾事務(wù)。
* 那么什么時(shí)候事務(wù)會(huì)回滾,有2種方式:
* (1)transactionStatus.setRollbackOnly();將事務(wù)狀態(tài)標(biāo)注為回滾狀態(tài)
* (2)execute方法或者executeWithoutResult方法內(nèi)部拋出異常
* 什么時(shí)候事務(wù)會(huì)提交?
* 方法沒(méi)有異常 && 未調(diào)用過(guò)transactionStatus.setRollbackOnly();
*/
transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
@Override
public void accept(TransactionStatus transactionStatus) {
jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-1");
jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-2");
}
});
System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
}
到了這里,關(guān)于【Spring Boot系列】- Spring Boot事務(wù)應(yīng)用詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!