必要
電子商取引のシナリオを例に取ると、メイン注文に複数のサブ注文が含まれていると仮定すると、メイン注文が送信されたときに一部のサブ注文が正常に完了しない可能性があり、正常に完了したサブ注文は実行する必要があります。対応するデータベース操作は正常に実行され、完了できないサブオーダーはロールバックされます。データベース操作が実行されました。
技術ポイントの復習
宣言的トランザクション
現在、Springフレームワークで一般的に使用されているトランザクションロールバック方法は、メソッドに@Transactionalを追加する「アノテーションベースの宣言型トランザクション」です。
@Transactional で一般的に使用される属性:
属性 | タイプ | 説明 |
---|---|---|
属性 | タイプ | 説明 |
価値 | 弦 | 使用するトランザクション マネージャーを指定するオプションの修飾記述子 |
伝搬 | 列挙型: 伝播 | オプションのトランザクション伝播動作設定 |
隔離 | 列挙型: 分離 | オプションのトランザクション分離レベル設定 |
読み取り専用 | ブール値 | 読み取り/書き込みまたは読み取り専用トランザクション、デフォルトは読み取り/書き込み |
タイムアウト | int (秒単位の粒度) | トランザクションタイムアウト設定 |
ロールバック用 | Class オブジェクトの配列。Throwable から継承する必要があります。 | トランザクションのロールバックを引き起こした例外クラスの配列 |
クラス名のロールバック | クラス名の配列。Throwable から継承する必要があります。 | トランザクションのロールバックの原因となった例外クラス名の配列 |
noRollbackFor | Class オブジェクトの配列。Throwable から継承する必要があります。 | トランザクションのロールバックを引き起こさない例外クラスの配列 |
noRollbackForClassName | クラス名の配列。Throwable から継承する必要があります。 | トランザクションのロールバックを引き起こさない例外クラス名の配列 |
このうち、伝播属性のオプションの値は次のとおりです。
-
propagation はトランザクションの伝播動作を表します。デフォルト値は Propagation.REQUIRED です。その他の属性情報は次のとおりです。
-
Propagation.REQUIRED: 現在のトランザクションがある場合はトランザクションに参加し、現在のトランザクションがない場合は新しいトランザクションを作成します。
-
Propagation.SUPPORTS: 現在トランザクションがある場合はトランザクションに参加し、現在トランザクションがない場合は非トランザクション方式で実行を継続します。
-
Propagation.MANDATORY: 現在トランザクションがある場合はトランザクションに参加し、現在トランザクションがない場合は例外をスローします。
-
Propagation.REQUIRES_NEW: 新しいトランザクションを再作成します。現在のトランザクションがある場合は、現在のトランザクションを一時停止します。
-
Propagation.NOT_SUPPORTED: 非トランザクション方式で実行します。現在のトランザクションがある場合は、現在のトランザクションを一時停止します。
-
Propagation.NEVER: 非トランザクション方式で実行し、現在のトランザクションがある場合は例外をスローします。
-
Propagation.NESTED : Propagation.REQUIRED と同じ効果。
プログラムによるトランザクション
プログラムによるトランザクション方法では、開発者はコード内でトランザクションの開始、送信、ロールバック、その他の操作を手動で管理する必要がありますが、これはコードにとって非常に煩わしいものです。
解決策 1 (実現不可能)
最初に、作成者はこのメソッドが実行可能かどうかをローカルでテストしましたが、再度実行すると、次のエラーが報告されました。確認したところ、
org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope
同じ
サブメソッドを呼び出すときに親メソッドがプロキシを経由せず、@サブメソッドのトランザクション アノテーションは当然無効です。したがって、この方法は現実的ではなく、参考としてのみ使用してください。
public void executeMainOrder() {
for (Order subOrder: subOrderList){
try {
executeSubOrder(subOrder);
} catch(Exception e){
// 子订单执行失败则记录相关参数
log(e, subOrder);
}
}
}
// 每个子订单采用独立的事务进行入库操作
@Transactional(propagation = Propagation.REQUIRES_NEW)
private void executeSubOrder(Order subOrder){
try {
// 执行入库操作
baseMapper.insert(subOrder);
// 执行业务操作
handleOrder(subOrder);
} catch (Exception e){
// 手动回滚当前子订单所处的事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
// 抛出自定义异常
throw new ServiceException("");
}
}
解決策 2 (実現可能)
解決策も非常に簡単で、同種のメソッドを呼び出すとプロキシを経由しないことになるので、メソッドを別のクラスに分割して呼び出した方が良いため、サブメソッドを1つのクラスに抽出することにしました。関連するアノテーションを追加すると、親メソッドは正常に呼び出すことができます。
// A类
@Autowire
BService bService;
public void executeMainOrder() {
for (Order subOrder: subOrderList){
try {
bService.executeSubOrder(subOrder);
} catch(Exception e){
// 子订单执行失败则记录相关参数
log(e, subOrder);
}
}
}
// B类
// 每个子订单采用独立的事务进行入库操作
@Transactional(rollbackFor = "Exceptioin.class", propagation = Propagation.REQUIRES_NEW)
private void executeSubOrder(Order subOrder){
try {
// 执行入库操作
baseMapper.insert(subOrder);
// 执行业务操作
handleOrder(subOrder);
} catch (Exception e){
// 抛出自定义异常
throw new ServiceException("");
}
}
参考記事
https://www.cnblogs.com/better-farther-world2099/articles/14982412.html
https://www.cnblogs.com/process-h/p/16777472.html
https://www.jianshu.com/ p/00758c77bf60