久久精品国产精品国产精品污,男人扒开添女人下部免费视频,一级国产69式性姿势免费视频,夜鲁夜鲁很鲁在线视频 视频,欧美丰满少妇一区二区三区,国产偷国产偷亚洲高清人乐享,中文 在线 日韩 亚洲 欧美,熟妇人妻无乱码中文字幕真矢织江,一区二区三区人妻制服国产

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

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

發布時間:2024/1/11 javascript 26 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 动态数据源事务的处理的全部內容,希望文章能夠幫你解決所遇到的問題。

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

无码帝国www无码专区色综合 | 日韩精品乱码av一区二区 | 国产综合色产在线精品 | 成人影院yy111111在线观看 | 国产精品18久久久久久麻辣 | 久久国产精品二国产精品 | 国产成人无码一二三区视频 | 中文字幕无码日韩欧毛 | 少妇被粗大的猛进出69影院 | 日日碰狠狠丁香久燥 | 日本一卡2卡3卡四卡精品网站 | 国产精品va在线观看无码 | 未满成年国产在线观看 | 激情内射亚州一区二区三区爱妻 | 在线播放亚洲第一字幕 | 99er热精品视频 | av无码电影一区二区三区 | 国产精品美女久久久网av | 大胆欧美熟妇xx | 国产精品爱久久久久久久 | 水蜜桃av无码 | 成人免费视频视频在线观看 免费 | 亚洲aⅴ无码成人网站国产app | 欧美黑人性暴力猛交喷水 | 一本无码人妻在中文字幕免费 | 日韩av无码一区二区三区不卡 | 天天躁日日躁狠狠躁免费麻豆 | 久久精品国产99久久6动漫 | 国产偷国产偷精品高清尤物 | 美女极度色诱视频国产 | 亚洲自偷自偷在线制服 | 亚洲va欧美va天堂v国产综合 | 国产明星裸体无码xxxx视频 | 国产一区二区不卡老阿姨 | av无码久久久久不卡免费网站 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 亚洲日韩av一区二区三区中文 | 久久精品人人做人人综合 | 蜜臀av在线播放 久久综合激激的五月天 | 午夜精品一区二区三区在线观看 | 99久久婷婷国产综合精品青草免费 | 亚洲自偷自偷在线制服 | 国产在线一区二区三区四区五区 | 成人试看120秒体验区 | 妺妺窝人体色www在线小说 | 人妻人人添人妻人人爱 | 国产国语老龄妇女a片 | 一区二区三区乱码在线 | 欧洲 | 久久午夜无码鲁丝片 | 亚洲人成网站免费播放 | 波多野结衣乳巨码无在线观看 | 国产香蕉尹人综合在线观看 | 色情久久久av熟女人妻网站 | 国产一区二区三区日韩精品 | 夜精品a片一区二区三区无码白浆 | 国产精品永久免费视频 | 日韩精品一区二区av在线 | 国产国产精品人在线视 | 99国产精品白浆在线观看免费 | 精品水蜜桃久久久久久久 | 日本精品人妻无码77777 天堂一区人妻无码 | 又大又黄又粗又爽的免费视频 | 国产乡下妇女做爰 | 亚洲国产精品一区二区美利坚 | 麻豆国产人妻欲求不满 | 牲欲强的熟妇农村老妇女视频 | 99久久无码一区人妻 | 欧美日韩一区二区综合 | 亚洲小说春色综合另类 | 特黄特色大片免费播放器图片 | 亚洲欧美日韩成人高清在线一区 | 未满成年国产在线观看 | 日韩亚洲欧美精品综合 | 亚洲の无码国产の无码影院 | 久久精品无码一区二区三区 | 亚洲国产精品久久久久久 | 久久熟妇人妻午夜寂寞影院 | 97夜夜澡人人双人人人喊 | 精品熟女少妇av免费观看 | 亚洲欧美日韩国产精品一区二区 | 无码福利日韩神码福利片 | 2020久久香蕉国产线看观看 | 亚洲精品久久久久久久久久久 | 午夜时刻免费入口 | 国产无av码在线观看 | 亚洲日韩一区二区 | 国产人妻精品一区二区三区 | 国模大胆一区二区三区 | 毛片内射-百度 | 欧美国产日韩亚洲中文 | 久久久久国色av免费观看性色 | 高清不卡一区二区三区 | 国产亚洲人成a在线v网站 | 99精品无人区乱码1区2区3区 | 日本熟妇人妻xxxxx人hd | 国产美女极度色诱视频www | 精品乱码久久久久久久 | 日日碰狠狠丁香久燥 | 久久国产精品_国产精品 | 久久久久免费看成人影片 | 免费播放一区二区三区 | 波多野结衣一区二区三区av免费 | 性欧美疯狂xxxxbbbb | 亚洲成av人影院在线观看 | 亚洲色无码一区二区三区 | 色一情一乱一伦一区二区三欧美 | 亚洲国产精品毛片av不卡在线 | 四虎影视成人永久免费观看视频 | 永久黄网站色视频免费直播 | 日韩少妇白浆无码系列 | 国产亚洲精品久久久久久 | 亚洲欧美国产精品专区久久 | 麻豆国产丝袜白领秘书在线观看 | 日本大乳高潮视频在线观看 | 草草网站影院白丝内射 | 麻豆md0077饥渴少妇 | 亚洲a无码综合a国产av中文 | 国产av无码专区亚洲awww | 国产激情艳情在线看视频 | 亚洲中文字幕在线无码一区二区 | 精品日本一区二区三区在线观看 | 思思久久99热只有频精品66 | 丰满少妇人妻久久久久久 | 久久综合激激的五月天 | 午夜熟女插插xx免费视频 | 久久国内精品自在自线 | 亚洲自偷自偷在线制服 | 久久久久亚洲精品中文字幕 | 扒开双腿疯狂进出爽爽爽视频 | 无码国模国产在线观看 | 樱花草在线播放免费中文 | 亚洲精品无码人妻无码 | 一区二区三区乱码在线 | 欧洲 | 久久精品视频在线看15 | 国产真实夫妇视频 | 亚洲人亚洲人成电影网站色 | 欧美老妇交乱视频在线观看 | av无码久久久久不卡免费网站 | 在线天堂新版最新版在线8 | 麻豆国产97在线 | 欧洲 | 亚洲国产高清在线观看视频 | 装睡被陌生人摸出水好爽 | 欧美日韩一区二区免费视频 | 成人精品天堂一区二区三区 | 亚洲成色www久久网站 | 国产超级va在线观看视频 | 免费无码一区二区三区蜜桃大 | 亚洲春色在线视频 | 亚洲一区二区三区 | 亚洲男人av香蕉爽爽爽爽 | 激情国产av做激情国产爱 | 久久久久成人精品免费播放动漫 | 精品无码av一区二区三区 | 久久天天躁狠狠躁夜夜免费观看 | 亚洲欧美中文字幕5发布 | 丰满少妇女裸体bbw | 欧美性生交xxxxx久久久 | 国产av一区二区精品久久凹凸 | 领导边摸边吃奶边做爽在线观看 | 亚洲狠狠婷婷综合久久 | 久久精品国产一区二区三区肥胖 | 图片小说视频一区二区 | 亚洲第一无码av无码专区 | 久久久久人妻一区精品色欧美 | 国产av一区二区三区最新精品 | 亚洲精品中文字幕久久久久 | 丰满岳乱妇在线观看中字无码 | 精品熟女少妇av免费观看 | 女人高潮内射99精品 | 日本熟妇大屁股人妻 | 99国产精品白浆在线观看免费 | 婷婷五月综合激情中文字幕 | 少妇被粗大的猛进出69影院 | 丁香啪啪综合成人亚洲 | 国产激情一区二区三区 | 狠狠噜狠狠狠狠丁香五月 | 中文字幕日韩精品一区二区三区 | 在线播放亚洲第一字幕 | 久久国产精品精品国产色婷婷 | 欧美成人高清在线播放 | 亚洲精品中文字幕久久久久 | 国产激情综合五月久久 | 亚洲一区二区三区香蕉 | 一区二区三区乱码在线 | 欧洲 | 久久午夜夜伦鲁鲁片无码免费 | 撕开奶罩揉吮奶头视频 | 国产两女互慰高潮视频在线观看 | 99re在线播放 | 任你躁国产自任一区二区三区 | 国产三级精品三级男人的天堂 | 人妻aⅴ无码一区二区三区 | 成人三级无码视频在线观看 | 亚洲男人av天堂午夜在 | 国产av无码专区亚洲a∨毛片 | 少妇人妻偷人精品无码视频 | 人妻与老人中文字幕 | 性生交大片免费看l | 300部国产真实乱 | 精品夜夜澡人妻无码av蜜桃 | 日本精品人妻无码77777 天堂一区人妻无码 | 大地资源中文第3页 | 色偷偷人人澡人人爽人人模 | 天天躁日日躁狠狠躁免费麻豆 | 中文字幕无码日韩欧毛 | 少妇愉情理伦片bd | 欧美日韩亚洲国产精品 | 国产情侣作爱视频免费观看 | 亚洲欧美国产精品专区久久 | 成人精品天堂一区二区三区 | 欧美乱妇无乱码大黄a片 | 人妻插b视频一区二区三区 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 麻豆精产国品 | 18无码粉嫩小泬无套在线观看 | 亚洲日韩av一区二区三区中文 | 大乳丰满人妻中文字幕日本 | 十八禁视频网站在线观看 | 精品aⅴ一区二区三区 | 无码国模国产在线观看 | 97无码免费人妻超级碰碰夜夜 | 55夜色66夜色国产精品视频 | 国产69精品久久久久app下载 | 人妻少妇精品久久 | 国模大胆一区二区三区 | 国产电影无码午夜在线播放 | 对白脏话肉麻粗话av | 成人无码精品一区二区三区 | 兔费看少妇性l交大片免费 | 人妻中文无码久热丝袜 | 天天爽夜夜爽夜夜爽 | 97精品国产97久久久久久免费 | 亚洲精品国产精品乱码视色 | 精品亚洲成av人在线观看 | 亚洲成av人在线观看网址 | 国精产品一区二区三区 | 日韩少妇内射免费播放 | 欧美真人作爱免费视频 | 亚洲熟妇色xxxxx欧美老妇 | 亚洲精品国产品国语在线观看 | 水蜜桃亚洲一二三四在线 | 亚洲欧美色中文字幕在线 | 欧美熟妇另类久久久久久多毛 | 成 人 网 站国产免费观看 | 内射爽无广熟女亚洲 | 成熟妇人a片免费看网站 | 久久精品国产99久久6动漫 | 高清无码午夜福利视频 | 国产69精品久久久久app下载 | 九九综合va免费看 | 色综合久久88色综合天天 | 波多野结衣一区二区三区av免费 | 国产日产欧产精品精品app | 国产精品-区区久久久狼 | 蜜桃视频韩日免费播放 | 日本丰满熟妇videos | 国产av剧情md精品麻豆 | 久精品国产欧美亚洲色aⅴ大片 | 大地资源中文第3页 | 99久久人妻精品免费一区 | 99精品久久毛片a片 | 亚洲色欲色欲欲www在线 | 国产又爽又猛又粗的视频a片 | 色综合天天综合狠狠爱 | 人妻体内射精一区二区三四 | 色欲人妻aaaaaaa无码 | 无码人妻精品一区二区三区不卡 | 成人精品天堂一区二区三区 | 日韩人妻无码一区二区三区久久99 | 亚洲成色在线综合网站 | 精品国产一区二区三区四区在线看 | 一本色道久久综合狠狠躁 | 亚洲一区二区三区在线观看网站 | 亚洲色欲色欲欲www在线 | 偷窥村妇洗澡毛毛多 | 正在播放东北夫妻内射 | 亚洲国产av精品一区二区蜜芽 | 成人免费视频一区二区 | 久久精品女人天堂av免费观看 | 婷婷六月久久综合丁香 | 东京无码熟妇人妻av在线网址 | 少妇高潮喷潮久久久影院 | 图片区 小说区 区 亚洲五月 | 亚洲精品鲁一鲁一区二区三区 | 午夜精品一区二区三区的区别 | 欧美性猛交内射兽交老熟妇 | 国产性生大片免费观看性 | 久久久精品欧美一区二区免费 | 中文字幕+乱码+中文字幕一区 | 国产av无码专区亚洲awww | 亚洲色偷偷男人的天堂 | 亚洲人亚洲人成电影网站色 | 强伦人妻一区二区三区视频18 | 欧洲熟妇精品视频 | 300部国产真实乱 | 网友自拍区视频精品 | 精品国产乱码久久久久乱码 | 99久久亚洲精品无码毛片 | 欧洲精品码一区二区三区免费看 | 欧美自拍另类欧美综合图片区 | 国产成人精品视频ⅴa片软件竹菊 | 99在线 | 亚洲 | 国产精品国产自线拍免费软件 | 国产网红无码精品视频 | 日本肉体xxxx裸交 | 成人欧美一区二区三区黑人免费 | 国产无遮挡又黄又爽又色 | 中文字幕av无码一区二区三区电影 | 国产精品无码mv在线观看 | 无码免费一区二区三区 | 欧美老人巨大xxxx做受 | 又粗又大又硬毛片免费看 | 亚洲国产av精品一区二区蜜芽 | 无码人妻精品一区二区三区不卡 | 午夜福利试看120秒体验区 | 天天燥日日燥 | 超碰97人人做人人爱少妇 | 亚洲国精产品一二二线 | 亚洲高清偷拍一区二区三区 | 久精品国产欧美亚洲色aⅴ大片 | 精品国产麻豆免费人成网站 | 老头边吃奶边弄进去呻吟 | 真人与拘做受免费视频 | 亚洲日韩一区二区三区 | 99久久精品国产一区二区蜜芽 | www国产亚洲精品久久网站 | 国产精品久久精品三级 | 久久综合激激的五月天 | 精品成人av一区二区三区 | 日本一卡二卡不卡视频查询 | 妺妺窝人体色www婷婷 | 精品国产aⅴ无码一区二区 | 日本大香伊一区二区三区 | 永久免费观看国产裸体美女 | 久久综合网欧美色妞网 | 人妻aⅴ无码一区二区三区 | 日韩人妻无码一区二区三区久久99 | 正在播放东北夫妻内射 | 中文字幕无码热在线视频 | 久久熟妇人妻午夜寂寞影院 | 老太婆性杂交欧美肥老太 | 国产无av码在线观看 | 国产精品沙发午睡系列 | 婷婷丁香六月激情综合啪 | 中文字幕av日韩精品一区二区 | 天天躁日日躁狠狠躁免费麻豆 | √天堂资源地址中文在线 | 成人试看120秒体验区 | 伊人久久大香线蕉午夜 | 无码成人精品区在线观看 | 在线亚洲高清揄拍自拍一品区 | 一本久道久久综合狠狠爱 | 亚洲精品一区二区三区在线 | 在线亚洲高清揄拍自拍一品区 | 国产乱人偷精品人妻a片 | 色情久久久av熟女人妻网站 | 日韩人妻无码中文字幕视频 | 国产成人精品久久亚洲高清不卡 | 国语精品一区二区三区 | 亚洲一区二区三区含羞草 | 99riav国产精品视频 | 国产精品久久国产三级国 | 精品久久久中文字幕人妻 | 老司机亚洲精品影院无码 | 久久久成人毛片无码 | 真人与拘做受免费视频 | 久久国产精品精品国产色婷婷 | 四虎永久在线精品免费网址 | 国产精品va在线观看无码 | 国产无遮挡又黄又爽又色 | 日日天日日夜日日摸 | 日本精品少妇一区二区三区 | 国产在线精品一区二区高清不卡 | 熟妇激情内射com | 无码一区二区三区在线观看 | 少妇人妻av毛片在线看 | 日韩成人一区二区三区在线观看 | 理论片87福利理论电影 | 亚洲中文无码av永久不收费 | 国内少妇偷人精品视频 | 亚洲中文字幕无码中字 | 中文字幕av日韩精品一区二区 | 中文字幕人妻无码一夲道 | 夜先锋av资源网站 | 无码国模国产在线观看 | 国产人成高清在线视频99最全资源 | 久久久精品成人免费观看 | 性欧美videos高清精品 | 日本在线高清不卡免费播放 | 帮老师解开蕾丝奶罩吸乳网站 | 成人免费视频视频在线观看 免费 | 久久精品国产99久久6动漫 | 青青青爽视频在线观看 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 精品国产一区二区三区四区 | 无码帝国www无码专区色综合 | 午夜精品久久久久久久 | 久久亚洲精品成人无码 | 久久精品国产大片免费观看 | 欧美激情一区二区三区成人 | 国产午夜亚洲精品不卡下载 | 欧美精品无码一区二区三区 | 亚洲一区二区三区偷拍女厕 | 国产97色在线 | 免 | 久久综合给合久久狠狠狠97色 | 色婷婷香蕉在线一区二区 | 人妻少妇被猛烈进入中文字幕 | 久久午夜无码鲁丝片 | 在线天堂新版最新版在线8 | 久久综合九色综合97网 | 少妇被粗大的猛进出69影院 | 久久久中文字幕日本无吗 | 亚洲s色大片在线观看 | 丰满少妇人妻久久久久久 | 香港三级日本三级妇三级 | 亚洲爆乳大丰满无码专区 | 亚洲精品国产精品乱码视色 | 999久久久国产精品消防器材 | 夫妻免费无码v看片 | 亚洲人成人无码网www国产 | 少妇的肉体aa片免费 | 国产在线无码精品电影网 | 国产片av国语在线观看 | 天天拍夜夜添久久精品大 | 国产精品亚洲а∨无码播放麻豆 | 欧美国产日韩亚洲中文 | 丰满妇女强制高潮18xxxx | 国内精品久久毛片一区二区 | 日本一区二区三区免费高清 | 国产成人无码一二三区视频 | 精品国产av色一区二区深夜久久 | 亚洲大尺度无码无码专区 | 鲁一鲁av2019在线 | 亚洲乱码中文字幕在线 | 午夜时刻免费入口 | 四虎影视成人永久免费观看视频 | 国产亚洲视频中文字幕97精品 | 亚洲国产日韩a在线播放 | 国产精品久久国产精品99 | 国产精品.xx视频.xxtv | 欧美色就是色 | 亚洲热妇无码av在线播放 | 300部国产真实乱 | 午夜性刺激在线视频免费 | 亚洲自偷精品视频自拍 | 四虎影视成人永久免费观看视频 | 无套内谢的新婚少妇国语播放 | 亚洲欧美精品伊人久久 | 日韩欧美中文字幕公布 | 国产9 9在线 | 中文 | 大色综合色综合网站 | 国产无套内射久久久国产 | 精品无人区无码乱码毛片国产 | 成人欧美一区二区三区黑人 | 精品乱子伦一区二区三区 | 日日摸天天摸爽爽狠狠97 | 亚洲s色大片在线观看 | 久久综合色之久久综合 | 亚洲精品成a人在线观看 | 欧美日本精品一区二区三区 | 亚洲精品中文字幕久久久久 | 黑人粗大猛烈进出高潮视频 | 国产激情艳情在线看视频 | 亚洲 欧美 激情 小说 另类 | 强辱丰满人妻hd中文字幕 | 亚洲s色大片在线观看 | 国产精品igao视频网 | 亚洲s码欧洲m码国产av | 天堂亚洲2017在线观看 | 狠狠色噜噜狠狠狠狠7777米奇 | 国产精品久久久久久久9999 | 亚拍精品一区二区三区探花 | 丰满少妇人妻久久久久久 | 人人妻人人澡人人爽精品欧美 | 亚洲成av人综合在线观看 | 亚洲 另类 在线 欧美 制服 | 久久精品国产亚洲精品 | 国产精品永久免费视频 | 国产人妻人伦精品 | 免费网站看v片在线18禁无码 | 全黄性性激高免费视频 | 亚洲国产精品一区二区第一页 | 黄网在线观看免费网站 | 丁香花在线影院观看在线播放 | 国产热a欧美热a在线视频 | 欧美日本免费一区二区三区 | 亚洲性无码av中文字幕 | 国产精品无套呻吟在线 | 特大黑人娇小亚洲女 | 久久久久成人片免费观看蜜芽 | 小鲜肉自慰网站xnxx | 性欧美熟妇videofreesex | 波多野结衣av在线观看 | 人妻尝试又大又粗久久 | 美女黄网站人色视频免费国产 | 成年女人永久免费看片 | 一个人看的www免费视频在线观看 | 又大又紧又粉嫩18p少妇 | 正在播放东北夫妻内射 | 成人无码精品1区2区3区免费看 | 无码乱肉视频免费大全合集 | 色综合久久久无码网中文 | 欧美国产日韩久久mv | 国产三级精品三级男人的天堂 | 蜜桃视频韩日免费播放 | 亚洲男人av香蕉爽爽爽爽 | 亚洲熟妇色xxxxx欧美老妇y | 熟妇人妻中文av无码 | а√资源新版在线天堂 | 未满成年国产在线观看 | 日韩精品久久久肉伦网站 | 俺去俺来也www色官网 | 领导边摸边吃奶边做爽在线观看 | 97se亚洲精品一区 | 人人妻人人藻人人爽欧美一区 | 国产无遮挡又黄又爽又色 | 国产成人无码av片在线观看不卡 | 久久五月精品中文字幕 | 亚洲一区二区三区含羞草 | 帮老师解开蕾丝奶罩吸乳网站 | 亚洲码国产精品高潮在线 | 无码国内精品人妻少妇 | 九九综合va免费看 | 亚洲中文字幕久久无码 | 99国产欧美久久久精品 | 波多野结衣高清一区二区三区 | 人人爽人人澡人人高潮 | 夜夜影院未满十八勿进 | 国产亚洲精品久久久久久久 | 伊在人天堂亚洲香蕉精品区 | 性色欲情网站iwww九文堂 | 一个人免费观看的www视频 | 国产香蕉97碰碰久久人人 | 无码乱肉视频免费大全合集 | 国产一区二区不卡老阿姨 | 日韩欧美中文字幕在线三区 | 妺妺窝人体色www婷婷 | 人妻人人添人妻人人爱 | 最近免费中文字幕中文高清百度 | 色婷婷久久一区二区三区麻豆 | 丰满肥臀大屁股熟妇激情视频 | 亚洲日韩中文字幕在线播放 | 精品无码国产一区二区三区av | 国产人妖乱国产精品人妖 | 久久精品人人做人人综合 | 欧美老人巨大xxxx做受 | 永久免费精品精品永久-夜色 | 亚洲精品久久久久avwww潮水 | 丰满人妻翻云覆雨呻吟视频 | 亚洲成av人影院在线观看 | 亚洲一区二区三区 | 日韩精品久久久肉伦网站 | 日本精品少妇一区二区三区 | 国产精品久久久久久久影院 | 精品国产麻豆免费人成网站 | 真人与拘做受免费视频一 | 久久久婷婷五月亚洲97号色 | 国产亚洲美女精品久久久2020 | 久久午夜无码鲁丝片午夜精品 | 人人妻人人澡人人爽欧美精品 | 波多野结衣一区二区三区av免费 | 77777熟女视频在线观看 а天堂中文在线官网 | 亚洲欧洲日本综合aⅴ在线 | 婷婷综合久久中文字幕蜜桃三电影 | 又大又硬又爽免费视频 | 国产真实夫妇视频 | 久久伊人色av天堂九九小黄鸭 | 野狼第一精品社区 | 88国产精品欧美一区二区三区 | 国产在线一区二区三区四区五区 | a在线亚洲男人的天堂 | 欧美三级不卡在线观看 | 欧美日本免费一区二区三区 | 国产激情艳情在线看视频 | 天下第一社区视频www日本 | 精品人妻人人做人人爽 | 中文字幕中文有码在线 | 丰满人妻被黑人猛烈进入 | 99精品视频在线观看免费 | 国产黑色丝袜在线播放 | 国产97色在线 | 免 | 中文字幕人妻无码一夲道 | 亚洲熟妇自偷自拍另类 | 狠狠噜狠狠狠狠丁香五月 | 国产女主播喷水视频在线观看 | 婷婷六月久久综合丁香 | 欧美野外疯狂做受xxxx高潮 | 女人被男人躁得好爽免费视频 | 一本久道久久综合婷婷五月 | 国产色精品久久人妻 | 人妻互换免费中文字幕 | 亚洲国产高清在线观看视频 | 伊在人天堂亚洲香蕉精品区 | 午夜肉伦伦影院 | 福利一区二区三区视频在线观看 | 日韩少妇内射免费播放 | 日本va欧美va欧美va精品 | 99精品国产综合久久久久五月天 | 久久久成人毛片无码 | 人妻熟女一区 | 呦交小u女精品视频 | 亚洲国产欧美日韩精品一区二区三区 | 亚洲经典千人经典日产 | 少妇太爽了在线观看 | 真人与拘做受免费视频一 | 国产激情无码一区二区 | 熟妇女人妻丰满少妇中文字幕 | 人人妻人人澡人人爽欧美精品 | 国产乱子伦视频在线播放 | 丰满岳乱妇在线观看中字无码 | 学生妹亚洲一区二区 | 精品欧洲av无码一区二区三区 | 国产精品久免费的黄网站 | 亚洲欧洲中文日韩av乱码 | 国产另类ts人妖一区二区 | 玩弄少妇高潮ⅹxxxyw | 国产在线无码精品电影网 | 成人精品天堂一区二区三区 | 久久这里只有精品视频9 | 日韩 欧美 动漫 国产 制服 | 亚洲啪av永久无码精品放毛片 | 国产精品久久久久无码av色戒 | 日韩av激情在线观看 | 无码免费一区二区三区 | 99精品国产综合久久久久五月天 | 国产猛烈高潮尖叫视频免费 | 亚洲综合精品香蕉久久网 | 99精品无人区乱码1区2区3区 | 一本色道久久综合狠狠躁 | 色婷婷欧美在线播放内射 | 麻豆人妻少妇精品无码专区 | 国内揄拍国内精品少妇国语 | 女人被男人爽到呻吟的视频 | 麻豆国产97在线 | 欧洲 | 免费看少妇作爱视频 | 国产精品第一区揄拍无码 | 成人精品视频一区二区三区尤物 | 99久久精品国产一区二区蜜芽 | 久久精品无码一区二区三区 | 免费人成网站视频在线观看 | 六月丁香婷婷色狠狠久久 | 国产97色在线 | 免 | 色综合视频一区二区三区 | 国产精华av午夜在线观看 | 乱人伦人妻中文字幕无码 | 国内综合精品午夜久久资源 | 亚洲啪av永久无码精品放毛片 | 国产精品无码一区二区桃花视频 | 免费无码的av片在线观看 | 一本久久伊人热热精品中文字幕 | 无码播放一区二区三区 | 天天拍夜夜添久久精品 | 熟妇人妻无码xxx视频 | 国产人妻精品午夜福利免费 | 18无码粉嫩小泬无套在线观看 | 日本乱人伦片中文三区 | 妺妺窝人体色www婷婷 | 丝袜足控一区二区三区 | 4hu四虎永久在线观看 | 免费看男女做好爽好硬视频 | 色欲av亚洲一区无码少妇 | 国产高潮视频在线观看 | 一本一道久久综合久久 | 超碰97人人做人人爱少妇 | 国产激情艳情在线看视频 | 亚洲爆乳精品无码一区二区三区 | 日日噜噜噜噜夜夜爽亚洲精品 | 天堂在线观看www | 亚洲中文字幕在线观看 | 欧美亚洲日韩国产人成在线播放 | 成人精品视频一区二区 | 香蕉久久久久久av成人 | 2020最新国产自产精品 | 久久久精品欧美一区二区免费 | 国产成人无码一二三区视频 | 动漫av一区二区在线观看 | 国产一区二区三区四区五区加勒比 | 国产精品视频免费播放 | 亚洲a无码综合a国产av中文 | 激情内射日本一区二区三区 | 国产电影无码午夜在线播放 | 噜噜噜亚洲色成人网站 | 亚洲中文字幕在线观看 | 日本成熟视频免费视频 | 日产精品99久久久久久 | 日本饥渴人妻欲求不满 | 欧美 日韩 人妻 高清 中文 | 国产激情一区二区三区 | 日本丰满护士爆乳xxxx | 婷婷五月综合缴情在线视频 | 3d动漫精品啪啪一区二区中 | 熟妇人妻无码xxx视频 | 亚洲 日韩 欧美 成人 在线观看 | 超碰97人人射妻 | 欧美丰满熟妇xxxx性ppx人交 | 亚洲中文字幕av在天堂 | 中文字幕无码视频专区 | 国产在线精品一区二区三区直播 | 亚洲热妇无码av在线播放 | 亚洲日韩乱码中文无码蜜桃臀网站 | 欧美激情综合亚洲一二区 | 东京热无码av男人的天堂 | 牲欲强的熟妇农村老妇女视频 | 久久午夜无码鲁丝片秋霞 | 老熟女重囗味hdxx69 | 久久亚洲中文字幕精品一区 | 自拍偷自拍亚洲精品被多人伦好爽 | 青青久在线视频免费观看 | 成人毛片一区二区 | 国产一区二区三区四区五区加勒比 | 真人与拘做受免费视频一 | 亚洲精品中文字幕久久久久 | 狠狠噜狠狠狠狠丁香五月 | 又大又硬又黄的免费视频 | 福利一区二区三区视频在线观看 | 激情五月综合色婷婷一区二区 | 久久综合九色综合97网 | 乱中年女人伦av三区 | 日韩精品无码一区二区中文字幕 | 亚洲精品一区三区三区在线观看 | 中文字幕乱码中文乱码51精品 | 久久久久成人精品免费播放动漫 | 亚洲va欧美va天堂v国产综合 | 久久综合狠狠综合久久综合88 | 捆绑白丝粉色jk震动捧喷白浆 | 久久99国产综合精品 | aⅴ在线视频男人的天堂 | 99er热精品视频 | 亚洲无人区一区二区三区 | 高潮毛片无遮挡高清免费 | 亚洲精品综合一区二区三区在线 | 欧美国产亚洲日韩在线二区 | 疯狂三人交性欧美 | 国产av无码专区亚洲a∨毛片 | 欧美日韩综合一区二区三区 | 精品久久久无码人妻字幂 | 无遮无挡爽爽免费视频 | 无码人妻精品一区二区三区不卡 | 兔费看少妇性l交大片免费 | 欧美人妻一区二区三区 | 好男人社区资源 | 一本大道久久东京热无码av | 夜精品a片一区二区三区无码白浆 | 成熟人妻av无码专区 | 在线精品国产一区二区三区 | 欧美怡红院免费全部视频 | 久久精品人人做人人综合试看 | 日产精品高潮呻吟av久久 | 亚洲精品国产a久久久久久 | 亚洲成av人在线观看网址 | 久久久久久a亚洲欧洲av冫 | 久久综合九色综合97网 | 日本xxxx色视频在线观看免费 | 一区二区传媒有限公司 | 7777奇米四色成人眼影 | 精品国产aⅴ无码一区二区 | 宝宝好涨水快流出来免费视频 | 国产精品爱久久久久久久 | 精品国产一区二区三区av 性色 | 亚洲国产欧美日韩精品一区二区三区 | 伊人久久大香线焦av综合影院 | 人人妻人人澡人人爽欧美一区九九 | 51国偷自产一区二区三区 | 俺去俺来也在线www色官网 | 一本久道久久综合婷婷五月 | 国产精品内射视频免费 | 国产精品久久久久久亚洲毛片 | 亚洲狠狠婷婷综合久久 | 欧美国产日韩亚洲中文 | 中文字幕无码免费久久9一区9 | 亚洲大尺度无码无码专区 | 久久zyz资源站无码中文动漫 | 亚洲日本在线电影 | 日本一区二区三区免费高清 | 18精品久久久无码午夜福利 | 乱码午夜-极国产极内射 | 一个人看的视频www在线 | 国产亚洲tv在线观看 | 天海翼激烈高潮到腰振不止 | 精品久久综合1区2区3区激情 | 亚洲国产一区二区三区在线观看 | 久久精品国产99精品亚洲 | 三上悠亚人妻中文字幕在线 | 国产sm调教视频在线观看 | 亚洲一区二区三区国产精华液 | 日本熟妇人妻xxxxx人hd | 99国产欧美久久久精品 | 国产成人综合色在线观看网站 | 色综合久久久无码中文字幕 | 乱码av麻豆丝袜熟女系列 | 久久精品国产精品国产精品污 | 国产人妻久久精品二区三区老狼 | 天天拍夜夜添久久精品 | 性做久久久久久久免费看 | 特黄特色大片免费播放器图片 | 精品国产一区二区三区四区在线看 | 午夜精品久久久久久久久 | 国产人妻精品一区二区三区不卡 | 国产精品高潮呻吟av久久 | 久久午夜无码鲁丝片午夜精品 | 少妇的肉体aa片免费 | 少妇久久久久久人妻无码 | 色老头在线一区二区三区 | 国产精品免费大片 | 99精品无人区乱码1区2区3区 | 亚洲国产高清在线观看视频 | 欧美高清在线精品一区 | 色窝窝无码一区二区三区色欲 | 精品国产福利一区二区 | 一本久道久久综合狠狠爱 | 88国产精品欧美一区二区三区 | 两性色午夜免费视频 | 18黄暴禁片在线观看 | 高潮喷水的毛片 | 欧洲熟妇色 欧美 | 亚洲精品一区二区三区四区五区 | 欧美日韩色另类综合 | 亚洲成av人影院在线观看 | 国产性生大片免费观看性 | 丰满人妻被黑人猛烈进入 | 小泽玛莉亚一区二区视频在线 | 人妻少妇精品视频专区 | 亚洲人成无码网www | 国产内射爽爽大片视频社区在线 | 国产精品久久久久影院嫩草 | 国产一区二区三区精品视频 | 无码成人精品区在线观看 | 国产日产欧产精品精品app | 亚洲熟悉妇女xxx妇女av | 国产精品久久久久9999小说 | 久久精品中文字幕大胸 | 国产精品对白交换视频 | 国产av一区二区精品久久凹凸 | 国产成人一区二区三区在线观看 | 国产成人无码午夜视频在线观看 | 国产香蕉尹人综合在线观看 | 女人和拘做爰正片视频 | 精品久久久无码人妻字幂 | 亚洲 高清 成人 动漫 | 色综合久久久久综合一本到桃花网 | 国产精品久久久久9999小说 | 在线 国产 欧美 亚洲 天堂 | 免费看少妇作爱视频 | 好爽又高潮了毛片免费下载 | 丰满人妻一区二区三区免费视频 | 欧美成人家庭影院 | 天堂久久天堂av色综合 | 强伦人妻一区二区三区视频18 | 亚洲色成人中文字幕网站 | 亚洲色偷偷男人的天堂 | 国产精品国产三级国产专播 | 无码一区二区三区在线观看 | 一本色道久久综合亚洲精品不卡 | 乱人伦人妻中文字幕无码久久网 | 国产精品手机免费 | 东北女人啪啪对白 | 精品国产青草久久久久福利 | 国产另类ts人妖一区二区 | 国产精品丝袜黑色高跟鞋 | 日韩精品a片一区二区三区妖精 | 亚洲综合精品香蕉久久网 | 国产麻豆精品一区二区三区v视界 | 精品国产成人一区二区三区 | 国产人妻人伦精品1国产丝袜 | 欧洲美熟女乱又伦 | 99久久人妻精品免费一区 | 水蜜桃av无码 | 99精品国产综合久久久久五月天 | 性欧美疯狂xxxxbbbb | 一本久道久久综合婷婷五月 | 2020最新国产自产精品 | 国产午夜无码视频在线观看 | 少妇厨房愉情理9仑片视频 | 亚洲欧美综合区丁香五月小说 | 亚洲国产精华液网站w | 国产免费久久精品国产传媒 | 免费无码肉片在线观看 | 中文字幕人成乱码熟女app | 亚洲精品一区二区三区四区五区 | 人人妻人人澡人人爽人人精品浪潮 | 成人aaa片一区国产精品 | 日韩视频 中文字幕 视频一区 | 亚洲精品无码人妻无码 | 亚洲国产精品美女久久久久 | а天堂中文在线官网 | 伊人久久大香线蕉av一区二区 | 性生交大片免费看女人按摩摩 | 国产乱人伦av在线无码 | 日韩亚洲欧美精品综合 | 国产精品办公室沙发 | 狂野欧美激情性xxxx | 粗大的内捧猛烈进出视频 | 欧美色就是色 | 欧美阿v高清资源不卡在线播放 | 久久久久久久人妻无码中文字幕爆 | 少女韩国电视剧在线观看完整 | 国产午夜手机精彩视频 | 国产麻豆精品一区二区三区v视界 | 蜜桃臀无码内射一区二区三区 | 国产精品手机免费 | 7777奇米四色成人眼影 | 国产精品美女久久久网av | 精品熟女少妇av免费观看 | 国产深夜福利视频在线 | 久久精品成人欧美大片 | 亚洲熟悉妇女xxx妇女av | 国产精品美女久久久 | 性欧美牲交xxxxx视频 | 亚洲毛片av日韩av无码 | 99精品久久毛片a片 | 免费观看的无遮挡av | 亚洲欧美日韩国产精品一区二区 | 午夜精品久久久内射近拍高清 | 色综合天天综合狠狠爱 | 日韩精品a片一区二区三区妖精 | 成人性做爰aaa片免费看 | 一本一道久久综合久久 | 一二三四社区在线中文视频 | 亚洲精品一区二区三区在线 | 日韩少妇内射免费播放 | 亚洲人成网站色7799 | 精品久久久久久人妻无码中文字幕 | 蜜臀av无码人妻精品 | 亚洲精品午夜无码电影网 | 色五月丁香五月综合五月 | 内射巨臀欧美在线视频 | 亚洲精品一区二区三区在线观看 | 国产成人av免费观看 | 黑森林福利视频导航 | 99久久人妻精品免费一区 | 精品日本一区二区三区在线观看 | 免费播放一区二区三区 | 久久人人爽人人爽人人片ⅴ | 波多野结衣 黑人 | 5858s亚洲色大成网站www | 伊人久久大香线蕉午夜 | 国产做国产爱免费视频 | 国产婷婷色一区二区三区在线 | 在线观看免费人成视频 | 毛片内射-百度 | 亚洲精品国产a久久久久久 | 色欲久久久天天天综合网精品 | 日本精品少妇一区二区三区 | 人妻少妇被猛烈进入中文字幕 | 久久99精品久久久久婷婷 | 久久久久久av无码免费看大片 | 国产精品久久久久9999小说 | 狠狠色噜噜狠狠狠7777奇米 | 欧美国产日韩亚洲中文 | 偷窥村妇洗澡毛毛多 | 亚洲国产欧美国产综合一区 | 国产成人av免费观看 | 亚洲中文字幕乱码av波多ji | 天天综合网天天综合色 | 又大又硬又黄的免费视频 | 草草网站影院白丝内射 | 久久99久久99精品中文字幕 | 亚洲欧美色中文字幕在线 | 男人的天堂2018无码 | 在线看片无码永久免费视频 | 中文字幕无码视频专区 | 少妇被粗大的猛进出69影院 | 亚洲中文无码av永久不收费 | 亚洲啪av永久无码精品放毛片 | 久久精品国产精品国产精品污 | 亚洲 欧美 激情 小说 另类 | 国产成人人人97超碰超爽8 | 丰满少妇高潮惨叫视频 | 精品久久久无码人妻字幂 | 国内精品久久毛片一区二区 | 国产无遮挡又黄又爽免费视频 | 嫩b人妻精品一区二区三区 | 18精品久久久无码午夜福利 | 亚洲人成无码网www | 狠狠综合久久久久综合网 | 免费播放一区二区三区 | 国产成人人人97超碰超爽8 | 台湾无码一区二区 | 51国偷自产一区二区三区 | 夜夜躁日日躁狠狠久久av | 强伦人妻一区二区三区视频18 | 最新国产乱人伦偷精品免费网站 | 久久人人97超碰a片精品 | 色综合久久久无码网中文 | 国产香蕉97碰碰久久人人 | 六月丁香婷婷色狠狠久久 | 中文字幕日产无线码一区 | 蜜桃臀无码内射一区二区三区 | 99精品国产综合久久久久五月天 | 中文字幕无码乱人伦 | 亚洲综合另类小说色区 | 国产成人精品无码播放 | 国产内射爽爽大片视频社区在线 | 国产免费久久精品国产传媒 | 欧美高清在线精品一区 | 亚洲精品国偷拍自产在线麻豆 | 扒开双腿吃奶呻吟做受视频 | 青春草在线视频免费观看 | 高清国产亚洲精品自在久久 | 国产亚洲精品久久久久久国模美 | 奇米影视7777久久精品 | 国产成人无码一二三区视频 | 色一情一乱一伦一区二区三欧美 | 少妇一晚三次一区二区三区 | 亚洲另类伦春色综合小说 | 欧美精品在线观看 | 中文字幕无码免费久久9一区9 | 日日麻批免费40分钟无码 | 成人欧美一区二区三区 | 国产熟妇高潮叫床视频播放 | 在线视频网站www色 | 牲欲强的熟妇农村老妇女视频 | 成在人线av无码免费 | 亚洲精品鲁一鲁一区二区三区 | 日产精品99久久久久久 | 76少妇精品导航 | 亚洲人亚洲人成电影网站色 | 狠狠色噜噜狠狠狠狠7777米奇 | 强辱丰满人妻hd中文字幕 | 久久久婷婷五月亚洲97号色 | 国产偷自视频区视频 | 美女张开腿让人桶 | 亚洲欧美精品伊人久久 | 国产情侣作爱视频免费观看 | 久久天天躁狠狠躁夜夜免费观看 | 男女下面进入的视频免费午夜 | 国产9 9在线 | 中文 | 国产麻豆精品精东影业av网站 | 精品国精品国产自在久国产87 | 精品一区二区三区无码免费视频 | 色 综合 欧美 亚洲 国产 | 日韩亚洲欧美中文高清在线 | 久久午夜夜伦鲁鲁片无码免费 | 熟妇激情内射com | 四十如虎的丰满熟妇啪啪 | 久久精品国产亚洲精品 | 午夜时刻免费入口 | 免费国产成人高清在线观看网站 | 人妻aⅴ无码一区二区三区 | 天天拍夜夜添久久精品大 | 日韩av无码中文无码电影 | 欧美成人家庭影院 | 麻豆果冻传媒2021精品传媒一区下载 | 久久国产精品精品国产色婷婷 | 国产在线一区二区三区四区五区 | 亚洲日韩乱码中文无码蜜桃臀网站 | 亚洲 激情 小说 另类 欧美 | 亚洲国产精品久久人人爱 | 狠狠色噜噜狠狠狠狠7777米奇 | 国产成人亚洲综合无码 | 国产精品丝袜黑色高跟鞋 | 又大又硬又爽免费视频 | 国产成人无码区免费内射一片色欲 | 永久免费精品精品永久-夜色 | 国产农村乱对白刺激视频 | 无码毛片视频一区二区本码 | 国产在线无码精品电影网 | 无码一区二区三区在线 | 国内精品久久久久久中文字幕 | 最近的中文字幕在线看视频 | 国产精品igao视频网 | 蜜臀av无码人妻精品 | 亚洲欧美精品aaaaaa片 | 无码人妻出轨黑人中文字幕 | 天天综合网天天综合色 | 亚洲综合另类小说色区 | 丝袜美腿亚洲一区二区 | 久久精品中文闷骚内射 | 无码人妻精品一区二区三区下载 | 亚洲乱码中文字幕在线 | 樱花草在线社区www | 亚洲国产欧美国产综合一区 | 久久久精品人妻久久影视 | 国产xxx69麻豆国语对白 | 国产热a欧美热a在线视频 | 亚洲中文字幕成人无码 | 四十如虎的丰满熟妇啪啪 | 国产成人人人97超碰超爽8 | 欧美人与物videos另类 | 在线 国产 欧美 亚洲 天堂 | 国产在线精品一区二区高清不卡 | 丰满少妇高潮惨叫视频 | 中文字幕中文有码在线 | 麻豆av传媒蜜桃天美传媒 | 国产国语老龄妇女a片 | 亚洲色欲色欲天天天www | 色窝窝无码一区二区三区色欲 | 亚洲欧美精品伊人久久 | 少妇被黑人到高潮喷出白浆 | 日本爽爽爽爽爽爽在线观看免 | 久久久久se色偷偷亚洲精品av | 青青久在线视频免费观看 | 国产成人综合在线女婷五月99播放 | 丁香花在线影院观看在线播放 | 精品成在人线av无码免费看 | 亚洲国产欧美日韩精品一区二区三区 | 日本精品人妻无码免费大全 | 夜精品a片一区二区三区无码白浆 | 免费视频欧美无人区码 | 激情内射亚州一区二区三区爱妻 | 少妇性荡欲午夜性开放视频剧场 | 中文无码精品a∨在线观看不卡 | 欧美黑人性暴力猛交喷水 | 亚洲成a人一区二区三区 | 中文字幕精品av一区二区五区 | 国产欧美熟妇另类久久久 | 日日摸日日碰夜夜爽av | 鲁鲁鲁爽爽爽在线视频观看 | 18禁黄网站男男禁片免费观看 | 国产人妖乱国产精品人妖 | 99久久久国产精品无码免费 | 成人精品天堂一区二区三区 | 天堂а√在线中文在线 | 精品水蜜桃久久久久久久 | 国产成人av免费观看 | 欧美日韩在线亚洲综合国产人 | 午夜男女很黄的视频 | 老司机亚洲精品影院 | 强奷人妻日本中文字幕 | 青草青草久热国产精品 | 在教室伦流澡到高潮hnp视频 | 青草青草久热国产精品 | 四虎4hu永久免费 | 久久国内精品自在自线 | 牲欲强的熟妇农村老妇女视频 | 欧美熟妇另类久久久久久不卡 | 精品国产福利一区二区 | 丰满少妇熟乱xxxxx视频 | 无码毛片视频一区二区本码 | 给我免费的视频在线观看 | 欧美精品无码一区二区三区 | 亚洲色大成网站www国产 | 精品国精品国产自在久国产87 | 久久久久av无码免费网 | 成人三级无码视频在线观看 | 十八禁真人啪啪免费网站 | 东京一本一道一二三区 | 一本一道久久综合久久 | 中文字幕无码av波多野吉衣 | 国产高潮视频在线观看 | 久久精品人人做人人综合试看 | 亚洲中文字幕va福利 | 日日躁夜夜躁狠狠躁 | 亚无码乱人伦一区二区 | 精品国产av色一区二区深夜久久 | 成人无码精品1区2区3区免费看 | 国产精品美女久久久久av爽李琼 | 76少妇精品导航 | 影音先锋中文字幕无码 | 天天拍夜夜添久久精品大 | 日日麻批免费40分钟无码 | 亚洲熟熟妇xxxx | 久久精品一区二区三区四区 | 午夜免费福利小电影 | 亚洲成av人综合在线观看 | 四十如虎的丰满熟妇啪啪 | 成人亚洲精品久久久久 | 精品国产麻豆免费人成网站 | 又黄又爽又色的视频 | 中文字幕无码人妻少妇免费 | 久久精品人妻少妇一区二区三区 | 亚洲乱码日产精品bd | 少妇久久久久久人妻无码 | 亚洲春色在线视频 | 久久亚洲中文字幕精品一区 | 久久久久久亚洲精品a片成人 | 免费人成在线视频无码 | 久久国产36精品色熟妇 | 久久97精品久久久久久久不卡 | 国产猛烈高潮尖叫视频免费 | 久久久久久久女国产乱让韩 | 久久久久亚洲精品中文字幕 | 99riav国产精品视频 | 精品久久久无码中文字幕 | 日本欧美一区二区三区乱码 | 亚洲国产精品久久久久久 | 亚洲精品一区国产 | 亚洲男人av天堂午夜在 | 内射白嫩少妇超碰 | 欧美激情综合亚洲一二区 | 亚洲娇小与黑人巨大交 | 精品偷拍一区二区三区在线看 | 日日摸夜夜摸狠狠摸婷婷 | 亚洲成a人一区二区三区 | 国产在线aaa片一区二区99 | 中文无码伦av中文字幕 | 久久综合狠狠综合久久综合88 | 国产精品久久久久9999小说 | 对白脏话肉麻粗话av | 女人被男人爽到呻吟的视频 | 亚洲一区二区三区在线观看网站 | 中国女人内谢69xxxxxa片 | 日日天干夜夜狠狠爱 | 女高中生第一次破苞av | 夜夜躁日日躁狠狠久久av | 国产精品国产三级国产专播 | 欧美大屁股xxxxhd黑色 | 天天做天天爱天天爽综合网 | 日韩av激情在线观看 | 欧美日本精品一区二区三区 | 高潮毛片无遮挡高清免费视频 | 内射后入在线观看一区 | 乌克兰少妇xxxx做受 | 国产精品嫩草久久久久 | 无码国产色欲xxxxx视频 | 中文字幕中文有码在线 | 久久综合香蕉国产蜜臀av | 国産精品久久久久久久 | 欧美自拍另类欧美综合图片区 | 乱人伦人妻中文字幕无码久久网 | 人妻体内射精一区二区三四 | 人妻夜夜爽天天爽三区 | 最近免费中文字幕中文高清百度 | 男女爱爱好爽视频免费看 | 国内精品人妻无码久久久影院 | 久久精品国产精品国产精品污 | 中文字幕无码av激情不卡 | 成人无码精品1区2区3区免费看 | 国产69精品久久久久app下载 | 国产成人精品一区二区在线小狼 | 久久精品丝袜高跟鞋 | 特大黑人娇小亚洲女 | 亚洲一区二区三区播放 | 亚洲一区二区三区国产精华液 | 国产香蕉尹人视频在线 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 少妇高潮喷潮久久久影院 | 无码帝国www无码专区色综合 | 久久国产精品偷任你爽任你 | aⅴ亚洲 日韩 色 图网站 播放 | 成人欧美一区二区三区 | 久久国内精品自在自线 | 国产在线aaa片一区二区99 | 国产又粗又硬又大爽黄老大爷视 | 中文无码精品a∨在线观看不卡 | 日韩欧美中文字幕在线三区 | 色婷婷av一区二区三区之红樱桃 | 亚洲国产精品毛片av不卡在线 | 欧美野外疯狂做受xxxx高潮 | 97精品国产97久久久久久免费 | 久久精品女人天堂av免费观看 | 强伦人妻一区二区三区视频18 | 老头边吃奶边弄进去呻吟 | 亚洲综合色区中文字幕 | 国产麻豆精品一区二区三区v视界 | 无码国产激情在线观看 | 日日摸夜夜摸狠狠摸婷婷 | 亚洲精品国偷拍自产在线观看蜜桃 | 在线看片无码永久免费视频 | 精品国产麻豆免费人成网站 | 国产成人精品久久亚洲高清不卡 | 亚洲aⅴ无码成人网站国产app | 熟妇人妻激情偷爽文 | 久久99精品国产麻豆 | 骚片av蜜桃精品一区 | 中国大陆精品视频xxxx | 黑人粗大猛烈进出高潮视频 | 日本乱偷人妻中文字幕 | 欧美激情综合亚洲一二区 | 日日夜夜撸啊撸 | 少妇邻居内射在线 | 国产精品高潮呻吟av久久 | 久久亚洲精品中文字幕无男同 | 国产精品久久久久久久9999 | 免费网站看v片在线18禁无码 | 在线亚洲高清揄拍自拍一品区 | 少妇被粗大的猛进出69影院 | 国产成人一区二区三区在线观看 | 亚洲国产午夜精品理论片 | 亚洲 a v无 码免 费 成 人 a v | 亚洲自偷自偷在线制服 | 久久久久亚洲精品男人的天堂 | 强辱丰满人妻hd中文字幕 | 久久精品99久久香蕉国产色戒 | 亚洲成a人一区二区三区 | 免费无码肉片在线观看 | 香港三级日本三级妇三级 | 理论片87福利理论电影 | 国产国产精品人在线视 | 婷婷综合久久中文字幕蜜桃三电影 | 丰满肥臀大屁股熟妇激情视频 | 国产婷婷色一区二区三区在线 | 亚洲大尺度无码无码专区 | 在教室伦流澡到高潮hnp视频 | 一个人免费观看的www视频 | 国产精品毛多多水多 | 亚洲欧洲日本无在线码 | 欧美三级a做爰在线观看 | 国产办公室秘书无码精品99 | 强奷人妻日本中文字幕 | 性色av无码免费一区二区三区 | 午夜精品久久久久久久久 | 在教室伦流澡到高潮hnp视频 | 国产激情无码一区二区app | 日产精品高潮呻吟av久久 | 无码av免费一区二区三区试看 | 强伦人妻一区二区三区视频18 | 波多野结衣 黑人 | 亚洲高清偷拍一区二区三区 | 免费无码午夜福利片69 | 日韩成人一区二区三区在线观看 | 玩弄中年熟妇正在播放 | 亚洲 a v无 码免 费 成 人 a v | 狠狠色丁香久久婷婷综合五月 | 无码av最新清无码专区吞精 | 一本大道伊人av久久综合 | 天天躁日日躁狠狠躁免费麻豆 | 99久久久无码国产aaa精品 | 九九久久精品国产免费看小说 | 曰韩无码二三区中文字幕 | 欧美xxxxx精品 | 亚洲欧美精品aaaaaa片 | 97se亚洲精品一区 | 亚洲色无码一区二区三区 | 国产午夜无码精品免费看 | 精品国产青草久久久久福利 | 国语精品一区二区三区 | 亚洲精品成人福利网站 | 少妇邻居内射在线 | 欧美zoozzooz性欧美 | 偷窥日本少妇撒尿chinese | 免费乱码人妻系列无码专区 | 免费乱码人妻系列无码专区 | 亚洲a无码综合a国产av中文 | 国产深夜福利视频在线 | 亚洲 a v无 码免 费 成 人 a v | 久久午夜无码鲁丝片 | 成在人线av无码免费 | 中文无码成人免费视频在线观看 | 男人扒开女人内裤强吻桶进去 | 欧美亚洲日韩国产人成在线播放 | 99精品无人区乱码1区2区3区 | 亚洲熟熟妇xxxx | 偷窥村妇洗澡毛毛多 | 亚洲人交乣女bbw | 国产人妻精品一区二区三区不卡 | 欧美人妻一区二区三区 | 国产超碰人人爽人人做人人添 | 99er热精品视频 | 无码一区二区三区在线 | 久久人妻内射无码一区三区 | 国产av无码专区亚洲a∨毛片 | 四十如虎的丰满熟妇啪啪 | av无码电影一区二区三区 | 精品无人国产偷自产在线 | 国产亚洲欧美日韩亚洲中文色 | 国产69精品久久久久app下载 | 欧美成人高清在线播放 | 牛和人交xxxx欧美 | 国产午夜福利100集发布 | 国产在线aaa片一区二区99 | 极品尤物被啪到呻吟喷水 | 国产精品亚洲五月天高清 | 狠狠亚洲超碰狼人久久 | 美女扒开屁股让男人桶 | 精品国产青草久久久久福利 | 中文字幕无码免费久久9一区9 | 一本久道久久综合狠狠爱 | 又粗又大又硬又长又爽 | 一本色道久久综合狠狠躁 | 小sao货水好多真紧h无码视频 | 300部国产真实乱 | 亚洲精品久久久久中文第一幕 | 亚洲国产高清在线观看视频 | 亚洲国产欧美日韩精品一区二区三区 | 窝窝午夜理论片影院 | 大胆欧美熟妇xx | 狠狠色噜噜狠狠狠狠7777米奇 | 免费观看又污又黄的网站 | 色婷婷久久一区二区三区麻豆 | 精品国产一区av天美传媒 | 色老头在线一区二区三区 | 亚洲欧洲日本无在线码 | 日韩无套无码精品 | 亚洲一区二区观看播放 | 国产精品亚洲综合色区韩国 | 久久亚洲精品中文字幕无男同 | 风流少妇按摩来高潮 | 欧美熟妇另类久久久久久多毛 | 亚洲一区二区三区在线观看网站 | 夜夜躁日日躁狠狠久久av | 色婷婷久久一区二区三区麻豆 | 一二三四社区在线中文视频 | 欧美老妇交乱视频在线观看 | 又湿又紧又大又爽a视频国产 | 免费人成在线观看网站 | 中文字幕人妻无码一区二区三区 | 大肉大捧一进一出视频出来呀 | 亚洲一区二区三区播放 | 国产欧美精品一区二区三区 | 男女下面进入的视频免费午夜 | 亚洲一区二区三区无码久久 | 日韩欧美中文字幕在线三区 | 18无码粉嫩小泬无套在线观看 | 狠狠综合久久久久综合网 | 亚洲精品国产精品乱码不卡 | 正在播放老肥熟妇露脸 | 亚洲国产一区二区三区在线观看 | 精品亚洲成av人在线观看 | 国产成人综合美国十次 | 久久久国产一区二区三区 | 久久人人爽人人爽人人片av高清 | 欧美激情综合亚洲一二区 | 国产无遮挡又黄又爽又色 | 国产精品久久久久7777 | 亚洲高清偷拍一区二区三区 | 久久97精品久久久久久久不卡 | 一区二区三区乱码在线 | 欧洲 | 沈阳熟女露脸对白视频 | 成 人影片 免费观看 | 国产精品久久久久久无码 | 男人的天堂av网站 | 久久久久av无码免费网 | 欧美国产日韩亚洲中文 | 国产av一区二区三区最新精品 | 久久zyz资源站无码中文动漫 | 欧美日韩人成综合在线播放 | 久久精品国产亚洲精品 | 樱花草在线社区www | 蜜臀aⅴ国产精品久久久国产老师 | 国产又爽又猛又粗的视频a片 | 精品人妻av区 | 精品国产乱码久久久久乱码 | 亚洲人成影院在线无码按摩店 | 一二三四在线观看免费视频 | 国产精品久久久久久久9999 | 亚洲精品国产品国语在线观看 | 男女爱爱好爽视频免费看 | 欧美丰满老熟妇xxxxx性 | 久久伊人色av天堂九九小黄鸭 | 久久综合九色综合欧美狠狠 | 国产超碰人人爽人人做人人添 | 国产高清不卡无码视频 | 国精产品一区二区三区 | 偷窥日本少妇撒尿chinese | 色综合久久中文娱乐网 | 亚洲性无码av中文字幕 | 亚洲精品一区三区三区在线观看 | 亚洲经典千人经典日产 | 欧洲vodafone精品性 | 欧美怡红院免费全部视频 | 国产综合在线观看 | 清纯唯美经典一区二区 | 欧美国产亚洲日韩在线二区 | 色婷婷香蕉在线一区二区 | 狂野欧美性猛xxxx乱大交 | 漂亮人妻洗澡被公强 日日躁 | 十八禁视频网站在线观看 | 午夜男女很黄的视频 | 亚洲中文字幕在线无码一区二区 | 亚洲七七久久桃花影院 | 国产成人精品三级麻豆 | 国产女主播喷水视频在线观看 | 午夜熟女插插xx免费视频 | 久久天天躁夜夜躁狠狠 | 欧美日韩综合一区二区三区 | 丰满岳乱妇在线观看中字无码 | 亚洲中文字幕在线无码一区二区 | 国产人妻精品一区二区三区不卡 | 欧洲美熟女乱又伦 | 精品乱子伦一区二区三区 | 白嫩日本少妇做爰 | 99久久人妻精品免费一区 | 精品成在人线av无码免费看 | 国产精品久免费的黄网站 | 国产精品福利视频导航 | 99久久久国产精品无码免费 | 国产无av码在线观看 | 麻豆国产丝袜白领秘书在线观看 | 久久久久se色偷偷亚洲精品av | 日本精品人妻无码免费大全 | 久久99国产综合精品 | 国产精品无码久久av | 成在人线av无码免观看麻豆 | 一本久久a久久精品vr综合 | 亚洲欧洲日本无在线码 | 国产精品国产自线拍免费软件 | 国产成人午夜福利在线播放 | 国产精品永久免费视频 | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 久久综合香蕉国产蜜臀av | 2020久久超碰国产精品最新 | 大色综合色综合网站 | 亚洲爆乳大丰满无码专区 | 精品水蜜桃久久久久久久 | 波多野结衣一区二区三区av免费 | 国精产品一品二品国精品69xx | 激情爆乳一区二区三区 | 麻豆国产人妻欲求不满 | 久久精品女人天堂av免费观看 | 亚洲精品成人av在线 | 免费播放一区二区三区 | 色老头在线一区二区三区 | 国产成人综合色在线观看网站 | 好男人www社区 | 亚洲精品中文字幕久久久久 | 爱做久久久久久 | 亚洲国产av精品一区二区蜜芽 | 中文字幕无线码 | 人人妻人人藻人人爽欧美一区 | 国产在线无码精品电影网 | 久久人人爽人人人人片 | 极品嫩模高潮叫床 | 国模大胆一区二区三区 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 一本久道久久综合狠狠爱 | 久久久久免费精品国产 | 蜜臀av在线观看 在线欧美精品一区二区三区 | 丰满肥臀大屁股熟妇激情视频 | 欧洲欧美人成视频在线 | 国产精品国产自线拍免费软件 | 国产成人综合美国十次 | 久久五月精品中文字幕 | 牲欲强的熟妇农村老妇女视频 | 动漫av一区二区在线观看 | 亚洲综合精品香蕉久久网 | 一本大道伊人av久久综合 | 精品熟女少妇av免费观看 | 久久综合色之久久综合 | 中文字幕 人妻熟女 | 人妻少妇被猛烈进入中文字幕 | 免费中文字幕日韩欧美 | 国产人妻精品午夜福利免费 | 无码人妻精品一区二区三区下载 | 漂亮人妻洗澡被公强 日日躁 | 久久久久久久久蜜桃 | 日韩成人一区二区三区在线观看 | 2020久久超碰国产精品最新 | 亚洲日韩乱码中文无码蜜桃臀网站 | 成人欧美一区二区三区黑人免费 | 131美女爱做视频 | 国内精品九九久久久精品 | 国产偷国产偷精品高清尤物 | 亚洲乱码日产精品bd | 精品熟女少妇av免费观看 | 国产农村妇女高潮大叫 | 狠狠色欧美亚洲狠狠色www | 欧美精品在线观看 | 99久久精品无码一区二区毛片 | 领导边摸边吃奶边做爽在线观看 | 一本久道高清无码视频 | 日韩精品无码免费一区二区三区 | 国产午夜亚洲精品不卡下载 | 精品久久8x国产免费观看 | 人妻少妇精品视频专区 | 亚洲中文字幕在线无码一区二区 | 日本熟妇人妻xxxxx人hd | 综合人妻久久一区二区精品 | 大屁股大乳丰满人妻 | 亚洲国产精品久久久久久 | 日本免费一区二区三区最新 | 国产两女互慰高潮视频在线观看 | 18禁黄网站男男禁片免费观看 | 欧洲精品码一区二区三区免费看 | 乱人伦人妻中文字幕无码久久网 | 国产人成高清在线视频99最全资源 | 国产一区二区三区精品视频 | 超碰97人人做人人爱少妇 | 中文字幕人妻无码一区二区三区 | 麻豆精产国品 | 啦啦啦www在线观看免费视频 | 撕开奶罩揉吮奶头视频 | 久久亚洲日韩精品一区二区三区 | 人妻熟女一区 | av人摸人人人澡人人超碰下载 | 性色欲网站人妻丰满中文久久不卡 | 久久久久久亚洲精品a片成人 | 风流少妇按摩来高潮 | 一本久久伊人热热精品中文字幕 | 内射后入在线观看一区 | 久久99精品久久久久久 | 无码精品国产va在线观看dvd | 爽爽影院免费观看 | 九九热爱视频精品 | 一本精品99久久精品77 | 日本xxxx色视频在线观看免费 | 无码人妻少妇伦在线电影 | 无套内谢的新婚少妇国语播放 | 精品欧洲av无码一区二区三区 | 少妇邻居内射在线 | 天天av天天av天天透 | 国产偷自视频区视频 | 人妻天天爽夜夜爽一区二区 | 99久久久无码国产aaa精品 | 中文字幕av无码一区二区三区电影 | 双乳奶水饱满少妇呻吟 | 国产成人无码av在线影院 | 国产女主播喷水视频在线观看 | 天堂а√在线中文在线 | 国产成人综合色在线观看网站 | 久久无码专区国产精品s | 国产色在线 | 国产 | 精品少妇爆乳无码av无码专区 | 色婷婷欧美在线播放内射 | 国内丰满熟女出轨videos | 免费国产成人高清在线观看网站 | 狂野欧美性猛交免费视频 | 久久综合给久久狠狠97色 | 六十路熟妇乱子伦 | 国产一区二区三区四区五区加勒比 | 无遮无挡爽爽免费视频 | 奇米影视7777久久精品 | 丰满岳乱妇在线观看中字无码 | 久久zyz资源站无码中文动漫 | 高潮毛片无遮挡高清免费视频 | 性欧美牲交xxxxx视频 | 又紧又大又爽精品一区二区 | 久久久无码中文字幕久... | 激情综合激情五月俺也去 | 国产肉丝袜在线观看 | 久久99精品久久久久久动态图 | 中文无码成人免费视频在线观看 | 精品国产乱码久久久久乱码 | 性色欲网站人妻丰满中文久久不卡 | 久久99精品久久久久久 | 成人精品天堂一区二区三区 | 99久久精品国产一区二区蜜芽 | 久久亚洲a片com人成 | 色情久久久av熟女人妻网站 | 两性色午夜免费视频 | 精品无码一区二区三区爱欲 | 国精产品一区二区三区 | 宝宝好涨水快流出来免费视频 | 亚洲日本va午夜在线电影 | 少妇人妻av毛片在线看 | 国内丰满熟女出轨videos | 妺妺窝人体色www在线小说 | 久久精品国产一区二区三区 | 特黄特色大片免费播放器图片 | 亚洲区欧美区综合区自拍区 | 欧美刺激性大交 | 我要看www免费看插插视频 | 精品偷拍一区二区三区在线看 | 久久亚洲国产成人精品性色 | 成人一区二区免费视频 | 欧美国产日韩久久mv | 国产又爽又黄又刺激的视频 | 天天做天天爱天天爽综合网 | 精品午夜福利在线观看 | 露脸叫床粗话东北少妇 | 国产人妻人伦精品1国产丝袜 | 在线观看国产一区二区三区 | 狠狠色噜噜狠狠狠狠7777米奇 | 久久午夜无码鲁丝片 | 国产午夜亚洲精品不卡 | 久久成人a毛片免费观看网站 | 亚洲 日韩 欧美 成人 在线观看 | 麻豆精产国品 | 亚洲欧美国产精品久久 | 亚洲а∨天堂久久精品2021 | 中文字幕av无码一区二区三区电影 | 荫蒂被男人添的好舒服爽免费视频 | 少妇久久久久久人妻无码 | 99久久久无码国产aaa精品 | 中文字幕人妻无码一区二区三区 | 国产亚洲美女精品久久久2020 | 自拍偷自拍亚洲精品10p | 国产av一区二区精品久久凹凸 | 国产精品a成v人在线播放 | 好屌草这里只有精品 | 黑森林福利视频导航 | 国产精品毛多多水多 | 鲁鲁鲁爽爽爽在线视频观看 | 免费网站看v片在线18禁无码 | 丰满护士巨好爽好大乳 | 在线看片无码永久免费视频 | 精品欧洲av无码一区二区三区 | 日本乱人伦片中文三区 | 亚洲中文字幕无码中字 | 国产午夜亚洲精品不卡 | 国产熟女一区二区三区四区五区 | 婷婷五月综合激情中文字幕 | 精品国产麻豆免费人成网站 | 免费无码的av片在线观看 | ass日本丰满熟妇pics | 国产无套粉嫩白浆在线 | 女人被爽到呻吟gif动态图视看 |