記事ディレクトリ
- 失敗シナリオ
失敗シナリオ
1 データベースはまずトランザクションをサポートする必要があります。
mysql を例にとると、その MyISAM エンジンはトランザクション操作をサポートしませんが、InnoDB はトランザクションのみをサポートします。mysql5.5 以降、デフォルトのエンジンは InnoDB で、以前のデフォルトは MyISAM でした。
2. データ ソースにはトランザクション マネージャーが構成されていません
3. 春によって管理されない
トランザクションアノテーションはサービス層に追加されますが、 @Service アノテーションが無い場合、このクラスはBeanにロードされず、Springで管理されず、当然トランザクションは失敗します。
4. メソッドは非公開です
Spring の公式 Web サイトによると、@Transactional はパブリック メソッドでのみ使用でき、それ以外の場合はトランザクションは有効になりませんが、非パブリック メソッドで使用する必要がある場合は、AspectJ プロキシ モードを有効にすることができます。AspectJ はロード時ウィービングを使用してすべてのポイントカットをサポートするため、内部メソッドのトランザクション設定をサポートできます。
5.@Transactionalアノテーション属性伝播設定エラー
注釈設定がトランザクションをサポートしていない場合は、伝播が使用されます。NOT_SUPPORTED: トランザクションの実行が容易ではないことを示します。現在トランザクションが存在する場合、トランザクションは一時停止されます。
6. 同じクラス内のメソッド呼び出しにより、@Transactional が無効になります。
開発中、同じクラス内のメソッドを呼び出すことが避けられません。たとえば、メソッド a を持つクラス Test があり、A はこのクラスのメソッド b を呼び出します (メソッド b がパブリックまたはプライベートで変更されているかどうかは関係ありません)。 , しかし、メソッド A はそうではありません。アノテーション トランザクションを宣言し、b メソッドはそれを持ちます。メソッド a を外部から呼び出した後、メソッド B のトランザクションは有効になりません。ここは間違いが起こりやすい箇所でもあります。
では、なぜこのようなことが起こるのでしょうか? 実際、トランザクション メソッドが現在のクラス外のコードによって呼び出された場合にのみ、Spring によって生成されたプロキシ オブジェクトによって管理されるため、これは依然として Spring AOP プロキシの使用によって発生します。
//@Transactional
@GetMapping("/test")
private void a(User user) throws Exception {
userMapper.insert(user);
b(user);
}
@Transactional
public void b(User user) throws Exception {
doSomething();
}
補充:
メソッド a にも @Transactional アノテーションが付けられている場合、トランザクションはこの時点で有効になりますか?
上面的代码只是放开a方法上的@Transactional注解,此时a方法的事务依然是无法生效的。我们看到在事务方法a中,直接调用事务方法b。从前面介绍的内容可知,b方法拥有事务是因为spring aop生成了代理对象,但是这种方法直接调用了this对象的方法,所以a方法不会生成事务。
同じクラスのメソッドを内部で直接呼び出すと、トランザクションが失敗することがわかります。
そこで問題は、このシナリオをどのように解決するかということです。
方法 1: 新しいサービス メソッドを追加する
この方法は非常に簡単で、新しい Service メソッドを追加し、その新しい Service メソッドに @Transactional アノテーションを追加し、トランザクションの実行が必要なコードを新しいメソッドに移動するだけです。
@Servcie
public class ServiceA {
@Autowired
prvate ServiceB serviceB;
public void save(User user) {
queryData1();
queryData2();
serviceB.doSave(user);
}
}
@Servcie
public class ServiceB {
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
userMapper.insert(user);
b(user);
}
}
方法 2: Service クラスに自分自身を挿入する
新しい Service クラスを追加したくない場合は、自分自身を Service クラスに注入することもできます。
@Servcie
public class ServiceA {
@Autowired
prvate ServiceA serviceA;
public void save(User user) {
queryData1();
queryData2();
serviceA.doSave(user);
}
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
userMapper.insert(user);
b(user);
}
}
このアプローチでは、循環依存関係の問題は発生せず、実際、Spring IOC 内の 3 次キャッシュにより、循環依存関係の問題は発生しません。
方法 3: AopContext クラスを使用する
依存関係を導入する
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
AOP 取得を有効にする (SpringBoot スタートアップ クラスまたは構成クラスに追加)
@EnableAspectJAutoProxy(exposeProxy = true)
Service クラスの AopContext.currentProxy() を使用してプロキシ オブジェクトを取得します
@Servcie
public class ServiceA {
public void save(User user) {
queryData1();
queryData2();
((ServiceA)AopContext.currentProxy()).doSave(user);
}
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
userMapper.insert(user);
b(user);
}
}
例外が発生してスローされなかった場合、トランザクションはどのようにロールバックできますか?
Spring トランザクションを通常どおりロールバックしたい場合は、処理できる例外をスローする必要があります。例外がスローされない場合、Spring はプログラムが正常であるとみなします。
率直に言うと、開発者が手動で例外をキャッチしなくても、スローされた例外が正しくない場合、Spring トランザクションはロールバックされません。
Spring トランザクションは、デフォルトでは RuntimeException (実行時例外) と Error (エラー) のみをロールバックするため、通常の Exception (非実行時例外) はロールバックしません。
7. try catch を通じて例外を処理する
8. 例外種別エラー[@Transactionalアノテーション属性ロールバック設定エラー]
デフォルトのロールバックは RuntimeException ですが、他の例外ロールバックをトリガーしたい場合は、アノテーションを設定する必要があります。
@Transactional(rollbackFor = Exception.class)
9. メソッドは、final で変更されます。
メソッドがサブクラスによってオーバーライドされたくない場合は、メソッドをfinalとして定義できます。通常のメソッドをこのように定義するのは問題ありませんが、トランザクションをfinalとして定義するとトランザクションが失敗してしまいます。
なぜ?
spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。
但如果某个方法被final修饰了,那么在它的代理类中,就无法重写该方法,事务也就失效了。
注: メソッドが静的である場合、動的プロキシを介してトランザクション メソッドに変換することはできません。
10. マルチスレッド呼び出し
実際のプロジェクト開発では、マルチスレッドの利用シーンがまだまだたくさんあります。Spring トランザクションをマルチスレッド シナリオで使用すると問題は発生しますか?
@Slf4j
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
new Thread(() -> {
roleService.doOtherThing();
}).start();
}
}
@Service
public class RoleService {
@Transactional
public void doOtherThing() {
System.out.println("保存role表数据");
}
}
上記の例から、トランザクション メソッド doOtherThing はトランザクション メソッド add で呼び出されますが、トランザクション メソッド doOtherThing は別のスレッドで呼び出されていることがわかります。
これにより、2 つのメソッドが同じスレッドに存在せず、異なるデータベース接続を取得することになり、2 つの異なるトランザクションが発生します。doOtherThing メソッドで例外がスローされた場合、add メソッドをロールバックすることはできません。
Spring トランザクションのソース コードを読んだことがある場合は、Spring トランザクションがデータベース接続を通じて実装されていることをご存じかもしれません。マップは現在のスレッドに保存され、キーはデータ ソース、値はデータベース接続です。
ここで話している同じトランザクションは、実際には同じデータベース接続を参照しており、コミットとロールバックを同時に行うことができるのは、同じデータベース接続がある場合のみです。異なるスレッドにある場合は、取得されるデータベース接続も異なる必要があるため、異なるトランザクションになります。