日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

MyBatis—Spring 动态数据源事务的处理

發布時間:2024/1/11 33 coder
生活随笔 收集整理的這篇文章主要介紹了 MyBatis—Spring 动态数据源事务的处理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在一般的 Spring 應用中,如果底層數據庫訪問采用的是 MyBatis,那么在大多數情況下,只使用一個單獨的數據源,Spring 的事務管理在大多數情況下都是有效的。然而,在一些復雜的業務場景下,如需要在某一時刻訪問不同的數據庫,由于 Spring 對于事務管理實現的方式,可能不能達到預期的效果。本文將簡要介紹 Spring 中事務的實現方式,并對以 MyBatis 為底層數據庫訪問的系統為例,提供多數據源事務處理的解決方案

Spring 事務的實現原理

常見地,在 Spring 中添加事務的方式通常都是在對應的方法或類上加上 @Transactional 注解顯式地將這部分處理加上事務,對于 @Transactional 注解,Spring 會在 org.springframework.transaction.annotation.AnnotationTransactionAttributeSource 定義方法攔截的匹配規則(即 AOP 部分中的 PointCut),而具體的處理邏輯(即 AOP 中的 Advice)則是在 org.springframework.transaction.interceptor.TransactionInterceptor 中定義

具體事務執行的調用鏈路如下

Spring 對于事務切面采取的具體行為實現如下:

public class TransactionInterceptor 
    extends TransactionAspectSupport 
    implements MethodInterceptor, Serializable {
    
    // 這里的方法定義為 MethodInterceptor,即 AOP 實際調用點
    @Override
	@Nullable
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// Work out the target class: may be {@code null}.
		// The TransactionAttributeSource should be passed the target class
		// as well as the method, which may be from an interface.
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

		// Adapt to TransactionAspectSupport's invokeWithinTransaction...
        // invokeWithinTransaction 為父類 TransactionAspectSupport 定義的方法
		return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
			@Override
			@Nullable
			public Object proceedWithInvocation() throws Throwable {
				return invocation.proceed();
			}
			@Override
			public Object getTarget() {
				return invocation.getThis();
			}
			@Override
			public Object[] getArguments() {
				return invocation.getArguments();
			}
		});
	}
}

繼續進入 TransactionAspectSupportinvokeWithinTransaction 方法:

public abstract class TransactionAspectSupport 
    implements BeanFactoryAware, InitializingBean {
    protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
			final InvocationCallback invocation) throws Throwable {
        // 省略響應式事務和編程式事務的處理邏輯

        // 當前事務管理的實際
		PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

		if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
			// Standard transaction demarcation with getTransaction and commit/rollback calls.
            /*
            	檢查在當前的執行上下文中,是否需要創建新的事務,這是因為當前執行的業務處理可能在上一個已經開始
            	的事務處理中
            */
			TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

			Object retVal;
			try {
				// This is an around advice: Invoke the next interceptor in the chain.
				// This will normally result in a target object being invoked.
				retVal = invocation.proceedWithInvocation(); // 實際業務代碼的業務處理
			}
			catch (Throwable ex) {
				// target invocation exception
				completeTransactionAfterThrowing(txInfo, ex); // 出現異常的回滾處理
				throw ex;
			}
			finally {
				cleanupTransactionInfo(txInfo);
			}

			if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
				// Set rollback-only in case of Vavr failure matching our rollback rules...
				TransactionStatus status = txInfo.getTransactionStatus();
				if (status != null && txAttr != null) {
					retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
				}
			}

            // 如果沒有出現異常,則提交本次事務
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}
	}
}

在獲取事務信息對象時,首先需要獲取到對應的事務狀態對象 TransactionStatus,這個狀態對象決定了 Spring 后續要對當前事務采取的何種行為,具體代碼在 org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction

// 這里的 definition 是通過解析 @Transactional 注解中的屬性得到的配置對象
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
    throws TransactionException {

    // Use defaults if no transaction definition given.
    TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

    /*
    	這里獲取事務相關的對象(如持有的數據庫連接等),具體由子類來定義相關的實現
    */
    Object transaction = doGetTransaction();
    boolean debugEnabled = logger.isDebugEnabled();

    // 如果當前已經在一個事務中,那么需要按照定義的屬性采取對應的行為
    if (isExistingTransaction(transaction)) {
        // Existing transaction found -> check propagation behavior to find out how to behave.
        return handleExistingTransaction(def, transaction, debugEnabled);
    }

    // Check definition settings for new transaction.
    if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
        throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
    }

    // No existing transaction found -> check propagation behavior to find out how to proceed.
    if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
        throw new IllegalTransactionStateException(
            "No existing transaction found for transaction marked with propagation 'mandatory'");
    }
    // 需要重新開啟一個新的事務的情況,具體在 org.springframework.transaction.TransactionDefinition 有相關的定義
    else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
             def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
             def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        SuspendedResourcesHolder suspendedResources = suspend(null);
        if (debugEnabled) {
            logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
        }
        try {
            // 開啟一個新的事務
            return startTransaction(def, transaction, debugEnabled, suspendedResources);
        }
        catch (RuntimeException | Error ex) {
            resume(null, suspendedResources);
            throw ex;
        }
    }
    else {
        // Create "empty" transaction: no actual transaction, but potentially synchronization.
        if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
            logger.warn("Custom isolation level specified but no actual transaction initiated; " +
                        "isolation level will effectively be ignored: " + def);
        }
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
    }
}

AbstractPlatformTransactionManager 中已經定義了事務處理的大體框架,而實際的事務實現則交由具體的子類實現,在一般情況下,由 org.springframework.jdbc.datasource.DataSourceTransactionManager 采取具體的實現

主要關注的點在于對于事務信息對象的創建,事務的開啟、提交回滾操作,具體對應的代碼如下:

事務信息對象的創建代碼:

protected Object doGetTransaction() {
    /*
    	簡單地理解,DataSourceTransactionObject 就是一個持有數據庫連接的資源對象
    */
    DataSourceTransactionObject txObject = new DataSourceTransactionObject();
    txObject.setSavepointAllowed(isNestedTransactionAllowed());
    /*
    	TransactionSynchronizationManager 是用于管理在事務執行過程相關的信息對象的一個工具類,基本上
    	這個類持有的事務信息貫穿了整個 Spring 事務管理
    */
    ConnectionHolder conHolder =
        (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
    txObject.setConnectionHolder(conHolder, false);
    return txObject;
}

開啟事務對應的源代碼:

protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    Connection con = null;

    try {
        /*
        	如果當前事務對象沒有持有數據庫連接,則需要從對應的 DataSource 中獲取對應的連接
        */
        if (!txObject.hasConnectionHolder() ||
            txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
            Connection newCon = obtainDataSource().getConnection();
            if (logger.isDebugEnabled()) {
                logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
            }
            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
        }

        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
        con = txObject.getConnectionHolder().getConnection();

        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
        txObject.setPreviousIsolationLevel(previousIsolationLevel);
        txObject.setReadOnly(definition.isReadOnly());

        // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
        // so we don't want to do it unnecessarily (for example if we've explicitly
        // configured the connection pool to set it already).
        
        /*
        	由于當前的事務已經交由 Spring 進行管理,那么在這種情況下,原有數據庫連接的自動提交
        	必須是關閉的,因為如果開啟了自動提交,那么實際上就相當于每一次的 SQL 都會執行一次事務的提交,
        	這種情況下事務的管理沒有意義
        */
        if (con.getAutoCommit()) {
            txObject.setMustRestoreAutoCommit(true);
            if (logger.isDebugEnabled()) {
                logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
            }
            con.setAutoCommit(false);
        }

        prepareTransactionalConnection(con, definition);
        txObject.getConnectionHolder().setTransactionActive(true);

        int timeout = determineTimeout(definition);
        if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
            txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
        }

        // Bind the connection holder to the thread.
        
        /*
        	如果是新創建的事務,那么需要綁定這個數據庫連接對象到這個事務中,使得后續再進來的業務處理
        	能夠順利地進入原有的事務中
        */
        if (txObject.isNewConnectionHolder()) {
            TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
        }
    }

    catch (Throwable ex) {
        if (txObject.isNewConnectionHolder()) {
            DataSourceUtils.releaseConnection(con, obtainDataSource());
            txObject.setConnectionHolder(null, false);
        }
        throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
    }
}

事務提交相關的代碼:

private void processCommit(DefaultTransactionStatus status) throws TransactionException {
    try {
        boolean beforeCompletionInvoked = false;

        try {
            boolean unexpectedRollback = false;
            
            /*
            	一些事務提交時的鉤子方法,使得第三方的數據庫持久話框架(如 MyBatis)的
            	事務能夠被 Spring 管理
            */
            prepareForCommit(status);
            triggerBeforeCommit(status);
            triggerBeforeCompletion(status);
            beforeCompletionInvoked = true;

            if (status.hasSavepoint()) {
                if (status.isDebug()) {
                    logger.debug("Releasing transaction savepoint");
                }
                unexpectedRollback = status.isGlobalRollbackOnly();
                status.releaseHeldSavepoint();
            }
            else if (status.isNewTransaction()) {
                if (status.isDebug()) {
                    logger.debug("Initiating transaction commit");
                }
                unexpectedRollback = status.isGlobalRollbackOnly();
                doCommit(status);
            }
            else if (isFailEarlyOnGlobalRollbackOnly()) {
                unexpectedRollback = status.isGlobalRollbackOnly();
            }

            // Throw UnexpectedRollbackException if we have a global rollback-only
            // marker but still didn't get a corresponding exception from commit.
            if (unexpectedRollback) {
                throw new UnexpectedRollbackException(
                    "Transaction silently rolled back because it has been marked as rollback-only");
            }
        }
        catch (UnexpectedRollbackException ex) {
            // can only be caused by doCommit
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
            throw ex;
        }
        catch (TransactionException ex) {
            // can only be caused by doCommit
            if (isRollbackOnCommitFailure()) {
                doRollbackOnCommitException(status, ex);
            }
            else {
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
            }
            throw ex;
        }
        catch (RuntimeException | Error ex) {
            if (!beforeCompletionInvoked) {
                triggerBeforeCompletion(status);
            }
            doRollbackOnCommitException(status, ex);
            throw ex;
        }

        // Trigger afterCommit callbacks, with an exception thrown there
        // propagated to callers but the transaction still considered as committed.
        try {
            // 事務正常提交后的鉤子方法
            triggerAfterCommit(status);
        }
        finally {
            // 事務正常提交后有關資源清理的鉤子方法
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
        }

    }
    finally {
        cleanupAfterCompletion(status);
    }
}

事務回滾的相關代碼:

private void doRollbackOnCommitException(DefaultTransactionStatus status, Throwable ex) throws TransactionException {
    try {
        if (status.isNewTransaction()) {
            if (status.isDebug()) {
                logger.debug("Initiating transaction rollback after commit exception", ex);
            }
            doRollback(status);
        }
        else if (status.hasTransaction() && isGlobalRollbackOnParticipationFailure()) {
            if (status.isDebug()) {
                logger.debug("Marking existing transaction as rollback-only after commit exception", ex);
            }
            doSetRollbackOnly(status);
        }
    }
    catch (RuntimeException | Error rbex) {
        logger.error("Commit exception overridden by rollback exception", ex);
        triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
        throw rbex;
    }
    // 一些事務相關的鉤子方法
    triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
}

MyBatis 與 Spring 事務的整合

在 MyBatis 中,實際獲取連接是通過 BaseExecutorTransaction 屬性來獲取對應的連接,實際上 MyBatis 執行時并不會意識到當前上下文是否處于一個事務中,在整合到 Spring 的過程中,默認的 Transaction 實現類為 org.mybatis.spring.transaction.SpringManagedTransaction

public class SpringManagedTransaction implements Transaction {

    private static final Logger LOGGER = LoggerFactory.getLogger(SpringManagedTransaction.class);

    private final DataSource dataSource;

    private Connection connection;

    private boolean isConnectionTransactional;

    private boolean autoCommit;

    public SpringManagedTransaction(DataSource dataSource) {
        notNull(dataSource, "No DataSource specified");
        this.dataSource = dataSource;
    }

    /**
   * {@inheritDoc}
   */
    @Override
    public Connection getConnection() throws SQLException {
        if (this.connection == null) {
            openConnection();
        }
        return this.connection;
    }

    /*
    	從當前的數據源對象 dataSource 中獲取一個連接對象,而結合上文 Spring 中對于事務的處理,如果已經將
    	dataSource 屬性綁定到了當前的線程,那么在這里就會獲取到原有創建事務時已經創建的連接,而不是從頭重新生成一個連接
    */
    private void openConnection() throws SQLException {
        this.connection = DataSourceUtils.getConnection(this.dataSource);
        this.autoCommit = this.connection.getAutoCommit();
        /*
        	這里的目的是為了處理 MyBatis 部分關于事務提交的處理,因為 MyBatis 會將自己的事務處理放入到 Spring 事務中的
        	鉤子方法中進行處理,如果此時持有的連接對象與整個 Spring 事務持有的連接對象一致時,由于 MyBatis 的事務提交會
        	早于 Spring 的事務提交(triggerBeforeCommit() 鉤子方法),從而導致 Spring 在提交事務時出現事務重復提交的異常
        */
        this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

        LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will"
                     + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring");
    }

    @Override
    public void commit() throws SQLException {
        if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
            LOGGER.debug(() -> "Committing JDBC Connection [" + this.connection + "]");
            this.connection.commit();
        }
    }

    @Override
    public void rollback() throws SQLException {
        if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
            LOGGER.debug(() -> "Rolling back JDBC Connection [" + this.connection + "]");
            this.connection.rollback();
        }
    }

    @Override
    public void close() throws SQLException {
        DataSourceUtils.releaseConnection(this.connection, this.dataSource);
    }

    @Override
    public Integer getTimeout() throws SQLException {
        ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        if (holder != null && holder.hasTimeout()) {
            return holder.getTimeToLiveInSeconds();
        }
        return null;
    }
}

而 MyBatis 對于 Transaction 中的提交處理,需要將其整合到 Spring 中,是通過向 TransactionSynchronizationManager 注冊 TransactionSynchronization 來實現的,在 MyBatis 中,實際的實現類為 SqlSessionSynchronization

private static final class SqlSessionSynchronization extends TransactionSynchronizationAdapter {

    private final SqlSessionHolder holder; // 當前持有的 SqlSession

    /*
    	用于綁定到 TransactionSynchronizationManager 的 Key 對象,由于 Spring 對于 Bean 的單例處理,實際上每次
    	都是唯一的 SqlSessionFactory 實例對象,因此在 TransactionSynchronizationManager 中的 ThreadLocal 可以通過
    	這個對象找到當前線程綁定的實際 Value 對象
    */
    private final SqlSessionFactory sessionFactory;

    private boolean holderActive = true;

    public SqlSessionSynchronization(SqlSessionHolder holder, SqlSessionFactory sessionFactory) {
        notNull(holder, "Parameter 'holder' must be not null");
        notNull(sessionFactory, "Parameter 'sessionFactory' must be not null");

        this.holder = holder;
        this.sessionFactory = sessionFactory;
    }


    @Override
    public int getOrder() {
        // order right before any Connection synchronization
        return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 1;
    }

    @Override
    public void suspend() {
        if (this.holderActive) {
            LOGGER.debug(() -> "Transaction synchronization suspending SqlSession [" + this.holder.getSqlSession() + "]");
            TransactionSynchronizationManager.unbindResource(this.sessionFactory);
        }
    }

    @Override
    public void resume() {
        if (this.holderActive) {
            LOGGER.debug(() -> "Transaction synchronization resuming SqlSession [" + this.holder.getSqlSession() + "]");
            TransactionSynchronizationManager.bindResource(this.sessionFactory, this.holder);
        }
    }

    @Override
    public void beforeCommit(boolean readOnly) {
        /*
        	注意 Spring 事務中的 triggerBeforeCommit() 鉤子方法,在事務提交前會依次檢查 TransactionSynchronizationManager 中綁定的 TransactionSynchronization,并在事務實際提交前(即當前事務信息是新開啟的事務)前調用每個 TransactionSynchronization 的 beforeCommit 方法
        */
        if (TransactionSynchronizationManager.isActualTransactionActive()) {
            try {
                LOGGER.debug(() -> "Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]");
                /*
                	由于 SqlSession 最終的方法調用會委托給對應的 Executor 進行處理,而 executor 的 commit()
                	則會繼續調用 Transaction 對象的 commit() 方法,從而實現與上文 SpringManagedTransaction 對象整合
                */
                this.holder.getSqlSession().commit();
            } catch (PersistenceException p) {
                if (this.holder.getPersistenceExceptionTranslator() != null) {
                    DataAccessException translated = this.holder.getPersistenceExceptionTranslator()
                        .translateExceptionIfPossible(p);
                    if (translated != null) {
                        throw translated;
                    }
                }
                throw p;
            }
        }
    }

    /*
    	triggerBeforeCompletion() 鉤子方法
    */
    @Override
    public void beforeCompletion() {
        // Issue #18 Close SqlSession and deregister it now
        // because afterCompletion may be called from a different thread
        if (!this.holder.isOpen()) {
            LOGGER
                .debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
            TransactionSynchronizationManager.unbindResource(sessionFactory);
            this.holderActive = false;
            LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
            this.holder.getSqlSession().close();
        }
    }

    /*
    	triggerAfterCompletion() 鉤子方法,主要是為了清理相關 ThreadLocal 綁定的資源對象
     */
    @Override
    public void afterCompletion(int status) {
        if (this.holderActive) {
            // afterCompletion may have been called from a different thread
            // so avoid failing if there is nothing in this one
            LOGGER
                .debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
            TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory);
            this.holderActive = false;
            LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
            this.holder.getSqlSession().close();
        }
        this.holder.reset();
    }
}

為了使得 MyBatis 在執行的過程中能夠 Spring 進行管理,因此需要代理實際執行的 SqlSession,實際執行類為 SqlSessionTemplate,在執行的過程中,實際行為在 SqlSessionInterceptor 中定義:

// InvocationHandler 為 JDK 動態代理的部分,定義了代理類需要采取的相關行為
private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        /*
        	getSqlSession 為 SqlSessionUtil 的靜態方法,實際上在執行過程中也是通過  TransactionSynchronizationManager 來感知當前上下文所處的事務信息,當處于同一個事務中時,則會通過 sqlSessionFactory
        	作為 key 來獲取之前的 SqlSession,從而保證事務的正常運行
        */
        SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
                                              SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
        try {
            Object result = method.invoke(sqlSession, args);
            if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                // force commit even on non-dirty sessions because some databases require
                // a commit/rollback before calling close()
                sqlSession.commit(true);
            }
            return result;
        } catch (Throwable t) {
            Throwable unwrapped = unwrapThrowable(t);
            // 省略部分異常處理代碼
            throw unwrapped;
        } finally {
            if (sqlSession != null) {
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
        }
    }
}

getSqlSessionSqlSessionUtil 的靜態方法,實際源代碼如下所示:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, 
                                       ExecutorType executorType,
                                       PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    /*
    	如果能在 TransactionSynchronizationManager 中找到和當前 SqlSessionFactory 綁定的 SqlSession
    	信息,則說明當前可能處于一個事務中
    */
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
        return session;
    }

    /*
    	執行到這里,說明要么此時是第一次進入事務,或者當前的執行方式是以非事務的形式執行的,但無論是那種形式,都需要創建一個新的 SqlSession
    */
    LOGGER.debug(() -> "Creating a new SqlSession");
    session = sessionFactory.openSession(executorType);

    /*
    	如果當前是以事務的形式執行的,則需要將創建的 SqlSession 注冊到當前事務上下文中
    */
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
}

private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
    SqlSession session = null;
    /* 
    	holder 在注冊到 TransactionSynchronizationManager 中時就會將 synchronizedWithTransaction
    	設置為 true,因此實際上只要注冊到了 TransactionSynchronizationManager 中則說明已經在一個事務中了
    */
    if (holder != null && holder.isSynchronizedWithTransaction()) {
        if (holder.getExecutorType() != executorType) {
            throw new TransientDataAccessResourceException(
                "Cannot change the ExecutorType when there is an existing transaction");
        }

        holder.requested();

        LOGGER.debug(() -> "Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
        session = holder.getSqlSession();
    }
    return session;
}

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
                                          PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    SqlSessionHolder holder;
    /*
    	TransactionSynchronizationManager.isSynchronizationActive() 檢查當前是否處于一個事務上下文中,這個屬性
    	會在創建事務的時候進行初始化
    */
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
        Environment environment = sessionFactory.getConfiguration().getEnvironment();

        if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
            LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");

            holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
            
            /*
            	注冊 sessionFactory 到事務上下文,使得能夠被后續的處理感知
            */
            TransactionSynchronizationManager.bindResource(sessionFactory, holder);
            
            /*
            	注冊一個 TransactionSynchronization,這個 TransactionSynchronization 相關的方法會在 Spring 事務的鉤子方法中被調用
            */
            TransactionSynchronizationManager
                .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
            holder.setSynchronizedWithTransaction(true); // 與上面 sessionHolder 同步
            holder.requested();
        } else {
            if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
                LOGGER.debug(() -> "SqlSession [" + session
                             + "] was not registered for synchronization because DataSource is not transactional");
            } else {
                throw new TransientDataAccessResourceException(
                    "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
            }
        }
    } else {
        LOGGER.debug(() -> "SqlSession [" + session
                     + "] was not registered for synchronization because synchronization is not active");
    }

具體整合關系如下圖所示:

動態數據源的處理

基本處理

一般在 Spring 中實現動態數據源都是基于 AbstractRoutingDataSource 并實現 determineCurrentLookupKey 來實現的,在實現的過程中,AbstractRoutingDataSource 會持有一個關于數據源 DataSource 的映射關系,通過 determineCurrentLookupKey 作為 key 來決定實際要采取的實際數據源。這種方式相當于多累加了一層,在一般的使用場景下可能不會有什么問題,但是當涉及到事務時,可能會出現一些不可思議的問題

假如現在我們有兩個數據源:MySQLPostgreSQL,我們可以定義自己的數據源枚舉類(當然直接使用字符串也可以,但是使用枚舉會更好)DataSourceType

public enum DataSourceType {

    MYSQL,

    POSTGRESQL
}

現在,我們需要在系統中定義我們自己的實際數據源,這里為了簡便,直接使用 DataSourceBuilder 的方式進行構建:

import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    /*
    	MySQL 數據源
    */
    @Bean(name = "mysqlDataSource")
    public DataSource mysqlDataSource() {
        return DataSourceBuilder.create()
                .url("jdbc:mysql://127.0.0.1:3306/lxh_db")
                .username("root")
                .password("12345678")
                .type(DruidDataSource.class)
                .build();
    }

    /*
    	PostgreSQL 數據源
    */
    @Bean(name = "psqlDataSource")
    public DataSource psqlDataSource() {
        return DataSourceBuilder.create()
                .url("jdbc:postgresql://127.0.0.1:5432/lxh_db")
                .username("postgres")
                .password("12345678")
                .type(DruidDataSource.class)
                .build();
    }
}

為了實現動態數據源,我們需要繼承 AbstractRoutingDataSource,并實現 determineCurrentLookupKey 方法。為了能夠動態地改變當前執行上下文的數據源類型,我們使用一個 ThreadLocal 來存儲當前需要的數據源類型:

public class DataSourceHolder {

    private static final ThreadLocal<DataSourceType> dataSourceHolder = new ThreadLocal<>();

    public static void setCurDataSource(DataSourceType type) {
        dataSourceHolder.set(type);
    }

    public static DataSourceType getCurDataSource() {
        return dataSourceHolder.get();
    }
}

之后,我們重新定義我們自己的動態數據源類型:

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource
        extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceHolder.getCurDataSource();
    }
}

現在我們的動態數據源還沒有實際的 DataSource 映射,因此我們在實例化 DynamicDataSource 時需要手動注冊:

import com.google.common.collect.ImmutableMap;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.util.Map;

@Configuration
public class DataSourceConfig {
    
    /*
    	由于我們已經在系統中定義了多個 DataSource,因此我們需要使用 @Primary 注解來標記當前定義的 DataSource 是實際需要用到的 DataSource
    */
    @Primary
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource(@Qualifier("mysqlDataSource") DataSource mysqlDataSource,
                                        @Qualifier("psqlDataSource") DataSource psqlDataSource) {
        DynamicDataSource dataSource = new DynamicDataSource();
        
        // 綁定目標 key 到實際數據源的映射關系,并將它們注冊到我們的動態數據源中
        Map<Object, Object> dataSourceMap = ImmutableMap.builder()
                .put(DataSourceType.MYSQL, mysqlDataSource)
                .put(DataSourceType.POSTGRESQL, psqlDataSource)
                .build();
        dataSource.setTargetDataSources(dataSourceMap);
        
        // 當通過 key 無法找到對應的數據源時,默認的數據源類型
        dataSource.setDefaultTargetDataSource(mysqlDataSource);
        return dataSource;
    }
}

這樣做就可以使用我們的動態數據源了,在使用前,只需要調用 DataSourceHolder.setCurDataSource 來進行數據源切換即可:

public class XXService {
    
    @Resource
    private BBService bbService;
    
    public void handler() {
        DataSourceType prevType = DataSourceHolder.getCurDataSource();
        DataSourceHolder.setCurDataSource(DataSourceType.XXX); // 設置當前的數據源類型
        bbService.handler(); // bbService 在處理時就會使用 XXX 對應的數據源
        DataSourceHolder.setCurDataSource(prevType); // 還原回之前的數據源
    }
}

進一步簡化

上面動態數據源的使用似乎有些繁瑣,我們可以使用 AOP 來簡化這個步驟,由于我們無法在運行中得知用戶需要使用的數據源類型,因此我們只能要求用戶決定。為了達到這一目的,我們可以自己定義一個注解來標記用戶希望使用的數據源類型:

import java.lang.annotation.*;

/**
 * 用于定義處理上下文的所需要持有的數據源類型
 */
@Inherited
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface DataSource {

    DataSourceType value() default DataSourceType.MYSQL;
}

這樣,用戶如果希望在 XXService 服務中都使用 MySQL 數據源,而在 BBService 中都使用 PostrgreSQL 數據源,可以這么做:

@Service
@DataSource(MYSQL)
public class XXService {
}

@Service
@DataSource(POSTGRESQL)
public class BBService {
}

現在我們已經定義了需要攔截的位置,還需要定義相關的行為來達到自動切換數據源上下文的目的:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.xhliu.springtransaction.annotation.DataSource;
import org.xhliu.springtransaction.datasource.DataSourceHolder;
import org.xhliu.springtransaction.datasource.DataSourceType;

@Aspect
@Component
public class DataSourceAspect {

    private final static Logger log = LoggerFactory.getLogger(DataSourceAspect.class);

    @Around("@annotation(org.xhliu.springtransaction.annotation.DataSource)")
    public Object dataSourceSelect(ProceedingJoinPoint pjp) throws Throwable {
        DataSourceType prevType = DataSourceHolder.getCurDataSource();
        // 獲取當前用戶需要使用的動態數據源類型
        DataSource dataSource = parseDataSourceAnno(pjp);
        try {
            log.debug("當前執行的上下文中,數據源的所屬類型: {}", dataSource.value());
            DataSourceHolder.setCurDataSource(dataSource.value());
            return pjp.proceed();
        } finally {
            // 最終需要還原回一開始的數據源
            DataSourceHolder.setCurDataSource(prevType);
        }
    }

    private static DataSource parseDataSourceAnno(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        DataSource dataSource = signature.getMethod().getDeclaredAnnotation(DataSource.class);
        if (dataSource != null) return dataSource;
        Object target = pjp.getTarget();
        return target.getClass().getDeclaredAnnotation(DataSource.class);
    }
}

修改 MyBatis 事務的行為

基本處理

由于 Spring 事務是通過 TransactionSynchronizationManagerThreadLocal 綁定 DataSource 和對應的 Connection 來實現事務的上下文檢測,因此我們創建的 DataSource 在事務的執行過程中是無法再動態地切換數據源。為了解決這一問題,我們需要重新定義 MyBatis 事務的處理邏輯,使得它能夠動態地切換數據源

我們定義自己的 DynamicTransaction 來替換現有的 SpringManagedTransaction

import org.apache.ibatis.transaction.Transaction;
import org.mybatis.spring.transaction.SpringManagedTransaction;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.datasource.DelegatingDataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.xhliu.springtransaction.datasource.DataSourceType;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 用于定義在當前 MyBatis 處理上下文中,正在被使用的事務對象類型,由于現有的 {@link SpringManagedTransaction}
 * 實現只能綁定到一個數據源,在基于 {@link AbstractRoutingDataSource} 的數據源中,當同屬于一個事務時,無法切換到希望的
 * 數據源,為此,需要定義一個特殊的事務類型來替換現有的事務類型,從而實現在一個事務中能夠切換數據源的效果
 *
 * @author lxh
 */
public class DynamicTransaction
        extends SpringManagedTransaction {

    // 緩存當前數據源之間的映射關系
    private final Map<DataSourceType, Transaction> txMap = new ConcurrentHashMap<>();

    // 實際當前系統中持有的動態數據源對象
    private final DataSource dataSource;

    public DynamicTransaction(DataSource dataSource) {
        super(dataSource);
        this.dataSource = dataSource;
    }

    @Override
    public Connection getConnection() throws SQLException {
        Connection connection = getConnection(DynamicDataSourceUtils.determineDataSourceType());
        if (TransactionSynchronizationManager.isActualTransactionActive()) {
            connection.setAutoCommit(false); // 如果當前已經持有了事務,那么獲取到的連接應當都是非自動提交的
        }
        return connection;
    }

    @Override
    public void commit() throws SQLException {
        /*
        	由于該方法的調用發生在 Spring 事務提交之前 `triggerBeforeCommit` 鉤子方法
        */
        for (Map.Entry<DataSourceType, Transaction> entry : txMap.entrySet()) {
            if (!entry.getValue().getConnection().getAutoCommit()) {
                entry.getValue().getConnection().commit();
            }
        }
    }

    @Override
    public void rollback() throws SQLException {
        /*
        	前面提到,MyBatis 整合 Spring 的事務過程中是通過 AbstractPlatformTransactionManager 的鉤子方法實現的,
        	在回滾時如果能夠檢測到事務存活,那么說明此時事務依舊被 Spring 管理,因此此時這部分的處理不應當被回滾
        */
        if (TransactionSynchronizationManager.isActualTransactionActive()) return;
        for (Map.Entry<DataSourceType, Transaction> entry : txMap.entrySet()) {
            entry.getValue().getConnection().rollback();
        }
    }

    @Override
    public void close() throws SQLException {
        if (TransactionSynchronizationManager.isActualTransactionActive()) return;
        for (Map.Entry<DataSourceType, Transaction> entry : txMap.entrySet()) {
            DataSourceUtils.releaseConnection(entry.getValue().getConnection(), curDataSource(entry.getKey()));
        }
    }

    private Connection getConnection(DataSourceType type) throws SQLException {
        if (txMap.containsKey(type)) {
            return txMap.get(type).getConnection();
        }

        txMap.put(type, new SpringManagedTransaction(curDataSource(type)));
        return txMap.get(type).getConnection();
    }

    private DataSource curDataSource(DataSourceType type) {
        DataSource curDS = dataSource;
        /*
        	由于有可能存在代理,因此需要不斷剝離現有數據源對象,直到獲取到實際的數據源對象
        */
        while (curDS instanceof DelegatingDataSource) {
            curDS = ((DelegatingDataSource) curDS).getTargetDataSource();
        }
        /*
        	對于動態數據源對象,需要通過對應 Key 獲取到對應的實際 DataSource 對象
        */
        if (curDS instanceof AbstractRoutingDataSource) {
            Map<Object, DataSource> dss = ((AbstractRoutingDataSource) curDS).getResolvedDataSources();
            return dss.getOrDefault(type, ((AbstractRoutingDataSource) curDS).getResolvedDefaultDataSource());
        }
        
        return curDS; // 其它一般情況的數據源。。。。
    }
}

為了使得 MyBatis能夠使用我們自定義的 Transaction,我們需要重新配置 MyBatisTransactionFactory,因此我們需要重新定義自己的 TransactionFactory

import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;

import javax.sql.DataSource;

/**
 * 重新定義 MyBatis 中的事務工廠,使得自定義的動態數據源事務能夠被 MyBatis 加載
 *
 * @author lxh
 */
public class DynamicTransactionFactory
        extends SpringManagedTransactionFactory {

    @Override
    public Transaction newTransaction(DataSource dataSource,
                                      TransactionIsolationLevel level,
                                      boolean autoCommit) {
        return new DynamicTransaction(dataSource);
    }
}

現在,我們要做的是替換現有 MyBatis 中的 TransactionFactory,這個配置是在 MybatisAutoConfiguration (如果是第三方的擴展的 MyBatis,則是在其對應的 **AutoConfiguration 中)中完成的配置:

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
        factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    /*
    	應用相關的 Configuration,包括 Mapper 的路徑,日志等配置信息
    */
    applyConfiguration(factory);
    
    // 省略部分代碼。。。。

    /*
    	這里是 MyBatis 提供的一個擴展點,用于修改 SqlSessionFactoryBean 的相關配置屬性,如 TransactionFactory 等相關信息,具體詳情可以查看 org.mybatis.spring.boot.autoconfigure.SqlSessionFactoryBeanCustomizer
    */
    applySqlSessionFactoryBeanCustomizers(factory);

    return factory.getObject();
}

由于 MyBatis 已經提供了相關的擴展點,因此我們可以由此將我們自定義的 TransactionFactory 替換掉 MyBatis 中默認的 TransactionFactory

import org.apache.ibatis.transaction.TransactionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.boot.autoconfigure.SqlSessionFactoryBeanCustomizer;

public class TransactionSqlSessionFactoryBeanCustomizer
        implements SqlSessionFactoryBeanCustomizer {

    private final TransactionFactory txFactory;

    public TransactionSqlSessionFactoryBeanCustomizer(TransactionFactory txFactory) {
        this.txFactory = txFactory;
    }

    @Override
    public void customize(SqlSessionFactoryBean factoryBean) {
        factoryBean.setTransactionFactory(txFactory);
    }
}

我們需要將這個類添加到 Spring 上下文中,使得 Spring 能夠發現并實例化它(這里我們使用注解的形式):

import org.mybatis.spring.SqlSessionFactoryBean;
import org.apache.ibatis.transaction.TransactionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.xhliu.springtransaction.transaction.DynamicTransactionFactory;

/**
 * @author lxh
 */
@Configuration
public class MyBatisConfig {

    @Bean(name = "dynamicTransactionFactory")
    public TransactionFactory dynamicTransactionFactory() {
        return new DynamicTransactionFactory();
    }

    @Bean(name = "dynamicDataSourceCustomizer")
    public SqlSessionFactoryBeanCustomizer
    dynamicDataSourceCustomizer(
            @Qualifier("dynamicTransactionFactory") TransactionFactory dynamicTransactionFactory
    ) {
        return new TransactionSqlSessionFactoryBeanCustomizer(dynamicTransactionFactory);
    }
}

現在每個組件的關系如下:

一些可能出現的問題

TransactionFactory 無法注冊

在一些低版本的 MyBatis 或者第三方 MyBatis 組件中,可能使用 SqlSessionFactoryBeanCustomizer 來配置 SqlSessionFactoryBean,在這種情況下,最佳的解決方式是提高 MyBatis 的版本,但是在一些三方組件中,這部分可能很難發生變化(不再維護或者其它原因無法修改),這種情況下,需要我們手動替換 SqlSessionFactory 的定義,比如我們創建自己的 MineMyBatisAutoConfiguration

/**
 * @author lxh
 */
@Configuration
public class MineMyBatisAutoConfiguration {

    private final static Logger log = LoggerFactory.getLogger(MineMyBatisAutoConfiguration.class);

    private final MybatisProperties properties;

    private final Interceptor[] interceptors;

    private final TypeHandler[] typeHandlers;

    private final LanguageDriver[] languageDrivers;

    private final ResourceLoader resourceLoader;

    private final DatabaseIdProvider databaseIdProvider;

    private final List<ConfigurationCustomizer> configurationCustomizers;

    private final List<SqlSessionFactoryBeanCustomizer> sqlSessionFactoryBeanCustomizers;

    public MineMyBatisAutoConfiguration(
        MybatisProperties properties,
        ObjectProvider<Interceptor[]> interceptorsProvider,
        ObjectProvider<TypeHandler[]> typeHandlersProvider,
        ObjectProvider<LanguageDriver[]> languageDriversProvider,
        ResourceLoader resourceLoader,
        ObjectProvider<DatabaseIdProvider> databaseIdProvider,
        ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,
        ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers
    ) {
        this.properties = properties;
        this.interceptors = interceptorsProvider.getIfAvailable();
        this.typeHandlers = typeHandlersProvider.getIfAvailable();
        this.languageDrivers = languageDriversProvider.getIfAvailable();
        this.resourceLoader = resourceLoader;
        this.databaseIdProvider = databaseIdProvider.getIfAvailable();
        this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
        this.sqlSessionFactoryBeanCustomizers = sqlSessionFactoryBeanCustomizers.getIfAvailable();
    }

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        // 三方組件相關的源碼。。。。
        
        /*
        	將 SqlSessionFactoryBeanCustomizer 配置到當前的 SqlSessionFactoryBean,使得我們現有的
        	TransactionFactory 的配置能夠生效
        */
        applySqlSessionFactoryBeanCustomizers(factory);
        return factory.getObject();
    }

    private void applySqlSessionFactoryBeanCustomizers(SqlSessionFactoryBean factory) {
        if (!CollectionUtils.isEmpty(this.sqlSessionFactoryBeanCustomizers)) {
            for (SqlSessionFactoryBeanCustomizer customizer : this.sqlSessionFactoryBeanCustomizers) {
                customizer.customize(factory);
            }
        }
    }

    private void applyConfiguration(SqlSessionFactoryBean factory) {
        org.apache.ibatis.session.Configuration configuration = this.properties.getConfiguration();
        if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
            configuration = new org.apache.ibatis.session.Configuration();
        }
        if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
            for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
                customizer.customize(configuration);
            }
        }
        factory.setConfiguration(configuration);
    }
}

由于配置類的加載順序問題,可能需要手動地修改配置類定義的順序,由于 Spring 會首先加載被 @ComponentScan 注解修飾的配置類,因此在啟動類中需要將這個類作為最開始掃描的基類,從而不會被其它 MyBatis 組件替換:

/*
	強制將 MineMyBatisAutoConfiguration 的配置類定義放到最前
*/
@ComponentScan(basePackageClasses = {MineMyBatisAutoConfiguration.class, DemoApplication.class})
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

死鎖

實際上,由于 Spring 事務會綁定 DataSource 作為事務的關鍵信息對象,同時會通過 DataSourcegetConnection() 方法作為此 DataSource 對應事務的唯一連接,這在原有的事務處理中是沒有問題的。然而,由于我們修改了 MyBatis 獲取數據庫連接的方式,使得它不再是直接當前線程綁定的事務信息中的連接了,也就是說,MyBatis 獲取到的 Connection 和 Spring 事務中的存活的 Connection 不再是同一個。在這種情況下,Spring 事務在等待 MyBatis 處理的結束去釋放連接,而 MyBatis 獲取數據又需要重新從 DataSource 中再獲取一次(一般是通過數據庫連接池,如果此時連接池中的連接數已經被耗盡了,那么此時 MyBatis 的處理會被阻塞),而 MyBatis 的阻塞又會導致 Spring 事務中的數據庫連接無法被釋放,這可能導致 MyBatis 永遠無法再獲取到新的連接!

具體情況如下圖所示:

回想一下死鎖出現的幾個條件:持有互斥鎖、持有并等待、非搶占式以及構成循環回路。盡管在這個問題中并不存在實際意義上的互斥鎖,但是對于連接池的請求也間接地相當于希望獲取互斥鎖,同時內部的兩個獲取連接的操作也在形式上滿足了其余的幾個條件。

為了解決死鎖,只需要去掉其中的一個條件即可,最佳的條件去除就是互斥鎖。經過上文的分析,出現死鎖的原因是因為一個事務中多次獲取了連接,我們只需要保證在一個事務中不會出現對同一個數據源多次獲取連接即可

首先,我們需要確保在一個事務中綁定的 DataSource 為我們實際需要獲取連接的數據源,而不是 AbstractRoutingDataSource(綁定該數據源就會使得后續 MyBatis 在獲取連接時重新獲取一次),因此,我們需要修改現在的 DataSourceTransactionManager,使得它能夠綁定到正確的實際數據源:

import org.jetbrains.annotations.NotNull;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DelegatingDataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.Map;

public class DynamicDataSourceTransactionManager
        extends DataSourceTransactionManager {

    public DynamicDataSourceTransactionManager(DataSource dataSource) {
        super(dataSource);
    }

    @NotNull
    @Override
    protected DataSource obtainDataSource() {
        /* 
        	剝離 AbstractRoutingDataSource,使得事務能夠綁定實際的 DataSource,后續的 MyBatis 獲取連接時即可通過 DataSource 獲取到當前事務上下文中關聯的數據庫連接
        */
        DataSource curDataSource = super.obtainDataSource();
        while (curDataSource instanceof DelegatingDataSource) {
            curDataSource = ((DelegatingDataSource) curDataSource).getTargetDataSource();
        }
        if (curDataSource instanceof AbstractRoutingDataSource) {
            Map<Object, DataSource> dss = ((AbstractRoutingDataSource) curDataSource).getResolvedDataSources();
            return dss.getOrDefault(DynamicDataSourceUtils.determineDataSourceType(),
                    ((AbstractRoutingDataSource) curDataSource).getResolvedDefaultDataSource());
        }
        assert curDataSource != null;
        return curDataSource;
    }
}

為了使得這個事務管理能夠生效,我們需要替換現有的 DataSourceTransactionManager Bean 定義:

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Configuration
public class TransactionConfiguration {

    /*
    	由于 spring-jdbc 定義的 DataSourceTransactionManager 是被 @ConditionalOnMissingBean 修飾的,因此我們
    	在這里直接定義 Bean 就可以重新覆蓋原有的 DataSourceTransactionManager 定義
    */
    @Bean(name = "dynamicDataSourceTransactionManager")
    public DataSourceTransactionManager dynamicDataSourceTransactionManager(
        @Qualifier("dynamicDataSource") DataSource dataSource,
        ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers
    ) {
        DynamicDataSourceTransactionManager transactionManager = new DynamicDataSourceTransactionManager(dataSource);
        transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
        return transactionManager;
    }
}

現在,死鎖的問題便得到了順利地解決

多線程中的事務

由于 Spring 的事務信息是通過 ThreadLocal 控制的,因此在不同的線程中,Spring 事務便不能很好地工作,為了解決這個問題,我們可以在線程執行任務前將現有線程關聯的事務信息綁定到當前工作線程,當出現異常時,我們可以將這個事務信息標記為 “只能回滾”,從而達到整體的一致性的目標

以下面的例子為例:

public TransactionStatus run() {
    /*
    	txManager 為創建任務時必須的 DataSourceTransactionManager 事務管理對象
    	resource 為之前事務所在線程綁定的資源對象,我們知道就是 DataSourceTransactionObject,持有數據庫連接的信息對象,
    	這樣,當前線程中后續的 MyBatis 組件在獲取連接時也能夠復用現有的數據庫連接
    */
    Object key = txManager.getResourceFactory();
    TransactionSynchronizationManager.bindResource(key, resource);
    TransactionStatus status = txManager.getTransaction(definition);
    try {
        runnable.run();
    } catch (Throwable t) {
        log.debug("任務執行出現異常", t);
        status.setRollbackOnly(); // 出現異常時將整個事務設置為只能回滾的狀態
    } finally {
        // 移除與當前線程執行的關聯關系,避免任務執行過程中的資源混亂
        TransactionSynchronizationManager.unbindResource(key);
    }
    return status;
}

具體 demo 地址:https://github.com/LiuXianghai-coder/Spring-Study/tree/master/spring-transaction

總結

以上是生活随笔為你收集整理的MyBatis—Spring 动态数据源事务的处理的全部內容,希望文章能夠幫你解決所遇到的問題。

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

91在线网址 | 五月激情站 | 色噜噜狠狠狠狠色综合 | 日韩午夜在线播放 | 91成人免费看 | 成人一区二区三区中文字幕 | ww亚洲ww亚在线观看 | 午夜影院一级 | 91精品国产一区二区三区 | 欧美精品黑人性xxxx | 在线一区av | 91最新视频在线观看 | 免费情缘 | 国产日产精品久久久久快鸭 | www.com久久久 | 中文字幕激情 | 国产亚洲欧美在线视频 | 亚洲欧美国产精品va在线观看 | 国产午夜不卡 | 日韩大片免费在线观看 | 一级片视频在线 | 视频一区二区免费 | 亚洲一区网 | 天天操网| 91视频 - v11av| 色先锋资源网 | av成人在线观看 | 国产精品久久久久久久久免费看 | 一级性视频 | 色婷婷www | 国内精自线一二区永久 | 91精品网站在线观看 | 天天干天天操av | 久久草草影视免费网 | 91久久黄色 | 久久综合亚洲鲁鲁五月久久 | 日韩黄色一级电影 | 91热视频在线观看 | 超碰人人在线 | 九九热免费在线观看 | 久久久鲁| 91精品毛片 | 久草综合在线 | 久久精品久久精品久久 | 免费看国产视频 | 91九色视频在线 | 日本三级中文字幕在线观看 | 久久刺激视频 | 日韩精品视频网站 | 日本天天色 | 久久a久久| 97视频在线观看网址 | 亚洲一级影院 | 久久久久久久综合色一本 | 成片视频免费观看 | 麻豆国产露脸在线观看 | 久久99精品久久久久久清纯直播 | 精品免费久久久久久 | 国产免费叼嘿网站免费 | 日韩精品视频一二三 | 亚洲成人av在线播放 | 最新日本中文字幕 | 国产精品99精品 | 欧美午夜视频在线 | 亚洲欧洲精品视频 | 免费在线观看av不卡 | 国产99自拍 | 国产亚洲精品久久久久久网站 | 中文永久字幕 | 免费看短| 91在线播放国产 | 亚洲激情网站免费观看 | 国产精品久久久久国产精品日日 | 婷婷久草| 久久精品国产v日韩v亚洲 | 久久伊人五月天 | 99re亚洲国产精品 | 国产不卡一 | 午夜视频一区二区 | 欧美成人aa | 日日夜夜亚洲 | 欧美精品三级在线观看 | 免费看麻豆| 不卡日韩av | 中文字幕一区二区三区久久 | 国产亚洲精品女人久久久久久 | 色婷婷综合五月 | 香蕉在线观看视频 | 亚洲精品中文字幕视频 | 久久国产高清视频 | 97电影手机版 | 黄色看片 | 久久尤物电影视频在线观看 | 精品国自产在线观看 | 菠萝菠萝在线精品视频 | 欧美一级高清片 | 国产二区免费视频 | 久久精彩| av在线电影网站 | 天天操天天干天天插 | 婷婷在线资源 | 蜜臀av在线一区二区三区 | 最近中文字幕免费大全 | 久久久午夜电影 | 九色视频网站 | 成年人在线免费看视频 | 国内精品久久久 | 色婷婷一 | 亚洲视频一区二区三区在线观看 | 国产亚洲观看 | 综合久久久久久久久 | 国产视频精品久久 | 日韩高清在线一区 | 久久久免费在线观看 | 我要色综合天天 | 夜夜躁狠狠燥 | 日韩精品久久中文字幕 | 国产精品久久嫩一区二区免费 | 日韩v在线91成人自拍 | 欧美性大战久久久久 | 久久精品精品电影网 | 久久精品国产免费看久久精品 | 免费在线观看的av网站 | 亚洲 欧洲 国产 精品 | 日日躁天天躁 | 成人午夜久久 | 国内一级片在线观看 | 欧美日韩国产欧美 | 亚洲视屏在线播放 | 免费一级特黄录像 | 国内免费久久久久久久久久久 | 中文字幕成人在线观看 | 亚洲视频电影在线 | 国内揄拍国内精品 | 欧美91精品久久久久国产性生爱 | 中文字幕 国产 一区 | 91在线视频观看免费 | 欧美色图视频一区 | 热re99久久精品国产66热 | 亚洲春色奇米影视 | 久久久久网址 | 国产精品一区二区三区免费看 | 亚洲五月婷 | 午夜国产一区 | 日本最新中文字幕 | 欧美片一区二区三区 | 欧美精品亚州精品 | 亚洲小视频在线观看 | 成年人天堂com | 人成午夜视频 | 黄网站色视频 | 欧美污污网站 | 伊人干综合 | 久久久久综合网 | 一区二区欧美激情 | 亚洲日本韩国一区二区 | 二区在线播放 | 亚洲日韩精品欧美一区二区 | 超碰人人射 | 在线不卡中文字幕播放 | 国产黄色精品在线观看 | 久99久在线视频 | 人人插人人做 | 亚洲一区日韩 | 免费av片在线 | 免费在线激情电影 | 中文字幕专区高清在线观看 | 色综合久久久久综合体 | 日日夜夜操操操操 | 久久久久久久久久久影视 | 九九视频免费 | 欧美日韩激情视频8区 | 久久久久中文 | 激情五月婷婷 | 激情影音 | 久久er99热精品一区二区三区 | 日韩视频一区二区在线 | 欧美在一区 | 日韩免费中文 | 99精品国产福利在线观看免费 | 久久精品9 | 亚洲精品天天 | 一级淫片在线观看 | 狠狠干天天操 | 亚洲美女视频网 | 91成人精品国产刺激国语对白 | 亚洲影音先锋 | 久久社区视频 | 福利视频一二区 | 天天av综合网 | av中文字幕在线免费观看 | 深爱开心激情 | 综合激情久久 | av一区在线| 欧美日韩中文字幕视频 | 国产一级二级在线观看 | 毛片一区二区 | 麻豆av一区二区三区在线观看 | 国产91精品一区二区麻豆网站 | 色激情在线 | 欧美国产日韩一区二区 | 成人av网址大全 | 成人精品视频久久久久 | 亚洲狠狠操 | 男女免费视频观看 | 色婷婷综合久久久中文字幕 | 亚洲精品免费播放 | 五月婷婷深开心 | 国产精品日韩久久久久 | 日韩电影在线观看中文字幕 | 色在线免费观看 | 97在线观看免费观看 | 青青河边草观看完整版高清 | 99精品欧美一区二区三区黑人哦 | 国产一区二区精品久久 | 深夜成人av | 在线观看久草 | 激情五月伊人 | 娇妻呻吟一区二区三区 | 久久蜜臀av | 一区二区伦理电影 | 国产一区二区在线免费播放 | 国内精品亚洲 | 久久久久国产a免费观看rela | 国产视频美女 | 最近最新中文字幕 | 久久久久久久久久网站 | 三级黄色理论片 | 在线 国产 亚洲 欧美 | 欧美一二三视频 | 黄色官网在线观看 | 日韩在线观看一区 | 在线观看亚洲国产 | 人人人爽 | 日韩欧美高清不卡 | 国产精品精品久久久久久 | 欧美日韩精品在线免费观看 | 久久国产电影院 | 国产精品高清免费在线观看 | 亚洲v欧美v国产v在线观看 | 亚洲人xxx| 毛片网站免费在线观看 | 日本中文字幕网站 | 色欲综合视频天天天 | 日韩大片免费观看 | 日韩av片无码一区二区不卡电影 | 国产成人精品亚洲日本在线观看 | 91福利社在线观看 | 久久综合婷婷 | 国精产品999国精产品岳 | www.99av| 日韩精品中文字幕在线播放 | 91精品国产麻豆国产自产影视 | 狠狠色丁香久久婷婷综 | 日韩高清无线码2023 | 国产精品日韩高清 | 午夜视频播放 | 中文字幕在线网址 | 国产色在线视频 | 久久久不卡影院 | 国产黄色片一级三级 | 成人香蕉视频 | 欧美日韩中文视频 | 日韩在线 一区二区 | 中文字幕在线国产精品 | 国产精品久久久久久久久久妇女 | 久久综合九色综合欧美狠狠 | av手机在线播放 | 国产手机在线播放 | 国产免费午夜 | 在线看国产日韩 | av在线a | 久久视频免费在线 | 日韩精品免费一线在线观看 | 日本女人逼 | 国产原创中文在线 | wwwwww色 | 亚洲高清av在线 | 在线观看视频 | 国产精品尤物 | 狠狠色狠狠色综合日日92 | 三级黄色大片在线观看 | 欧美在线18 | 九九热视频在线免费观看 | 精品国产一区二区三区不卡 | 在线观看国产91 | 欧美a性 | 日韩免费视频网站 | 国产裸体bbb视频 | 丁香婷婷基地 | 成年人在线观看网站 | 天天操天天爱天天干 | 亚洲精品国偷拍自产在线观看蜜桃 | 国产视频中文字幕 | 在线观看av的网站 | 奇米影视999 | 国产一区国产二区在线观看 | 国产高清视频免费在线观看 | 91xav| 国产一在线精品一区在线观看 | 亚洲mv大片欧洲mv大片免费 | 97超碰资源总站 | 在线日韩一区 | 久久99精品久久久久久秒播蜜臀 | 国产尤物一区二区三区 | 手机色站| 中文字幕一区在线观看视频 | 精品国产一区二区久久 | 999久久国精品免费观看网站 | 91精品秘密在线观看 | 欧美视频二区 | 亚洲精品视频第一页 | 超碰在线人 | 免费在线色视频 | 国产不卡在线 | 97人人模人人爽人人喊网 | 狠狠狠狠狠狠操 | 99草视频 | 激情五月***国产精品 | 91自拍视频在线 | 国产一级片免费视频 | 国产精品一区二区三区在线看 | 久久国产精品久久w女人spa | 久久艹在线 | 国产96视频| 天无日天天操天天干 | 久草视频中文在线 | jizz18欧美18| 日韩免费网站 | 日韩一级电影在线 | 97视频资源 | 久久精品看片 | 亚州精品在线视频 | 成人高清av在线 | 96精品高清视频在线观看软件特色 | 国产一区二区精品在线 | 亚洲色图av | 在线一区av| 国产精品久久久久久欧美 | 久久视频一区二区 | 毛片1000部免费看 | 久久人人爽人人爽人人片av免费 | 国产一区二区免费在线观看 | 日本在线精品视频 | 日韩在线国产 | 亚洲综合少妇 | 福利二区视频 | 中文字幕在线观看免费观看 | 国产自在线观看 | 成人毛片久久 | 天天操天天爱天天爽 | 午夜精品久久久久久久久久久久 | 免费视频国产 | 久久精品屋| 黄色av一区二区三区 | 97色综合 | 九九导航| 午夜视频在线观看欧美 | 中文字幕在线看 | 国产三级香港三韩国三级 | 久草网站在线观看 | 国产精品岛国久久久久久久久红粉 | 黄色国产大片 | 五月色综合 | 综合色综合色 | 欧美一级爽 | 97人人人人 | 一 级 黄 色 片免费看的 | 美女视频黄免费网站 | 国产精品黄色在线观看 | 日韩激情精品 | 国产一性一爱一乱一交 | 色网站免费在线看 | 日韩黄色在线观看 | 欧美精品亚洲精品 | 日韩在线一级 | 中文字幕视频三区 | 丁香 久久 综合 | 日韩精品在线视频 | 久久久久亚洲最大xxxx | 色妞色视频一区二区三区四区 | 国产99久久精品一区二区永久免费 | 久久国产经典视频 | 97成人在线免费视频 | 欧美在线一 | 一本一本久久a久久精品牛牛影视 | 美女免费视频一区 | 国产一区二区三区在线免费观看 | 黄网站色视频 | 国产中文字幕精品 | 91欧美精品 | 日韩免费专区 | 久久久久久蜜av免费网站 | 久久精品香蕉 | 最新国产精品久久精品 | 五月婷婷六月丁香在线观看 | 伊人色综合久久天天 | 又粗又长又大又爽又黄少妇毛片 | 久久久久久久久久久免费 | 人人揉人人揉人人揉人人揉97 | 免费成人在线观看视频 | 青青射| 久久免费的精品国产v∧ | 四虎海外影库www4hu | 99999精品 | 成人理论电影 | 久久久www免费电影网 | 国产亚洲精品久久久久久 | 美女国产在线 | 五月天婷亚洲天综合网鲁鲁鲁 | 最近中文字幕高清字幕免费mv | 亚洲精品黄色 | 欧美91视频 | 日韩精品在线免费观看 | 日韩有码在线观看视频 | 免费看毛片在线 | 久久最新视频 | 亚洲欧美视频在线 | 99精品小视频 | 24小时日本在线www免费的 | 欧美成人h版在线观看 | 精品国产一区二区三区在线观看 | 日韩欧美视频 | 久久精品男人的天堂 | 美女久久久久 | 日本黄色大片儿 | 日韩色视频在线观看 | 免费观看午夜视频 | 精品国产综合区久久久久久 | 国产一二区视频 | 99精品在线免费视频 | 天天色综合1 | 国产精品一区二区av影院萌芽 | 久久不卡日韩美女 | 91丨九色丨勾搭 | 在线观看va| 国产91粉嫩白浆在线观看 | 天天操综合网站 | 97在线资源 | 人人射人人爱 | 国产剧情一区二区在线观看 | 丁香在线观看完整电影视频 | 国产一区二区日本 | 日韩国产精品毛片 | 日韩欧三级 | 国产又粗又硬又爽视频 | 狠狠亚洲| 96久久精品 | 99在线视频观看 | 91在线免费观看网站 | 国产在线精品一区二区 | 视频91在线 | 97成人超碰 | 国产片免费在线观看视频 | 欧美国产精品一区二区 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 91九色视频在线 | 欧美精品久久久久久久久久久 | 国产高清在线视频 | 精品在线观看一区二区三区 | 国产精品久久久久久久久久久久久久 | 五月天综合色 | 成年人视频在线 | 亚洲国产视频在线 | 久久av影视 | 日本精品久久久久 | 国产日韩欧美视频 | 久草综合视频 | 婷婷丁香在线观看 | 日韩欧美在线观看 | 999久久久国产精品 高清av免费观看 | 日韩av片无码一区二区不卡电影 | 黄色国产高清 | 久久久久www | 一区二区三区免费看 | 天天色天天操天天爽 | 国产精品久久精品 | 色视频网站在线观看一=区 a视频免费在线观看 | 国产麻豆精品久久一二三 | 伊人日日干 | 亚洲aⅴ久久精品 | 97超碰福利久久精品 | 免费黄色av| 久久成人国产精品入口 | 97超碰站 | 日韩高清av| 久久精品亚洲一区二区三区观看模式 | 99亚洲天堂 | 日本公妇在线观看 | 欧美日本高清视频 | 国产精品涩涩屋www在线观看 | 久久久麻豆视频 | 在线观看视频一区二区 | 国产视频欧美视频 | 天天做天天爱天天综合网 | 在线电影播放 | 精品国产成人在线影院 | 国产小视频免费在线网址 | 亚洲成人精品av | 久久久久久久久久久免费 | 久久精品99国产精品酒店日本 | www.久久久com | 午夜精品一区二区三区免费 | 色综合久久中文综合久久牛 | 在线视频免费观看 | 粉嫩aⅴ一区二区三区 | 欧美怡红院视频 | 999久久久久久久久 69av视频在线观看 | 91色在线观看视频 | 青青久草在线视频 | 久久99久久精品 | 欧美999 | 久久久久久久久久福利 | 91 中文字幕 | 91日韩在线视频 | 日韩av免费一区 | 99在线观看免费视频精品观看 | 欧美日韩免费在线视频 | 国产精品毛片一区二区在线 | 色婷av| 操操日日 | 黄色影院在线免费观看 | 国产成人久久久77777 | 香蕉网站在线观看 | 六月丁香激情网 | 欧美性爽爽 | 天天操伊人 | 成人a在线观看高清电影 | 婷婷丁香花 | 婷婷激情综合五月天 | 日韩三级av | 99精品热视频只有精品10 | 福利精品在线 | 9797在线看片亚洲精品 | av电影一区二区三区 | 国产视频美女 | 看黄色91 | 91超碰在线播放 | 九九在线精品视频 | 丝袜美腿在线播放 | 奇米影视在线99精品 | 久久久国产精品免费 | 97av在线视频免费播放 | 久久精品国产免费看久久精品 | 国产高清视频在线免费观看 | 五月天亚洲婷婷 | 女人18片| 日韩成人av在线 | 91桃色在线播放 | 色综合天天视频在线观看 | www麻豆视频 | 国产视频不卡一区 | 91成人精品国产刺激国语对白 | 日日夜夜草 | 亚洲精品综合在线观看 | 欧美日韩国产伦理 | 国产精品大片免费观看 | 精品视频不卡 | 97人人澡人人添人人爽超碰 | 欧洲高潮三级做爰 | 久久精品视频网站 | 99国产在线 | 色一色在线 | 国产一区高清在线 | 91最新网址在线观看 | 亚洲一级免费观看 | 国产精品2019 | 黄色www免费| 99热精品在线观看 | 三级视频片 | 中文字幕丝袜 | 免费看的视频 | 精品视频999 | 天天做日日爱夜夜爽 | 国产高清网站 | 午夜免费福利片 | 欧美一级特黄aaaaaa大片在线观看 | 激情网五月婷婷 | 日韩视频免费 | 欧美性生爱 | 国产999精品久久久影片官网 | 日韩簧片在线观看 | 一级黄色a视频 | 综合成人在线 | 天天干天天操天天干 | 九九免费观看全部免费视频 | 99久久久久久国产精品 | 日韩午夜精品福利 | 丁香免费视频 | 丁香影院在线 | 日韩一二区在线 | 最新国产精品拍自在线播放 | 成人网中文字幕 | 久草精品视频在线观看 | 日产乱码一二三区别在线 | 亚洲国产中文字幕在线观看 | 国产成人免费在线观看 | 免费黄色av| 91伊人久久大香线蕉蜜芽人口 | 99热最新精品 | 女人18片| 色永久免费视频 | 成人永久在线 | 免费视频91蜜桃 | 久久夜色精品国产欧美乱极品 | 天堂在线视频免费观看 | 99国内精品久久久久久久 | 91看片麻豆| 精品一二三四五区 | 69亚洲精品| 日韩成人不卡 | 久久久国产一区二区 | 91成人在线免费观看 | 中字幕视频在线永久在线观看免费 | 日韩最新理论电影 | 91丨精品丨蝌蚪丨白丝jk | 免费看短| 成年人网站免费观看 | 亚洲精品456在线播放 | 成年人在线播放视频 | 亚洲国产片 | 黄色一级影院 | 久草在线播放视频 | 欧美专区日韩专区 | 超碰人人91| 精品国内自产拍在线观看视频 | 蜜桃视频在线视频 | 免费观看性生交大片3 | 亚洲欧美日韩在线一区二区 | 在线视频成人 | 欧美 亚洲 另类 激情 另类 | 日本午夜在线亚洲.国产 | 97精品久久 | 欧美色精品天天在线观看视频 | 亚洲精品在线免费 | 91黄色影视 | 玖玖国产精品视频 | 精品二区久久 | 国产精品乱码久久久久 | 欧美激情综合色综合啪啪五月 | 午夜.dj高清免费观看视频 | 91在线视频免费观看 | 国产精品高潮呻吟久久久久 | 欧美久久久久久久久中文字幕 | 麻豆成人在线观看 | 国产资源免费在线观看 | 日本精品xxxx | 久久一及片 | 精品视频久久久久久 | www.黄色片网站 | 久久草av | 九九视频在线观看视频6 | 国产中文字幕视频在线观看 | 国内精品二区 | 色在线高清 | 在线观看视频中文字幕 | 日夜夜精品视频 | 精品在线观看视频 | 国产美女视频一区 | 日韩在线视频免费播放 | 久久久久久久电影 | 国产中文字幕久久 | av综合网址 | av最新资源 | 成人免费在线网 | 久久影院中文字幕 | 99日韩精品| 91香蕉视频黄 | 91传媒在线播放 | 97超碰国产精品女人人人爽 | 成人精品一区二区三区电影免费 | av电影在线观看完整版一区二区 | 黄污在线看 | 韩国视频一区二区三区 | 天天综合网~永久入口 | 蜜臀久久99精品久久久无需会员 | 国产精品免费久久久久 | 天堂av在线 | 在线免费观看一区二区三区 | 99热国产在线 | 玖玖在线资源 | 国产很黄很色的视频 | 欧美一级特黄高清视频 | 中文字幕精品一区二区精品 | 四虎影视成人永久免费观看视频 | 中文字幕在线观看的网站 | 亚洲国产高清在线观看视频 | 天海翼一区二区三区免费 | 91麻豆精品国产91久久久久久久久 | 右手影院亚洲欧美 | 亚洲观看黄色网 | 久久国产亚洲 | 国产一级一片免费播放放a 一区二区三区国产欧美 | 日韩精品视频第一页 | 狠狠综合久久av | 国产高清视频免费最新在线 | 麻豆一精品传二传媒短视频 | 91在线产啪| www.日日日.com| 欧美日本高清视频 | 久久影视中文字幕 | 国产精品久久久久久久久久东京 | 九色视频网站 | 国产麻豆精品传媒av国产下载 | 日韩精品一区二区三区在线视频 | 99999精品视频 | 日韩国产在线观看 | 国产欧美精品在线观看 | 麻豆综合网 | 国产精品麻豆一区二区三区 | 国产精品网红福利 | 久久免费成人精品视频 | 久久久久久久久久福利 | 99热精品国产| 国产无遮挡又黄又爽在线观看 | 不卡视频在线看 | 一本色道久久精品 | 亚洲成人网在线 | 成人蜜桃 | www.午夜 | 国产精品 日韩精品 | 特级a毛片 | 四虎在线免费观看视频 | 久艹在线观看视频 | 国内视频1区 | 人人澡av| 久草视频在线免费看 | 国产二级视频 | 国产无区一区二区三麻豆 | www.婷婷色 | 激情久久久久 | 日韩网站在线观看 | 五月综合网 | 久久高清毛片 | 免费网站v | 日本精品午夜 | 韩日电影在线 | 久热av| 亚洲成a人片综合在线 | 日韩电影一区二区在线 | 草久久久久 | 亚洲最新视频在线播放 | 国产一级片久久 | 999电影免费在线观看2020 | 日韩av播放在线 | av在线免费观看不卡 | 日韩av在线免费看 | 岛国av在线 | 国产裸体无遮挡 | 欧美日韩久| 久久99精品久久久久蜜臀 | 婷婷综合影院 | 国产亚洲午夜高清国产拍精品 | 国产午夜精品视频 | 免费在线h | 在线免费观看亚洲视频 | 在线精品一区二区 | 狠狠操电影网 | 免费看黄在线看 | 中文字幕一区二区三区乱码不卡 | 国产精品免费av | 天天操天天操天天操天天操 | 99这里只有精品99 | 国产精品美女久久久久久久 | 日韩三级精品 | 狠狠操狠狠干2017 | 天天综合成人 | 久久99深爱久久99精品 | 亚洲 欧洲av| 中文字幕亚洲精品日韩 | 操操操人人 | 欧美aaa大片 | 97天堂 | 黄色毛片视频 | 成人性生活大片 | 日韩高清精品一区二区 | 极品久久久久久久 | 天天色综合三 | 久久99久久99精品中文字幕 | 国产九九九精品视频 | 色综合久久中文综合久久牛 | 中文字幕国语官网在线视频 | 久久久久久久久久久国产精品 | 久久在草| 日韩免费看视频 | 国产成人a亚洲精品v | 一级片视频在线 | 国产精品美女久久久久久久 | 亚洲国产大片 | 日韩中文字幕在线看 | 天天干夜夜干 | 九草在线观看 | 美女精品 | 精品在线免费观看 | 97综合网| 国产3p视频 | 久久免费视频2 | 国产精品一区二区免费在线观看 | 亚洲精品视频一 | 色综合久久88色综合天天免费 | 亚洲高清在线观看视频 | 丰满少妇麻豆av | 久久久久久久99精品免费观看 | 99久久久国产精品免费99 | 国产精品理论在线观看 | 国产欧美最新羞羞视频在线观看 | 97电影网站 | 日韩网页| 久久人人爽人人爽人人片av免费 | 丝袜美女视频网站 | 午夜10000| 日日干天天射 | 狠狠综合网 | 九九久久影院 | 久久tv视频 | 久久精品视频在线观看 | 在线视频日韩一区 | 日韩精品免费一区二区三区 | 蜜臀av性久久久久蜜臀aⅴ涩爱 | 欧美日本一区 | 一级黄色片在线免费看 | 久久久久久久久久久久久国产精品 | 亚洲人久久久 | 五月激情姐姐 | 国产原厂视频在线观看 | 国产精品 日韩 | 亚洲精品国产精品国自产观看 | 黄色aa久久 | 日韩高清二区 | 高清在线观看av | 天天综合精品 | 成人在线视频免费观看 | 久久久久国产精品免费免费搜索 | 日韩丝袜视频 | 国产在线精品一区二区三区 | 欧美日韩后 | 特级毛片在线免费观看 | 久久久精品成人 | 黄色小说网站在线 | 伊人成人激情 | 免费看91的网站 | 国产精品一区二区精品视频免费看 | 亚洲三级视频 | 青草视频在线 | 丁香久久五月 | 日韩精品亚洲专区在线观看 | 免费在线观看av网址 | 超碰97网站 | 国产成人精品电影久久久 | 国产视频在 | 精品a在线 | 日韩精品免费一区二区在线观看 | 黄色的网站在线 | 九九九九热精品免费视频点播观看 | 日韩久久久 | 国产一区二区在线看 | 国产一区二区三区高清播放 | 91麻豆精品91久久久久同性 | 国产精品久久久久久久久搜平片 | 91视频大全| 天天天色综合a | 一区二区三区韩国免费中文网站 | 69av国产 | 毛片网免费 | 日韩精品高清视频 | 九九热免费观看 | 色在线国产 | 99热99| 国产精品入口麻豆 | 天天拍天天爽 | 久久综合天天 | 蜜桃麻豆www久久囤产精品 | 亚洲精品自拍视频在线观看 | 久久久久久国产精品 | 国产日韩欧美在线 | 亚洲成人av片在线观看 | 亚洲第一色 | 午夜aaaa| 久久久国产精品成人免费 | 在线观看亚洲免费视频 | 欧美日韩另类在线 | 久久精品男人的天堂 | 偷拍精偷拍精品欧洲亚洲网站 | 99精品视频在线观看免费 | 国产视频1区2区3区 久久夜视频 | 日韩免费视频网站 | 亚色视频在线观看 | www,黄视频 | 亚洲欧美在线综合 | 欧美性一级观看 | 欧美综合久久 | 国产成人三级一区二区在线观看一 | 精品美女在线观看 | 久久综合久久综合久久 | 丝袜一区在线 | 国语对白少妇爽91 | 久草精品视频在线看网站免费 | 久久在线看 | 亚洲最大的av网站 | 天天综合网国产 | 99久久er热在这里只有精品66 | 激情五月五月婷婷 | 99视频在线精品 | 国产一级片不卡 | 最近日本中文字幕 | 国产一区视频免费在线观看 | 日韩在线视频二区 | 国内精品久久久久影院优 | 日本精品视频一区二区 | 99这里都是精品 | 丁香在线视频 | 亚洲精品视频网址 | 免费成人在线电影 | 色中射 | 久久久久久久久久久久久久av | 国产精品 中文字幕 亚洲 欧美 | 国产一级一级国产 | 国产91在| 2019国产精品 | 园产精品久久久久久久7电影 | 欧洲精品码一区二区三区免费看 | 亚洲欧美综合精品久久成人 | 免费亚洲视频在线观看 | 极品嫩模被强到高潮呻吟91 | 99视频在线观看一区三区 | 国产精品成人久久久久久久 | 六月丁香在线观看 | 成人网看片| 国产精品久久久久9999吃药 | 亚洲黄色免费电影 | avlulu久久精品| 亚洲最新视频在线播放 | 亚洲无吗av| 97精品国产91久久久久久久 | 最新av在线网站 | 99精品视频精品精品视频 | 亚洲欧美成人网 | 亚洲精品在线播放视频 | 91亚色视频 | 亚洲少妇xxxx | 最新av电影网站 | 91看片看淫黄大片 | 国产福利专区 | 亚洲高清免费在线 | 亚洲动漫在线观看 | 天天玩天天干天天操 | 日韩精品观看 | 婷婷在线精品视频 | 三级黄色片在线观看 | 亚洲国产日韩欧美在线 | 高清av免费观看 | 69视频国产| 外国av网| 久久国产精品二国产精品中国洋人 | 午夜在线观看一区 | 亚洲人av免费网站 | 欧美巨乳波霸 | 免费下载高清毛片 | 亚洲成人av一区 | 黄色免费网战 | 婷婷精品进入 | 国产在线色 | 亚洲精品午夜国产va久久成人 | 国产九色视频在线观看 | 91精品国产乱码久久桃 | 黄色aa久久| 91成人精品国产刺激国语对白 | 久久不卡免费视频 | 日本护士撒尿xxxx18 | 国产美女无遮挡永久免费 | 色在线高清 | 天天做日日爱夜夜爽 | 日韩av专区 | 97视频免费观看 | 欧洲视频一区 | 日韩精品一区二区在线视频 | 成人精品99| 夜夜操夜夜干 | 久在线 | 97人人艹 | 国产午夜一区 | 九九热有精品 | 四虎成人在线 | 欧美三人交 | 黄色一级大片免费看 | 国产日韩欧美综合在线 | 日韩在观看线 | 中文字幕 二区 | 99热这里是精品 | 久久97久久97精品免视看 | 91在线观看视频网站 | 96视频免费在线观看 | 久久视频这里只有精品 | 久久se视频 | 亚洲精色 | 亚洲欧美国内爽妇网 | 国产在线视频一区 | 美国人与动物xxxx |