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

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

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

3天內不再提示

一文掌握MyBatis的動態SQL使用與原理

OSC開源社區 ? 來源:OSCHINA 社區 ? 2023-01-06 11:27 ? 次閱讀

來源| OSCHINA 社區

作者 |華為云開發者聯盟

摘要:使用動態 SQL 并非一件易事,但借助可用于任何 SQL 映射語句中的強大的動態 SQL 語言,MyBatis 顯著地提升了這一特性的易用性。

本文分享自華為云社區《MyBatis 詳解 - 動態 SQL 使用與原理》,作者:龍哥手記 。 動態 SQL 是 MyBatis 的強大特性之一。如果你使用過 JDBC 或其它類似的框架,你應該能理解根據不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最后一個列名的逗號。利用動態 SQL,可以徹底擺脫這種痛苦。 使用動態 SQL 并非一件易事,但借助可用于任何 SQL 映射語句中的強大的動態 SQL 語言,MyBatis 顯著地提升了這一特性的易用性。 如果你之前用過 JSTL 或任何基于類 XML 語言的文本處理器,你對動態 SQL 元素可能會感覺似曾相識。在 MyBatis 之前的版本中,需要花時間了解大量的元素。借助功能強大的基于 OGNL 的表達式,MyBatis 3 替換了之前的大部分元素,大大精簡了元素種類,現在要學習的元素種類比原來的一半還要少。

if

choose (when, otherwise)

trim (where, set)

foreach

if

使用動態 SQL 最常見情景是根據條件包含 where 子句的一部分。比如:

這條語句提供了可選的查找文本功能。如果不傳入 “title”,那么所有處于 “ACTIVE” 狀態的 BLOG 都會返回;如果傳入了 “title” 參數,那么就會對 “title” 一列進行模糊查找并返回對應的 BLOG 結果(細心的讀者可能會發現,“title” 的參數值需要包含查找掩碼或通配符字符)。

如果希望通過 “title” 和 “author” 兩個參數進行可選搜索該怎么辦呢?首先,我想先將語句名稱修改成更名副其實的名稱;接下來,只需要加入另一個條件即可。

choose、when、otherwise

有時候,我們不想使用所有的條件,而只是想從多個條件中選擇一個使用。針對這種情況,MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句。

還是上面的例子,但是策略變為:傳入了 “title” 就按 “title” 查找,傳入了 “author” 就按 “author” 查找的情形。若兩者都沒有傳入,就返回標記為 featured 的 BLOG(這可能是管理員認為,與其返回大量的無意義隨機 Blog,還不如返回一些由管理員挑選的 Blog)。

trim、where、set

前面幾個例子已經合宜地解決了一個臭名昭著的動態 SQL 問題。現在回到之前的 “if” 示例,這次我們將 “state = ‘ACTIVE’” 設置成動態條件,看看會發生什么。

如果沒有匹配的條件會怎么樣?最終這條 SQL 會變成這樣:

SELECT * FROM BLOG
WHERE

這會導致查詢失敗。如果匹配的只是第二個條件又會怎樣?這條 SQL 會是這樣:

SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’

這個查詢也會失敗。這個問題不能簡單地用條件元素來解決。這個問題是如此的難以解決,以至于解決過的人不會再想碰到這種問題。

MyBatis 有一個簡單且適合大多數場景的解決辦法。而在其他場景中,可以對其進行自定義以符合需求。而這,只需要一處簡單的改動:

where 元素只會在子元素返回任何內容的情況下才插入 “WHERE” 子句。而且,若子句的開頭為 “AND” 或 “OR”,where 元素也會將它們去除。

如果 where 元素與你期望的不太一樣,你也可以通過自定義 trim 元素來定制 where 元素的功能。比如,和 where 元素等價的自定義 trim 元素為:


  ...

prefixOverrides 屬性會忽略通過管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子會移除所有 prefixOverrides 屬性中指定的內容,并且插入 prefix 屬性中指定的內容。

用于動態更新語句的類似解決方案叫做 set。set 元素可以用于動態包含需要更新的列,忽略其它不更新的列。比如:


  update Author
 
 username=#{username},
 password=#{password},
 email=#{email},
 bio=#{bio}
 
  where id=#{id}

這個例子中,set 元素會動態地在行首插入 SET 關鍵字,并會刪掉額外的逗號(這些逗號是在使用條件語句給列賦值時引入的)。

來看看與 set 元素等價的自定義 trim 元素吧:


  ...

注意,我們覆蓋了后綴值設置,并且自定義了前綴值。

foreach

動態 SQL 的另一個常見使用場景是對集合進行遍歷(尤其是在構建 IN 條件語句的時候)。比如:

foreach 元素的功能非常強大,它允許你指定一個集合,聲明可以在元素體內使用的集合項(item)和索引(index)變量。它也允許你指定開頭與結尾的字符串以及集合項迭代之間的分隔符。這個元素也不會錯誤地添加多余的分隔符,看它多智能!

提示:你可以將任何可迭代對象(如 List、Set 等)、Map 對象或者數組對象作為集合參數傳遞給 foreach。當使用可迭代對象或者數組時,index 是當前迭代的序號,item 的值是本次迭代獲取到的元素。當使用 Map 對象(或者 Map.Entry 對象的集合)時,index 是鍵,item 是值。

至此,我們已經完成了與 XML 配置及映射文件相關的討論。下一章將詳細探討 Java API,以便你能充分利用已經創建的映射配置。

script

要在帶注解的映射器接口類中使用動態 SQL,可以使用 script 元素。比如:

 @Update({""})
 void updateAuthorValues(Author author);

bind

bind 元素允許你在 OGNL 表達式以外創建一個變量,并將其綁定到當前的上下文。比如:

多數據庫支持

如果配置了 databaseIdProvider,你就可以在動態代碼中使用名為 “_databaseId” 的變量來為不同的數據庫構建特定的語句。比如下面的例子:


 
 
      select seq_users.nextval from dual
 
 
      select nextval for seq_users from sysibm.sysdummy1"
 
 
  insert into users values (#{id}, #{name})

動態 SQL 中的插入腳本語言

MyBatis 從 3.2 版本開始支持插入腳本語言,這允許你插入一種語言驅動,并基于這種語言來編寫動態 SQL 查詢語句。

可以通過實現以下接口來插入一種語言:

public interface LanguageDriver {
 ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
 SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType);
 SqlSource createSqlSource(Configuration configuration, String script, Class parameterType);
}

實現自定義語言驅動后,你就可以在 mybatis-config.xml 文件中將它設置為默認語言:


 


 

或者,你也可以使用 lang 屬性為特定的語句指定語言:

或者,在你的 mapper 接口上添加@Lang 注解:

public interface Mapper {
 @Lang(MyLanguageDriver.class)
 @Select("SELECT * FROM BLOG")
 List selectBlog();
}

提示:可以使用 Apache Velocity 作為動態語言,更多細節請參考 MyBatis-Velocity 項目。

你前面看到的所有 xml 標簽都由默認 MyBatis 語言提供,而它由語言驅動 org.apache.ibatis.scripting.xmltags.XmlLanguageDriver(別名為 xml)所提供。

動態 SQL 解析原理

我們在使用 mybatis 的時候,會在 xml 中編寫 sql 語句。比如這段動態 sql 代碼:


    UPDATE users
 
 
            name = #{name}
 
 
            , age = #{age}
 
 
            , birthday = #{birthday}
 
 
    where id = ${id}

mybatis 底層是如何構造這段 sql 的?下面帶著這個疑問,我們一步一步分析。

關于動態 SQL 的接口和類

SqlNode 接口,簡單理解就是 xml 中的每個標簽,比如上述 sql 的 update,trim,if 標簽:

public interface SqlNode {
 boolean apply(DynamicContext context);
}

d41aea24-8d2b-11ed-bfe3-dac502259ad0.png

SqlSource Sql 源接口,代表從 xml 文件或注解映射的 sql 內容,主要就是用于創建 BoundSql,有實現類 DynamicSqlSource (動態 Sql 源),StaticSqlSource (靜態 Sql 源) 等:

public interface SqlSource {
 BoundSql getBoundSql(Object parameterObject);
}

d42ae2d0-8d2b-11ed-bfe3-dac502259ad0.png

BoundSql 類,封裝 mybatis 最終產生 sql 的類,包括 sql 語句,參數,參數源數據等參數:

d435576a-8d2b-11ed-bfe3-dac502259ad0.png

XNode,一個 Dom API 中的 Node 接口的擴展類:

d43d7fa8-8d2b-11ed-bfe3-dac502259ad0.png

BaseBuilder 接口及其實現類 (屬性,方法省略了,大家有興趣的自己看), 這些 Builder 的作用就是用于構造 sql:

d442c85a-8d2b-11ed-bfe3-dac502259ad0.png

下面我們簡單分析下其中 4 個 Builder:

XMLConfigBuilder:解析 mybatis 中 configLocation 屬性中的全局 xml 文件,內部會使用 XMLMapperBuilder 解析各個 xml 文件。

XMLMapperBuilder:遍歷 mybatis 中 mapperLocations 屬性中的 xml 文件中每個節點的 Builder,比如 user.xml,內部會使用 XMLStatementBuilder 處理 xml 中的每個節點。

XMLStatementBuilder:解析 xml 文件中各個節點,比如 select,insert,update,delete 節點,內部會使用 XMLScriptBuilder 處理節點的 sql 部分,遍歷產生的數據會丟到 Configuration 的 mappedStatements 中。

XMLScriptBuilder:解析 xml 中各個節點 sql 部分的 Builder。

LanguageDriver 接口及其實現類 (屬性,方法省略了,大家有興趣的自己看),該接口主要的作用就是構造 sql:

d448500e-8d2b-11ed-bfe3-dac502259ad0.png

簡單分析下 XMLLanguageDriver (處理 xml 中的 sql,RawLanguageDriver 處理靜態 sql):XMLLanguageDriver 內部會使用 XMLScriptBuilder 解析 xml 中的 sql 部分。

源碼分析走起

Spring 與 Mybatis 整合的時候需要配置 SqlSessionFactoryBean,該配置會加入數據源和 mybatis xml 配置文件路徑等信息


 
 
 

我們就分析這一段配置背后的細節:

SqlSessionFactoryBean 實現了 Spring 的 InitializingBean 接口,InitializingBean 接口的 afterPropertiesSet 方法中會調用 buildSqlSessionFactory 方法 該方法內部會使用 XMLConfigBuilder 解析屬性 configLocation 中配置的路徑,還會使用 XMLMapperBuilder 屬性解析 mapperLocations 屬性中的各個 xml 文件。部分源碼如下:

d44e590e-8d2b-11ed-bfe3-dac502259ad0.png

由于 XMLConfigBuilder 內部也是使用 XMLMapperBuilder,我們就看看 XMLMapperBuilder 的解析細節:

d4599116-8d2b-11ed-bfe3-dac502259ad0.png

d4617282-8d2b-11ed-bfe3-dac502259ad0.png

我們關注一下,增刪改查節點的解析:

d46c8942-8d2b-11ed-bfe3-dac502259ad0.png

XMLStatementBuilder 的解析:

d479221a-8d2b-11ed-bfe3-dac502259ad0.jpg

默認會使用 XMLLanguageDriver 創建 SqlSource(Configuration 構造函數中設置)。

XMLLanguageDriver 創建 SqlSource:

d47edca0-8d2b-11ed-bfe3-dac502259ad0.png

XMLScriptBuilder 解析 sql:

d487443a-8d2b-11ed-bfe3-dac502259ad0.png

得到 SqlSource 之后,會放到 Configuration 中,有了 SqlSource,就能拿 BoundSql 了,BoundSql 可以得到最終的 sql。

實例分析

以下面的 xml 解析大概說下 parseDynamicTags 的解析過程:


    UPDATE users
 
 
            name = #{name}
 
 
            , age = #{age}
 
 
            , birthday = #{birthday}
 
 
    where id = ${id}

parseDynamicTags 方法的返回值是一個 List,也就是一個 Sql 節點集合。SqlNode 本文一開始已經介紹,分析完解析過程之后會說一下各個 SqlNode 類型的作用。

首先根據 update 節點 (Node) 得到所有的子節點,分別是 3 個子節點:

文本節點 UPDATE users

trim 子節點 ...

文本節點 where id = #

遍歷各個子節點:

如果節點類型是文本或者 CDATA,構造一個 TextSqlNode 或 StaticTextSqlNode;

如果節點類型是元素,說明該 update 節點是個動態 sql,然后會使用 NodeHandler 處理各個類型的子節點。這里的 NodeHandler 是 XMLScriptBuilder 的一個內部接口,其實現類包括 TrimHandler、WhereHandler、SetHandler、IfHandler、ChooseHandler 等。看類名也就明白了這個 Handler 的作用,比如我們分析的 trim 節點,對應的是 TrimHandler;if 節點,對應的是 IfHandler... 這里子節點 trim 被 TrimHandler 處理,TrimHandler 內部也使用 parseDynamicTags 方法解析節點。

遇到子節點是元素的話,重復以上步驟:

trim 子節點內部有 7 個子節點,分別是文本節點、if 節點、是文本節點、if 節點、是文本節點、if 節點、文本節點。文本節點跟之前一樣處理,if 節點使用 IfHandler 處理。遍歷步驟如上所示,下面我們看下幾個 Handler 的實現細節。

IfHandler 處理方法也是使用 parseDynamicTags 方法,然后加上 if 標簽必要的屬性:

private class IfHandler implements NodeHandler {
 public void handleNode(XNode nodeToHandle, List targetContents) {
 List contents = parseDynamicTags(nodeToHandle);
 MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
 String test = nodeToHandle.getStringAttribute("test");
 IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
 targetContents.add(ifSqlNode);
 }
}

TrimHandler 處理方法也是使用 parseDynamicTags 方法,然后加上 trim 標簽必要的屬性:

private class TrimHandler implements NodeHandler {
 public void handleNode(XNode nodeToHandle, List targetContents) {
 List contents = parseDynamicTags(nodeToHandle);
 MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
 String prefix = nodeToHandle.getStringAttribute("prefix");
 String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
 String suffix = nodeToHandle.getStringAttribute("suffix");
 String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
 TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
 targetContents.add(trim);
 }
}

以上 update 方法最終通過 parseDynamicTags 方法得到的 SqlNode 集合如下:

d48f7952-8d2b-11ed-bfe3-dac502259ad0.png

trim 節點:

d49d45a0-8d2b-11ed-bfe3-dac502259ad0.png

由于這個 update 方法是個動態節點,因此構造出了 DynamicSqlSource。DynamicSqlSource 內部就可以構造 sql 了:

d4a82e16-8d2b-11ed-bfe3-dac502259ad0.png

DynamicSqlSource 內部的 SqlNode 屬性是一個 MixedSqlNode。然后我們看看各個 SqlNode 實現類的 apply 方法。下面分析一下各個 SqlNode 實現類的 apply 方法實現:

MixedSqlNode:MixedSqlNode 會遍歷調用內部各個 sqlNode 的 apply 方法。

public boolean apply(DynamicContext context) {
 for (SqlNode sqlNode : contents) {
 sqlNode.apply(context);
 }
 return true;
}

StaticTextSqlNode:直接 append sql 文本。

public boolean apply(DynamicContext context) {
 context.appendSql(text);
 return true;
}

IfSqlNode:這里的 evaluator 是一個 ExpressionEvaluator 類型的實例,內部使用了 OGNL 處理表達式邏輯。

public boolean apply(DynamicContext context) {
 if (evaluator.evaluateBoolean(test, context.getBindings())) {
 contents.apply(context);
 return true;
 }
 return false;
}

TrimSqlNode:

public boolean apply(DynamicContext context) {
 FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
 boolean result = contents.apply(filteredDynamicContext);
 filteredDynamicContext.applyAll();
 return result;
}
public void applyAll() {
 sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
 String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
 if (trimmedUppercaseSql.length() > 0) {
 applyPrefix(sqlBuffer, trimmedUppercaseSql);
 applySuffix(sqlBuffer, trimmedUppercaseSql);
 }
 delegate.appendSql(sqlBuffer.toString());
}
private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
 if (!prefixApplied) {
 prefixApplied = true;
 if (prefixesToOverride != null) {
 for (String toRemove : prefixesToOverride) {
 if (trimmedUppercaseSql.startsWith(toRemove)) {
 sql.delete(0, toRemove.trim().length());
 break;
 }
 }
 }
 if (prefix != null) {
 sql.insert(0, " ");
 sql.insert(0, prefix);
 }
 }
}

TrimSqlNode 的 apply 方法也是調用屬性 contents (一般都是 MixedSqlNode) 的 apply 方法,按照實例也就是 7 個 SqlNode,都是 StaticTextSqlNode 和 IfSqlNode。最后會使用 FilteredDynamicContext 過濾掉 prefix 和 suffix。

審核編輯:湯梓紅

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

    關注

    68

    文章

    19259

    瀏覽量

    229652
  • SQL
    SQL
    +關注

    關注

    1

    文章

    762

    瀏覽量

    44117
  • OGNL
    +關注

    關注

    0

    文章

    2

    瀏覽量

    1937
  • mybatis
    +關注

    關注

    0

    文章

    60

    瀏覽量

    6709

原文標題:一文掌握 MyBatis 的動態 SQL 使用與原理

文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    了解MyBatis的查詢原理

    本文通過MyBatis個低版本的bug(3.4.5之前的版本)入手,分析MyBatis次完整的查詢流程,從配置文件的解析到個查詢的完
    的頭像 發表于 10-10 11:42 ?1422次閱讀

    Mybatis的特點和核心配置文件

    Mybatis—01基礎&動態SQL
    發表于 08-07 14:20

    為什么要動態sql語句?

    為什么要動態sql語句?因為動態sql語句能夠提供些比較友好的機制1、可以使得些在編譯過程中
    發表于 12-20 06:00

    jdbc與mybatis的區別

    MyBatis款優秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。JDBC是
    發表于 02-02 17:43 ?1.1w次閱讀
    jdbc與<b class='flag-5'>mybatis</b>的區別

    mybatis是什么_MyBatis的優缺點詳解_mybatis框架入門詳解

    Mybatis框架是別人開發的種半成品軟件,可以用來通過定制輔助快速開發是工具。MyBatis應用程序根據XML配置文件創建SqlSessionFactory,SqlSessionFactory在
    發表于 02-24 09:16 ?2w次閱讀

    mybatis動態sql詳解

    本文詳細介紹了mybatis執行動態sql語句的方法。
    發表于 02-24 11:37 ?3833次閱讀

    mybatis中#和$的區別

    注入。$方式無法防止Sql注入。$方式般用于傳入數據庫對象,例如傳入表名。般能用#的就別用$。所以我們在使用mybatis的時候,盡量的使用#方式,這是大家要注意的地方。
    發表于 02-24 13:35 ?2270次閱讀

    在使用MyBatisSQL語句優化總結

    MyBatis 作為款優秀的持久層框架,它支持自定義SQL、存儲過程以及高級映射。它免除了幾乎所有的 JDBC 代碼以及設置參數和獲取結果集的工作。還可以通過簡單的 XML 或注解來配置和映射原始
    的頭像 發表于 02-04 15:20 ?2785次閱讀

    Fluent Mybatis、原生MybatisMybatis Plus對比

    使用fluent mybatis可以不用寫具體的xml文件,通過java api可以構造出比較復雜的業務sql語句,做到代碼邏輯和sql邏輯的合。不再需要在Dao中組裝查詢或更新操作
    的頭像 發表于 09-15 15:41 ?1430次閱讀

    MybatisSQL注入審計的基本方法

    SQL注入漏洞作為WEB安全的最常見的漏洞之,在java中隨著預編譯與各種ORM框架的使用,注入問題也越來越少。新手代碼審計者往往對Java Web應用的多個框架組合而心生畏懼,不知如何下手,希望通過Mybatis框架使用不當
    的頭像 發表于 10-17 11:16 ?1306次閱讀

    動態Sql介紹

    動態Sql介紹 動態 SQLMyBatis 的強大特性之。如果你使用過 JDBC 或其它
    的頭像 發表于 05-31 09:34 ?1380次閱讀
    <b class='flag-5'>動態</b><b class='flag-5'>Sql</b>介紹

    MyBatis動態sql是什么?MyBatis動態SQL最全教程

    動態 SQLMyBatis 的強大特性之。在 JDBC 或其它類似的框架中,開發人員通常需要手動拼接 SQL 語句。根據不同的條件拼
    的頭像 發表于 08-10 10:18 ?953次閱讀

    mybatis接口動態代理原理

    MyBatis款輕量級的Java持久化框架,它通過XML或注解配置的方式,將數據庫操作與SQL語句解耦,提供了種簡單、靈活的數據訪問方式。在M
    的頭像 發表于 12-03 11:52 ?935次閱讀

    mybatis和mybatisplus的區別

    個輕量級的持久層框架,它提供了個靈活的SQL映射機制,使得開發者可以編寫原生SQL語句來操作數據庫。MyBatis的設計目標是將原生SQL
    的頭像 發表于 12-03 11:53 ?2547次閱讀

    mybatis中$和井號區別

    MyBatis個開源的Java持久層框架,它提供了許多強大的功能用于簡化數據庫操作。在MyBatis中,我們可以使用兩種方式來動態生成SQL
    的頭像 發表于 12-03 14:53 ?968次閱讀
    主站蜘蛛池模板: 各种肉黄浪荡故事集| 99久久国产露脸精品麻豆 | 日日夜夜噜噜| 亚洲欧洲日产国码久在线| 2019在秋霞理论| 国产精品2020观看久久| 久久99国产精品无码AV| 人人做人人干| 一个人的免费高清影院| sihu国产精品永久免费| 狠狠人妻久久久久久综合九色| 免费人妻无码AV不卡在线| 亚洲AV无码乱码国产精品品麻豆| 中文字幕s级优女区| 国产精品久久久久久久久久免费| 久久精品一区二区免费看| 色欲人妻AAAAAAA无码| 4480YY旧里番在线播放| 国产美女视频一区二区二三区| 欧美精品色视频| 亚洲人人为我我为人人| 国产成人精品系列在线观看| 免费视频久久只有精品| 又爽又黄又粗又大免费视频| 国产成人精品免费视频大 | 宫交拔不出来了h黑人| 免费精品美女久久久久久久久| 亚洲爱视频| 国产亚洲精品AV片在线观看播放| 奇米色偷偷| caoporn超碰视频| 蜜臀AV人妻久久无码精品麻豆| 伊人香蕉在线播放视频免费| 国产午夜精AV在线麻豆| 偷偷鲁青春草原视频分类| adc我们的永久网址| 明星三级电影| 99视频在线免费| 蜜臀AV999无码精品国产| 婬香婬色天天视频| 久久精品国产亚洲AV蜜臀|