環境:
Springboot:2.2.3.RELEASE
春:5.2.3.RELEASE
春のトランザクション伝播の振る舞い列挙ソースを参照してください。 org.springframework.transaction.annotation.Propagationを
春のトランザクション伝播メカニズムで見てみましょう(下手な英語による、グーグルの直接の翻訳)
単純に最後を見て:
列挙 | 翻訳 |
必須 |
トランザクションが存在しない場合は、新しい(デフォルト)を作成し、現在のトランザクションをサポートしています
|
SUPPORTS | 非トランザクションの実行がある場合は、現在のトランザクションをサポートしています |
MANDATORY | スローされる例外がない場合、現在のトランザクションをサポートしています。 |
REQUIRES_NEW | (存在する場合)新しいトランザクションを作成し、現在のトランザクションを一時停止 |
サポートされていません | 非トランザクションの実行、現在のトランザクションの場合、現在のトランザクションが保留されています |
決して | トランザクションがある場合は非トランザクションの実行、例外がスローされます |
NESTED | 現在のトランザクションが存在する場合は、ネストされたトランザクションを実行 |
次に、確認コード(以下のコード例は、最終的に支払い部材にパッケージ化されます)
準備:
データベース(各検証の前に空のデータベースとなっています):
create table demo
(
id int auto_increment
primary key,
uid varchar(30) not null,
remark varchar(30) null,
inputdate datetime not null,
constraint demo_uid_uindex
unique (uid)
);
要求ルーティング
@RequestMapping("/test/{trans}")
public String test(@PathVariable("trans") String trans){
if("never".equals(trans)){
transService.neverTrans();
}else if("mandatory".equals(trans)){
transService.mandatoryTrans();
}else if("required".equals(trans)){
transService.requiredTrans();
}else if("trans".equals(trans)){
transService.trans();
}else if("supports".equals(trans)){
transService.supportsTrans();
}else if("supportswithtrans".equals(trans)){
transService.supportsWithTrans();
}else if("requeirednew".equals(trans)){
transService.requiredNewTrans();
}else if("notsupported".equals(trans)){
transService.notsupportedTrans();
}else if("netsted".equals(trans)){
transService.nestedTrans();
}else if("netstedwithserviceerror".equals(trans)){
transService.nestedWithTransServiceError();
}else if("netstedwithdaoerror".equals(trans)){
transService.nestedWithTransDaoError();
}
return "ok";
}
Mapper.xml
<insert id="insertDemoWithMandatory">
insert into demo (uid, remark, inputdate) value (#{uid,jdbcType=VARCHAR},#{remark,jdbcType=VARCHAR},CURRENT_TIMESTAMP);
</insert>
<insert id="insertDemoWithNever">
insert into demo (uid, remark, inputdate) value (#{uid,jdbcType=VARCHAR},#{remark,jdbcType=VARCHAR},CURRENT_TIMESTAMP);
</insert>
<insert id="insertDemo">
insert into demo (uid, remark, inputdate) value (#{uid,jdbcType=VARCHAR},#{remark,jdbcType=VARCHAR},CURRENT_TIMESTAMP);
</insert>
<insert id="insertDemoError">
insert into demo (uid, remark, inputdate) value (#{uid1,jdbcType=VARCHAR},#{remark,jdbcType=VARCHAR},CURRENT_TIMESTAMP);
</insert>
<insert id="insertDemoWithSupportnew">
insert into demo (uid, remark, inputdate) value (#{uid,jdbcType=VARCHAR},#{remark,jdbcType=VARCHAR},CURRENT_TIMESTAMP);
</insert>
<insert id="insertDemoWithNested">
insert into demo (uid, remark, inputdate) value (#{uid,jdbcType=VARCHAR},#{remark,jdbcType=VARCHAR},CURRENT_TIMESTAMP);
</insert>
<insert id="insertDemoWithNestedOnError">
insert into demo (uid, remark, inputdate) value (#{uid1,jdbcType=VARCHAR},#{remark,jdbcType=VARCHAR},CURRENT_TIMESTAMP);
</insert>
DAO:
@Transactional(propagation = Propagation.NEVER)
int insertDemoWithNever(@Param("uid")String uid,@Param("remark") String remark);
@Transactional(propagation = Propagation.MANDATORY)
int insertDemoWithMandatory(@Param("uid")String uid, @Param("remark") String remark);
@Transactional(propagation = Propagation.REQUIRES_NEW)
int insertDemoWithSupportnew(@Param("uid")String uid, @Param("remark") String remark);
int insertDemo(@Param("uid")String uid, @Param("remark") String remark);
int insertDemoError(@Param("uid")String uid, @Param("remark") String remark);
@Transactional(propagation = Propagation.NESTED)
int insertDemoWithNested(@Param("uid")String uid, @Param("remark") String remark);
@Transactional(propagation = Propagation.NESTED)
int insertDemoWithNestedOnError(@Param("uid")String uid, @Param("remark") String remark);
-
0.なしノー取引
データの矛盾にトランザクションがないので、SQL、エラーの1の2つのストレージを用意となります:アイデアを確認してください
パス:/テスト/トランス
TransService:
/**
* 不带事务的 遇到错误不会回滚,会导致数据不一致(部分写入成功,部分写入失败)
* @return
*/
@Override
public boolean trans() {
demoMapper.insertDemo("uid1","transService");
demoMapper.insertDemoError("uid2","transService1");
return false;
}
分析:
demoMapper.insertDemoError(「UID2」、「transService1」);なぜなら割り当てにおけるエラーのSQL時間、時間の異常な結合パラメータが得られるが、例外が発生したときにこれらの2つの方法は、事務オンされていないため、demoMapper.insertDemo( " UID1「」transService「);それは、矛盾したデータが得られ、完成されたものです。
データベースの実装後:
总结:没有事务容易造成数据的不一致性。
-
1. REQUIRED 支持当前事务,如果不存在则创建新事务
验证思路:准备两条入库的SQL,其中一条报错,开启事务,报错会导致事物回滚
路径:/test/required
TransService:
/**
* 该方法在事务里执行,如果当前没有事务开启一个事务(默认没有事务,见this.trans())
* @return
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public boolean requiredTrans() {
demoMapper.insertDemo("uid1","transService");
demoMapper.insertDemoError("uid2","transService1");
return false;
}
分析:
与没有事务相比,该方法前声明了事务传播行为REQUIRED,执行到 demoMapper.insertDemoError("uid2","transService1");时报错,导致事务回滚,数据库数据库没有保存任何信息
总结:REQUIRED会开启事务,且发生异常时,事务会进行回滚,数据一致性的到保证。
执行完毕后数据库:
-
2. SUPPORTS 支持当前事务,如果不存在则非事务执行
验证思路:分别在有/没有事务的方法里,调用另一个事务传播行为SUPPORTS的方法
路径:
1. 当前没有事务:/test/supports
2. 当前有事务:/test/supportswithtrans
TransService:
/**
* 当前没有事物,demoService.supportsTrans()的也不会有事务,会插入一条数据,造成数据不一致
* @return
*/
@Override
public boolean supportsTrans() {
demoService.supportsTrans();
return false;
}
/**
* 当前有事物,demoService.supportsTrans()的也会有事务,不会造成数据不一致
* @return
*/
@Transactional(propagation = Propagation.REQUIRED)
@Override
public boolean supportsWithTrans() {
demoService.supportsTrans();
return false;
}
DemoService
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public boolean supportsTrans() {
demoMapper.insertDemo("uid1","demoService");
demoMapper.insertDemoError("uid2","demoService1");
return false;
}
分析:
supportsTrans()未声明事务,demoService.supportsTrans();以无事务的行为执行,Mapper执行时遇到错误不会回滚。
supportsWithTrans()声明事务,demoService.supportsTrans();以有事务的行为执行,Mapper执行时遇到错误会回滚。
总结:
SUPPORTS 不会主动创建事务,是否支持事务来自于其调用方是否开启了事务。
执行完毕后数据库:
-
3. MANDATORY 支持当前事务,如果不存在则抛出异常
验证思路:分别在没有事务的方法里,调用另一个事务传播行为MANDATORY的方法
路径:/test/mandatory
TransService:
/**
* 当前方法未开启事务,调用的demoMapper.insertDemoWithMandatory(String,String) 必须在事务中执行,会报错
* @return
*/
@Override
public boolean mandatoryTrans() {
System.out.println("service[none trans],dao[propagration=mandatory]");
demoMapper.insertDemoWithMandatory("uid","mandatorytest");
return false;
}
分析:
mandatoryTrans()未声明事务,demoMapper.insertDemoWithMandatory();声明了行为为MANDATORY的事务,insertDemoWithMandatory会因为没有事务而报错
IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
总结:
MANDATORY不会主动创建事务,但是其要求当前必须有事务
执行完毕后数据库:
未触发
-
4. REQUIRES_NEW 创建一个新事务,并暂停当前事务(如果存在)
验证思路:在有事务的方法里,调用另一个事务传播行为REQUIRED_NEW的方法,且在执行完后执行一条错误的代码,导致当前事务回滚
路径:/test/requeirednew
TransService:
/**
* 当前有事务,demoMapper.insertDemoWithSupportnew(String,String)开始了自己的事务,
* 当前事务回滚不会造成demoMapper.insertDemoWithRequiresNew事务的回滚
* @return
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public boolean requiredNewTrans() {
demoMapper.insertDemo("uid1","transService1");
demoMapper.insertDemoWithRequiresNew("uid2","transService1");
demoMapper.insertDemoError("uid3","transService1");
return false;
}
分析:
执行完毕demoMapper.insertDemoWithRequiresNew("uid2","transService1");后继续执行requiredNewTrans()引发异常,会导致requiredNewTrans()的事务回滚,但并不影响insertDemoWithRequiresNew的事务,insertDemoWithRequiresNew会正常执行
总结:
REQUIRES_NEW开启了的新事务并不是加入到当前的事务中来,当前事务的异常并不会引发REQUIRES_NEW方法的事务回滚。
执行完毕后数据库:
-
5. NOT_SUPPORTED 非事务执行,如果存在当前事务,则挂起当前事务
验证思路:在有事务的方法里,调用另一个事务传播行为NOT_SUPPORTED的方法, 且NOT_SUPPORTED的方法会执行异常,但会被捕获并处理。
路径:/test/notsupported
TransService:
/**
* 当前有事务,demoMapper.insertDemoWithSupportnew(String,String)开始了自己的事务,
* * 当前事务回滚不会造成demoMapper.insertDemoWithSupportnew事务的回滚
* @return
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public boolean notsupportedTrans() {
demoMapper.insertDemo("uid1","transService1");
try {
demoService.insertDemoWithnotSupported();
}catch (Exception e){
System.out.println(" demoService.insertDemoWithnotSupported执行报错,其数据不一致");
}
demoMapper.insertDemo("uid2","transService1");
return false;
}
DemoService
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public boolean insertDemoWithnotSupported() {
demoMapper.insertDemo("uid1-1","demoService");
demoMapper.insertDemoError("uid2-1","demoService1");
return false;
}
分析:
notsupportedTrans()执行过程中未发生异常,事务正常提交,insertDemoWithnotSupported执行到demoMapper.insertDemoError("uid2-1","demoService1");会发生异常,当前DemoService没有事务,第一条会正常插入,数据库中应该为三条(2+1)。
总结:
NOT_SUPPORTED对当前的事务做了一个挂起后执行自己的部分,并且自己这部分时以无事务的形式执行,其异常并不会回滚,会导致数据不一致。NOT_SUPPORTED并不会影响挂起的事务的继续执行及提交或回滚。
执行完毕后数据库:
-
6. NEVER 非事务执行,如果存在事务,则引发异常
验证思路:在有事务的方法里,调用另一个事务传播行为NEVER的方法
路径:/test/never
TransService:
/**
* 当前方法开启事务,调用的demoMapper.insertDemoWithNever(String,String) 不允许事务,会报错
* @return
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public boolean neverTrans() {
System.out.println("service[propagation=requeired],dao[propagration=never]");
demoMapper.insertDemoWithNever("uid","nevertest");
return false;
}
分析:
neverTrans()开启了当前事务,调用不支持的事务方法时,触发了异常:
IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
总结:
NEVER并不会挂起当前事务,而是在当前存在事务时直接异常,强制无事务。
执行完毕后数据库:
未触发
-
7. NESTED 如果当前事务存在,则在嵌套事务中执行
验证思路:step1. 在无事务的方法里,调用另一个事务传播行为NESTED的方法,同时NESTED的方法会报错,以此来检查嵌套事务是否会影响无事务方法的开启。
step2. 在有事务的方法里,调用另一个事务传播行为NESTED的方法,同时NESTED的方法会报错,以此来检查嵌套事务是否会影响当前事务方法的提交。
step3. 在有事务的方法里,调用另一个事务传播行为NESTED的方法,当前事务会报错,以此来检查是否会影响嵌套事务提交。
路径:
1.当前无事务 /test/netsted
2. 当前有事务,嵌套事务异常:/test/netstedwithdaoerror
3. 当前有事务,且有异常:/test/netstedwithserviceerror
TransService:
/**
* 当前没有事务,demoService.insertDemoWithNested会开启事务,其失败不会造成其本身的数据不一致,
* 但不会影响当前事务的特性(当前异常会因为没有事务而引发数据不一致)
* @return
*/
@Override
public boolean nestedTrans() {
demoMapper.insertDemo("uid1","transService1");
try {
demoService.insertDemoWithNested();
}catch (Exception e){
System.out.println(" demoService.insertDemoWithNested,其数据一致");
}
demoMapper.insertDemoError("uid2","transService1");
return false;
}
/**
* 当前有事务,demoMapper.insertDemoWithNested会开启嵌套事务,其事务的提交和回滚依赖当前事务,
* 当前事务失败,其事务不会被提交
* @return
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public boolean nestedWithTransServiceError() {
demoMapper.insertDemo("uid1","transService1");
try {
demoMapper.insertDemoWithNested("uid-0","transService");
}catch (Exception e){
System.out.println(" demoService.insertDemoWithNested,其数据一致");
}
demoMapper.insertDemoError("uid2","transService1");
return false;
}
/**
* 当前有事务,demoMapper.insertDemoWithNested会开启嵌套事务,其事务的提交和回滚依赖当前事务,
* 当前事务成功,其事务将被提交,但其失败不会影响当前事务
* @return
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public boolean nestedWithTransDaoError() {
demoMapper.insertDemo("uid1","transService1");
try {
demoMapper.insertDemoWithNestedOnError("uid-0","transService");
}catch (Exception e){
System.out.println(" demoService.insertDemoWithNestedOnError");
}
return false;
}
DemoService
@Override
@Transactional(propagation = Propagation.NESTED)
public boolean insertDemoWithNested() {
demoMapper.insertDemo("uid1-1","demoService");
demoMapper.insertDemoError("uid2-1","demoService1");
return false;
}
分析:
当前无事务的情况:嵌套事务遇到错误时是回滚,对当前调用方没有影响。
当前有事务,嵌套事务异常情况:当前事务正常提交,嵌套事务因为异常并未成功保存数据。
当前有事务有异常,嵌套事务无异常情况:嵌套事务未提交成功,当前是一回滚。
总结:
通过三组对比发现,嵌套事务的在当前事务失败的时候不会进行提交,但当前正常提交时,嵌套事务是能正常提交和回滚的,也就是嵌套事务的提交取决于当前事务的执行情况。
特别注意:NESTED传播行为在有些地方解释为不通的厂商实现机制不一样,用之前需要再充分了解一下,从Spring的源码上看,也确实如此。