この記事では、Spring でのトランザクション管理について例を示して説明します。皆さんの参考に共有してください。具体的な分析は次のとおりです。
事業紹介:
トランザクション管理は、データの整合性と一貫性を確保するためのエンタープライズ アプリケーション開発において不可欠なテクノロジーです。
トランザクションは、単一の作業単位として扱われる一連のアクションです。これらのアクションがすべて機能するか、どれも機能しないかのどちらかです
トランザクションの 4 つの主要な属性 (ACID)
① アトミック性: トランザクション ルームでのアトミックな操作は一連のアクションで構成されます。トランザクションのアトミック性により、アクションが完全に完了するか、まったく影響がないことが保証されます。 ② 一貫性: すべてのトランザクション アクションが完了すると、トランザクションはコミットされます。データとリソースは、ビジネス ルールを満たす一貫した状態にあります。 ③ 分離: 同じデータを同時に処理するトランザクションが多数存在する可能性があるため、データの破損を防ぐために、それぞれを他のトランザクションから分離する必要があります。 ④ 耐久性: トランザクションが完了すると、 、どのようなシステム エラーが発生しても、その結果は影響を受けません。通常、トランザクションの結果は永続ストレージに書き込まれます。
Spring のトランザクション管理
エンタープライズ アプリケーション フレームワークとして、Spring はさまざまなトランザクション管理 API の上に抽象化レイヤーを定義します。アプリケーション開発者は、Spring のトランザクション管理メカニズムを使用するために、基礎となるトランザクション管理 API を理解する必要はありません。
Spring は、プログラムによるトランザクション管理と宣言的なトランザクション管理の両方をサポートしています。
プログラムによるトランザクション管理: トランザクション管理コードをビジネス メソッドに埋め込んで、トランザクションの送信とロールバックを制御します。プログラムによるトランザクションでは、各ビジネス操作に追加のトランザクション管理コードを含める必要があります。
宣言型トランザクション管理: ほとんどの場合、プログラムによるトランザクション管理よりも使用する方が適しています。トランザクション管理コードをビジネス メソッドから分離し、宣言的な方法でトランザクション管理を実装します。トランザクション管理は、横断的な関心事として、AOP メソッドを通じてモジュール化できます。 Spring は、Spring AOP フレームワークを通じて宣言型トランザクション管理をサポートします。
Spring トランザクションの伝播プロパティ:
トランザクション メソッドが別のトランザクション メソッドによって呼び出される場合、トランザクションを伝播する方法を指定する必要があります。たとえば、メソッドは既存のトランザクションで実行し続けることも、新しいトランザクションを開始して独自のトランザクションで実行することもできます。
トランザクションの伝播動作は、伝播属性によって指定できます。 Spring では 7 つの通信動作が定義されています。
コミュニケーション行動 | 意味 |
PROPAGATION_MANDATORY | 現在のトランザクションが存在しない場合、メソッドをトランザクション内で実行する必要があることを示します。 |
PROPAGATION_NESTED | トランザクションが現在存在する場合、このメソッドがネストされたトランザクションで実行されることを示します。ネストされたトランザクションは、現在のトランザクションとは独立してコミットまたはロールバックできます。現在のトランザクションが存在しない場合、動作は PROPAGATION_REQUIRED と同じです。この伝播動作に対する各メーカーのサポートは異なることに注意してください。リソース マネージャーのドキュメントを参照して、ネストされたトランザクションをサポートしているかどうかを確認できます。 |
PROPAGATION_NEVER | 現在のメソッドをトランザクション コンテキストで実行しないことを示します。現在実行中のトランザクションがある場合、例外がスローされます。 |
PROPAGATION_NOT_SUPPORTED | このメソッドをトランザクション内で実行しないことを示します。現在のトランザクションが存在する場合、このメソッドの実行中はトランザクションが一時停止されます。 JTATransactionManagerを使用する場合は、TransactionManagerにアクセスする必要があります |
PROPAGATION_REQUIRED | 現在のメソッドをトランザクション内で実行する必要があることを示します。現在のトランザクションが存在する場合、メソッドはそのトランザクション内で実行されます。それ以外の場合は、新しいトランザクションが開始されます |
PROPAGATION_REQUIRED_NEW | 現在のメソッドが独自のトランザクションで実行される必要があることを示します。新しいトランザクションが開始されます。現在のトランザクションが存在する場合、このメソッドの実行中にトランザクションは一時停止されます。 JTATransactionManagerを使用する場合は、TransactionManagerにアクセスする必要があります |
PROPAGATION_SUPPORTS | 現在のメソッドにトランザクション コンテキストが必要ないことを示しますが、現在のトランザクションが存在する場合、メソッドはこのトランザクションで実行されます。 |
その中で、PROPAGATION_REQUIRED がデフォルトの伝播属性です。
同時トランザクションによって引き起こされる問題
同じアプリケーションまたは異なるアプリケーションで複数のトランザクションが同じデータ セットに対して同時に実行されると、多くの予期せぬ問題が発生する可能性があります。
同時トランザクションによって発生する問題は、次の 3 つのカテゴリに分類できます。
① ダーティリード: ダーティリードは、あるトランザクションが別のトランザクションによって書き換えられたがまだコミットされていないデータを読み取るときに発生します。後で書き換えがロールバックされた場合、最初のトランザクションで取得されたデータは無効になります。
② 非反復読み取り: 非反復読み取りは、トランザクションが同じクエリを 2 回以上実行し、毎回異なるデータを取得する場合に発生します。これは通常、別の同時トランザクションがクエリ間でデータを更新したことが原因です。
③ファントム・リーディング:ファントム・リーディングは、繰り返し不可能なリーディングに似ています。これは、1 つのトランザクション (T1) が数行のデータを読み取り、別の同時トランザクション (T2) がデータを挿入したときに発生します。後続のクエリでは、最初のトランザクション (T1) で、元々存在しなかったレコードがさらにいくつかあることがわかります。
コード例
最初はデータベース テーブルです。
book(isbn、book_name、価格)、account(ユーザー名、残高)、book_stock(isbn、在庫)を含む
次に、クラスが使用したものは次のとおりです。
ブックショップダオ
次のようにコードをコピーします。
パッケージcom.yl.spring.tx;
パブリックインターフェース BookShopDao {
//書籍番号から書籍の単価を取得します
public int findBookPriceByIsbn(String isbn);
//書籍番号に対応する在庫が-1になるように書籍の在庫を更新します
public void updateBookStock(String isbn);
// ユーザーのアカウント残高を更新します: make ユーザー名の残高-価格
public void updateUserAccount(String ユーザー名, int 価格);
}
BookShopDaoImpl
次のようにコードをコピーします。
パッケージcom.yl.spring.tx;
org.springframework.beans.factory.annotation.Autowired をインポートします。
org.springframework.jdbc.core.JdbcTemplate をインポートします。
org.springframework.stereotype.Repository をインポートします。
@Repository("bookShopDao")
パブリック クラス BookShopDaoImpl は BookShopDao を実装します {
@Autowired
プライベート JdbcTemplate JdbcTemplate;
@オーバーライド
public int findBookPriceByIsbn(String isbn) {
文字列 sql = "本の価格を選択します。isbn = ?";
return JdbcTemplate.queryForObject(sql、Integer.class、isbn);
}
@オーバーライド
public void updateBookStock(String isbn) {
//本の在庫が十分であるかどうかを確認し、十分でない場合は例外をスローします
文字列 sql2 = "book_stock WHERE から在庫を選択します。 isbn = ?";
int Stock = JdbcTemplate.queryForObject(sql2、Integer.class、isbn);
if (ストック == 0) {
throw new BookStockException("在庫が不足しています!");
}
文字列 sql = "UPDATE book_stock SET 在庫 = 在庫 - 1 WHERE isbn = ?";
JdbcTemplate.update(sql、isbn);
}
@オーバーライド
public void updateUserAccount(String ユーザー名, int 価格) {
//残高が不足しているかどうかを確認し、不足している場合は例外をスローします
文字列 sql2 = "アカウント WHERE ユーザー名 = ?" から残高を選択します。
int Balance = JdbcTemplate.queryForObject(sql2, Integer.class, username);
if (残高 < 価格) {
throw new UserAccountException("残高が不足しています!");
}
文字列 sql = "UPDATE アカウント SET 残高 = 残高 - ? WHERE ユーザー名 = ?";
JdbcTemplate.update(SQL, 価格, ユーザー名);
}
}
ブックショップサービス
次のようにコードをコピーします。
パブリック インターフェイス BookShopService {
public void Purchase(String username, String isbn);
}
BookShopServiceImpl
次のようにコードをコピーします。
パッケージcom.yl.spring.tx;
org.springframework.beans.factory.annotation.Autowired をインポートします。
org.springframework.stereotype.Service をインポートします。
org.springframework.transaction.annotation.Isolation をインポートします。
org.springframework.transaction.annotation.Propagation をインポートします。
org.springframework.transaction.annotation.Transactional をインポートします。
@Service("bookShopService")
パブリック クラス BookShopServiceImpl は BookShopService {を実装します。
@Autowired
プライベート BookShopDao bookShopDao;
/**
* 1. トランザクションのアノテーションを追加する
* 伝播を使用して、トランザクションの伝播動作、つまり、現在のトランザクション メソッドが別のトランザクション メソッドによって呼び出されたときにトランザクションを使用する方法を指定します。
* デフォルト値は REQUIRED です。これは、メソッドを呼び出すトランザクションを使用することを意味します。
* REQUIRES_NEW: 独自のトランザクションを使用し、呼び出されたトランザクション メソッドのトランザクションは一時停止されます。
*
* 2. トランザクションの分離レベルを指定するには、isolation を使用します。最も一般的に使用される値は READ_COMMITTED です。
* 3. デフォルトでは、Spring の宣言型トランザクションはすべてのランタイム例外をロールバックします。これは、対応するプロパティを通じて設定することもできます。通常はデフォルト値で十分です。
* 4. readOnly を使用して、トランザクションが読み取り専用かどうかを指定します。 このトランザクションはデータの読み取りのみを行い、データの更新は行わないことを示します。これは、データベース エンジンがトランザクションを最適化するのに役立ちます。実際にデータベースを読み取るだけのメソッドである場合は、readOnly=true を設定する必要があります。
* 5. timeOut を使用して、トランザクションが強制的にロールバックされるまでの時間を指定します。
*/
@Transactional(propagation=Propagation.REQUIRES_NEW,
隔離=隔離.READ_COMMITTED、
noRollbackFor={UserAccountException.class}、
readOnly=true、タイムアウト=3)
@オーバーライド
public void Purchase(String username, String isbn) {
//1.本の単価を取得します。
int 価格 = bookShopDao.findBookPriceByIsbn(isbn);
//2.書籍在庫を更新します。
bookShopDao.updateBookStock(isbn);
//3. ユーザー残高を更新します。
bookShopDao.updateUserAccount(ユーザー名, 価格);;
}
}
BookStockException
次のようにコードをコピーします。
パッケージcom.yl.spring.tx;
public class BookStockException extends RuntimeException {
/**
*
*/
プライベート静的最終ロングシリアルバージョンUID = 1L;
public BookStockException() {
素晴らしい();
// TODO 自動生成されたコンストラクター スタブ
}
public BookStockException(String arg0, Throwable arg1, boolean arg2,
ブール値 arg3) {
super(arg0, arg1, arg2, arg3);
// TODO 自動生成されたコンストラクター スタブ
}
public BookStockException(String arg0, Throwable arg1) {
super(arg0, arg1);
// TODO 自動生成されたコンストラクター スタブ
}
public BookStockException(String arg0) {
スーパー(引数0);
// TODO 自動生成されたコンストラクター スタブ
}
public BookStockException(Throwable arg0) {
スーパー(引数0);
// TODO 自動生成されたコンストラクター スタブ
}
}
ユーザーアカウント例外
次のようにコードをコピーします。
パッケージcom.yl.spring.tx;
public class UserAccountException extends RuntimeException {
/**
*
*/
プライベート静的最終ロングシリアルバージョンUID = 1L;
public UserAccountException() {
素晴らしい();
// TODO 自動生成されたコンストラクター スタブ
}
public UserAccountException(String arg0, Throwable arg1, boolean arg2,
ブール値 arg3) {
super(arg0, arg1, arg2, arg3);
// TODO 自動生成されたコンストラクター スタブ
}
public UserAccountException(String arg0, Throwable arg1) {
super(arg0, arg1);
// TODO 自動生成されたコンストラクター スタブ
}
public UserAccountException(String arg0) {
スーパー(引数0);
// TODO 自動生成されたコンストラクター スタブ
}
public UserAccountException(Throwable arg0) {
スーパー(引数0);
// TODO 自動生成されたコンストラクター スタブ
}
}
キャッシャー
次のようにコードをコピーします。
パッケージcom.yl.spring.tx;
java.util.Listをインポートします。
パブリック インターフェイス レジ係 {
public void checkout(String username, List<String>isbns);
}
レジ係Impl. CashierImpl.checkout と bookShopService.purchase はトランザクションの伝播動作を共同でテストします
次のようにコードをコピーします。
パッケージcom.yl.spring.tx;
java.util.Listをインポートします。
org.springframework.beans.factory.annotation.Autowired をインポートします。
org.springframework.stereotype.Service をインポートします。
org.springframework.transaction.annotation.Transactional をインポートします。
@Service("レジ係")
public class CashierImpl は Cashier を実装します {
@Autowired
private BookShopService bookShopService;
@トランザクション
@オーバーライド
public void checkout(String username, List<String>isbns) {
for(String isb : isbns) {
bookShopService.purchase(ユーザー名、isbn);
}
}
}
テストクラス:
次のようにコードをコピーします。
パッケージcom.yl.spring.tx;
java.util.Arraysをインポートします。
org.junit.Test をインポートします。
org.springframework.context.ApplicationContext をインポートします。
org.springframework.context.support.ClassPathXmlApplicationContext をインポートします。
パブリック クラス SpringTransitionTest {
プライベート ApplicationContext ctx = null;
プライベート BookShopDao bookShopDao = null;
private BookShopService bookShopService = null;
private Cashier レジ係 = null;
{
ctx = 新しい ClassPathXmlApplicationContext("applicationContext.xml");
bookShopDao = ctx.getBean(BookShopDao.class);
bookShopService = ctx.getBean(BookShopService.class);
キャッシャー = ctx.getBean(Cashier.class);
}
@テスト
public void testBookShopDaoFindPriceByIsbn() {
System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
}
@テスト
public void testBookShopDaoUpdateBookStock(){
bookShopDao.updateBookStock("1001");
}
@テスト
public void testBookShopDaoUpdateUserAccount(){
bookShopDao.updateUserAccount("AA", 100);
}
@テスト
public void testBookShopService(){
bookShopService.purchase("AA", "1001");
}
@テスト
public void testTransactionPropagation(){
Cashier.checkout("AA", Arrays.asList("1001", "1002"));
}
}
この記事が皆さんの Spring プログラミング設計に役立つことを願っています。