日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

mysql+after+commit_Spring事务aftercommit原理及实践

發布時間:2023/12/2 数据库 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 mysql+after+commit_Spring事务aftercommit原理及实践 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

來道題

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

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段代碼都提交了嗎,為什么?

如果你知道這個知識點,那么本文對于你來說很容易理解。

一個知識點

首先,上面4段代碼都會提交成功。

主要的知識點是, autocommit 的狀態切換時,會對自動提交之前執行的內容。

看下這個方法的注釋就知道了。

image.png

他這邊說,如果事務執行過程中,如果 autocommit 狀態改變了,會提交之前的事務。

額,這有個邏輯上的問題,如果autocommit本身就是true,我們的語句不是直接就提交了么,那這個描述應該改成從false改成true的時候。

其實這段注釋還有前半段。

image.png

針對DML和DDL語句,autocommit=true的情況下,statement是立刻提交的。

而對于select語句,要等到關聯的result set被關閉,對于存儲過程....

而這個知識點太偏了,懂的朋友了解下,告訴我是啥..

所以我們這邊的知識點嚴謹點來說就是: 對于DDL和DML語句,當autocommit從false切換為true時,事務會自動提交。

spring事務中的aftercommit

afterCommit是Spring事務機制中的一個回調鉤子,用于在事務提交后做一些操作。

我們可以這么使用它

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){

public void afterCommit() {

//...執行一些操作

}

});

也可以通過@TransactionalEventListener間接使用它,它的底層原理就是上面這段代碼

@TransactionalEventListener

public void handleEvent(Event event){

//...執行一些操作

}

重點是在事務提交后執行一些操作,也就是我題目中conn.commit()之后再執行一些操作。

這個時候存在一個問題,如果這個操作是數據庫相關的操作,會不會被提交。

根據我文章開篇的代碼,你肯定就知道答案就是會提交,但是是autocommit的切換導致的提交。

額,其實并不是,對比2個常用db框架,Mybatis和JPA(Hibernate),Mybatis會提交,而Hibernate會丟失。

image.png

在afercomit的注釋中,他也警告我們了,在aftercommit中做數據庫操作可能不會被提交。如果你要做數據庫操作,你需要在一個新的事務中,可以使用PROPAGATION_REQUIRES_NEW隔離級別。

源碼中的NOTE要仔細的看!!很重點

在不創建新事務的前提下,為什么對于Mybatis和JPA在aftercommit中執行操作,一個提交,一個不提交?開始我們的源碼解析。

源碼解析

Spring Transaction的核心邏輯封裝在TransactionAspectSupport的invokeWithinTransaction方法中,而核心流程中重要的三個操作,獲取/提交/回滾事務,由PlatformTransactionManager來實現。

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又對上面三個方法做了抽象,暴露了一一系列鉤子方法讓子類實現。

最常用的子類就是DataSourceTransactionManager和HibernateTransactionManager,分別對應Mybatis和JPA框架。

本文講解的aftercommit同步鉤子在AbstractPlatformTransactionManager的processCommit中被觸發。

回顧我們上面展示的場景,我們在一個事務里,注冊了一個aftercommit鉤子,并且aftercommit里面,也會再次操作數據庫,執行dml操作。

private void processCommit(DefaultTransactionStatus status) throws TransactionException {

try {

//...

else if (status.isNewTransaction()) {

if (status.isDebug()) {

logger.debug("Initiating transaction commit");

}

unexpectedRollback = status.isGlobalRollbackOnly();

//...假設事務提交成功

doCommit(status);

}

try {

triggerAfterCommit(status);

}

finally {

triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);

}

}

finally {

cleanupAfterCompletion(status);

}

在第一個事務doCommit成功,他會通過triggerAfterCommit觸發它的aftercommit鉤子邏輯,進行下一次事務操作,但是此時的Transaction還沒有釋放,并且它也不是newTransaction了。

為什么不是newTransaction,見以下代碼

private TransactionStatus handleExistingTransaction(

TransactionDefinition definition, Object transaction, boolean debugEnabled)

throws TransactionException {

//...

return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);

}

因為 status.isNewTransaction() 不成立,所以 doCommit(status); 不會執行。

doCommit中會進行什么操作?

對于DataSourceTransactionManager,就是調用了Connection的commit方法,對事務進行提交。

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);

}

}

雖然錯失了doCommit這個機會,但是在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也是一個鉤子,這個邏輯也由DataSourceTransactionManager實現

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();

}

調用到了 con.setAutoCommit(true);間接了提交了事務

然后我們再來看看HibernateTransactionManager對這個兩個方法的實現

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中,沒有見到設置autocommit的身影。

所以在JPA中你在aftercommit中進行dml操作是會丟失的。

另外一個點是,如果你在aftercommit進行了事務操作,但是中間發生了異常,比如2條insert語句后,發生了異常,這兩條insert會不會回滾?

答案是不會

回顧processCommit方法

private void processCommit(DefaultTransactionStatus status) throws TransactionException {

try {

//...

else if (status.isNewTransaction()) {

if (status.isDebug()) {

logger.debug("Initiating transaction commit");

}

unexpectedRollback = status.isGlobalRollbackOnly();

//...假設事務提交成功

doCommit(status);

}

try {

triggerAfterCommit(status);

}

finally {

triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);

}

}

finally {

cleanupAfterCompletion(status);

}

}

我們的aftercommit在triggerAfterCommit執行,這個方法里面拋出了異常,因為沒有catch,異常會往上傳遞,在cleanupAfterCompletion里也沒有處理異常,但是對于mybatis來講,它改變了autocommit狀態,所以更改被提交了。這是一個你想不到的坑。

最佳實踐

aftercommit或者說是transactionlistener,最好不要有dml操作

一但aftercommit中有事務操作,存在的風險是,一致性得不到保證,異常不會讓這部分的事務回滾

demo

寫了一個工程,用于測試mybatis和jpa中對于aftercommit中執行dml操作是否會提交

地址如下

參考資料

總結

以上是生活随笔為你收集整理的mysql+after+commit_Spring事务aftercommit原理及实践的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。