基本原理春情勢
春は実際には、トランザクションのトランザクションデータベースのサポートなしトランザクションデータベースのサポートの性質である、春には、トランザクション機能を提供することができません。純粋なJDBCデータベース操作のために、あなたがサービスを使用する場合は、次の手順を実行することができます。
-
接続接続CONを取得=たDriverManager.getConnection()
-
オープントランザクションcon.setAutoCommit(真/偽)。
-
CRUDの実行
-
トランザクション/ロールバックトランザクションcon.commit()/ con.rollbackをコミット();
-
)(接続はconn.closeを閉じます。
Springのトランザクション管理機能を使用した後、我々は、ステップ2と4で、もはや書き込みコードは、しかしSpirngによって自動的に実行されていないことができます。だから、どのように春には、私たちが書く前に、トランザクションの後に開き、CRUDとセッションそれをクローズ?この問題を解決するためには、全体を達成するためにSpringのトランザクション管理の原則を理解することができます。
次の例のように簡単な説明、注釈モードです。
-
特定のアノテーション@Transactional関連するクラスとメソッドに、設定ファイルの注釈ドライブを開きます。
-
それは、関連するBeanを生成します解析に始まり春とき、この時間は、ノートに関連するクラスとメソッドを表示しなければならないので、射出@Transactionに基づいて、これらのクラスとメソッド、およびコンフィギュレーションパラメータのプロキシ、エージェントが生成されます。私たちはに関する事項を処分するため(トランザクションのコミットオープン通常、トランザクションはバック異常圧延されます)。
-
実際のトランザクションがコミットとロールバックデータベース層がbinlogのかのREDOログによって達成されます。
Springのトランザクション・メカニズム
すべてのデータアクセステクノロジは、トランザクションがデータ操作、データの完了にコミットまたはエラーが発生したときにロールバックされ、トランザクション処理は、これらの技術は、トランザクションを可能にするためのAPIを提供しています。
異なるデータアクセス技術を処理するトランザクションを処理するための統一メカニズムとSpringのトランザクション・メカニズム。表に示すようにスプリングのトランザクション機構は、PlatformTransactionManagerインターフェイス、異なるデータ・アクセス・テクノロジー・インタフェースを使用して、異なるトランザクションを提供します。
データアクセステクノロジと実装
データアクセステクノロジ | 実現 |
---|---|
JDBC | DataSourceTransactionManager |
JPA | JapTransactionManager |
休止状態 | HibernateTransactionManager |
JDO | JdoTransactionManager |
分散トランザクション | JtaTransactionManager |
コードは次のようにプログラム内のトランザクションマネージャである定義します。
@Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setDataSource(dataSource());
return transactionManager;
}
宣言的トランザクション
春の宣言型トランザクションサポート、ユーザーが方法を選択することができ、このメソッドは、トランザクションのサポートを必要とすることを法の番組に@Transactionalのアノテーションを使用して注釈を使用してトランザクションを使用する必要があります。これは、AOPベースのオペレーションの実現です。
@Transactional
public void saveSomething(Long id, String name) {
//数据库操作
}
ここで特に注目すべきなのであるorg.springframework.transaction.annotationパッケージの代わりのjavax.transactionからこの@Transactionalコメント。
AOPプロキシ実装の二種類:
-
JDKは、エージェントインターフェースで、プライベートメソッドがインターフェイスに存在してはならない、それが傍受されることはありません。
-
CGLIBがサブクラスである、プライベートメソッドは、まだそれが傍受することはできません、サブクラスには表示されません。
Javaの動的プロキシ。
具体的には以下の4つのステップ:
-
独自のコールプロセッサを作成するためのインタフェースを実装することでのInvocationHandler。
-
プロキシクラスとしてインタフェースオブジェクトとクラスローダのセットを指定することにより、動的プロキシクラスを作成します。
-
動的プロキシクラスのコンストラクタを反射することによって得られる、唯一のパラメータのタイプは、コールプロセッサインタフェースタイプです。
-
ハンドラを呼び出すように構成された動的プロキシクラスインスタンスコンストラクタオブジェクトを作成することによって、パラメータとして渡されます。
GCLIBエージェント
CGLIB(コード生成ライブラリ)は、強力な、高性能、高品質のコード生成ライブラリです。これは、ランタイムJavaクラスで拡張され、Javaインタフェースを実装することができます。
-
CGLIBカプセル化ASM、実行時に動的に新しいクラス(サブクラス)を生成します。
-
AOPのためのCGLIBは、プロキシがインタフェース、CGLIBはこの制限はありません基づいていなければならないをjdk。
区別の原則:
プロセスに特定のメソッドを呼び出す前に、実装インタフェースコールInvokeHandlerことを匿名プロキシクラスを生成するためにリフレクションを使用してJavaの動的プロキシ。オープンソースパッケージを使用して動的プロキシCGLIBのASM、クラスはサブクラスのバイトコードを変更することによって処理にロードされたプロキシオブジェクトクラスを、ファイル。
-
ターゲット・オブジェクトが実装インタフェース場合は、JDKの動的プロキシのAOPの実装のデフォルトを使用します
-
ターゲットオブジェクトがインタフェースを実装している場合、あなたはAOPを実現CGLIBの使用を強制することができます
-
インタフェースを実装していない対象物がCGLIBライブラリを使用しなければならない場合、ばねが自動的にJDKの動的プロキシと変換CGLIB間
このクラスは、プロキシの独自のインスタンスを維持することにより、機関内のこの時間に行く直接的な方法ではない場合。
@Service
public class PersonServiceImpl implements PersonService {
@Autowired
PersonRepository personRepository;
// 注入自身代理对象,在本类内部方法调用事务的传递性才会生效
@Autowired
PersonService selfProxyPersonService;
/**
* 测试事务的传递性
*
* @param person
* @return
*/
@Transactional
public Person save(Person person) {
Person p = personRepository.save(person);
try {
// 新开事务 独立回滚
selfProxyPersonService.delete();
} catch (Exception e) {
e.printStackTrace();
}
try {
// 使用当前事务 全部回滚
selfProxyPersonService.save2(person);
} catch (Exception e) {
e.printStackTrace();
}
personRepository.save(person);
return p;
}
@Transactional
public void save2(Person person) {
personRepository.save(person);
throw new RuntimeException();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void delete() {
personRepository.delete(1L);
throw new RuntimeException();
}
}
春のトランザクション伝播特性
複数の同時トランザクションがあるときは、いわゆるバネ性取引の普及が定義され、春には、これらの問題の行動に対処する方法でなければなりません。これらの属性は、TransactionDefinitionの特定の定数は、以下の表で説明する定義します。
定数名 | 定数を説明しました |
---|---|
PROPAGATION_REQUIRED | 何の取引、私たちは新たなビジネスを作成していない場合は、現在のトランザクションをサポートしています。これは、最も一般的な選択でなく、春のデフォルトの取引の普及です。 |
PROPAGATION_REQUIRES_NEW | 新しいトランザクション、現在のトランザクションが存在する場合は、保留中の現在のトランザクション。新しいトランザクションおよび保留中のトランザクションは何の関係もありません2つの別個の取引であり、外側のトランザクションは結果が、内側のトランザクションの実行をロールバックすることはできません、障害が発生した後にロールバックされ、内側のトランザクションは、外側のトランザクションを例外をスロー失敗キャプチャ、処理はロールバックではないかもしれません |
PROPAGATION_SUPPORTS | 何のトランザクションが非トランザクションの方法で実行されない場合は、現在のトランザクションをサポートしています。 |
PROPAGATION_MANDATORY | 現在のトランザクションをサポートし、トランザクションなした場合、例外をスローします。 |
PROPAGATION_NOT_SUPPORTED | 現在のトランザクションが存在する場合は、非トランザクションウェイに操作を実行し、保留中の現在のトランザクションを置きます。 |
PROPAGATION_NEVER | 現在のトランザクションが存在する場合は、非トランザクションの方法で実行、例外がスローされます。 |
PROPAGATION_NESTED | アクティブなトランザクションがある場合は、ネストされたトランザクションで実行されます。アクティブなトランザクションがない場合は、プレスREQUIREDプロパティを実行します。これは、ポイントはロールバックすることができ、省トランザクションが複数あり、別のトランザクションを使用しています。ロールバック内政は対外には影響しません。DataSourceTransactionManagerトランザクションマネージャのだけが発症。 |
データベースの分離レベル
分離レベル | 分離レベルの値 | 問題の原因 |
---|---|---|
読み取りコミット | 0 | ダーティリードにつながります |
読み取りコミット | 1 | 防ぎ汚れは、読み込みまたとない可能に読み込み、ファントム読み取り |
反復-読みます | 2 | 防ぎ汚れは、ファントム読み取りが可能、非反復可能読み取り、読み取っ |
Serializableを | 3 | 読み込み連載の取引のみの汚い読み込み、非反復可能読み取り、ファントム読み取りを回避するために、一つ一つを実行することができます。効率遅い、ご注意ください |
ダーティ読み取り:トランザクションデータを追加および削除していたが、別のトランザクションがコミットされていないデータを読み取ることができ、提出しませんでした。最初のトランザクションがロールバックされている場合は、この時点では、2番目のトランザクションは、ダーティデータに出席するため。
非反復可能読み取り:さらに修飾トランザクションデータ、最初の読み出し動作と第2動作と、トランザクション内の2つの読み出し動作されているが、今回は、二つのリードデータが矛盾しています。
マジック読書:最初のトランザクションが新しいデータを変更するときに、データの範囲のための最初のトランザクション一括編集を行い、この範囲の2番目のトランザクションデータを追加するには、それが失われます。
要約:
分離レベルが高いほど、それがデータの整合性と一貫性を確保することができるが、同時パフォーマンスへの影響も大きいです。
データベースのデフォルトの分離レベルのほとんどは、このようなのSQLServer、Oracleなど、読むコミットです
いくつかのデータベースのデフォルトの分離レベル:反復可能読み取り例:MySQLのInnoDBの
スプリング分離レベル
定数 | 説明 |
---|---|
ISOLATION_DEFAULT | これがPlatfromTransactionManagerデフォルトの分離レベルで、デフォルトのデータベースのトランザクション分離レベルを使用します。それぞれさらにJDBC分離レベルと4つ、。 |
ISOLATION_READ_UNCOMMITTED | これは最低のトランザクション分離レベルで、それは別のトランザクション料を約束し、このデータコミットされていないトランザクションを見ることができます。汚れを持っています。この分離レベルは、読み取り、繰り返し不可の読み取り、およびファントム読み取り。 |
ISOLATION_READ_COMMITTED | 取引後の順序で送信されたデータが別のトランザクションを読み取るように変更することを確認します。別のトランザクションがコミットされていないトランザクションを読み取ることができません。 |
ISOLATION_REPEATABLE_READ | このトランザクションの分離レベルの防止の汚れは、非反復読み取り、読み取っ。しかし、ファントム読み込みが発生することがあります。 |
ISOLATION_SERIALIZABLE | これは最も高価ですが、最も信頼性の高いトランザクション分離レベルのコスト。トランザクションが順次実行として処理されます。 |
ネストされたトランザクション
通过上面的理论知识的铺垫,我们大致知道了数据库事务和spring事务的一些属性和特点,接下来我们通过分析一些嵌套事务的场景,来深入理解spring事务传播的机制。
假设外层事务 Service A 的 Method A() 调用 内层Service B 的 Method B()
PROPAGATION_REQUIRED(spring 默认)
如果ServiceB.methodB() 的事务级别定义为 PROPAGATION_REQUIRED,那么执行 ServiceA.methodA() 的时候spring已经起了事务,这时调用 ServiceB.methodB(),ServiceB.methodB() 看到自己已经运行在 ServiceA.methodA() 的事务内部,就不再起新的事务。
假如 ServiceB.methodB() 运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
这样,在 ServiceA.methodA() 或者在 ServiceB.methodB() 内的任何地方出现异常,事务都会被回滚。
PROPAGATION_REQUIRES_NEW
比如我们设计 ServiceA.methodA() 的事务级别为 PROPAGATION_REQUIRED,ServiceB.methodB() 的事务级别为 PROPAGATION_REQUIRES_NEW。
那么当执行到 ServiceB.methodB() 的时候,ServiceA.methodA() 所在的事务就会挂起,ServiceB.methodB() 会起一个新的事务,等待 ServiceB.methodB() 的事务完成以后,它才继续执行。
他与 PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为 ServiceB.methodB() 是新起一个事务,那么就是存在两个不同的事务。如果 ServiceB.methodB() 已经提交,那么 ServiceA.methodA() 失败回滚,ServiceB.methodB() 是不会回滚的。如果 ServiceB.methodB() 失败回滚,如果他抛出的异常被 ServiceA.methodA() 捕获,ServiceA.methodA() 事务仍然可能提交(主要看B抛出的异常是不是A会回滚的异常)。
PROPAGATION_SUPPORTS
假设ServiceB.methodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到ServiceB.methodB()时,如果发现ServiceA.methodA()已经开启了一个事务,则加入当前的事务,如果发现ServiceA.methodA()没有开启事务,则自己也不开启事务。这种时候,内部方法的事务性完全依赖于最外层的事务。
PROPAGATION_NESTED
现在的情况就变得比较复杂了, ServiceB.methodB() 的事务属性被配置为 PROPAGATION_NESTED, 此时两者之间又将如何协作呢? ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:
a、捕获异常,执行异常分支逻辑
void methodA() {
try {
ServiceB.methodB();
} catch (SomeException) {
// 执行其他业务, 如 ServiceC.methodC();
}
}
这种方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。
b、 外部事务回滚/提交 代码不做任何修改, 那么如果内部事务(ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback
另外三种事务传播属性基本用不到,在此不做分析。
总结
对于项目中需要使用到事务的地方,我建议开发者还是使用spring的TransactionCallback接口来实现事务,不要盲目使用spring事务注解,如果一定要使用注解,那么一定要对spring事务的传播机制和隔离级别有个详细的了解,否则很可能发生意想不到的效果。
Spring Boot 对事务的支持
通过org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration类。我们可以看出Spring Boot自动开启了对注解事务的支持 Spring
只读事务(@Transactional(readOnly = true))的一些概念
-
概念:从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!(查询中不会出现别人在时间点a之后提交的数据)。
@Transcational(readOnly=true) 这个注解一般会写在业务类上,或者其方法上,用来对其添加事务控制。当括号中添加readOnly=true, 则会告诉底层数据源,这个是一个只读事务,对于JDBC而言,只读事务会有一定的速度优化。
而这样写的话,事务控制的其他配置则采用默认值,事务的隔离级别(isolation) 为DEFAULT,也就是跟随底层数据源的隔离级别,事务的传播行为(propagation)则是REQUIRED,所以还是会有事务存在,一代在代码中抛出RuntimeException,依然会导致事务回滚。
-
应用场合:
-
如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
-
如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。
【注意是一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用只读事务】
References:
-
http://www.codeceo.com/article/spring-transactions.html
-
http://www.cnblogs.com/fenglie/articles/4097759.html
-
https://www.zhihu.com/question/39074428/answer/88581202
-
http://blog.csdn.net/andyzhaojianhui/article/details/51984157