メソッド '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()?
春の_@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チェック済み例外リストでより具体的になるようにしてください)。
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
}
}
最初に提示するコードはUserTransactions用です。つまり、アプリケーションがトランザクション管理を行う必要があります。通常は、コンテナがそれを処理し、@ Transactionalアノテーションを使用するようにします。あなたの場合の問題は、プライベートメソッドに注釈があることだと思います。アノテーションをクラスレベルに移動します
@Transactional
public class MyFacade {
public void databaseChanges() throws Exception {
A(); //update.
B(); //insert
}
その後、適切にロールバックする必要があります。詳細については、こちらをご覧ください Springの@Transactional属性はプライベートメソッドで機能しますか?
これを試して:
@TransactionManagement(TransactionManagementType.BEAN)
public class MyFacade {
@TransactionAttribute(TransactionAttribute.REQUIRES_NEW)
public void databaseChanges() throws Exception {
A(); //update.
B(); //insert
}
不足していると思われるのは、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を使用したトランザクションのスプリングガイド -これには、試すことができるサンプルコードがあります