web-dev-qa-db-ja.com

Bが間違っている場合は、Aをロールバックします。スプリングブート、jdbctemplate

メソッド 'databaseChanges'があり、2つの操作を繰り返します:A、Bを繰り返します。 「A」が最初、「B」が最後です。 'A' 'B'は[〜#〜] c [〜#〜] reate、[〜#〜] u [〜#〜] pdate [〜#〜] d [〜#〜]永続ストレージであるOracle Database 11gのエレテ機能。

まあ言ってみれば、

'A'は、テーブルUsers、属性Zipのレコードを更新します(id = 1)。

'B'はテーブル趣味にレコードを挿入します。

シナリオ: databaseChangesメソッドが呼び出され、「A」が操作されてレコードが更新されます。 「B」が動作し、レコードを挿入しようとすると、何かが起こり、例外がスローされ、例外がdatabaseChangesメソッドにバブリングします。

予想: 'A'および 'B'は何も変更しませんでした。 「A」が行った更新はロールバックされます。 「B」は何も変更しませんでした。まあ...例外がありました。

実際:「A」更新はロールバックされていないようです。 「B」は何も変更しませんでした。まあ...例外がありました。


一部のコード

接続できたら、次のようなことをします。

private void databaseChanges(Connection conn) {
   try {
          conn.setAutoCommit(false);
          A(); //update.
          B(); //insert
          conn.commit();
   } catch (Exception e) { 
        try {
              conn.rollback();
        } catch (Exception ei) {
                    //logs...
        }
   } finally {
          conn.setAutoCommit(true);
   }
}

問題:接続がありません(質問とともに投稿するタグを参照)

やってみました:

@Service
public class SomeService implements ISomeService {
    @Autowired
    private NamedParameterJdbcTemplate jdbcTemplate;
    @Autowired
    private NamedParameterJdbcTemplate npjt;

    @Transactional
    private void databaseChanges() throws Exception {   
        A(); //update.
        B(); //insert
    }
}

私のAppConfigクラス:

import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

@Configuration
public class AppConfig {    
    @Autowired
    private DataSource dataSource;

    @Bean
    public NamedParameterJdbcTemplate namedParameterJdbcTemplate() {
        return new NamedParameterJdbcTemplate(dataSource);
    }   
}

「A」は更新を行います。 「B」から例外がスローされます。 「A」によって行われた更新はロールバックされません。

私が読んだことから、私は@Transactionalを正しく使用していないことを理解しています。私は私の問題を解決するために成功せずにいくつかのブログ投稿とstackverflow Q Aを読んで試しました。

助言がありますか?


[〜#〜] edit [〜#〜]

DatabaseChanges()メソッドを呼び出すメソッドがあります

public void changes() throws Exception {
    someLogicBefore();
    databaseChanges();
    someLogicAfter();
}

どのメソッドに@Transactionalアノテーションを付けるか、

changes()? databaseChanges()?

10
lolo

春の_@Transactional_アノテーションは、オブジェクトをプロキシでラップすることで機能し、プロキシは、トランザクションで_@Transactional_アノテーションが付けられたメソッドをラップします。 プライベートメソッドは継承できないため(例のように)そのアノテーションはプライベートメソッドでは機能しません=>ラップすることはできません(これは真ではありません aspectj で宣言トランザクションを使用すると、以下のプロキシ関連の警告は適用されません)。

_@Transactional_スプリングマジックの仕組みの基本的な説明を次に示します。

あなたが書いた:

_class A {
    @Transactional
    public void method() {
    }
}
_

しかし、これはBeanを注入したときに実際に得られるものです。

_class ProxiedA extends A {
   private final A a;

   public ProxiedA(A a) {
       this.a = a;
   }

   @Override
   public void method() {
       try {
           // open transaction ...
           a.method();
           // commit transaction
       } catch (RuntimeException e) {
           // rollback transaction
       } catch (Exception e) {
           // commit transaction
       }
   }
} 
_

これには制限があります。オブジェクトがプロキシされる前に呼び出されるため、_@PostConstruct_メソッドでは機能しません。そして、すべてを正しく構成したとしても、トランザクションはデフォルトでunchecked例外でのみロールバックされます。チェック済みの例外でロールバックが必要な場合は、@Transactional(rollbackFor={CustomCheckedException.class})を使用します。

私が知っている別の頻繁に発生する警告:

_@Transactional_メソッドは、「外部から」呼び出す場合にのみ機能します。次の例では、b()はトランザクションにラップされません。

_class X {
   public void a() {
      b();
   }

   @Transactional
   public void b() {
   }
}
_

また、_@Transactional_はオブジェクトをプロキシすることで機能するためです。上記の例では、a()X.b()を呼び出し、拡張された「スプリングプロキシ」メソッドb()ではないため、トランザクションはありません。回避策として、別のBeanからb()を呼び出す必要があります。

これらの警告のいずれかが発生し、推奨される回避策(メソッドを非プライベートにするか、別のBeanからb()を呼び出す)を使用できない場合、宣言トランザクションの代わりにTransactionTemplateを使用できます。

_public class A {
    @Autowired
    TransactionTemplate transactionTemplate;

    public void method() {
        transactionTemplate.execute(status -> {
            A();
            B();
            return null;
        });
    }

...
} 
_

更新

上記の情報を使用して、OP更新された質問に答えます。

どのメソッドに@Transactionalアノテーションを付ける必要があります:changes()? databaseChanges()?

_@Transactional(rollbackFor={Exception.class})
public void changes() throws Exception {
    someLogicBefore();
    databaseChanges();
    someLogicAfter();
}
_

changes()が、クラス自体からではなく、コンテキストがインスタンス化された後ではなく、Beanの「外部から」呼び出されることを確認してください(たとえば、これはafterPropertiesSet()または_@PostConstruct_アノテーション付きメソッドではありません)。デフォルトでは、未チェックの例外に対してのみスプリングがトランザクションをロールバックすることを理解してください(rollbackForチェック済み例外リストでより具体的になるようにしてください)。

19
frenzykryger

RuntimeExceptionはロールバックをトリガーしますが、チェックされた例外はロールバックしません。

これは、すべてのSpringトランザクションAPIに共通の動作です。 デフォルト、トランザクションコード内からRuntimeExceptionがスローされた場合、トランザクションはロールバックされます。チェック例外(つまり、RuntimeExceptionではない)がスローされた場合、トランザクションはロールバックされません。

databaseChanges関数内で取得している例外に依存します。したがって、すべての例外をキャッチするには、rollbackFor = Exception.classを追加するだけです。

変更はサービスクラスにあるはずで、コードは次のようになります。

@Service
public class SomeService implements ISomeService {
    @Autowired
    private NamedParameterJdbcTemplate jdbcTemplate;
    @Autowired
    private NamedParameterJdbcTemplate npjt;

    @Transactional(rollbackFor = Exception.class)
    private void databaseChanges() throws Exception {   
        A(); //update
        B(); //insert
    }
}

さらに、あなたはそれを使って素敵なことができるので、常にrollbackFor = Exception.classを書く必要はありません。独自のカスタムアノテーションを記述することで、これを実現できます。

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(rollbackFor = Exception.class)
@Documented
public @interface CustomTransactional {  
}

最終的なコードは次のようになります。

@Service
public class SomeService implements ISomeService {
    @Autowired
    private NamedParameterJdbcTemplate jdbcTemplate;
    @Autowired
    private NamedParameterJdbcTemplate npjt;

    @CustomTransactional
    private void databaseChanges() throws Exception {   
        A(); //update
        B(); //insert
    }
}
5
choop

最初に提示するコードはUserTransactions用です。つまり、アプリケーションがトランザクション管理を行う必要があります。通常は、コンテナがそれを処理し、@ Transactionalアノテーションを使用するようにします。あなたの場合の問題は、プライベートメソッドに注釈があることだと思います。アノテーションをクラスレベルに移動します

@Transactional
public class MyFacade {

public void databaseChanges() throws Exception {   
    A(); //update.
    B(); //insert
}

その後、適切にロールバックする必要があります。詳細については、こちらをご覧ください Springの@Transactional属性はプライベートメソッドで機能しますか?

1
Guenther

これを試して:

@TransactionManagement(TransactionManagementType.BEAN)
public class MyFacade {

@TransactionAttribute(TransactionAttribute.REQUIRES_NEW)
public void databaseChanges() throws Exception {   
    A(); //update.
    B(); //insert
}
0
user3805841

不足していると思われるのは、TransactionManagerです。 TransactionManagerの目的は、データベーストランザクションを管理できるようにすることです。トランザクションには、プログラムと宣言の2種類があります。あなたが説明しているのは、注釈による宣言的トランザクションの必要性です。

したがって、プロジェクトに必要なものは次のとおりです。

Spring Transactions Dependency(Gradleを例として使用)

_compile("org.springframework:spring-tx")
_

Spring Boot Configurationでトランザクションマネージャーを定義する

このようなもの

_@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource)
{
    return new DataSourceTransactionManager(dataSource);
}
_

また、_@EnableTransactionManagement_注釈を追加する必要があります(これが新しいバージョンのスプリングブートで無料かどうかはわかりません)。

_@EnableTransactionManagement
public class AppConfig {
...
}
_

@ Transactionalを追加

ここでは、トランザクションに参加するメソッドに_@Transactional_注釈を追加します

_@Transactional
public void book(String... persons) {
    for (String person : persons) {
        log.info("Booking " + person + " in a seat...");
        jdbcTemplate.update("insert into BOOKINGS(FIRST_NAME) values (?)", person);
    }
};
_

このメソッドはパブリックであり、プライベートではないことに注意してください。 databaseChanges()を呼び出すパブリックメソッドに_@Transactional_を配置することを検討する必要があります。

_@Transactional_がどこに行くべきか、どのように振る舞うかについての高度なトピックもありますので、最初に何かを動作させてから、少し後でこの領域を探索することをお勧めします:)

これらがすべて配置された後(依存関係+ transactionManager構成+注釈)、トランザクションはそれに応じて機能するはずです。

参考文献

トランザクションに関するスプリングリファレンスドキュメント

Spring Bootを使用したトランザクションのスプリングガイド -これには、試すことができるサンプルコードがあります

0
Shiraaz.M