Spring@Transactionalでのトランザクションの伝播と分離
1. 序章
このチュートリアルでは、 @Transactional アノテーションと、その分離および伝播設定について説明します。
2. @Transactionalとは何ですか?
@Transactional を使用して、データベーストランザクションでメソッドをラップできます。
これにより、トランザクションの伝播、分離、タイムアウト、読み取り専用、およびロールバックの条件を設定できます。 私たちもできます トランザクションマネージャーを指定します。
2.1. @Transactional実装の詳細
Springは、トランザクションの作成、コミット、およびロールバックを管理するために、プロキシを作成するか、クラスのバイトコードを操作します。 プロキシの場合、Springは無視します @Transactional 内部メソッド呼び出しで。
簡単に言うと、 callMethod のようなメソッドがあり、それを @Transactionalとしてマークすると、Springは呼び出し@Transactionalメソッドの周りにトランザクション管理コードをラップします。 :
createTransactionIfNecessary();
try {
callMethod();
commitTransactionAfterReturning();
} catch (exception) {
completeTransactionAfterThrowing();
throw exception;
}
2.2. 使用方法@Transactional
インターフェイス、クラスの定義、またはメソッドに直接アノテーションを付けることができます。 それらは優先順位に従って相互にオーバーライドします。 低いものから高いものへと、インターフェイス、スーパークラス、クラス、インターフェイスメソッド、スーパークラスメソッド、およびクラスメソッドがあります。
Springは、アノテーションを付けなかったこのクラスのすべてのパブリックメソッドにクラスレベルのアノテーションを適用します @Transactional.
ただし、プライベートメソッドまたは保護されたメソッドにアノテーションを付けると、Springはエラーなしでそれを無視します。
インターフェイスのサンプルから始めましょう。
@Transactional
public interface TransferService {
void transfer(String user1, String user2, double val);
}
通常、インターフェイスで@Transactionalを設定することはお勧めしません。 ただし、@RepositoryとSpring Dataのような場合は許容されます。 クラス定義にアノテーションを付けて、インターフェース/スーパークラスのトランザクション設定をオーバーライドできます。
@Service
@Transactional
public class TransferServiceImpl implements TransferService {
@Override
public void transfer(String user1, String user2, double val) {
// ...
}
}
次に、メソッドに直接アノテーションを設定してオーバーライドしましょう。
@Transactional
public void transfer(String user1, String user2, double val) {
// ...
}
3. トランザクションの伝播
伝播は、ビジネスロジックのトランザクション境界を定義します。 Springは、propagation設定に従ってトランザクションを開始および一時停止することができます。
SpringはTransactionManager:: getTransaction を呼び出して、伝播に従ってトランザクションを取得または作成します。 すべてのタイプのTransactionManagerの伝播の一部をサポートしますが、TransactionManagerの特定の実装によってのみサポートされる伝播のいくつかがあります。
さまざまな伝播とそれらがどのように機能するかを見ていきましょう。
3.1. 必要な伝播
REQUIREDがデフォルトの伝播です。 Springはアクティブなトランザクションがあるかどうかをチェックし、何も存在しない場合は新しいトランザクションを作成します。 それ以外の場合、ビジネスロジックは現在アクティブなトランザクションに追加されます。
@Transactional(propagation = Propagation.REQUIRED)
public void requiredExample(String user) {
// ...
}
さらに、 REQUIRED がデフォルトの伝播であるため、コードを削除することでコードを簡略化できます。
@Transactional
public void requiredExample(String user) {
// ...
}
REQUIRED伝播でトランザクション作成がどのように機能するかの擬似コードを見てみましょう。
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
return createNewTransaction();
3.2. サポート伝播
SUPPORTS の場合、Springは最初にアクティブなトランザクションが存在するかどうかをチェックします。 トランザクションが存在する場合は、既存のトランザクションが使用されます。 トランザクションがない場合は、非トランザクションで実行されます。
@Transactional(propagation = Propagation.SUPPORTS)
public void supportsExample(String user) {
// ...
}
SUPPORTSのトランザクション作成の擬似コードを見てみましょう。
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
return emptyTransaction;
3.3. 必須の伝播
伝播がMANDATORYの場合、アクティブなトランザクションがあれば、それが使用されます。 アクティブなトランザクションがない場合、Springは例外をスローします。
@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryExample(String user) {
// ...
}
擬似コードをもう一度見てみましょう。
if (isExistingTransaction()) {
if (isValidateExistingTransaction()) {
validateExisitingAndThrowExceptionIfNotValid();
}
return existing;
}
throw IllegalTransactionStateException;
3.4. 決して伝播しない
NEVER 伝播を伴うトランザクションロジックの場合、アクティブなトランザクションがある場合、Springは例外をスローします。
@Transactional(propagation = Propagation.NEVER)
public void neverExample(String user) {
// ...
}
NEVER伝播でトランザクション作成がどのように機能するかの擬似コードを見てみましょう。
if (isExistingTransaction()) {
throw IllegalTransactionStateException;
}
return emptyTransaction;
3.5. NOT_SUPPORTED伝搬
現在のトランザクションが存在する場合、最初にSpringがそれを一時停止し、次にビジネスロジックがトランザクションなしで実行されます。
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupportedExample(String user) {
// ...
}
JTATransactionManager は、すぐに使用できる実際のトランザクションの一時停止をサポートします。 他の人は、既存のものへの参照を保持し、スレッドコンテキストからそれをクリアすることによって、一時停止をシミュレートします
3.6. REQUIRES_NEW伝搬
伝播がREQUIRES_NEWの場合、Springは現在のトランザクションが存在する場合はそれを一時停止し、新しいトランザクションを作成します。
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewExample(String user) {
// ...
}
NOT_SUPPORTED と同様に、実際のトランザクションの一時停止にはJTATransactionManagerが必要です。
擬似コードは次のようになります。
if (isExistingTransaction()) {
suspend(existing);
try {
return createNewTransaction();
} catch (exception) {
resumeAfterBeginException();
throw exception;
}
}
return createNewTransaction();
3.7. ネストされた伝播
NESTED 伝播の場合、Springはトランザクションが存在するかどうかをチェックし、存在する場合はセーブポイントをマークします。 これは、ビジネスロジックの実行で例外がスローされた場合、トランザクションがこのセーブポイントにロールバックすることを意味します。 アクティブなトランザクションがない場合は、次のように機能します 必要.
DataSourceTransactionManager は、この伝播をすぐにサポートします。 JTATransactionManagerの一部の実装もこれをサポートする場合があります。
JpaTransactionManager は、JDBC接続に対してのみNESTEDをサポートします。 ただし、nestedTransactionAllowedフラグをtrueに設定すると、JDBCドライバーがセーブポイントをサポートしている場合、JPAトランザクションのJDBCアクセスコードでも機能します。
最後に、伝播をNESTEDに設定しましょう。
@Transactional(propagation = Propagation.NESTED)
public void nestedExample(String user) {
// ...
}
4. トランザクションの分離
分離は、一般的なACIDプロパティの1つです。つまり、原子性、一貫性、分離、および耐久性です。 分離は、並行トランザクションによって適用された変更が互いにどのように見えるかを示します。
各分離レベルは、トランザクションに対するゼロ以上の同時実行の副作用を防ぎます。
- ダーティリード:並行トランザクションのコミットされていない変更を読み取ります
- 繰り返し不可の読み取り:同時トランザクションが同じ行を更新してコミットした場合、行の再読み取り時に異なる値を取得します
- ファントム読み取り:別のトランザクションが範囲内の一部の行を追加または削除してコミットした場合、範囲クエリの再実行後に異なる行を取得します
トランザクションの分離レベルは、次の方法で設定できます。
4.1. 春の隔離管理
デフォルトの分離レベルはDEFAULTです。 その結果、Springが新しいトランザクションを作成するとき、分離レベルはRDBMSのデフォルトの分離になります。 したがって、データベースを変更する場合は注意が必要です。
分離が異なるメソッドのチェーンを呼び出す場合も考慮する必要があります。通常のフローでは、分離は新しいトランザクションが作成されたときにのみ適用されます。 したがって、何らかの理由でメソッドを別の分離で実行することを許可したくない場合は、 TransactionManager ::setValidateExistingTransactionをtrueに設定する必要があります。
その場合、トランザクション検証の擬似コードは次のようになります。
if (isolationLevel != ISOLATION_DEFAULT) {
if (currentTransactionIsolationLevel() != isolationLevel) {
throw IllegalTransactionStateException
}
}
次に、さまざまな分離レベルとその影響について詳しく見ていきましょう。
4.2. READ_UNCOMMITTED分離
READ_UNCOMMITTED は最低の分離レベルであり、最も多くの同時アクセスを可能にします。
その結果、前述の3つの同時実行性の副作用すべてに悩まされます。 この分離を使用するトランザクションは、他の同時トランザクションのコミットされていないデータを読み取ります。 また、繰り返し不可能な読み取りとファントム読み取りの両方が発生する可能性があります。 したがって、行の再読み取りまたは範囲クエリの再実行で異なる結果を得ることができます。
メソッドまたはクラスのisolationレベルを設定できます。
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void log(String message) {
// ...
}
PostgresはREAD_UNCOMMITTED分離をサポートせず、代わりにREAD_COMMITEDにフォールバックします。 また、OracleはREAD_UNCOMMITTEDをサポートまたは許可していません。
4.3. READ_COMMITTED分離
分離の第2レベル、 READ_COMMITTED、 ダーティリードを防ぎます。
残りの同時実行の副作用はまだ発生する可能性があります。 したがって、並行トランザクションでコミットされていない変更は影響を与えませんが、トランザクションが変更をコミットした場合、再クエリによって結果が変わる可能性があります。
ここでは、分離レベルを設定します。
@Transactional(isolation = Isolation.READ_COMMITTED)
public void log(String message){
// ...
}
READ_COMMITTED は、Postgres、SQL Server、およびOracleのデフォルトレベルです。
4.4. REPEATABLE_READ分離
分離の第3レベル、 REPEATABLE_READ、 ダーティで繰り返し不可能な読み取りを防ぎます。 したがって、並行トランザクションのコミットされていない変更の影響を受けません。
また、行を再クエリしても、別の結果は得られません。 ただし、範囲クエリの再実行では、新しく追加または削除された行が取得される場合があります。
さらに、更新が失われるのを防ぐために必要な最低レベルです。 失われた更新は、2つ以上の同時トランザクションが同じ行を読み取って更新したときに発生します。 REPEATABLE_READ は、行への同時アクセスをまったく許可しません。 したがって、失われた更新は発生しません。
メソッドのisolationレベルを設定する方法は次のとおりです。
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void log(String message){
// ...
}
REPEATABLE_READはMysqlのデフォルトレベルです。 OracleはREPEATABLE_READをサポートしていません。
4.5. シリアライズ可能分離
シリアル化可能 最高レベルの分離です。 上記のすべての同時実行の副作用を防ぎますが、同時呼び出しを順番に実行するため、同時アクセス率が最も低くなる可能性があります。
つまり、シリアル化可能なトランザクションのグループを同時に実行すると、それらをシリアルで実行した場合と同じ結果になります。
次に、SERIALIZABLEをisolationレベルとして設定する方法を見てみましょう。
@Transactional(isolation = Isolation.SERIALIZABLE)
public void log(String message){
// ...
}
5. 結論
この記事では、@Transactionの伝播プロパティについて詳しく説明しました。 次に、同時実行の副作用と分離レベルについて学習しました。
いつものように、この記事の完全なコードは、GitHubでから入手できます。