小姐姐非要問我:spring編程式事務(wù)是啥?
本文開始,大概用10篇左右的文章來詳解spring中事務(wù)的使用,吃透spring事務(wù)。
本文內(nèi)容
詳解spring中編程式事務(wù)的使用。
spring中使用事務(wù)的2種方式
spring使事務(wù)操作變的異常容易了,spring中控制事務(wù)主要有2種方式
編程式事務(wù):硬編碼的方式 聲明式事務(wù):大家比較熟悉的注解@Transaction的方式
編程式事務(wù)
什么是編程式事務(wù)?
通過硬編碼的方式使用spring中提供的事務(wù)相關(guān)的類來控制事務(wù)。
編程式事務(wù)主要有2種用法
方式1:通過PlatformTransactionManager控制事務(wù) 方式2:通過TransactionTemplate控制事務(wù)
方式1:PlatformTransactionManager
這種是最原始的方式,代碼量比較大,后面其他方式都是對(duì)這種方式的封裝。
直接看案例,有詳細(xì)的注釋,大家一看就懂。
案例代碼位置

準(zhǔn)備sql
DROP?DATABASE?IF?EXISTS?javacode2018;
CREATE?DATABASE?if?NOT?EXISTS?javacode2018;
USE?javacode2018;
DROP?TABLE?IF?EXISTS?t_user;
CREATE?TABLE?t_user(
??id?int?PRIMARY?KEY?AUTO_INCREMENT,
??name?varchar(256)?NOT?NULL?DEFAULT?''?COMMENT?'姓名'
);
maven配置
????org.springframework
????spring-jdbc
????5.2.3.RELEASE
????org.springframework
????spring-tx
????5.2.3.RELEASE
測(cè)試代碼
代碼中會(huì)用到JdbcTemplate,對(duì)這個(gè)不理解的可以看一下:JdbcTemplate使用詳解
@Test
public?void?test1()?throws?Exception?{
????//定義一個(gè)數(shù)據(jù)源
????org.apache.tomcat.jdbc.pool.DataSource?dataSource?=?new?org.apache.tomcat.jdbc.pool.DataSource();
????dataSource.setDriverClassName("com.mysql.jdbc.Driver");
????dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
????dataSource.setUsername("root");
????dataSource.setPassword("root123");
????dataSource.setInitialSize(5);
????//定義一個(gè)JdbcTemplate,用來方便執(zhí)行數(shù)據(jù)庫增刪改查
????JdbcTemplate?jdbcTemplate?=?new?JdbcTemplate(dataSource);
????//1.定義事務(wù)管理器,給其指定一個(gè)數(shù)據(jù)源(可以把事務(wù)管理器想象為一個(gè)人,這個(gè)人來負(fù)責(zé)事務(wù)的控制操作)
????PlatformTransactionManager?platformTransactionManager?=?new?DataSourceTransactionManager(dataSource);
????//2.定義事務(wù)屬性:TransactionDefinition,TransactionDefinition可以用來配置事務(wù)的屬性信息,比如事務(wù)隔離級(jí)別、事務(wù)超時(shí)時(shí)間、事務(wù)傳播方式、是否是只讀事務(wù)等等。
????TransactionDefinition?transactionDefinition?=?new?DefaultTransactionDefinition();
????//3.開啟事務(wù):調(diào)用platformTransactionManager.getTransaction開啟事務(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"));
}
運(yùn)行輸出
before:[]
after:[{id=1,?name=test1-1},?{id=2,?name=test1-2}]
代碼分析
代碼中主要有5個(gè)步驟
步驟1:定義事務(wù)管理器PlatformTransactionManager
事務(wù)管理器相當(dāng)于一個(gè)管理員,這個(gè)管理員就是用來幫你控制事務(wù)的,比如開啟事務(wù),提交事務(wù),回滾事務(wù)等等。
spring中使用PlatformTransactionManager這個(gè)接口來表示事務(wù)管理器,
public?interface?PlatformTransactionManager?{
?//獲取一個(gè)事務(wù)(開啟事務(wù))
?TransactionStatus?getTransaction(@Nullable?TransactionDefinition?definition)
???throws?TransactionException;
?//提交事務(wù)
?void?commit(TransactionStatus?status)?throws?TransactionException;
?//回滾事務(wù)
?void?rollback(TransactionStatus?status)?throws?TransactionException;
}
PlatformTransactionManager多個(gè)實(shí)現(xiàn)類,用來應(yīng)對(duì)不同的環(huán)境

JpaTransactionManager:如果你用jpa來操作db,那么需要用這個(gè)管理器來幫你控制事務(wù)。
DataSourceTransactionManager:如果你用是指定數(shù)據(jù)源的方式,比如操作數(shù)據(jù)庫用的是:JdbcTemplate、mybatis、ibatis,那么需要用這個(gè)管理器來幫你控制事務(wù)。
HibernateTransactionManager:如果你用hibernate來操作db,那么需要用這個(gè)管理器來幫你控制事務(wù)。
JtaTransactionManager:如果你用的是java中的jta來操作db,這種通常是分布式事務(wù),此時(shí)需要用這種管理器來控制事務(wù)。
上面案例代碼中我們使用的是JdbcTemplate來操作db,所以用的是DataSourceTransactionManager這個(gè)管理器。
PlatformTransactionManager?platformTransactionManager?=?new?DataSourceTransactionManager(dataSource);
步驟2:定義事務(wù)屬性TransactionDefinition
定義事務(wù)屬性,比如事務(wù)隔離級(jí)別、事務(wù)超時(shí)時(shí)間、事務(wù)傳播方式、是否是只讀事務(wù)等等。
spring中使用TransactionDefinition接口來表示事務(wù)的定義信息,有個(gè)子類比較常用:DefaultTransactionDefinition。
關(guān)于事務(wù)屬性細(xì)節(jié)比較多,篇幅比較長,后面會(huì)專門有文章來詳解。
步驟3:開啟事務(wù)
調(diào)用事務(wù)管理器的getTransaction方法,即可以開啟一個(gè)事務(wù)
TransactionStatus?transactionStatus?=?platformTransactionManager.getTransaction(transactionDefinition);
這個(gè)方法會(huì)返回一個(gè)TransactionStatus表示事務(wù)狀態(tài)的一個(gè)對(duì)象,通過TransactionStatus提供的一些方法可以用來控制事務(wù)的一些狀態(tài),比如事務(wù)最終是需要回滾還是需要提交。
執(zhí)行了getTransaction后,spring內(nèi)部會(huì)執(zhí)行一些操作,為了方便大家理解,咱們看看偽代碼:
//有一個(gè)全局共享的threadLocal對(duì)象?resources
static?final?ThreadLocal上面代碼,將數(shù)據(jù)源datasource和connection映射起來放在了ThreadLocal中,ThreadLocal大家應(yīng)該比較熟悉,用于在同一個(gè)線程中共享數(shù)據(jù);后面我們可以通過resources這個(gè)ThreadLocal獲取datasource其對(duì)應(yīng)的connection對(duì)象。
步驟4:執(zhí)行業(yè)務(wù)操作
我們使用jdbcTemplate插入了2條記錄。
jdbcTemplate.update("insert?into?t_user?(name)?values?(?)",?"test1-1");
jdbcTemplate.update("insert?into?t_user?(name)?values?(?)",?"test1-2");
大家看一下創(chuàng)建JdbcTemplate的代碼,需要指定一個(gè)datasource
JdbcTemplate?jdbcTemplate?=?new?JdbcTemplate(dataSource);
再來看看創(chuàng)建事務(wù)管理器的代碼
PlatformTransactionManager?platformTransactionManager?=?new?DataSourceTransactionManager(dataSource);
2者用到的是同一個(gè)dataSource,而事務(wù)管理器開啟事務(wù)的時(shí)候,會(huì)創(chuàng)建一個(gè)連接,將datasource和connection映射之后丟在了ThreadLocal中,而JdbcTemplate內(nèi)部執(zhí)行db操作的時(shí)候,也需要獲取連接,JdbcTemplate會(huì)以自己內(nèi)部的datasource去上面的threadlocal中找有沒有關(guān)聯(lián)的連接,如果有直接拿來用,若沒找到將重新創(chuàng)建一個(gè)連接,而此時(shí)是可以找到的,那么JdbcTemplate就參與到spring的事務(wù)中了。
步驟5:提交 or 回滾
//5.提交事務(wù):platformTransactionManager.commit
platformTransactionManager.commit(transactionStatus);
//6.回滾事務(wù):platformTransactionManager.rollback
platformTransactionManager.rollback(transactionStatus);
方式2:TransactionTemplate
方式1中部分代碼是可以重用的,所以spring對(duì)其進(jìn)行了優(yōu)化,采用模板方法模式就其進(jìn)行封裝,主要省去了提交或者回滾事務(wù)的代碼。
案例代碼位置

測(cè)試代碼
@Test
public?void?test1()?throws?Exception?{
????//定義一個(gè)數(shù)據(jù)源
????org.apache.tomcat.jdbc.pool.DataSource?dataSource?=?new?org.apache.tomcat.jdbc.pool.DataSource();
????dataSource.setDriverClassName("com.mysql.jdbc.Driver");
????dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
????dataSource.setUsername("root");
????dataSource.setPassword("root123");
????dataSource.setInitialSize(5);
????//定義一個(gè)JdbcTemplate,用來方便執(zhí)行數(shù)據(jù)庫增刪改查
????JdbcTemplate?jdbcTemplate?=?new?JdbcTemplate(dataSource);
????//1.定義事務(wù)管理器,給其指定一個(gè)數(shù)據(jù)源(可以把事務(wù)管理器想象為一個(gè)人,這個(gè)人來負(fù)責(zé)事務(wù)的控制操作)
????PlatformTransactionManager?platformTransactionManager?=?new?DataSourceTransactionManager(dataSource);
????//2.定義事務(wù)屬性:TransactionDefinition,TransactionDefinition可以用來配置事務(wù)的屬性信息,比如事務(wù)隔離級(jí)別、事務(wù)超時(shí)時(shí)間、事務(wù)傳播方式、是否是只讀事務(wù)等等。
????DefaultTransactionDefinition?transactionDefinition?=?new?DefaultTransactionDefinition();
????transactionDefinition.setTimeout(10);//如:設(shè)置超時(shí)時(shí)間10s
????//3.創(chuàng)建TransactionTemplate對(duì)象
????TransactionTemplate?transactionTemplate?=?new?TransactionTemplate(platformTransactionManager,?transactionDefinition);
????/**
?????*?4.通過TransactionTemplate提供的方法執(zhí)行業(yè)務(wù)操作
?????*?主要有2個(gè)方法:
?????*?(1).executeWithoutResult(Consumer action):沒有返回值的,需傳遞一個(gè)Consumer對(duì)象,在accept方法中做業(yè)務(wù)操作
?????*?(2). T execute(TransactionCallback 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ì)提交?
?????*?方法沒有異常?&&?未調(diào)用過transactionStatus.setRollbackOnly();
?????*/
????transactionTemplate.executeWithoutResult(new?Consumer()?{
????????@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"));
}
運(yùn)行輸出
after:[{id=1,?name=transactionTemplate-1},?{id=2,?name=transactionTemplate-2}]
代碼分析
TransactionTemplate,主要有2個(gè)方法:
executeWithoutResult:無返回值場(chǎng)景
executeWithoutResult(Consumer
transactionTemplate.executeWithoutResult(new?Consumer()?{
????@Override
????public?void?accept(TransactionStatus?transactionStatus)?{
????????//執(zhí)行業(yè)務(wù)操作
????}
});
execute:有返回值場(chǎng)景
Integer?result?=?transactionTemplate.execute(new?TransactionCallback()?{
????@Nullable
????@Override
????public?Integer?doInTransaction(TransactionStatus?status)?{
????????return?jdbcTemplate.update("insert?into?t_user?(name)?values?(?)",?"executeWithoutResult-3");
????}
});
通過上面2個(gè)方法,事務(wù)管理器會(huì)自動(dòng)提交事務(wù)或者回滾事務(wù)。
什么時(shí)候事務(wù)會(huì)回滾,有2種方式
方式1
在execute或者executeWithoutResult內(nèi)部執(zhí)行transactionStatus.setRollbackOnly();將事務(wù)狀態(tài)標(biāo)注為回滾狀態(tài),spring會(huì)自動(dòng)讓事務(wù)回滾
方式2
execute方法或者executeWithoutResult方法內(nèi)部拋出任意異常即可回滾。
什么時(shí)候事務(wù)會(huì)提交?
方法沒有異常 && 未調(diào)用過transactionStatus.setRollbackOnly();
編程式事務(wù)正確的使用姿勢(shì)
如果大家確實(shí)想在系統(tǒng)中使用編程式事務(wù),那么可以參考下面代碼,使用spring來管理對(duì)象,更簡潔一些。
先來個(gè)配置類,將事務(wù)管理器PlatformTransactionManager、事務(wù)模板TransactionTemplate都注冊(cè)到spring中,重用。
package?com.javacode2018.tx.demo3;
import?org.springframework.context.annotation.Bean;
import?org.springframework.context.annotation.ComponentScan;
import?org.springframework.context.annotation.Configuration;
import?org.springframework.jdbc.core.JdbcTemplate;
import?org.springframework.jdbc.datasource.DataSourceTransactionManager;
import?org.springframework.transaction.PlatformTransactionManager;
import?org.springframework.transaction.support.TransactionTemplate;
import?javax.sql.DataSource;
@Configuration
@ComponentScan
public?class?MainConfig3?{
????@Bean
????public?DataSource?dataSource()?{
????????org.apache.tomcat.jdbc.pool.DataSource?dataSource?=?new?org.apache.tomcat.jdbc.pool.DataSource();
????????dataSource.setDriverClassName("com.mysql.jdbc.Driver");
????????dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8");
????????dataSource.setUsername("root");
????????dataSource.setPassword("root123");
????????dataSource.setInitialSize(5);
????????return?dataSource;
????}
????@Bean
????public?JdbcTemplate?jdbcTemplate(DataSource?dataSource)?{
????????return?new?JdbcTemplate(dataSource);
????}
????@Bean
????public?PlatformTransactionManager?transactionManager(DataSource?dataSource)?{
????????return?new?DataSourceTransactionManager(dataSource);
????}
????@Bean
????public?TransactionTemplate?transactionTemplate(PlatformTransactionManager?transactionManager)?{
????????return?new?TransactionTemplate(transactionManager);
????}
}
通常我們會(huì)將業(yè)務(wù)操作放在service中,所以我們也來個(gè)service:UserService。
package?com.javacode2018.tx.demo3;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.jdbc.core.JdbcTemplate;
import?org.springframework.stereotype.Component;
import?org.springframework.transaction.support.TransactionTemplate;
import?java.util.List;
@Component
public?class?UserService?{
????@Autowired
????private?JdbcTemplate?jdbcTemplate;
????@Autowired
????private?TransactionTemplate?transactionTemplate;
????//模擬業(yè)務(wù)操作1
????public?void?bus1()?{
????????this.transactionTemplate.executeWithoutResult(transactionStatus?->?{
????????????//先刪除表數(shù)據(jù)
????????????this.jdbcTemplate.update("delete?from?t_user");
????????????//調(diào)用bus2
????????????this.bus2();
????????});
????}
????//模擬業(yè)務(wù)操作2
????public?void?bus2()?{
????????this.transactionTemplate.executeWithoutResult(transactionStatus?->?{
????????????this.jdbcTemplate.update("insert?into?t_user?(name)?VALUE?(?)",?"java");
????????????this.jdbcTemplate.update("insert?into?t_user?(name)?VALUE?(?)",?"spring");
????????????this.jdbcTemplate.update("insert?into?t_user?(name)?VALUE?(?)",?"mybatis");
????????});
????}
????//查詢表中所有數(shù)據(jù)
????public?List?userList()?{
????????return?jdbcTemplate.queryForList("select?*?from?t_user");
????}
}
bus1中會(huì)先刪除數(shù)據(jù),然后調(diào)用bus2,此時(shí)bus1中的所有操作和bus2中的所有操作會(huì)被放在一個(gè)事務(wù)中執(zhí)行,這是spring內(nèi)部默認(rèn)實(shí)現(xiàn)的,bus1中調(diào)用executeWithoutResult的時(shí)候,會(huì)開啟一個(gè)事務(wù),而內(nèi)部又會(huì)調(diào)用bus2,而bus2內(nèi)部也調(diào)用了executeWithoutResult,bus內(nèi)部會(huì)先判斷一下上線文環(huán)境中有沒有事務(wù),如果有就直接參與到已存在的事務(wù)中,剛好發(fā)現(xiàn)有bus1已開啟的事務(wù),所以就直接參與到bus1的事務(wù)中了,最終bus1和bus2會(huì)在一個(gè)事務(wù)中運(yùn)行。
上面bus1代碼轉(zhuǎn)換為sql腳本如下:
start?transaction;?//開啟事務(wù)
delete?from?t_user;
insert?into?t_user?(name)?VALUE?('java');
insert?into?t_user?(name)?VALUE?('spring');
insert?into?t_user?(name)?VALUE?('mybatis');
commit;
來個(gè)測(cè)試案例,看一下效果
package?com.javacode2018.tx.demo3;
import?org.junit.Test;
import?org.springframework.context.annotation.AnnotationConfigApplicationContext;
public?class?Demo3Test?{
????@Test
????public?void?test1()?{
????????AnnotationConfigApplicationContext?context?=?new?AnnotationConfigApplicationContext(MainConfig3.class);
????????UserService?userService?=?context.getBean(UserService.class);
????????userService.bus1();
????????System.out.println(userService.userList());
????}
}
運(yùn)行test1()輸出
[{id=18,?name=java},?{id=19,?name=spring},?{id=20,?name=mybatis}]
上面代碼中,bus1或者bus2中,如果有異?;蛘邎?zhí)行transactionStatus.setRollbackOnly(),此時(shí)整個(gè)事務(wù)都會(huì)回滾,大家可以去試試!
總結(jié)一下
大家看了之后,會(huì)覺得這樣用好復(fù)雜啊,為什么要這么玩?
的確,看起來比較復(fù)雜,代碼中融入了大量spring的代碼,耦合性比較強(qiáng),不利于擴(kuò)展,本文的目標(biāo)并不是讓大家以后就這么用,主要先讓大家從硬編碼上了解spring中事務(wù)是如何控制的,后面學(xué)起來才會(huì)更容易。
我們用的最多的是聲明式事務(wù),聲明式事務(wù)的底層還是使用上面這種方式來控制事務(wù)的,只不過對(duì)其進(jìn)行了封裝,讓我們用起來更容易些。
