Spring事務(wù)aftercommit原理及實(shí)踐
題引示例
sql
CREATE TABLE `goods` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `good_id` varchar(20) DEFAULT NULL, `num` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `goods_good_id_index` (`good_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
java示例
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","");
//part1
conn.setAutoCommit(false);
Statement statement = conn.createStatement();
statement.execute("INSERT INTO test.goods ( good_id, num) VALUES ( 'sku123', 0);");
conn.commit();
//part2
statement = conn.createStatement();
statement.execute("INSERT INTO test.goods ( good_id, num) VALUES ( 'sku123', 0);");
conn.setAutoCommit(true);
//part3
try {
statement = conn.createStatement();
statement.execute("INSERT INTO test.goods ( good_id, num) VALUES ( 'sku123', 0);");
int i = 1/0;
}catch (Exception ex){
System.out.println("there is an error");
}
conn.setAutoCommit(true);
//part4
conn.setAutoCommit(false);
try {
statement = conn.createStatement();
statement.execute("INSERT INTO test.goods ( good_id, num) VALUES ( 'sku123', 0);");
int i = 1/0;
}catch (Exception ex){
System.out.println("there is an error");
}
conn.setAutoCommit(true);
你舉得這4段代碼都提交了嗎,為什么?
如果你知道這個(gè)知識(shí)點(diǎn),那么本文對(duì)于你來(lái)說(shuō)很容易理解。
一個(gè)知識(shí)點(diǎn)
首先,上面4段代碼都會(huì)提交成功。
主要的知識(shí)點(diǎn)是, autocommit 的狀態(tài)切換時(shí),會(huì)對(duì)自動(dòng)提交之前執(zhí)行的內(nèi)容。
看下這個(gè)方法的注釋就知道了。

他這邊說(shuō),如果事務(wù)執(zhí)行過(guò)程中,如果 autocommit 狀態(tài)改變了,會(huì)提交之前的事務(wù)。
額,這有個(gè)邏輯上的問(wèn)題,如果autocommit本身就是true,我們的語(yǔ)句不是直接就提交了么,那這個(gè)描述應(yīng)該改成從false改成true的時(shí)候。
其實(shí)這段注釋還有前半段。

針對(duì)DML和DDL語(yǔ)句,autocommit=true的情況下,statement是立刻提交的。
而對(duì)于select語(yǔ)句,要等到關(guān)聯(lián)的result set被關(guān)閉,對(duì)于存儲(chǔ)過(guò)程....
而這個(gè)知識(shí)點(diǎn)太偏了,懂的朋友了解下,告訴我是啥..
所以我們這邊的知識(shí)點(diǎn)嚴(yán)謹(jǐn)點(diǎn)來(lái)說(shuō)就是: 對(duì)于DDL和DML語(yǔ)句,當(dāng)autocommit從false切換為true時(shí),事務(wù)會(huì)自動(dòng)提交。
spring事務(wù)中的aftercommit
afterCommit是Spring事務(wù)機(jī)制中的一個(gè)回調(diào)鉤子,用于在事務(wù)提交后做一些操作。
我們可以這么使用它
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
public void afterCommit() {
//...執(zhí)行一些操作
}
});
也可以通過(guò)@TransactionalEventListener間接使用它,它的底層原理就是上面這段代碼
@TransactionalEventListener
public void handleEvent(Event event){
//...執(zhí)行一些操作
}
重點(diǎn)是在事務(wù)提交后執(zhí)行一些操作,也就是我題目中conn.commit()之后再執(zhí)行一些操作。
這個(gè)時(shí)候存在一個(gè)問(wèn)題,如果這個(gè)操作是數(shù)據(jù)庫(kù)相關(guān)的操作,會(huì)不會(huì)被提交。
根據(jù)我文章開(kāi)篇的代碼,你肯定就知道答案就是會(huì)提交,但是是autocommit的切換導(dǎo)致的提交。
額,其實(shí)并不是,對(duì)比2個(gè)常用db框架,Mybatis和JPA(Hibernate),Mybatis會(huì)提交,而Hibernate會(huì)丟失。

在afercomit的注釋中,他也警告我們了,在aftercommit中做數(shù)據(jù)庫(kù)操作可能不會(huì)被提交。如果你要做數(shù)據(jù)庫(kù)操作,你需要在一個(gè)新的事務(wù)中,可以使用PROPAGATION_REQUIRES_NEW隔離級(jí)別。
源碼中的NOTE要仔細(xì)的看?。『苤攸c(diǎn)
在不創(chuàng)建新事務(wù)的前提下,為什么對(duì)于Mybatis和JPA在aftercommit中執(zhí)行操作,一個(gè)提交,一個(gè)不提交?開(kāi)始我們的源碼解析。
源碼解析
Spring Transaction的核心邏輯封裝在TransactionAspectSupport的invokeWithinTransaction方法中,而核心流程中重要的三個(gè)操作,獲取/提交/回滾事務(wù),由PlatformTransactionManager來(lái)實(shí)現(xiàn)。
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
PlatformTransactionManager使用了策略模式和模板方法模式,它的子類AbstractPlatformTransactionManager又對(duì)上面三個(gè)方法做了抽象,暴露了一一系列鉤子方法讓子類實(shí)現(xiàn)。
最常用的子類就是DataSourceTransactionManager和HibernateTransactionManager,分別對(duì)應(yīng)Mybatis和JPA框架。
本文講解的aftercommit同步鉤子在AbstractPlatformTransactionManager的processCommit中被觸發(fā)。
回顧我們上面展示的場(chǎng)景,我們?cè)谝粋€(gè)事務(wù)里,注冊(cè)了一個(gè)aftercommit鉤子,并且aftercommit里面,也會(huì)再次操作數(shù)據(jù)庫(kù),執(zhí)行dml操作。
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
//...
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
unexpectedRollback = status.isGlobalRollbackOnly();
//...假設(shè)事務(wù)提交成功
doCommit(status);
}
try {
triggerAfterCommit(status);
}
finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
finally {
cleanupAfterCompletion(status);
}在第一個(gè)事務(wù)doCommit成功,他會(huì)通過(guò)triggerAfterCommit觸發(fā)它的aftercommit鉤子邏輯,進(jìn)行下一次事務(wù)操作,但是此時(shí)的Transaction還沒(méi)有釋放,并且它也不是newTransaction了。
為什么不是newTransaction,見(jiàn)以下代碼
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
//...
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}
因?yàn)?status.isNewTransaction() 不成立,所以 doCommit(status); 不會(huì)執(zhí)行。
doCommit中會(huì)進(jìn)行什么操作?
對(duì)于DataSourceTransactionManager,就是調(diào)用了Connection的commit方法,對(duì)事務(wù)進(jìn)行提交。
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Committing JDBC transaction on Connection [" + con + "]");
}
try {
con.commit();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not commit JDBC transaction", ex);
}
}
雖然錯(cuò)失了doCommit這個(gè)機(jī)會(huì),但是在cleanupAfterCompletion(status);方法
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
status.setCompleted();
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.clear();
}
if (status.isNewTransaction()) {
doCleanupAfterCompletion(status.getTransaction());
}
if (status.getSuspendedResources() != null) {
if (status.isDebug()) {
logger.debug("Resuming suspended transaction after completion of inner transaction");
}
Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
}
}
在doCleanupAfterCompletion的邏輯中,注意doCleanupAfterCompletion也是一個(gè)鉤子,這個(gè)邏輯也由DataSourceTransactionManager實(shí)現(xiàn)
protected void doCleanupAfterCompletion(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// Remove the connection holder from the thread, if exposed.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.unbindResource(obtainDataSource());
}
// Reset connection.
Connection con = txObject.getConnectionHolder().getConnection();
try {
if (txObject.isMustRestoreAutoCommit()) {
con.setAutoCommit(true);
}
DataSourceUtils.resetConnectionAfterTransaction(
con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
}
catch (Throwable ex) {
logger.debug("Could not reset JDBC Connection after transaction", ex);
}
if (txObject.isNewConnectionHolder()) {
if (logger.isDebugEnabled()) {
logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
}
DataSourceUtils.releaseConnection(con, this.dataSource);
}
txObject.getConnectionHolder().clear();
}
調(diào)用到了 con.setAutoCommit(true);間接了提交了事務(wù)
然后我們?cè)賮?lái)看看HibernateTransactionManager對(duì)這個(gè)兩個(gè)方法的實(shí)現(xiàn)
HibernateTransactionManager#doCommit
protected void doCommit(DefaultTransactionStatus status) {
HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction();
Transaction hibTx = txObject.getSessionHolder().getTransaction();
Assert.state(hibTx != null, "No Hibernate transaction");
if (status.isDebug()) {
logger.debug("Committing Hibernate transaction on Session [" +
txObject.getSessionHolder().getSession() + "]");
}
try {
//看這里
hibTx.commit();
}
catch (org.hibernate.TransactionException ex) {
// assumably from commit call to the underlying JDBC connection
throw new TransactionSystemException("Could not commit Hibernate transaction", ex);
}
catch (HibernateException ex) {
// assumably failed to flush changes to database
throw convertHibernateAccessException(ex);
}
catch (PersistenceException ex) {
if (ex.getCause() instanceof HibernateException) {
throw convertHibernateAccessException((HibernateException) ex.getCause());
}
throw ex;
}
}
HibernateTransactionManager#doCleanupAfterCompletion
protected void doCleanupAfterCompletion(Object transaction) {
HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
// Remove the session holder from the thread.
if (txObject.isNewSessionHolder()) {
TransactionSynchronizationManager.unbindResource(obtainSessionFactory());
}
// Remove the JDBC connection holder from the thread, if exposed.
if (getDataSource() != null) {
TransactionSynchronizationManager.unbindResource(getDataSource());
}
Session session = txObject.getSessionHolder().getSession();
if (this.prepareConnection && isPhysicallyConnected(session)) {
// We're running with connection release mode "on_close": We're able to reset
// the isolation level and/or read-only flag of the JDBC Connection here.
// Else, we need to rely on the connection pool to perform proper cleanup.
try {
Connection con = ((SessionImplementor) session).connection();
Integer previousHoldability = txObject.getPreviousHoldability();
if (previousHoldability != null) {
con.setHoldability(previousHoldability);
}
DataSourceUtils.resetConnectionAfterTransaction(
con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
}
catch (HibernateException ex) {
logger.debug("Could not access JDBC Connection of Hibernate Session", ex);
}
catch (Throwable ex) {
logger.debug("Could not reset JDBC Connection after transaction", ex);
}
}
if (txObject.isNewSession()) {
if (logger.isDebugEnabled()) {
logger.debug("Closing Hibernate Session [" + session + "] after transaction");
}
SessionFactoryUtils.closeSession(session);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Not closing pre-bound Hibernate Session [" + session + "] after transaction");
}
if (txObject.getSessionHolder().getPreviousFlushMode() != null) {
session.setFlushMode(txObject.getSessionHolder().getPreviousFlushMode());
}
if (!this.allowResultAccessAfterCompletion && !this.hibernateManagedSession) {
disconnectOnCompletion(session);
}
}
txObject.getSessionHolder().clear();
}docommit里的邏輯還是用到了底層connection的commit,而在doCleanupAfterCompletion中,沒(méi)有見(jiàn)到設(shè)置autocommit的身影。
所以在JPA中你在aftercommit中進(jìn)行dml操作是會(huì)丟失的。
另外一個(gè)點(diǎn)是,如果你在aftercommit進(jìn)行了事務(wù)操作,但是中間發(fā)生了異常,比如2條insert語(yǔ)句后,發(fā)生了異常,這兩條insert會(huì)不會(huì)回滾?
答案是不會(huì)
回顧processCommit方法
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
//...
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
unexpectedRollback = status.isGlobalRollbackOnly();
//...假設(shè)事務(wù)提交成功
doCommit(status);
}
try {
triggerAfterCommit(status);
}
finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
finally {
cleanupAfterCompletion(status);
}
}
我們的aftercommit在triggerAfterCommit執(zhí)行,這個(gè)方法里面拋出了異常,因?yàn)闆](méi)有catch,異常會(huì)往上傳遞,在cleanupAfterCompletion里也沒(méi)有處理異常,但是對(duì)于mybatis來(lái)講,它改變了autocommit狀態(tài),所以更改被提交了。這是一個(gè)你想不到的坑。
最佳實(shí)踐
- aftercommit或者說(shuō)是transactionlistener,最好不要有dml操作
- 一但aftercommit中有事務(wù)操作,存在的風(fēng)險(xiǎn)是,一致性得不到保證,異常不會(huì)讓這部分的事務(wù)回滾
demo
寫了一個(gè)工程,用于測(cè)試mybatis和jpa中對(duì)于aftercommit中執(zhí)行dml操作是否會(huì)提交
地址 https://github.com/shengchaojie/spring_tx_aftercommit_problem
參考資料 http://www.fzitv.net/article/279094.htm
以上就是Spring事務(wù)aftercommit原理及實(shí)踐的詳細(xì)內(nèi)容,更多關(guān)于Spring事務(wù)aftercommit原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- spring的TransactionalEventListener事務(wù)感知源碼解析
- Spring事務(wù)框架之TransactionDefinition源碼解析
- Spring事務(wù)框架之TransactionStatus源碼解析
- Spring?Boot多數(shù)據(jù)源事務(wù)@DSTransactional的使用詳解
- Spring事務(wù)控制策略及@Transactional失效問(wèn)題解決避坑
- springboot使用mybatis開(kāi)啟事務(wù)回滾
- Spring的嵌套事務(wù)(Propagation.NESTED)到底是個(gè)啥案例代碼講解
相關(guān)文章
JAVA異常處理機(jī)制之throws/throw使用情況
這篇文章主要介紹了JAVA異常處理機(jī)制之throws/throw使用情況的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
基于java SSM springboot實(shí)現(xiàn)景區(qū)行李寄存管理系統(tǒng)
這篇文章主要介紹了基于java SSM springboot實(shí)現(xiàn)的景區(qū)行李寄存管理系統(tǒng),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08
springboot使用maven實(shí)現(xiàn)多環(huán)境運(yùn)行和打包問(wèn)題
這篇文章主要介紹了springboot使用maven實(shí)現(xiàn)多環(huán)境運(yùn)行和打包問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
java程序員自己的圖片轉(zhuǎn)文字OCR識(shí)圖工具分享
這篇文章主要介紹了java程序員自己的圖片轉(zhuǎn)文字OCR識(shí)圖工具,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
java web SpringMVC后端傳json數(shù)據(jù)到前端頁(yè)面實(shí)例代碼
本篇文章主要介紹了java web SpringMVC后端傳json數(shù)據(jù)到前端頁(yè)面實(shí)例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03
Maven的國(guó)內(nèi)鏡像(快速解決jar下載過(guò)慢的問(wèn)題)
下面小編就為大家?guī)?lái)一篇Maven的國(guó)內(nèi)鏡像(快速解決jar下載過(guò)慢的問(wèn)題)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06
SpringMVC的@InitBinder參數(shù)轉(zhuǎn)換代碼實(shí)例
這篇文章主要介紹了SpringMVC的@InitBinder參數(shù)轉(zhuǎn)換代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09

