色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

SpringBoot多數據源及事務解決方案

jf_ro2CN3Fa ? 來源:CSDN ? 2023-04-12 11:22 ? 次閱讀


1. 背景

一個主庫和N個應用庫的數據源,并且會同時操作主庫和應用庫的數據,需要解決以下兩個問題:

  • 如何動態管理多個數據源以及切換?
  • 如何保證多數據源場景下的數據一致性(事務)?

本文主要探討這兩個問題的解決方案,希望能對讀者有一定的啟發。

基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

  • 項目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 視頻教程:https://doc.iocoder.cn/video/

2. 數據源切換原理

通過擴展Spring提供的抽象類AbstractRoutingDataSource,可以實現切換數據源。其類結構如下圖所示:

32fe5be6-d8d8-11ed-bfe3-dac502259ad0.png
  • targetDataSources&defaultTargetDataSource

項目上需要使用的所有數據源和默認數據源。

  • resolvedDataSources&resolvedDefaultDataSource

當Spring容器創建AbstractRoutingDataSource對象時,通過調用afterPropertiesSet復制上述目標數據源。由此可見,一旦數據源實例對象創建完畢,業務無法再添加新的數據源。

  • determineCurrentLookupKey

此方法為抽象方法,通過擴展這個方法來實現數據源的切換。目標數據源的結構為:Map其key為lookup key。

我們來看官方對這個方法的注釋:

3317a344-d8d8-11ed-bfe3-dac502259ad0.png

lookup key通常是綁定在線程上下文中,根據這個key去resolvedDataSources中取出DataSource。

根據目標數據源的管理方式不同,可以使用基于配置文件和數據庫表兩種方式。基于配置文件管理方案無法后續添加新的數據源,而基于數據庫表方案管理,則更加靈活。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

  • 項目地址:https://github.com/YunaiV/yudao-cloud
  • 視頻教程:https://doc.iocoder.cn/video/

3. 配置文件解決方案

根據上面的分析,我們可以按照下面的步驟去實現:

  • 定義DynamicDataSource類繼承AbstractRoutingDataSource,重寫determineCurrentLookupKey()方法。
  • 配置多個數據源注入targetDataSourcesdefaultTargetDataSource,通過afterPropertiesSet()方法將數據源寫入resolvedDataSourcesresolvedDefaultDataSource。
  • 調用AbstractRoutingDataSourcegetConnection()方法時,determineTargetDataSource()方法返回DataSource執行底層的getConnection()

其流程如下圖所示:

33237bd8-d8d8-11ed-bfe3-dac502259ad0.png

3.1 創建數據源

DynamicDataSource數據源的注入,目前業界主流實現步驟如下:

在配置文件中定義數據源

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driverClassName=com.mysql.jdbc.Driver
#主數據源
spring.datasource.druid.master.url=jdbcUrl
spring.datasource.druid.master.username=***
spring.datasource.druid.master.password=***
#其他數據源
spring.datasource.druid.second.url=jdbcUrl
spring.datasource.druid.second.username=***
spring.datasource.druid.second.password=***

在代碼中配置Bean

@Configuration
publicclassDynamicDataSourceConfig{
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
publicDataSourcefirstDataSource(){
returnDruidDataSourceBuilder.create().build();
}

@Bean
@ConfigurationProperties("spring.datasource.druid.second")
publicDataSourcesecondDataSource(){
returnDruidDataSourceBuilder.create().build();
}

@Bean
@Primary
publicDynamicDataSourcedataSource(DataSourcefirstDataSource,DataSourcesecondDataSource){
MaptargetDataSources=newHashMap<>(5);
targetDataSources.put(DataSourceNames.FIRST,firstDataSource);
targetDataSources.put(DataSourceNames.SECOND,secondDataSource);
returnnewDynamicDataSource(firstDataSource,targetDataSources);
}
}

3.2 AOP處理

通過DataSourceAspect切面技術來簡化業務上的使用,只需要在業務方法添加@SwitchDataSource注解即可完成動態切換:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public@interfaceSwitchDataSource{
Stringvalue();
}

DataSourceAspect攔截業務方法,更新當前線程上下文DataSourceContextHolder中存儲的key,即可實現數據源切換。

3.3 方案不足

基于AbstractRoutingDataSource的多數據源動態切換,有個明顯的缺點,無法動態添加和刪除數據源。在我們的產品中,不能把應用數據源寫死在配置文件。接下來分享一下基于數據庫表的實現方案。

4. 數據庫表解決方案

我們需要實現可視化的數據源管理,并實時查看數據源的運行狀態。所以我們不能把數據源全部配置在文件中,應該將數據源定義保存到數據庫表。參考AbstractRoutingDataSource的設計思路,實現自定義數據源管理。

4.1 設計數據源表

主庫的數據源信息仍然配置在項目配置文件中,應用庫數據源配置參數,則設計對應的數據表。表結構如下所示:

333864e4-d8d8-11ed-bfe3-dac502259ad0.png

這個表主要就是DataSource的相關配置參數,其相應的ORM操作代碼在此不再贅述,主要是實現數據源的增刪改查操作。

4.2 自定義數據源管理

4.2.1 定義管理接口

通過繼承AbstractDataSource即可實現DynamicDataSource。為了方便對數據源進行操作,我們定義一個接口DataSourceManager,為業務提供操作數據源的統一接口。

publicinterfaceDataSourceManager{
voidput(Stringvar1,DataSourcevar2);

DataSourceget(Stringvar1);

BooleanhasDataSource(Stringvar1);

voidremove(Stringvar1);

voidcloseDataSource(Stringvar1);

Collectionall();
}

該接口主要是對數據表中定義的數據源,提供基礎管理功能。

4.2.2 自定義數據源

DynamicDataSource的實現如下圖所示:

335837ce-d8d8-11ed-bfe3-dac502259ad0.png

根據前面的分析,AbstractRoutingDataSource是在容器啟動的時候,執行afterPropertiesSet注入數據源對象,完成之后無法對數據源進行修改。DynamicDataSource則實現DataSourceManager接口,可以將數據表中的數據源加載到dataSources。

4.2.3 切面處理

這一塊的處理跟配置文件數據源方案處理方式相同,都是通過AOP技術切換lookup key。

publicDataSourcedetermineTargetDataSource(){
StringlookupKey=DataSourceContextHolder.getKey();
DataSourcedataSource=Optional.ofNullable(lookupKey)
.map(dataSources::get)
.orElse(defaultDataSource);
if(dataSource==null){
thrownewIllegalStateException("CannotdetermineDataSourceforlookupkey["+lookupKey+"]");
}
returndataSource;
}

4.2.4 管理數據源狀態

在項目啟動的時候,加載數據表中的所有數據源,并執行初始化。初始化操作主要是使用SpringBoot提供的DataSourceBuilder類,根據數據源表的定義創建DataSource。在項目運行過程中,可以使用定時任務對數據源進行?;?,為了提升性能再添加一層緩存。

336da2da-d8d8-11ed-bfe3-dac502259ad0.png

AbstractRoutingDataSource 只支持單庫事務,切換數據源是在開啟事務之前執行。 Spring使用 DataSourceTransactionManager進行事務管理。開啟事務,會將數據源緩存到DataSourceTransactionObject對象中,后續的commit和 rollback事務操作實際上是使用的同一個數據源。

如何解決切庫事務問題?借助Spring的聲明式事務處理,我們可以在多次切庫操作時強制開啟新的事務:

@SwitchDataSource
@Transactional(rollbackFor=Exception.class,propagation=Propagation.REQUIRES_NEW)

這樣的話,執行切庫操作的時候強制啟動新事務,便可實現多次切庫而且事務能夠生效。但是這種事務方式,存在數據一致性問題:

337b63a2-d8d8-11ed-bfe3-dac502259ad0.png

假若ServiceB正常執行提交事務,接著返回ServiceA執行并且發生異常。因為兩次處理是不同的事務,ServiceA這個事務執行回滾,而ServiceA事務已經提交。這樣的話,數據就不一致了。接下來,我們主要討論如何解決多庫的事務問題。

6. 多庫事務處理

6.1 關于事務的理解

首先有必要理解事務的本質。

1.提到Spring事務,就離不開事務的四大特性和隔離級別、七大傳播特性。

事務特性和離級別是屬于數據庫范疇。Spring事務的七大傳播特性是什么呢?它是Spring在當前線程內,處理多個事務操作時的事務應用策略,數據庫事務本身并不存在傳播特性。

3390ad70-d8d8-11ed-bfe3-dac502259ad0.png

2.Spring事務的定義包括:begin、commit、rollback、close、suspend、resume等動作。

  • begin(事務開始): 可以認為存在于數據庫的命令中,比如Mysql的start transaction命令,但是在JDBC編程方式中不存在。
  • close(事務關閉): Spring事務的close()方法,是把Connection對象歸還給數據庫連接池,與事務無關。
  • suspend(事務掛起): Spring中事務掛起的語義是:需要新事務時,將現有的Connection保存起來(還有尚未提交的事務),然后創建新的Connection2,Connection2提交、回滾、關閉完畢后,再把Connection1取出來繼續執行。
  • resume(事務恢復): 嵌套事務執行完畢,返回上層事務重新綁定連接對象到事務管理器的過程。

實際上,只有commit、rollback、close是在JDBC真實存在的,而其他動作都是應用的語意,而非JDBC事務的真實命令。因此,事務真實存在的方法是:setAutoCommit()、commit()、rollback()。

close()語義為:

  • 關閉一個數據庫連接,這已經不再是事務的方法了。

使用DataSource并不會執行物理關閉,只是歸還給連接池。

6.2 自定義管理事務

為了保證在多個數據源中事務的一致性,我們可以手動管理Connetion的事務提交和回滾。考慮到不同ORM框架的事務管理實現差異,要求實現自定義事務管理不影響框架層的事務。

這可以通過使用裝飾器設計模式,對Connection進行包裝重寫commit和rolllback屏蔽其默認行為,這樣就不會影響到原生Connection和ORM框架的默認事務行為。其整體思路如下圖所示:

33a2cf3c-d8d8-11ed-bfe3-dac502259ad0.png

這里并沒有使用前面提到的@SwitchDataSource,這是因為我們在TransactionAop中已經執行了lookupKey的切換。

6.2.1 定義多事務注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public@interfaceMultiTransaction{
StringtransactionManager()default"multiTransactionManager";
//默認數據隔離級別,隨數據庫本身默認值
IsolationLevelisolationLevel()defaultIsolationLevel.DEFAULT;
//默認為主庫數據源
StringdatasourceId()default"default";
//只讀事務,若有更新操作會拋出異常
booleanreadOnly()defaultfalse;

業務方法只需使用該注解即可開啟事務,datasourceId指定事務用到的數據源,不指定默認為主庫。

6.2.3 包裝Connection

自定義事務我們使用包裝過的Connection,屏蔽其中的commit&rollback方法。這樣我們就可以在主事務里進行統一的事務提交和回滾操作。

publicclassConnectionProxyimplementsConnection{

privatefinalConnectionconnection;

publicConnectionProxy(Connectionconnection){
this.connection=connection;
}

@Override
publicvoidcommit()throwsSQLException{
//connection.commit();
}

publicvoidrealCommit()throwsSQLException{
connection.commit();
}

@Override
publicvoidclose()throwsSQLException{
//connection.close();
}

publicvoidrealClose()throwsSQLException{
if(!connection.getAutoCommit()){
connection.setAutoCommit(true);
}
connection.close();
}

@Override
publicvoidrollback()throwsSQLException{
if(!connection.isClosed())
connection.rollback();
}
...
}

這里commit&close方法不執行操作,rollback執行的前提是連接執行close才生效。這樣不管是使用哪個ORM框架,其自身事務管理都將失效。事務的控制就交由MultiTransaction控制了。

6.2.4 事務上下文管理

publicclassTransactionHolder{
//是否開啟了一個MultiTransaction
privatebooleanisOpen;
//是否只讀事務
privatebooleanreadOnly;
//事務隔離級別
privateIsolationLevelisolationLevel;
//維護當前線程事務ID和連接關系
privateConcurrentHashMapconnectionMap;
//事務執行棧
privateStackexecuteStack;
//數據源切換棧
privateStackdatasourceKeyStack;
//主事務ID
privateStringmainTransactionId;
//執行次數
privateAtomicIntegertransCount;

//事務和數據源key關系
privateConcurrentHashMapexecuteIdDatasourceKeyMap;

}

每開啟一個事物,生成一個事務ID并綁定一個ConnectionProxy。事務嵌套調用,保存事務ID和lookupKey至棧中,當內層事務執行完畢執行pop。這樣的話,外層事務只需在棧中執行peek即可獲取事務ID和lookupKey。

6.2.5 數據源兼容處理

為了不影響原生事務的使用,需要重寫getConnection方法。當前線程沒有啟動自定義事務,則直接從數據源中返回連接。

@Override
publicConnectiongetConnection()throwsSQLException{
TransactionHoldertransactionHolder=MultiTransactionManager.TRANSACTION_HOLDER_THREAD_LOCAL.get();
if(Objects.isNull(transactionHolder)){
returndetermineTargetDataSource().getConnection();
}
ConnectionProxyConnectionProxy=transactionHolder.getConnectionMap()
.get(transactionHolder.getExecuteStack().peek());
if(ConnectionProxy==null){
//沒開跨庫事務,直接返回
returndetermineTargetDataSource().getConnection();
}else{
transactionHolder.addCount();
//開了跨庫事務,從當前線程中拿包裝過的Connection
returnConnectionProxy;
}
}

6.2.6 切面處理

切面處理的核心邏輯是:維護一個嵌套事務棧,當業務方法執行結束,或者發生異常時,判斷當前棧頂事務ID是否為主事務ID。如果是的話這時候已經到了最外層事務,這時才執行提交和回滾。詳細流程如下圖所示:

33b5335c-d8d8-11ed-bfe3-dac502259ad0.png
packagecom.github.mtxn.transaction.aop;

importcom.github.mtxn.application.Application;
importcom.github.mtxn.transaction.MultiTransactionManager;
importcom.github.mtxn.transaction.annotation.MultiTransaction;
importcom.github.mtxn.transaction.context.DataSourceContextHolder;
importcom.github.mtxn.transaction.support.IsolationLevel;
importcom.github.mtxn.transaction.support.TransactionHolder;
importcom.github.mtxn.utils.ExceptionUtils;
importlombok.extern.slf4j.Slf4j;
importorg.aspectj.lang.ProceedingJoinPoint;
importorg.aspectj.lang.annotation.Around;
importorg.aspectj.lang.annotation.Aspect;
importorg.aspectj.lang.annotation.Pointcut;
importorg.aspectj.lang.reflect.MethodSignature;
importorg.springframework.core.annotation.Order;
importorg.springframework.stereotype.Component;

importjava.lang.reflect.Method;


@Aspect
@Component
@Slf4j
@Order(99999)
publicclassMultiTransactionAop{

@Pointcut("@annotation(com.github.mtxn.transaction.annotation.MultiTransaction)")
publicvoidpointcut(){
if(log.isDebugEnabled()){
log.debug("startintransactionpointcut...");
}
}


@Around("pointcut()")
publicObjectaroundTransaction(ProceedingJoinPointpoint)throwsThrowable{
MethodSignaturesignature=(MethodSignature)point.getSignature();
//從切面中獲取當前方法
Methodmethod=signature.getMethod();
MultiTransactionmultiTransaction=method.getAnnotation(MultiTransaction.class);
if(multiTransaction==null){
returnpoint.proceed();
}
IsolationLevelisolationLevel=multiTransaction.isolationLevel();
booleanreadOnly=multiTransaction.readOnly();
StringprevKey=DataSourceContextHolder.getKey();
MultiTransactionManagermultiTransactionManager=Application.resolve(multiTransaction.transactionManager());
//切數據源,如果失敗使用默認庫
if(multiTransactionManager.switchDataSource(point,signature,multiTransaction))returnpoint.proceed();
//開啟事務棧
TransactionHoldertransactionHolder=multiTransactionManager.startTransaction(prevKey,isolationLevel,readOnly,multiTransactionManager);
Objectproceed;

try{
proceed=point.proceed();
multiTransactionManager.commit();
}catch(Throwableex){
log.error("executemethod:{}#{},err:",method.getDeclaringClass(),method.getName(),ex);
multiTransactionManager.rollback();
throwExceptionUtils.api(ex,"系統異常:%s",ex.getMessage());
}finally{
//當前事務結束出棧
StringtransId=multiTransactionManager.getTrans().getExecuteStack().pop();
transactionHolder.getDatasourceKeyStack().pop();
//恢復上一層事務
DataSourceContextHolder.setKey(transactionHolder.getDatasourceKeyStack().peek());
//最后回到主事務,關閉此次事務
multiTransactionManager.close(transId);
}
returnproceed;

}


}

7.總結

本文主要介紹了多數據源管理的解決方案(應用層事務,而非XA二段提交保證),以及對多個庫同時操作的事務管理。

需要注意的是,這種方式只適用于單體架構的應用。因為多個庫的事務參與者都是運行在同一個JVM進行。如果是在微服務架構的應用中,則需要使用分布式事務管理(譬如:Seata)。



審核編輯 :李倩


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • spring
    +關注

    關注

    0

    文章

    340

    瀏覽量

    14353
  • 數據源
    +關注

    關注

    1

    文章

    63

    瀏覽量

    9688
  • 微服務
    +關注

    關注

    0

    文章

    137

    瀏覽量

    7360
  • SpringBoot
    +關注

    關注

    0

    文章

    173

    瀏覽量

    183

原文標題:SpringBoot 多數據源及事務解決方案

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    Mybatis 攔截器實現單數據源多數據庫切換

    作者:京東保險 王奕龍 物流的分揀業務在某些分揀場地只有一個數據源,因為數據量比較大,將所有數據存在一張表內查詢速度慢,也為了做不同設備數據的分庫管理,便在這個
    的頭像 發表于 12-12 10:23 ?746次閱讀

    LabView動態創建數據源的方法

    在LabView中LabSQL操作數據庫是通過ODBC連接,用戶需要在ODBC中指定數據源名稱和驅動程序。因此在使用LabSQL之前,首先需要在Windows操作系統中的ODBC數據源中創建一個
    發表于 09-23 01:53

    ODBC數據源的建立

    我找不到數據源這個選項啊,怎么辦?
    發表于 04-21 11:39

    SpringBoot中的Druid介紹

    SpringBoot中Druid數據源配置
    發表于 05-07 09:21

    SpringBoot項目多數據源配置數據

    SpringBoot項目多數據源配置
    發表于 06-05 09:51

    數據源配置工具

    odbc_for_access.exe1、可直接配置數據源。2、可直接創建Access數據庫文件。3、為數據庫配置密碼。
    發表于 07-01 16:22 ?4次下載

    基于元組水平對數據源進行分層抽樣

    深網查詢在Web上眾多的應用,需要查詢大量的數據源才能獲得足夠的數據,如多媒體數據搜索、團購網站信息聚合等.應用的成功,取決于查詢多數據源的效率和效果,當前研究側重查詢與
    發表于 12-29 10:55 ?0次下載
    基于元組水平對<b class='flag-5'>數據源</b>進行分層抽樣

    基于LDA主題模型進行數據源選擇方法

    聯邦搜索是從大規模深層網上獲取信息的一種重要技術。給定一個用戶查詢,聯邦搜索系統需要解決的一個主要問題是數據源選擇問題,即從海量數據源中選出一組最有可能返回相關結果的數據源?,F有的數據源
    發表于 01-04 15:00 ?0次下載
    基于LDA主題模型進行<b class='flag-5'>數據源</b>選擇方法

    Deep Web數據源選擇和集成方法

    針對基于數據源質量選擇方法的數據源數據爬取時存在代價大、重復率高的問題,提出一種結合兩層選擇模型的Deep Web數據源選擇和集成方法。該方法根據
    發表于 02-09 15:24 ?0次下載
    Deep Web<b class='flag-5'>數據源</b>選擇和集成方法

    數據倉庫入門之創建數據源

    首先需要創建一個數據源,SSAS(分析服務)將利用數據源來連接數據庫。一、準備環境二、啟動SSDT,新建項目三、創建數據源
    發表于 02-24 14:48 ?2540次閱讀
    <b class='flag-5'>數據</b>倉庫入門之創建<b class='flag-5'>數據源</b>

    Veritas收購Globanet,新功能將允許用戶獲得和使用更多數據源

    新功能將允許用戶獲得和使用更多數據源 北京 2020 年 11月11日 近日,全球數據保護及數據管理領域的領導者Veritas Technologies宣布已完成對Globanet的收購
    的頭像 發表于 11-12 16:31 ?1470次閱讀

    SpringBoot分布式事務解決方案(JTA+Atomic+多數據源

    首先,到底啥是分布式事務呢,比如我們在執行一個業務邏輯的時候有兩步分別操作A數據源和B數據源,當我們在A數據源執行數據更改后,在B
    的頭像 發表于 04-11 11:05 ?1647次閱讀

    多數據源數據轉換和同步的ETL工具推薦

    有許多支持多數據源數據轉換和同步的ETL工具可供選擇。以下是一些常見的ETL工具和它們支持多數據源數據轉換和同步的特點: Apache NiFi:Apache NiFi是一個開源的ET
    的頭像 發表于 07-28 16:32 ?1115次閱讀

    weblogic修改數據源需要重啟嗎

    WebLogic是一款支持Java EE(Java Enterprise Edition)規范的應用服務器,通過WebLogic可進行數據源的配置和管理。在WebLogic中修改數據源是否需要重啟
    的頭像 發表于 12-05 16:09 ?1452次閱讀

    SpringBoot實現動態切換數據源

    最近在做業務需求時,需要從不同的數據庫中獲取數據然后寫入到當前數據庫中,因此涉及到切換數據源問題。本來想著使用Mybatis-plus中提供的動態
    的頭像 發表于 12-08 10:53 ?1032次閱讀
    <b class='flag-5'>SpringBoot</b>實現動態切換<b class='flag-5'>數據源</b>
    主站蜘蛛池模板: 亚洲一区二区免费看| 俄罗斯XX性幻女18| 精品夜夜澡人妻无码AV| 国产亚洲精品看片在线观看| 脱女学小内内摸出水网站免费| 国产精品色无码AV在线观看| 亚洲无AV在线中文字幕| 蜜臀AV熟女人妻中文字幕| z00兽200俄罗斯| 亚洲 在线 日韩 欧美| 久青草国产在视频在线观看| yellow免费| 亚洲理论在线a中文字幕| 毛片TV网站无套内射TV网站| 荡乳乱公小说| 一色狗影院| 色偷偷在线视频| 久久热这里只有 精品| 成人国产一区| 一个人免费视频在线观看高清频道| 嗯啊快拔出来我是你老师视频| 国产精品一区二区AV白丝在线| 在线国产视频观看| 天天啪免费视频在线看| 久久综合伊人| 国产剧情在线精品视频不卡| 3344永久在线观看视频免费| 四虎成人影院| 女性性纵欲派对| 精品久久中文字幕有码| 成人高清护士在线播放| 重口味av| 亚洲人女同志video| 日日干夜夜爱| 美女的避毛| 精品国产手机视频在在线| 福利视频久久| a视频免费看| 中文字幕在线视频免费观看| 亚欧乱亚欧乱色视频| 人C交ZZZ0OOZZZ000|