ホームページ  >  記事  >  Java  >  Springトランザクション管理操作方法

Springトランザクション管理操作方法

PHP中文网
PHP中文网オリジナル
2017-06-20 16:20:291739ブラウズ

質問です。いつものようにサービスに注釈 @Transactional を追加しましたが、データベースのクエリ時に依然としてデータの不一致が見つかるのはなぜですか? トランザクションが機能していないためだと思います。発生した場合は異常です。ロールバックはありません。そこで、関連するコードをテストしたところ、確かにトランザクションがロールバックされていないことが原因でデータの不整合が発生していることがわかりました。学んだ教訓を以下にまとめましょう: @Transactional,为什么查询数据库时还是发现有数据不一致的情况,想想肯定是事务没起作用,出现异常的时候数据没有回滚。于是就对相关代码进行了一番测试,结果发现一下踩进了两个坑,确实是事务未回滚导致的数据不一致。下面总结一下经验教训:

Spring事务的管理操作方法

  • 编程式的事务管理

    • 实际应用中很少使用

    • 通过使用TransactionTemplate 手动管理事务

  • 声明式的事务管理

    • 开发中推荐使用(代码侵入最少)

    • Spring的声明式事务是通过AOP实现的

主要掌握声明式的事务管理。

spring事务不回滚的两个原因

总结一下导致事务不回滚的两个原因,一是Service类内部方法调用,二是try...catch异常。

1. Service类内部方法调用

大概就是 Service 中有一个方法 A,会内部调用方法 B, 方法 A 没有事务管理,方法 B 采用了声明式事务,通过在方法上声明 Transactional 的注解来做事务管理。示例代码如下:

@Servicepublic class RabbitServiceImpl implements RabbitService {

    @Autowiredprivate RabbitDao rabbitDao;
    @Autowiredprivate TortoiseDao tortoiseDao;

    @Overridepublic Rabbit methodA(String name){return methodB(name);
    }

    @Transactional(propagation = Propagation.REQUIRED)public boolean methodB(String name){
        rabbitDao.insertRabbit(name);
        tortoiseDao.insertTortoise(name);return true;
    }

}

单元测试代码如下:

public class RabbitServiceImplTest {

    @Autowiredprivate RabbitService rabbitService;// 事务未开启    @Testpublic void testA(){
        rabbitService.methodA("rabbit");
    }// 事务开启    @Testpublic void testB(){
        rabbitService.methodB("rabbit");
    }
}

从上一节中可以看到,声明式事务是通通过AOP动态代理实现的,这样会产生一个代理类来做事务管理,而目标类(service)本身是不能感知代理类的存在的。

对于加了@Transactional注解的方法来说,在调用代理类的方法时,会先通过拦截器TransactionInterceptor开启事务,然后在调用目标类的方法,最后在调用结束后,TransactionInterceptor 会提交或回滚事务,大致流程如下图:

Springトランザクション管理操作方法

总结,在方法 A 中调用方法 B,实际上是通过“this”的引用,也就是直接调用了目标类的方法,而非通过 Spring 上下文获得的代理类,所以事务是不会开启的。

2. try...catch异常

在一段业务逻辑中对数据库异常进行了处理,使用了try...catch子句捕获异常并throw了一个自定义异常,这种情况导致了事务未回滚,示例代码如下:

@Transactional(propagation = Propagation.REQUIRED)public boolean methodB(String name) throws BizException {try {
        rabbitDao.insertRabbit(name);
        tortoiseDao.insertTortoise(name);
    } catch (Exception e) {throw new BizException(ReturnCode.EXCEPTION.code, ReturnCode.EXCEPTION.msg);
    }return true;
}

BizException的定义如下:

public class BizException extends Exception {// 自定义异常}

上面代码中的声明式事务在出现异常的时候,事务是不会回滚的。在代码中我虽然捕获了异常,但是同时我也抛出了异常,为什么事务未回滚呢?猜测是异常类型不对,于是开始查询原因,翻看了Spring的官方文档,找到了答案。下面是翻译自Spring官网。

17.5.3 声明式事务的回滚

上一节中介绍了如何设置开启Spring事务,一般在你的应用的Service层代码中设置,这一节将介绍在简单流行的声明式事务中如何控制事务回滚。

在Spring FrameWork 的事务框架中推荐的事务回滚方法是,在当前执行的事务上下文中抛出一个异常。如果异常未被处理,当抛出异常调用堆栈的时候,Spring FrameWork 的事务框架代码将捕获任何未处理的异常,然后并决定是否将此事务标记为回滚。

  • 在默认配置中,Spring FrameWork 的事务框架代码只会将出现runtime, unchecked

    Spring トランザクション管理操作メソッド

プログラムによるトランザクション管理

      開発推奨(コード侵入最小限)
    • Springの宣言型トランザクションはAOPで実装されています

    主に宣言型のトランザクション管理をマスターします。

    Spring トランザクションがロールバックされない 2 つの理由

    トランザクションがロールバックされない 2 つの理由をまとめてみましょう。1 つは Service クラスの内部メソッド呼び出しで、もう 1 つは try...例外をキャッチします。 🎜
    1. Service クラスの内部メソッド呼び出し
    🎜 おそらく Service にはメソッド B を内部で呼び出すメソッド A があり、メソッド A にはトランザクション管理がなく、メソッド B は宣言型トランザクションを使用します。メソッドについて トランザクション管理のためのトランザクション アノテーション。サンプル コードは次のとおりです: 🎜🎜
    <advice>
      <attributes>
      <method></method>
      <method></method>
      </attributes>
    </advice>
    🎜🎜 単体テスト コードは次のとおりです: 🎜🎜
    @Transactional(rollbackForClassName={"Exception"})
    或者
    @Transactional(rollbackFor={Exception.class})
    🎜🎜 前のセクションからわかるように、宣言的トランザクションは AOP 動的プロキシを通じて実装され、これにより、次のプロキシ クラスが生成されます。トランザクション管理 、およびターゲット クラス (サービス) 自体はプロキシ クラスの存在を認識できません。 🎜🎜 @Transactional アノテーションが付けられたメソッドの場合、プロキシ クラスのメソッドを呼び出すと、インターセプター TransactionInterceptor を通じてトランザクションが開始され、呼び出しが完了した後、最後にターゲット クラスのメソッドが呼び出されます。コミットまたはロールバックします トランザクションの一般的なプロセスは次のとおりです: 🎜🎜トランザクション呼び出しの原則」/> 🎜🎜まとめると、メソッドAでメソッドBを呼び出すとき、実際にはthisの参照を介して、つまり、ターゲットクラスのメソッドを直接呼び出します。 Spring コンテキストなので、トランザクションは開始されません。 🎜<h5>2. try...catch 例外</h5>🎜 データベース例外はビジネス ロジックの一部で処理され、try...catch 句を使用して例外をキャプチャし、カスタム例外をスローします。この状況により、トランザクションはロールバックされません。サンプル コードは次のとおりです。 🎜🎜<pre class=<tx:advice id=" txadvice>   <attributes>   <method></method>   <method></method>   </attributes> 🎜🎜 BizException の定義は次のとおりです。 🎜🎜<pre class="brush:php;toolbar:false">@Transactional(noRollbackForClassName={"InstrumentNotFoundException"}) 或者 @Transactional(noRollbackFor={InstrumentNotFoundException.class})🎜🎜 上記のコードの宣言型トランザクションは、例外が発生します。コード内で例外をキャッチしましたが、同時に例外もスローしました。なぜトランザクションはロールバックされなかったのですか?例外の種類が間違っているのではないかと思い、その理由を探し始め、Spring の公式ドキュメントを調べて、答えを見つけました。以下はSpring公式サイトより転載です。 🎜
    17.5.3 宣言型トランザクションのロールバック
    🎜 前のセクションでは、Spring トランザクションを設定して有効にする方法を紹介しました。これは通常、アプリケーションのサービス層コードで設定されます。このセクションでは、シンプルで一般的なものを紹介します。宣言的トランザクションでトランザクションのロールバックを制御する方法。 🎜🎜 Spring FrameWork トランザクション フレームワークで推奨されるトランザクション ロールバック方法は、現在実行中のトランザクション コンテキストで例外をスローすることです。例外が処理されない場合、Spring FrameWork のトランザクション フレームワーク コードは、例外がコール スタックにスローされたときに未処理の例外をキャッチし、トランザクションにロールバックのマークを付けるかどうかを決定します。 🎜🎜🎜🎜 デフォルト設定では、Spring FrameWork のトランザクション フレームワーク コードは、runtime, unchecked 例外を持つトランザクションのみをロールバックとしてマークします。つまり、トランザクションでスローされる例外は RuntimeException またはその例外です。サブクラスを作成し、トランザクションがロールバックされるようにします (デフォルトでは、エラーによってもトランザクションがロールバックされます)。デフォルトの構成では、チェックされた例外はすべてトランザクションのロールバックを引き起こしません。 🎜🎜🎜🎜🎜 注: 未チェックの例外には、Error と RuntimeException が含まれます。RuntimeException のすべてのサブクラスもこのカテゴリに属します。もう 1 つのタイプは「例外」にチェックされます。 🎜🎜🎜🎜🎜例外タイプを正確に構成し、チェック例外を含むこの例外タイプのトランザクション ロールバックを指定できます。次の XML コード スニペットは、チェック例外を構成してトランザクション ロールバックを引き起こし、カスタム例外タイプを適用する方法を示しています: 🎜🎜🎜🎜
    <advice>
      <attributes>
      <method></method>
      </attributes>
    </advice>
    🎜🎜 同じ効果を持つ注釈フォームは次のとおりです: 🎜🎜
    public void resolvePosition() {  try {      // some business logic...
      } catch (NoProductInStockException ex) {      // trigger rollback programmatically      TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
      }
    }
    🎜
    • 在你遇到异常不想回滚事务的时候,同样的你也可指定不回滚的规则,下面的一个例子告诉你,即使遇到未处理的 InstrumentNotFoundException 异常时,Spring FrameWork 的事务框架同样会提交事务,而不回滚。

    <advice>
      <attributes>
      <method></method>
      <method></method>
      </attributes>
    </advice>

      与其有同样作用的注解形式如下:   

    @Transactional(noRollbackForClassName={"InstrumentNotFoundException"})
    或者
    @Transactional(noRollbackFor={InstrumentNotFoundException.class})
    • 还有更灵活的回滚规则配置方法,同时指定什么异常回滚,什么异常不回滚。当Spring FrameWork 的事务框架捕获到一个异常的时候,会去匹配配置的回滚规则来决定是否标记回滚事务,使用匹配度最强的规则结果。因此,下面的配置例子表达的意思是,除了异常 InstrumentNotFoundException 之外的任何异常都会导致事务回滚。

    <advice>
      <attributes>
      <method></method>
      </attributes>
    </advice>
    • 你也可以通过编程式的方式回滚一个事务,尽管方法非常简单,但是也有非常强的代码侵入性,使你的业务代码和Spring FrameWork 的事务框架代码紧密的绑定在一起,示例代码如下:

    public void resolvePosition() {  try {      // some business logic...
      } catch (NoProductInStockException ex) {      // trigger rollback programmatically      TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
      }
    }

      如果可能的话,强烈推荐您使用声明式事务方式回滚事务,对于编程式事务,如果你强烈需要它,也是可以使用的,but its usage flies in the face of achieving a clean POJO-based architecture.(没懂...)

    看完官方文档这节内容找到了问题的答案,原来是因为我们自定义的异常不是 RuntimeException。我的解决办法是,在注解@Transactional中添加 rollbackFor={BizException.class}。可能你会问我为什么不将自定义异常修改为继承RuntimeException,因为我需要BizException是一个checked 异常。

    以上がSpringトランザクション管理操作方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    声明:
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。