亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發(fā) > Java > 正文

MyBatis通用Mapper實現(xiàn)原理及相關(guān)內(nèi)容

2024-07-14 08:43:20
字體:
供稿:網(wǎng)友

MyBatis通用Mapper實現(xiàn)原理

本文會先介紹通用 Mapper 的簡單原理,然后使用最簡單的代碼來實現(xiàn)這個過程。

基本原理

通用 Mapper 提供了一些通用的方法,這些通用方法是以接口的形式提供的,例如。

public interface SelectMapper<T> {  /**   * 根據(jù)實體中的屬性值進行查詢,查詢條件使用等號   */  @SelectProvider(type = BaseSelectProvider.class, method = "dynamicSQL")  List<T> select(T record);}

接口和方法都使用了泛型,使用該通用方法的接口需要指定泛型的類型。通過 Java 反射可以很容易得到接口泛型的類型信息,代碼如下。

Type[] types = mapperClass.getGenericInterfaces();Class<?> entityClass = null;for (Type type : types) {  if (type instanceof ParameterizedType) {    ParameterizedType t = (ParameterizedType) type;    //判斷父接口是否為 SelectMapper.class    if (t.getRawType() == SelectMapper.class) {      //得到泛型類型      entityClass = (Class<?>) t.getActualTypeArguments()[0];      break;    }  }}

實體類中添加的 JPA 注解只是一種映射實體和數(shù)據(jù)庫表關(guān)系的手段,通過一些默認(rèn)規(guī)則或者自定義注解也很容易設(shè)置這種關(guān)系,獲取實體和表的對應(yīng)關(guān)系后,就可以根據(jù)通用接口方法定義的功能來生成和 XML 中一樣的 SQL 代碼。動態(tài)生成 XML 樣式代碼的方式有很多,最簡單的方式就是純 Java 代碼拼字符串,通用 Mapper 為了盡可能的少的依賴選擇了這種方式。如果使用模板(如FreeMarker,Velocity 和 beetl 等模板引擎)實現(xiàn),自由度會更高,也能方便開發(fā)人員調(diào)整。

在 MyBatis 中,每一個方法(注解或 XML 方式)經(jīng)過處理后,最終會構(gòu)造成 MappedStatement 實例,這個對象包含了方法id(namespace+id)、結(jié)果映射、緩存配置、SqlSource 等信息,和 SQL 關(guān)系最緊密的是其中的 SqlSource,MyBatis 最終執(zhí)行的 SQL 時就是通過這個接口的 getBoundSql 方法獲取的。

在 MyBatis 中,使用@SelectProvider 這種方式定義的方法,最終會構(gòu)造成 ProviderSqlSource,ProviderSqlSource 是一種處于中間的 SqlSource,它本身不能作為最終執(zhí)行時使用的 SqlSource,但是他會根據(jù)指定方法返回的 SQL 去構(gòu)造一個可用于最后執(zhí)行的 StaticSqlSource,StaticSqlSource的特點就是靜態(tài) SQL,支持在 SQL 中使用#{param} 方式的參數(shù),但是不支持 <if>,<where> 等標(biāo)簽。

為了能根據(jù)實體類動態(tài)生成支持動態(tài) SQL 的方法,通用 Mapper 從這里入手,利用ProviderSqlSource 可以生成正常的 MappedStatement,可以直接利用 MyBatis 各種配置和命名空間的特點(這是通用 Mapper 選擇這種方式的主要原因)。在生成 MappedStatement 后,“過河拆橋” 般的利用完就把 ProviderSqlSource 替換掉了,正常情況下,ProviderSqlSource 根本就沒有執(zhí)行的機會。在通用 Mapper 定義的實現(xiàn)方法中,提供了 MappedStatement 作為參數(shù),有了這個參數(shù),我們就可以根據(jù) ms 的 id(規(guī)范情況下是 接口名.方法名)得到接口,通過接口的泛型可以獲取實體類(entityClass),根據(jù)實體和表的關(guān)系我們可以拼出 XML 方式的動態(tài) SQL,一個簡單的方法如下。

/** * 查詢?nèi)拷Y(jié)果 * @param ms * @return */public String selectAll(MappedStatement ms) {  final Class<?> entityClass = getEntityClass(ms);  //修改返回值類型為實體類型  setResultType(ms, entityClass);  StringBuilder sql = new StringBuilder();  sql.append(SqlHelper.selectAllColumns(entityClass));  sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));  sql.append(SqlHelper.orderByDefault(entityClass));  return sql.toString();}

拼出的 XML 形式的動態(tài) SQL,使用 MyBatis 的 XMLLanguageDriver 中的 createSqlSource 方法可以生成 SqlSource。然后使用反射用新的 SqlSource 替換ProviderSqlSource 即可,如下代碼。

/** * 重新設(shè)置SqlSource * @param ms * @param sqlSource */protected void setSqlSource(MappedStatement ms, SqlSource sqlSource) {  MetaObject msObject = SystemMetaObject.forObject(ms);  msObject.setValue("sqlSource", sqlSource);}

MetaObject 是MyBatis 中很有用的工具類,MyBatis 的結(jié)果映射就是靠這種方式實現(xiàn)的。反射信息使用的 DefaultReflectorFactory,這個類會緩存反射信息,因此 MyBatis 的結(jié)果映射的效率很高。

到這里核心的內(nèi)容都已經(jīng)說完了,雖然知道怎么去替換 SqlSource了,但是!什么時候去替換呢?

這一直都是一個難題,如果不大量重寫 MyBatis 的代碼很難萬無一失的完成這個任務(wù)。通用 Mapper 并沒有去大量重寫,主要是考慮到以后的升級,也因此在某些特殊情況下,通用 Mapper 的方法會在沒有被替換的情況下被調(diào)用,這個問題在將來的 MyBatis 3.5.x 版本中會以更友好的方式解決(目前的 ProviderSqlSource 已經(jīng)比以前能實現(xiàn)更多的東西,后面會講)。

針對不同的運行環(huán)境,需要用不同的方式去替換。當(dāng)使用純 MyBatis (沒有Spring)方式運行時,替換很簡單,因為會在系統(tǒng)中初始化 SqlSessionFactory,可以初始化的時候進行替換,這個時候也不會出現(xiàn)前面提到的問題。替換的方式也很簡單,通過 SqlSessionFactory 可以得到 SqlSession,然后就能得到 Configuration,通過 configuration.getMappedStatements() 就能得到所有的 MappedStatement,循環(huán)判斷其中的方法是否為通用接口提供的方法,如果是就按照前面的方式替換就可以了。

在使用 Spring 的情況下,以繼承的方式重寫了 MapperScannerConfigurer 和 MapperFactoryBean,在 Spring 調(diào)用 checkDaoConfig 的時候?qū)?SqlSource 進行替換。在使用 Spring Boot 時,提供的 mapper-starter 中,直接注入 List<SqlSessionFactory> sqlSessionFactoryList 進行替換。

下面我們按照這個思路,以最簡練的代碼,實現(xiàn)一個通用方法。

實現(xiàn)一個簡單的通用Mapper

1. 定義通用接口方法

public interface BaseMapper<T> {  @SelectProvider(type = SelectMethodProvider.class, method = "select")  List<T> select(T entity);}

這里定義了一個簡單的 select 方法,這個方法判斷參數(shù)中的屬性是否為空,不為空的字段會作為查詢條件進行查詢,下面是對應(yīng)的 Provider。

public class SelectMethodProvider {  public String select(Object params) {    return "什么都不是!";  }}

這里的 Provider 不會最終執(zhí)行,只是為了在初始化時可以生成對應(yīng)的 MappedStatement。

2. 替換 SqlSource

下面代碼為了簡單,都指定的 BaseMapper 接口,并且沒有特別的校驗。

public class SimpleMapperHelper {  public static final XMLLanguageDriver XML_LANGUAGE_DRIVER      = new XMLLanguageDriver();  /**   * 獲取泛型類型   */  public static Class getEntityClass(Class<?> mapperClass){    Type[] types = mapperClass.getGenericInterfaces();    Class<?> entityClass = null;    for (Type type : types) {      if (type instanceof ParameterizedType) {        ParameterizedType t = (ParameterizedType) type;        //判斷父接口是否為 BaseMapper.class        if (t.getRawType() == BaseMapper.class) {          //得到泛型類型          entityClass = (Class<?>) t.getActualTypeArguments()[0];          break;        }      }    }    return entityClass;  }  /**   * 替換 SqlSource   */  public static void changeMs(MappedStatement ms) throws Exception {    String msId = ms.getId();    //標(biāo)準(zhǔn)msId為 包名.接口名.方法名    int lastIndex = msId.lastIndexOf(".");    String methodName = msId.substring(lastIndex + 1);    String interfaceName = msId.substring(0, lastIndex);    Class<?> mapperClass = Class.forName(interfaceName);    //判斷是否繼承了通用接口    if(BaseMapper.class.isAssignableFrom(mapperClass)){      //判斷當(dāng)前方法是否為通用 select 方法      if (methodName.equals("select")) {        Class entityClass = getEntityClass(mapperClass);        //必須使用<script>標(biāo)簽包裹代碼        StringBuffer sqlBuilder = new StringBuffer("<script>");        //簡單使用類名作為包名        sqlBuilder.append("select * from ").append(entityClass.getSimpleName());        Field[] fields = entityClass.getDeclaredFields();        sqlBuilder.append(" <where> ");        for (Field field : fields) {          sqlBuilder.append("<if test=/"")              .append(field.getName()).append("!=null/">");          //字段名直接作為列名          sqlBuilder.append(" and ").append(field.getName())               .append(" = #{").append(field.getName()).append("}");          sqlBuilder.append("</if>");        }        sqlBuilder.append("</where>");        sqlBuilder.append("</script>");        //解析 sqlSource        SqlSource sqlSource = XML_LANGUAGE_DRIVER.createSqlSource(            ms.getConfiguration(), sqlBuilder.toString(), entityClass);        //替換        MetaObject msObject = SystemMetaObject.forObject(ms);        msObject.setValue("sqlSource", sqlSource);      }    }  }}

changeMs 方法簡單的從 msId 開始,獲取接口和實體信息,通過反射回去字段信息,使用 <if> 標(biāo)簽動態(tài)判斷屬性值,這里的寫法和 XML 中一樣,使用 XMLLanguageDriver 處理時需要在外面包上 <script> 標(biāo)簽。生成 SqlSource 后,通過反射替換了原值。

3. 測試

針對上面代碼,提供一個 country 表和對應(yīng)的各種類。

實體類。

public class Country { private Long  id; private String countryname; private String countrycode; //省略 getter,setter}

Mapper 接口。

public interface CountryMapper extends BaseMapper<Country> {}

啟動 MyBatis 的公共類。

public class SqlSessionHelper {  private static SqlSessionFactory sqlSessionFactory;  static {    try {      Reader reader = Resources.getResourceAsReader("mybatis-config.xml");      sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);      reader.close();      //創(chuàng)建數(shù)據(jù)庫      SqlSession session = null;      try {        session = sqlSessionFactory.openSession();        Connection conn = session.getConnection();        reader = Resources.getResourceAsReader("hsqldb.sql");        ScriptRunner runner = new ScriptRunner(conn);        runner.setLogWriter(null);        runner.runScript(reader);        reader.close();      } finally {        if (session != null) {          session.close();        }      }    } catch (IOException ignore) {      ignore.printStackTrace();    }  }  public static SqlSession getSqlSession() {    return sqlSessionFactory.openSession();  }}

配置文件。

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration> <environments default="development">  <environment id="development">   <transactionManager type="JDBC">    <property name="" value=""/>   </transactionManager>   <dataSource type="UNPOOLED">    <property name="driver" value="org.hsqldb.jdbcDriver"/>    <property name="url" value="jdbc:hsqldb:mem:basetest"/>    <property name="username" value="sa"/>   </dataSource>  </environment> </environments> <mappers>  <package name="tk.mybatis.simple.mapper"/> </mappers></configuration>

初始化sql。

drop table country if exists;create table country ( id integer, countryname varchar(32), countrycode varchar(2));insert into country (id, countryname, countrycode) values(1,'Angola','AO');insert into country (id, countryname, countrycode) values(23,'Botswana','BW');-- 省略部分insert into country (id, countryname, countrycode) values(34,'Chile','CL');insert into country (id, countryname, countrycode) values(35,'China','CN');insert into country (id, countryname, countrycode) values(36,'Colombia','CO');

測試代碼。

public class SimpleTest {  public static void main(String[] args) throws Exception {    SqlSession sqlSession = SqlSessionHelper.getSqlSession();    Configuration configuration = sqlSession.getConfiguration();    HashSet<MappedStatement> mappedStatements        = new HashSet<MappedStatement>(configuration.getMappedStatements());    //如果注釋下面替換步驟就會出錯    for (MappedStatement ms : mappedStatements) {      SimpleMapperHelper.changeMs(ms);    }    //替換后執(zhí)行該方法    CountryMapper mapper = sqlSession.getMapper(CountryMapper.class);    Country query = new Country();    //可以修改條件或者注釋條件查詢?nèi)?   query.setCountrycode("CN");    List<Country> countryList = mapper.select(query);    for (Country country : countryList) {      System.out.printf("%s - %s/n",          country.getCountryname(),          country.getCountrycode());    }    sqlSession.close();  }}

通過簡化版的處理過程應(yīng)該可以和前面的內(nèi)容聯(lián)系起來,從而理解通用 Mapper 的簡單處理過程。

最新的 ProviderSqlSource

早期的 ProviderSqlSource 有個缺點就是定義的方法要么沒有參數(shù),要么只能是 Object parameterObject 參數(shù),這個參數(shù)最終的形式在開發(fā)時也不容易一次寫對,因為不同形式的接口的參數(shù)會被 MyBatis 處理成不同的形式,可以參考深入了解MyBatis參數(shù)。由于沒有提供接口和類型相關(guān)的參數(shù),因此無法根據(jù)類型實現(xiàn)通用的方法。

在最新的 3.4.5 版本中,ProviderSqlSource 增加了一個額外可選的 ProviderContext 參數(shù),這個類如下。

/** * The context object for sql provider method. * @author Kazuki Shimizu * @since 3.4.5 */public final class ProviderContext { private final Class<?> mapperType; private final Method mapperMethod; /**  * Constructor.  * @param mapperType A mapper interface type that specified provider  * @param mapperMethod A mapper method that specified provider  */ ProviderContext(Class<?> mapperType, Method mapperMethod) {  this.mapperType = mapperType;  this.mapperMethod = mapperMethod; } /**  * Get a mapper interface type that specified provider.  * @return A mapper interface type that specified provider  */ public Class<?> getMapperType() {  return mapperType; } /**  * Get a mapper method that specified provider.  * @return A mapper method that specified provider  */ public Method getMapperMethod() {  return mapperMethod; }}

有了這個參數(shù)后,就能獲取到接口和當(dāng)前執(zhí)行的方法信息,因此我們已經(jīng)可以實現(xiàn)通用方法了。

下面是一個官方測試中的簡單例子,定義的通用接口如下。

public interface BaseMapper<T> { @SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByIdProviderContextOnly") @ContainsLogicalDelete T selectById(Integer id); @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface ContainsLogicalDelete {  boolean value() default false; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @interface Meta {  String tableName(); }}

接口定義了一個簡單的根據(jù) id 查詢的方法,定義了一個邏輯刪除的注解、還有一個表名的元注解。

下面是 方法的實現(xiàn)。

public String buildSelectByIdProviderContextOnly(ProviderContext context) { //獲取方法上的邏輯刪除注解 final boolean containsLogicalDelete = context.getMapperMethod().      getAnnotation(BaseMapper.ContainsLogicalDelete.class) != null; //獲取接口上的元注解(不是實體) final String tableName = context.getMapperType().      getAnnotation(BaseMapper.Meta.class).tableName(); return new SQL(){{  SELECT("*");  FROM(tableName);  WHERE("id = #{id}");  if (!containsLogicalDelete){   WHERE("logical_delete = ${Constants.LOGICAL_DELETE_OFF}");  } }}.toString();}

這里相比之前,可以獲取到更多的信息,SQL 也不只是固定表的查詢,可以根據(jù) @Meta 注解制定方法查詢的表名,和原來一樣的是,最終還是返回一個簡單的 SQL 字符串,仍然不支持動態(tài) SQL 的標(biāo)簽。

下面是實現(xiàn)的接口。

@BaseMapper.Meta(tableName = "users")public interface Mapper extends BaseMapper<User> {}

上面實現(xiàn)的方法中,注解從接口獲取的,因此這里也是在 Mapper 上配置的 Meta 接口。

按照前面通用 Mapper 中的介紹,在實現(xiàn)方法中是可以獲取 User 類型的,因此如果把注解定義在實體類上也是可行的。

現(xiàn)在看起來已經(jīng)很不錯了,但是還不支持動態(tài) SQL,還不能緩存根據(jù) SQL 生成的 SqlSource,因此每次執(zhí)行都需要執(zhí)行方法去生成 SqlSource,仍然還有改進的地方,為了解決這個問題,我提交了兩個 PR #1111,#1120,目前還在討論階段,真正實現(xiàn)可能要到 3.5.0 版本。

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對VeVb武林網(wǎng)的支持。


注:相關(guān)教程知識閱讀請移步到JAVA教程頻道。
發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
亚洲天堂成人av| 久久久免费看| 欧美一区视频| 亚洲日本va在线观看| 欧美日韩999| 自拍视频在线播放| 国产在线综合网| 国产精品久久久久精| 中文字幕在线播放第一页| 国产黄色一区| 国产精品嫩草99a| 又爽又大又黄a级毛片在线视频| 成人三级视频在线观看一区二区| 国语对白精品一区二区| 欧美另类69xxxx| 午夜不卡久久精品无码免费| 九九视频在线免费观看| 国产精品ⅴa在线观看h| 中文在线а√在线8| 综合天天久久| 国产成人久久精品77777最新版本| 国产欧美日韩综合精品一区二区三区| 国内在线高清免费视频| 亚洲mv大片欧洲mv大片精品| 97精品视频在线观看| 欧美国产激情18| 国语对白在线视频| 无人视频在线观看免费| 鲁大师影院一区二区三区| 亚洲成人自拍网| 日韩久久一区| 无码中文字幕色专区| 91精品国产高清自在线| 日韩久久精品成人| 色婷婷精品久久二区二区蜜臂av| 98在线视频| 亚洲成国产人片在线观看| 五月天婷婷久久| 欧美激情免费在线| 北条麻妃一区二区三区在线观看| 欧美激情黄色片| 日韩激情一区二区三区| 欧洲一级在线观看| 亚洲va码欧洲m码| 欧美性一二三区| 最近2018中文字幕免费在线视频| 欧美videos大乳护士334| 国产电影一区二区三区| 免费在线一级视频| 激情欧美亚洲| 亚洲午夜国产成人av电影男同| 91精品国产高清一区二区三蜜臀| 69xxxx国产| jizz免费一区二区三区| 日本国产亚洲| 亚洲一级毛片| 中文字幕一区二区三区电影| 午夜视频在线| 99精品欧美一区二区三区小说| 欧美sm一区| 91精品视频免费在线观看| 日韩视频免费在线观看| 国产99精品国产| 日韩一区二区电影网| 人妻丰满熟妇aⅴ无码| 国产福利在线免费| 久久久综合久久| 久久国产欧美日韩精品| 亚洲婷婷综合久久一本伊一区| 蜜桃导航-精品导航| 国产精品99999| 欧美性生交大片免费| 亚洲一区二区中文| 久久久无码中文字幕久...| 中文字幕在线中文字幕二区| 一区二区在线观看视频在线观看| 欧美国产高跟鞋裸体秀xxxhd| 精品国产欧美日韩不卡在线观看| 国语对白在线播放| 蜜桃av鲁一鲁一鲁一鲁俄罗斯的| 午夜亚洲影视| 亚洲AV无码精品自拍| 欧美日韩xx| 男人操女人的视频在线观看欧美| 一级久久久久久| 欧美巨乳在线| 懂色av一区二区三区蜜臀| 在线中文字幕日韩| 天堂在线中文网官网| 国产高潮国产高潮久久久91| 成人黄色毛片| 日本欧美在线看| 在线观看色网站| 999精品视频在线观看播放| 日本中文字幕一区二区有限公司| 国产成人精品一区二区| 欧美电影免费播放| 阿v免费在线观看| 538国产视频| 超碰超碰在线观看| 中文字幕人成乱码在线观看| 国产精品尤物视频| 粉嫩av一区二区三区在线播放| 国产精品高潮呻吟久久av野狼| 裸体丰满少妇做受久久99精品| 久久这里只有精品一区二区| 日韩理论片av| 成人台湾亚洲精品一区二区| 欧美性猛交xxxx乱大交极品| 1024精品一区二区三区| 国产美女91呻吟求| 粗暴蹂躏中文一区二区三区| 男的操女的网站| 激情成人午夜视频| 影音先锋中文字幕在线播放| 奇门遁甲1982国语版免费观看高清| 男人的天堂亚洲在线| 99精品视频在线播放观看| 麻豆国产在线视频| 国产精品视频一区二区三区四区五区| 欧美亚男人的天堂| 亚洲综合中文字幕68页| 午夜免费福利视频| 毛片av免费观看| 青青在线免费观看视频| 一本一道波多野结衣一区二区| 欧洲美熟女乱又伦| 一级毛片久久久| 亚洲.欧美.日本.国产综合在线| 超碰在线资源站| 丝袜亚洲另类欧美综合| 国产98色在线|日韩| 欧美日韩不卡在线| 91老司机福利在线| 天天操天天爽天天射| 狠狠狠狠狠狠操| 国产成人精彩在线视频九色| 日韩pacopacomama| 五月香视频在线观看| 国产精品女人久久久久久| 国产 日韩 欧美 综合| 国产麻豆剧传媒精品国产| 日本在线三级| 黄色精品视频网站| 欧美日韩国产一级二级| 日韩电影网址| 91免费高清视频| 欧美男男青年gay1069videost| 成人av电影天堂| 国产免费黄色小视频| 国产精品久久亚洲不卡| 亚洲香蕉伊综合在人在线视看| 美腿丝袜亚洲三区| 亚洲性xxxx| 日本黄色片一级片| 亚洲最大在线| 精品久久亚洲一级α| 日韩一区二区电影网| 欧美精品久久| 男男受被啪到高潮自述| 激情视频综合网| 97在线观看视频免费| 中文字幕2020第一页| 亚洲天堂网视频| 91在线免费观看网站| 国产成人久久久精品一区| 欧美黄色一级视频| 欧美激情一区二区三区四区| 欧美亚日韩国产aⅴ精品中极品| 欧美激情一区二区久久久| 日韩三级影院| va视频在线观看| 国产一区二区三区的电影| 亚洲乱码国产一区三区| 国产不卡网站| 日韩av免费在线观看| xfplay资源站夜色先锋| 古典武侠综合av第一页| 国产美女在线看| 三年中国中文观看免费播放| 日本三级电影网| 91精品久久久久久久蜜月| 日韩精品第一区| 国产精品伦理一区二区三区| 毛葺葺老太做受视频| 麻豆精品蜜桃一区二区三区| 中文乱码免费一区二区| 中文字幕在线播放日韩| 不卡的av电影| 日本成人中文字幕| 在线欧美日韩国产| 国产成人免费观看| 四虎影视4hu4虎成人| 欧洲亚洲一区二区三区四区五区| 精品国产91乱码一区二区三区四区| 国产精品视频白浆免费视频| 五月婷婷六月香| 草莓视频性福宝| 人善交vide欧美| 亚洲成人免费在线观看| 婷婷六月国产精品久久不卡| gai在线观看免费高清| 国产麻豆精品入口在线观看| 日韩欧美视频免费观看| 亚洲一区二区三区观看| 日本韩国在线不卡| 亚洲毛片视频| 免费黄视频网站| 日本一区二区黄色| 色佬视频在线观看| 亚洲一区久久久| 亚洲区成人777777精品| 欧美亚洲丝袜传媒另类| 99久久久成人国产精品| 亚洲成人在线视频播放| 国产精品2024| 亚洲视频一二三区| 99久久99热这里只有精品| 制服丝袜专区在线| 亚洲精品aⅴ| 精品国产91九色蝌蚪| 久久亚洲一区二区三区四区五区高| 国精产品一区一区三区四川| 日韩高清三区| 日韩av三级在线| 久久久噜噜噜久久人人看| 欧洲亚洲视频| 亚洲性猛交xxxxwww| 青青草在线免费观看| 亚洲精品久久久久久宅男| 国产一区二区三区在线观看免费| 中文字幕亚洲欧美在线不卡| 在线观看国产精品一区| 精品在线视频观看| 91免费黄视频| www免费在线观看| 国产浴室偷窥在线播放| 精品久久免费| 亚洲精品91天天久久人人| 欧美激情亚洲精品| 青青草原国产在线观看| 精品久久久久久久久久| 欧美日韩三级在线| 日本一区精品久久久久影院| 免费视频爱爱太爽了| 日韩深夜影院| 天堂8在线视频| 欧美亚洲日本在线| 强开小嫩苞一区二区三区视频| 奇米影视四色在观看线| 亚洲欧美区自拍先锋| 国产精品1区在线| 最新真实国产在线视频| 一区二区视频网站| 中文字幕日韩国产| 色婷婷精品久久二区二区蜜臀av| 成人黄色大片在线观看| 欧美最猛性xxxxx亚洲精品| 中文字幕无码精品亚洲资源网久久| 97在线观看免费| 亚洲国产成人午夜在线一区| 欧美交受高潮1| 欧美,日韩,国产在线| 少妇精品视频在线观看| 国产一区二区丝袜高跟鞋图片| 最近2018中文字幕免费在线视频| 蜜臀91精品国产高清在线观看| 国产成人精品视频在线观看| 国产精品2023| 97超级碰在线看视频免费在线看| 在线观看欧美激情| 亚洲免费一在线| 日本视频免费高清一本18| 精品一区二区三区蜜桃| 天天操天天射天天插| 久久精品一偷一偷国产| 日韩成人短视频| 黄色av网站免费| 久久综合毛片| 欧美日韩性视频| 中文字幕 欧美 日韩| 国产精品成人免费在线| 精品丝袜一区二区三区| 成人欧美一区| 日韩一区在线视频| 国产精品人人爽| 中文在线一区二区| 亚洲欧美日韩国产综合| 国产美女视频一区二区| 手机看片久久久| 亚洲高清免费观看高清完整版| 蜜桃视频免费观看一区| 亚洲精品影院| 国产一区二区播放| 一色屋色费精品视频在线观看| 91高清免费观看| 一区二区三区四区五区精品| 精品一区二区男人吃奶| 亚洲综合123| 最新成人av网站| 伊人网中文字幕| 秘密影院久久综合亚洲综合| 国产欧美另类| 久久一级大片| 色婷婷免费视频| 污污美女网站| 亚洲sss视频| 97人摸人人澡人人人超一碰| 欧美自拍资源在线| 黄色成人羞羞视频| 国产精品美女久久久久久2018| 国产又粗又大又长| √资源天堂中文在线| 小视频在线播放| 久久久久久久久97黄色工厂| 久久久久香蕉视频| 国产91一区| 成人软件在线观看| www免费网站在线观看| 欧美黄网站在线观看| 91av久久| 亚洲播播91| 亚洲免费视频一区二区| 91蝌蚪|人| 黑人巨大国产9丨视频| 任你操视频在线观看| 九九热r在线视频精品| 午夜精品免费在线|