@Transactional アノテーションの失敗シナリオとソリューションのバージョン

失敗シナリオ

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 トランザクションがデータベース接続を通じて実装されていることをご存じかもしれません。マップは現在のスレッドに保存され、キーはデータ ソース、値はデータベース接続です。

ここで話している同じトランザクションは、実際には同じデータベース接続を参照しており、コミットとロールバックを同時に行うことができるのは、同じデータベース接続がある場合のみです。異なるスレッドにある場合は、取得されるデータベース接続も異なる必要があるため、異なるトランザクションになります。

おすすめ

転載: blog.csdn.net/u014212540/article/details/132203122