WeChat には多くの技術記事が集められています.私はそれらを読む時間がありませんでした.この間にそれらを見て、いくつかの重要なポイントを抽出します.後で読むことができます.
取引失敗6例
1. メソッドの自己呼び出し
宣言型トランザクションの最下層は実際には AOP であるため、宣言型トランザクションでは、取得するサービス クラスはサービス クラス自体ではなく、プロキシ オブジェクトであり、このプロキシ オブジェクトのプロキシ メソッドでは、自動的にトランザクションのロジックが壊れているため、このプロキシ オブジェクトを経由せずにメソッドを直接呼び出すと、トランザクションが無効になります。
public class UserService{
@Transactional
public void sayHello(){
}
}
UserService を UserController に注入すると、得られるのは UserService オブジェクトそのものではなく、動的プロキシを介して UserService 用に生成された動的プロキシ クラスです。この動的プロキシは次のようなものです (疑似コード)。
public class UserServiceProxy extends UserService{
public void sayHello(){
try{
//开启事务
//调用父类 sayHello
//提交事务
}catch(Exception e){
//回滚事务
}
}
}
したがって、最終的に呼び出すのは UserService 自体のメソッドではなく、動的プロキシ オブジェクトのメソッドです。したがって、次のようなコードがある場合:
public class UserService{
@Transactional
public void sayHello(){
}
public void useSayHello(){
sayHello();}
}
useSayHello で sayHello メソッドを呼び出す. sayHello メソッドには transaction アノテーションがありますが、ここでの transaction は有効になりません (呼び出しが動的プロキシ オブジェクトの sayHello メソッドではなく、現在のオブジェクトの sayHello メソッドであるため) .
2. 例外がキャッチされる
sayHello メソッドで例外をキャッチすると、動的プロキシ クラスのメソッドは、対象のメソッドに例外があることを認識し、当然、トランザクションのロールバックを自動的に処理しません。または、前の UserServiceProxy を例に取ります。
public class UserServiceProxy extends UserService{
public void sayHello(){
try{
//开启事务
//调用父类 sayHello
//提交事务
}catch(Exception e){
//回滚事务
}
}
}
親クラスの sayHello を呼び出すときに、sayHello メソッドが自動的に例外をキャッチする場合、明らかに、ここでロールバックする例外はありません 3.
メソッドは public ではあり
ません 4. 非ランタイム例外: デフォルトでは、RuntimeException のみがキャッチされます
5 .Not Spring Bean
の宣言型トランザクションは、主に動的プロキシを介してトランザクションを処理します.取得した UserService オブジェクトが元の UserService である場合 (新しい UserService を作成した場合)、トランザクション コードはどこにあるのでしょうか? トランザクション処理用のコードがなければ、トランザクションは当然有効になりません。
宣言型トランザクションの核となるのは、動的プロキシによって生成されるオブジェクトであり、オブジェクトが使用されない場合、トランザクションは役に立たなくなります。
6. データベースは
長いトランザクションをサポートしていません。
@Transactional アノテーションは AOP を使用して実装されており、その本質はターゲット メソッドの実行の前後にインターセプトすることです。対象のメソッドの実行前にトランザクションを結合または作成し、実行メソッドの実行後に実際の状況に応じてトランザクションをコミットまたはロールバックすることを選択します。
Spring がこのアノテーションに遭遇すると、自動的にデータベース接続プールから接続を取得し、トランザクションを開始してから ThreadLocal にバインドします. @Transactional アノテーションでラップされたメソッド全体は、同じ接続接続を使用します. サードパーティ インターフェイスの呼び出し、複雑なビジネス ロジック、大規模なデータ処理など、時間のかかる操作があると、この接続を長時間占有することになり、データベース接続が占有されます。そして解放されません。類似の操作が多すぎると、データベース接続プールが使い果たされます。
トランザクション内で RPC 操作を実行すると、データベース接続プールがバーストするのは、典型的なロング トランザクションの問題です. 同様の操作には、トランザクション内の大量のデータ クエリ、ビジネス ルール処理などが含まれます...
長時間の取引を避けるには?
長いトランザクションを解決する目的は、トランザクション メソッドを分割し、トランザクションを小さく高速化し、トランザクションの粒度を減らすことです。
宣言的トランザクション: メソッドに @Transactional アノテーションを使用してトランザクションを管理する操作を宣言的トランザクションと呼びます。欠点は、トランザクションの粒度がメソッド全体であり、細かく制御できないことです。
プログラムによるトランザクション: 開発者は、基礎となる API に基づいて、コードのオープン、コミット、ロールバックなどのトランザクションを手動で管理します。Spring プロジェクトでは、TransactionTemplate クラスのオブジェクトを使用して、トランザクションを手動で制御できます。
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(RequestBill requestBill) {
transactionTemplate.execute(transactionStatus -> {
requestBillDao.save(requestBill);
//保存明细表
requestDetailDao.save(requestBill.getDetail());
return Boolean.TRUE;
});
}
長いトランザクションを回避する最も簡単な方法は、宣言型トランザクション @Transactional を使用せず、プログラムによるトランザクションを使用して手動でトランザクション スコープを制御することです。
@Transactional を使用する場合は、メソッドを分割して、トランザクション管理を必要としないロジックをトランザクション操作から分離する必要があります。
@Service
public class OrderService{
public void createOrder(OrderCreateDTO createDTO){
query();
validate();
saveData(createDTO);
}
//事务操作
@Transactional(rollbackFor = Throwable.class)
public void saveData(OrderCreateDTO createDTO){
orderDao.insert(createDTO);
}
}
query() と validate() はトランザクションを必要としないため、それらをトランザクション メソッド saveData() から分離します。この種の分割は、@Transactional アノテーションを使用するとトランザクションが有効にならないという古典的なシナリオに当てはまり、多くの初心者はこの間違いを犯しやすいです。@Transactional で注釈が付けられた宣言型トランザクションは、Spring AOP を介して機能し、Spring AOP はプロキシ オブジェクトを生成する必要があります.同じクラスで直接メソッド呼び出しを行うと、元のオブジェクトが使用され、トランザクションは有効になりません.正しい分割メソッドは、以下の
2つ
1. 新しいマネージャーレイヤーを追加するなど、メソッドを別のクラスに入れ、Springを介して注入できます。これにより、オブジェクト間の呼び出しの条件を満たすことができます。
@Service
public class OrderService{
@Autowired
private OrderManager orderManager;
public void createOrder(OrderCreateDTO createDTO){
query();
validate();
orderManager.saveData(createDTO);
}
}
@Service
public class OrderManager{
@Autowired
private OrderDao orderDao;
@Transactional(rollbackFor = Throwable.class)
public void saveData(OrderCreateDTO createDTO){
orderDao.saveData(createDTO);
}
}
2. @EnableAspectJAutoProxy(exposeProxy = true) をスタートアップ クラスに追加し、メソッドで AopContext.currentProxy() を使用してプロキシ クラスを取得し、トランザクションを使用します。
SpringBootApplication.java
@EnableAspectJAutoProxy(exposeProxy = true)
@SpringBootApplication
public class SpringBootApplication {
}
OrderService.java
public void createOrder(OrderCreateDTO createDTO){
OrderService orderService = (OrderService)AopContext.currentProxy();
orderService.saveData(createDTO);
}
再入可能ロック
実装原理: volatile 変数 + CAS 設定値 + AQS + 2 つのキュー
ブロッキングの実現: 同期キュー + CAS は、valatile としてマークされた状態を横取りします
ウェイクアップの待機を実現する: await: ロックを保持し、パーク -> 待機キューに参加; シグナル: 次の待機キュー ノードをウェイクアップし、同期キューに転送し、ブロッキング キューに従って CAS がプリエンプトまたはプリエンプションを待機します。その後、次のコンテンツを待機するプログラムを実行し続けることができます。
ReentrantLock は Lock インターフェイスを継承し、lock メソッドは実際には Sync サブクラス NonfairSync (不公平なロック) の lock メソッドを呼び出します。ReentrantLock の実際の実装は、彼の 2 つの内部クラスである NonfairSync と FairSync にあり、デフォルトの実装は不公平なロックです。内部クラスはすべて内部クラス Sync から継承され、Sync の基本的な実装は有名な AbstractQueuedSynchronizer (AQS) です。
Synchronized と同じポイント:
1. ReentrantLock と synchronized はどちらも排他ロックであり、相互に排他的なクリティカル セクションへのアクセスのみをスレッドに許可します。
しかし、この 2 つは実装が異なります: 同期のロックとロック解除のプロセスは暗黙的であり、ユーザーが手動で操作する必要はありません. 利点は、操作が単純であるが、十分な柔軟性がないことです. 一般に、同時実行シナリオでは同期で十分です。ReentrantLock は手動でロックおよびロック解除する必要があり、ロック解除操作は、スレッドがロックを正しく解放するように、可能な限り finally コード ブロックに配置する必要があります。ReentrantLock 操作はより複雑ですが、ロックとロック解除のプロセスを手動で制御できるため、複雑な同時実行シナリオで役立ちます。
2. ReentrantLock と synchronized は両方とも再入可能ロックです。
Synchronized は再入可能であるため、再帰的に実行されるメソッドに配置でき、最後にスレッドが正しくロックを解放できるかどうかを心配する必要はありませんが、ReentrantLock は、ロックを繰り返し取得する回数を保証する必要があります。リエントラント時にロックを解放する回数と同じ回数を繰り返さないと、他のスレッドがロックを獲得できなくなる可能性があります。
3. スレッド間の待ち通知機構を実現できます。Object の wait メソッドと notify メソッドを組み合わせて synchronized を使用すると、スレッド間の待機通知メカニズムを実現できます。ReentrantLock と Condition インターフェイスを組み合わせることで、この機能を実現することもできます。また、前者よりも明確で使いやすいです。
Synchronized との違い:
1. ReentrantLock は Java レベルで実装され、synchronized は JVM レベルで実装されます。
2. 同期を実現するために synchronized キーワードを使用します. スレッドは同期コード ブロックの実行後に自動的にロックを解放します (スレッドは同期コードの実行後にロックを解放します. b はスレッド実行中に例外が発生したときにロックを解放します), ReentrantLock は手動でロックを解放する必要がありますが、最終的に手動でロックを解放する (unlock() メソッドがロックを解放する) と、スレッドのデッドロックが発生しやすくなります。
3. Synchronized は不公平なロックであり、ReentrantLock は公平なロックと不公平なロックを実装できます。
4. ReentrantLock は、ロックを取得するためのタイムアウトを設定できます。指定された期限までにロックを取得し、期限までにロックが取得されていない場合はリターンします。再試行メカニズムと連携して、デッドロックをより適切に解決します。
5. ReentrantLock でロックの取得を待機しているスレッドは割り込み可能であり、スレッドはロックの待機をあきらめることができます。そして、synchonized は無期限に待機します。
6. ReentrantLock の tryLock() メソッドは、非ブロッキングでロックを取得しようとし、このメソッドを呼び出した直後に戻ります. 取得できた場合は true を返し、取得できなかった場合は false を返します.
7. Synchronized はロックの状態を取得するかどうかを判断できませんが、Lock はロックを取得するかどうかを判断でき、アクティブにロックを取得しようとすることができます。
Spring と IDEA の両方が @Autowired アノテーションを推奨しない理由
IDEA で開発する場合、フィールドで Spring の依存性注入アノテーション @Autowired を使用すると、次の警告が表示されます。
フィールド注入は推奨されません (フィールド注入は推奨されません)
Spring の一般的な DI メソッド
コンストラクター注入: 構築メソッドのパラメーターを使用して依存関係を注入する
セッター注入: セッター メソッドを呼び出して依存関係を注入する
フィールド注入: フィールドに @Autowired/Resource アノテーションを使用する
@Autowired VS @Resource は、
@Autowired が Spring によって定義されているのに対し、@Resource は JSR-250 によって定義されていることを除いて、アノテーションを介して依存性注入を実装します。一般的な機能は基本的に同じですが、詳細にいくつかの違いがあります。
依存関係の識別方法: @Autowired のデフォルトは byType であり、@Qualifier を使用して名前を指定できます. @Resource のデフォルトは ByName. 見つからない場合、ByType はオブジェクトに適用されます: @Autowired は、コンストラクター、メソッド、パラメーター
、およびフィールド. @Resource はメソッドにのみ使用できます. フィールド使用
プロバイダー: @Autowired は Spring によって提供されます. @Resource は JSR-250 によって提供されます.
さまざまな DI メソッドの長所と短所
Spring の公式ドキュメントを参照すると、次の使用シナリオが提案されています。
コンストラクター注入: 強い依存関係 (つまり、この依存関係を使用する必要があります)、不変性 (各依存関係は頻繁に変更されません)
セッター注入: オプション (この依存関係がなくても機能します)、変数 (依存関係は頻繁に変更されます)
フィールド注入: ほとんどの場合必要に応じて、@Resource は @Autowired よりも IC コンテナーとの結合が少ない
フィールド注入の欠点
不変オブジェクトはコンストラクターのように注入することはできません 依存
関係は外の世界には見えず、コンストラクターとセッターは外の世界から見ることができますが、プライベート フィールドは見ることができず、必然的に必要な依存関係を理解できません. これにより、コンポーネントとコンポーネント間の密結合が発生します
。 IoC コンテナー (これが最も重要な理由は、IoC コンテナーなしで依存関係を注入することが非常に難しいということです) そのため、
単体テスト
でも IoC コンテナーを使用する必要があります。例えば10個の依存関係が必要な場合、コンストラクターで注入すると巨大に見えるので、このとき、このコンポーネントが単一責任の原則に違反していないかを検討する必要があります。
利点は利便性. コンストラクターまたはセッター注入を使用すると、ビジネスに依存しないコードをより多く作成する必要があり、これは非常に面倒ですが、フィールド注入はそれらを大幅に簡素化します. そしてほとんどの場合、ビジネス コードとフレームワークは強く結び付いており、完全な疎結合は理想的なことであり、俊敏性を犠牲にして疎結合を過度に追求することは価値がありません。
なぜ IDEA は、@Autowired が
Spring によって提供されていることを @Autowired に警告するだけなのか. これは、特定の IoC によって提供される特定の注釈であり、アプリケーションとフレームワークの間の強力なバインディングにつながります. 他の IoC フレームワークが使用されると、インジェクションはサポートされません. . また、@Resource は Java の標準である JSR-250 で提供されており、コンテナを入れ替えても正常に動作するように、使用する IoC コンテナはこれに対応している必要があります。