はじめに
序文
この投稿では、SpringBootのTransactionチュートリアルの使用に焦点を当てています。
SpringBoot Transaction
注:プロジェクトを直接入手したい場合は、直接下にジャンプしてリンクからプロジェクトコードをダウンロードしてください。
Transaction
トランザクション管理のアプローチ
Springでは、トランザクションはプログラム的トランザクション管理と宣言的トランザクション管理の2つの方法で実装されます。
PlatformTransactionManagerプログラムによるトランザクション管理: プログラムによるトランザクション管理には、TransactionTemplate を使うか、基礎となる .NET Framework を直接使います。プログラムによるトランザクション管理については、spring は TransactionTemplate の使用を推奨しています。- 宣言的トランザクション管理:AOPの上に構築されます。その本質は、前後のメソッドをインターセプトし、ターゲットメソッドでトランザクションを作成または参加することです、ターゲットメソッドの実行では、トランザクションを提出またはロールバックに応じて状況の実行後。宣言的トランザクション管理は、@Transactionalを通じて、コードを侵略する必要はありませんトランザクション操作することができます、より速く、よりシンプルで、それを使用することをお勧めします。
トランザクションコミットメソッド
デフォルトでは、データベースは自動コミットモードになっています。各ステートメントは個別のトランザクション内にあり、ステートメントの実行が完了すると、トランザクションは成功した場合は暗黙的にコミットされ、失敗した場合は暗黙的にロールバックされます。
通常のトランザクション管理では、トランザクション内の関連する一連の操作なので、データベースの自動コミットモードをオフにする必要があります。connection.setAutoCommit(false)connection.commit(); しかし、この点については心配する必要はありません。springは基礎となる接続の自動コミット機能をfalseに設定します。つまり、springを物事の管理に使用する場合、springは自動コミットがfalseに設定されているかどうかを設定します。これはJDBCの;と同じで、コミットで実行が行われた後、.
トランザクションの分離レベル
TransactionDefinitionインターフェイスは、分離レベルを表す5つの定数を定義しています:
TransactionDefinition.ISOLATION_DEFAULTこれはデフォルト値で、基礎となるデータベースのデフォルトの分離レベルが使用されることを示します。ほとんどのデータベースでは、これは通常TransactionDefinition.ISOLATION_READ_COMMITTEDです。TransactionDefinition.ISOLATION_READ_UNCOMMITTEDこの分離レベルはあるトランザクションが他のトランザクションによって変更されたがまだコミットされていないデータを読むことができることを意味します。このレベルはダーティ・リードや再現不可能なリード、ファントム・リードを保護しません。例えば、PostgreSQLにはこのレベルはありません。TransactionDefinition.ISOLATION_READ_COMMITTEDこの分離レベルはトランザクションが他のトランザクションによってコミットされたデータしか読み取れないことを意味します。このレベルはダーティ・リードを防止するもので、ほとんどの場合に推奨される値です。TransactionDefinition.ISOLATION_REPEATABLE_READこの分離レベルはトランザクションがクエリを複数回繰り返し、毎回同じレコードを返すことができることを意味します。このレベルはダーティで繰り返し不可能な読み取りを防ぎます。TransactionDefinition.ISOLATION_SERIALIZABLEすべてのトランザクションが1つずつ順次実行されるため、トランザクション間の干渉の可能性がありません。このレベルはダーティ・リード、再現不可能なリード、ファントムリードを防ぎますが、プログラムのパフォーマンスに重大な影響を与えます。通常、このレベルは使用しません。
トランザクションの伝播動作
トランザクションの伝播動作とは、現在のトランザクションの開始時にトランザクションコンテキストがすでに存在する場合に、トランザクションメソッドの実行動作を指定するためのいくつかのオプションがあることを意味します。TransactionDefinition伝播振る舞いを表す以下の定数が定義に含まれています:
TransactionDefinition.PROPAGATION_REQUIREDトランザクションが存在する場合はそのトランザクションに参加し、存在しない場合は新しいトランザクションを作成します。これはデフォルト値です。TransactionDefinition.PROPAGATION_REQUIRES_NEW新しいトランザクションを作成するか、現在のトランザクションが存在する場合はハングします。TransactionDefinition.PROPAGATION_SUPPORTS現在トランザクションが存在する場合は、そのトランザクションに参加し、現在トランザクションが存在しない場合は、非トランザクションとして実行を継続します。TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 非トランザクション方式で実行され、現在のトランザクションが存在する場合はハングします。TransactionDefinition.PROPAGATION_NEVERトランザクションが存在する場合は例外をスローします。TransactionDefinition.PROPAGATION_MANDATORYトランザクションが存在する場合は参加し、存在しない場合は例外をスローします。TransactionDefinition.PROPAGATION_NESTED現在のトランザクションが存在しない場合、TransactionDefinition.PROPAGATION_REQUIREDと同じ値になります。
トランザクションのロールバックルール
トランザクションをロールバックするように spring トランザクションマネージャに指示する推奨の方法は、現在のトランザクションのコンテキスト内で例外をスローすることです。spring トランザクションマネージャは処理されなかった例外をすべてキャッチし、例外をスローしたトランザクションをロールバックするかどうかをルールに基づいて決定します。
デフォルトでは、spring がトランザクションをロールバックするのは、スローされた例外が実行時にチェックされていない例外、つまり RuntimeException のサブクラスである場合のみで、チェックされた例外ではトランザクションはロールバックされません。チェックされた例外を含め、トランザクションをロールバックするためにスローされる例外を明示的に構成できます。また、スローされてもトランザクションがロールバックされない例外を明示的に定義することも可能です。
一般的なトランザクションの構成
- readOnly:この属性は、現在のトランザクションが読み取り専用かどうかを設定するために使用されます。trueに設定すると読み取り専用、falseに設定すると読み取り/書き込みを意味し、デフォルト値はfalseです:
@Transactional(readOnly=true)
- rollbackFor: この属性は、ロールバックされる例外クラスの配列を設定するために使用されます。例
rollbackForClassNameこの属性は、ロールバックされる例外クラス名の配列を設定するために使用されます。例
- noRollbackFor: この属性は、ロールバックする必要のない例外クラスの配列を設定するために使用します。これにより、指定した例外配列の例外がメソッド内でスローされた場合、トランザクションのロールバックは実行されません。例
noRollbackForClassNameこの属性は、ロールバックの対象とならない例外クラス名の配列を設定するために使用します。これにより、指定した例外名の配列に含まれる例外がメソッド内でスローされた場合、トランザクションのロールバックは実行されません。例
- propagation : この属性は、トランザクションの伝播動作を設定するために使用されます。例
Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)。
- 分離: この属性は基礎となるデータベースのトランザクション分離レベルを設定するために使用されます。トランザクション分離レベルは複数の同時トランザクションに対処するために使用されます。
- timeout: この属性は、トランザクションのタイムアウト秒数を設定するために使用されます。
知っておくべきこと
- 実際のニーズに合わせて使うかどうかを決めることが重要で、コーディングの段階で考えておかないと、後日のメンテナンスが大変になります;
- 多くの場合、モノは有効であると考えられていますが、実際には有効でない可能性があります!
- Transactionalアノテーションは、クラスのpublicメソッドで使用する必要があります。 protectedメソッドやprivateメソッドでも@Transactionalアノテーションが使用され、エラーは報告されませんが、トランザクションは無効であることに注意してください。
- Thing@Transactionalは、そのメソッド内のサブメソッドに対しては機能しません!つまり、publicメソッドAでthing@Transactionalを宣言していても、メソッドAの中に子メソッドBとCがあり、メソッドBがデータ操作を行い、例外はメソッドB自身が処理する場合、thing@Transactionalは効きません!逆に、メソッドBがthing@Transactionalを宣言していても、publicメソッドAがthingを宣言していなければ、thingも効力を持ちません!thingを有効にしたい場合は、サブメソッドのトランザクション制御を呼び出し側メソッドに引き渡し、サブメソッドでrollbackForアノテーションを使用して、呼び出し側メソッドで処理するために呼び出し側メソッドにロールバックまたはスローする必要がある例外を指定する必要があります。要するに、物事を使用する際の例外は呼び出し側で処理されるということです!
- Springで@Transactionalを制御している場合、例外がスローされるとロールバックされます。処理をキャッチするためにcatchを使用する場合は、有効ではありませんが、手動でロールバックするか、例外がスローされますキャッチ内部で、throw new RuntimeException();などの効果を取ることができます。
開発の準備
環境要件
JDK: 1.8SpringBoot: 1.5.17.RELEASE
まず、Mavenの依存関係があります。
pom.xmlファイルは以下の通りです。
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.17.RELEASE</version>
<relativePath />
</parent>
<dependencies>
<!-- Spring Boot Web 依赖 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
<!-- MySQL 连接驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<!-- Druid 数据连接池依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.8</version>
</dependency>
</dependencies>
application.propertiesファイルの設定。
banner.charset=UTF-8
server.tomcat.uri-encoding=UTF-8
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
spring.messages.encoding=UTF-8
spring.application.name=springboot-transactional
server.port=8182
spring.datasource.url=jdbc:mysql://localhost:3306/springBoot?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
spring.datasource.maxWait=60000
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.filters=stat,wall,log4j
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
logging.level.com.pancm.dao=debug
コードの記述
@EnableTransactionManagement SpringBootがTransactional thingを使用する場合、thing宣言を開発するmainメソッドにアノテーションを追加し、使用するサービスレイヤーのpublicメソッドに@Transactionalアノテーションを追加する必要があります。
使用例1
まず、@Transactionalアノテーションの使い方ですが、publicメソッドを追加するだけでアノテーションを追加することができます。しかし、このような使い方をすると、springが制御するために例外を投げる必要があります。
コードの例。
@Transactional
public boolean test1(User user) throws Exception {
long id = user.getId();
System.out.println("クエリデータ1:" + udao.findById(id));
// 2回追加すると、主キーIDが衝突する。
udao.insert(user);
System.out.println("クエリーデータ2:" + udao.findById(id));
udao.insert(user);
return false;
}
使用例2
Transactionalを使用する際に例外を自分で処理したい場合は、手動でロールバックすることができます。 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()手動でロールバックするには、catch に ; メソッドを追加します。ただし、例外が発生したとき、つまり例外がスローされたときに最初に手動でロールバックする必要があることに注意しましょう!
コードの例。
@Transactional
public boolean test2(User user) {
long id = user.getId();
try {
System.out.println("クエリデータ1:" + udao.findById(id));
// 2回追加すると、主キーIDが衝突するので、そのデータをロールバックできるか確認する
udao.insert(user);
System.out.println("クエリーデータ2:" + udao.findById(id));
udao.insert(user);
} catch (Exception e) {
System.out.println("例外が発生しました、手動でロールバックしてください!");
// 手動でロールバックする
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
e.printStackTrace();
}
return false;
}
使用例3
何か @Transactional を使用していて、データベース操作を実行するためにサブメソッドを呼び出しているが、それを有効にしたい場合は、rollbackFor アノテーションを使用するか、サブメソッドに例外を投げて、それを呼び出したメソッドで処理させることができます!
コードの例。
@Transactional
public boolean test3(User user) {
/*
* サブメソッドで例外をロールバックする
*/
try {
System.out.println("クエリデータ1:" + udao.findById(user.getId()));
deal1(user);
deal2(user);
deal3(user);
} catch (Exception e) {
System.out.println("例外が発生しました、手動でロールバックしてください");
// 手動でロールバックする
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
e.printStackTrace();
}
return false;
}
public void deal1(User user) throws SQLException {
udao.insert(user);
System.out.println("クエリーデータ2:" + udao.findById(user.getId()));
}
public void deal2(User user) throws SQLException{
if(user.getAge()<20){
//SQL
udao.insert(user);
}else{
user.setAge(21);
udao.update(user);
System.out.println("クエリデータ 3:" + udao.findById(user.getId()));
}
}
@Transactional(rollbackFor = SQLException.class)
public void deal3(User user) {
if(user.getAge()>20){
//SQL
udao.insert(user);
}
}
使用例4
DataSourceTransactionManagerTransactionDefinitionあなたが物事に@ Transactionalアノテーションを使用したくない場合は、コードの特定のセクションを有効に制御するために、自分で物事を制御したいが、自分でそんなに多くのコードを書きたくない場合は、手動でロールバックされるように制御するためにSpringBootのクラスと組み合わせて使用することができます。ただし、時間の使用では、ロールバックの時間に注意を払う必要があり、物事の開口部が、ロールバックの時間に開いていないか、または提出されている場合、提出されていないことを確認するには、例外が発生したキャッチ内にあります!
コードの例。
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
public boolean test4(User user) {
/*
* 手動で行う
*/
TransactionStatus transactionStatus=null;
boolean isCommit = false;
try {
transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
System.out.println("クエリデータ1:" + udao.findById(user.getId()));
// 追加/変更するには
udao.insert(user);
System.out.println("クエリーデータ2:" + udao.findById(user.getId()));
if(user.getAge()<20) {
user.setAge(user.getAge()+2);
udao.update(user);
System.out.println("クエリデータ 3:" + udao.findById(user.getId()));
}else {
throw new Exception("例外をシミュレートする!");
}
//マニュアルの提出
dataSourceTransactionManager.commit(transactionStatus);
isCommit= true;
System.out.println("手動でコミットすると成功する!");
throw new Exception("2番目の例外をシミュレートする!");
} catch (Exception e) {
//コミットしない場合はロールバックする
if(!isCommit){
System.out.println("例外が発生しました、手動でロールバックしてください");
//手動でロールバックする
dataSourceTransactionManager.rollback(transactionStatus);
}
e.printStackTrace();
}
return false;
}
これらの例は、より一般的な使用方法であり、基本的には物事の日常的な使用を満たすために、春は物事を制御する方法があります、ロールバックするブレークポイントを設定することです。しかし、この方法は、実際に確認される個人の信頼性を検証していません。メソッドの使用は次のとおりです。
Object savePoint =null;
try{
//ロールバックポイントを設定する
savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
}catch(Exception e){
//savePointへの例外ロールバック。
TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
}
上記の使用例の後、さらにいくつかの主要なクラスを紹介します。
まず第一に、それはまだエンティティクラスです。
エンティティ
またまた全能のユーザーシートです
public class User {
private Long id;
private String name;
private Integer age;
//getter 和 setter 略
}
コントローラー制御層
それからコントロールレイヤー!
制御層のコードは以下の通り。
@RestController
@RequestMapping(value = "/api/user")
public class UserRestController {
@Autowired
private UserService userService;
@Autowired
private UserDao userDao;
@PostMapping("/test1")
public boolean test1(@RequestBody User user) {
System.out.println("リクエストパラメータ:" + user);
try {
userService.test1(user);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("最後のクエリ:" + userDao.findById(user.getId()));
return true;
}
@PostMapping("/test2")
public boolean test2(@RequestBody User user) {
System.out.println("リクエストパラメータ:" + user);
userService.test2(user);
System.out.println("最後のクエリ:" + userDao.findById(user.getId()));
return true;
}
@PostMapping("/test3")
public boolean test3(@RequestBody User user) {
System.out.println("リクエストパラメータ:" + user);
userService.test3(user);
System.out.println("最後のクエリ:" + userDao.findById(user.getId()));
return true;
}
@PostMapping("/test4")
public boolean test4(@RequestBody User user) {
System.out.println("リクエストパラメータ:" + user);
userService.test4(user);
System.out.println("最後のクエリ:" + userDao.findById(user.getId()));
return true;
}
}
アプリポータル
@EnableTransactionManagement アノテーションを追加する必要がある以外は、基本的に通常のSpringBootプロジェクトと同じです!
コードは以下の通り。
@EnableTransactionManagement
@SpringBootApplication
public class TransactionalApp
{
public static void main( String[] args )
{
SpringApplication.run(TransactionalApp.class, args);
System.out.println("Transactional アプリケーションは...");
}
}
機能テスト
プログラムを起動すると、上記の使用例に対応するテストができるようになります。そのうちのいくつかは、動作していることを確認するために2つ以上の面でテストする必要があります!ここではテストにPostmanを使っています!
テスト例1
2つのテスト、1つ目は@Transactionalアノテーションなし、2つ目はアノテーションあり!
最初のテスト。
"http://localhost:8281"/api/user/test1
ボディパラメータは以下の通り。
{"id": 1, "name": "xuwujing", "age": 18}.
コンソールのプリントアウト。
2番目のテスト:
POSTリクエストを行う際に使用します。
"http://localhost:8281"/api/user/test1
ボディパラメータは以下の通り。
{"id": 1, "name": "xuwujing", "age": 18}.
コンソールのプリントアウト。
注:2回目のテストでは、1回目のテストでデータベースに書き込まれたid 1のデータは削除されました!
2回目のテストでは、@Transactionalアノテーションが付加されていなかったため、例外が発生してもデータが書き込まれていましたが、1回目のテストでは、@Transactionalアノテーションが付加されていたため、データが書き込まれても、例外が発生した後、結局データはロールバックされ、書き込まれないことがわかりました!上記のテストケースから、テストケースのようなものが効果を発揮していることがわかります!
テスト例2
例2のコードの使い方は例1の使い方とほとんど同じなので、違いは例外を自分でコントロールすることです!
POSTリクエストを行う際に使用します。
"http://localhost:8281"/api/user/test2
ボディパラメータは以下の通り。
{"id": 1, "name": "xuwujing", "age": 18}.
コンソールのプリントアウト。
リクエスト・パラメータ:User [id=1, name=xuwujing, age=18] 照会済みデータ1:null 照会済みデータ2:User [id=1, name=xuwujing, age=18] 例外が発生しました!キー'PRIMARY'のエントリ'1'が重複しています。 最後にクエリされたデータ:null 物事が有効になっていることがわかります!
テスト例3
例3ではサブメソッドの呼び出しが行われたので、ここでは異なるリクエスト条件に基づいて2つのテストが行われます!
最初のテスト:
POSTリクエストを行う際に使用します。
"http://localhost:8281"/api/user/test3
ボディパラメータは以下の通り。
{"id": 1, "name": "xuwujing", "age": 18}.
コンソールのプリントアウト。
リクエスト・パラメータ:User [id=1, name=xuwujing, age=18] 照会済みデータ 1:null 照会済みデータ 2:User [id=1, name=xuwujing, age=18] 例外が発生しました!キー 'PRIMARY' のエントリ '1' が重複しています。
2つ目のテスト:
POSTリクエストを行う際に使用します。
"http://localhost:8281"/api/user/test3
ボディパラメータは以下の通り。
{"id": 1, "name": "xuwujing", "age": 21}.
コンソールのプリントアウト。
Request Parameters:User [id=1, name=xuwujing, age=21] Queried Data1:null Queried Data2:User [id=1, name=xuwujing, age=21] Queried Data3:User [id=1, name=xuwujing2, age=21] 例外が発生しました。手動でロールバックしてください!キー 'PRIMARY' の重複エントリ '1' 最後にクエリされたデータ:null 上記の 2 つのテストに基づくと、rollbackFor アノテーションを使用するか、呼び出し側のメソッドで処理されるようにこのサブメソッドの例外を投げることで、うまくいくことが結論付けられます!
テスト例4
使用例4ではモノの手動制御を行ったので、ここでは異なる要求条件に基づいて2つのテストを行います!
最初のテスト:
POSTリクエストを行う際に使用します。
"http://localhost:8281"/api/user/test4
ボディパラメータは以下の通り。
{"id": 1, "name": "xuwujing", "age": 18}.
コンソールのプリントアウト。
リクエストパラメーター:User [id=1, name=xuwujing, age=18] 照会済みデータ1:null 照会済みデータ2:User [id=1, name=xuwujing, age=18] 照会済みデータ3:User [id=1, name=xuwujing2, age=20] 手動で送信するもの成功です!2番目の例外をシミュレートします!最後に照会されたデータ:User [id=1, name=xuwujing, age=20].
2つ目のテスト:
データベースIDが1のデータは、事前に削除してください!
POSTリクエストを行う際に使用します。
"http://localhost:8281"/api/user/test4
ボディパラメータは以下の通り。
{"id": 1, "name": "xuwujing", "age": 21}.
コンソールのプリントアウト。
{"id":1,"name":"xuwujing","age":18}
上記の2つのテストに基づくと、物事を手動で制御することはまったく問題なく、物事がコミットされている限り、例外が後で発生しても書き込みには影響しないという結論に達します!制御の範囲内で例外が発生した場合は、ロールバックすることができます!
テストチャートの例。
その他
SpringBoot トランザクションプロジェクトアドレス: https://github.com/SpringBoot-transactional
SpringBootコレクション全体のアドレス:
https://github.com/SpringBoot-study





