精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

手把手教你開發 MyBatis 分頁插件

開發 前端
今天主要和小伙伴們分享了我們如何自己開發一個 MyBatis 插件,插件功能其實都是次要的,最主要是希望小伙伴們能夠理解 MyBatis 的工作流程。

在日常開發中,小伙伴們多多少少都有用過 MyBatis 插件,松哥猜測大家用的最多的就是 MyBatis 的分頁插件!不知道小伙伴們有沒有想過有一天自己也來開發一個 MyBatis 插件?

其實自己動手擼一個 MyBatis 插件并不難,今天松哥就把手帶大家擼一個 MyBatis 插件!

1.MyBatis 插件接口

即使你沒開發過 MyBatis 插件,估計也能猜出來,MyBatis 插件是通過攔截器來起作用的,MyBatis 框架在設計的時候,就已經為插件的開發預留了相關接口,如下:

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP
  }

}

這個接口中就三個方法,第一個方法必須實現,后面兩個方法都是可選的。三個方法作用分別如下:

  1. intercept:這個就是具體的攔截方法,我們自定義 MyBatis 插件時,一般都需要重寫該方法,我們插件所完成的工作也都是在該方法中完成的。
  2. plugin:這個方法的參數 target 就是攔截器要攔截的對象,一般來說我們不需要重寫該方法。Plugin.wrap 方法會自動判斷攔截器的簽名和被攔截對象的接口是否匹配,如果匹配,才會通過動態代理攔截目標對象。
  3. setProperties:這個方法用來傳遞插件的參數,可以通過參數來改變插件的行為。我們定義好插件之后,需要對插件進行配置,在配置的時候,可以給插件設置相關屬性,設置的屬性可以通過該方法獲取到。插件屬性設置像下面這樣:
<plugins>
    <plugin interceptor="org.javaboy.mybatis03.plugin.CamelInterceptor">
        <property name="xxx" value="xxx"/>
    </plugin>
</plugins>

2.MyBatis 攔截器簽名

攔截器定義好了后,攔截誰?

這個就需要攔截器簽名來完成了!

攔截器簽名是一個名為 @Intercepts 的注解,該注解中可以通過 @Signature 配置多個簽名。@Signature 注解中則包含三個屬性:

  • type: 攔截器需要攔截的接口,有 4 個可選項,分別是:Executor、ParameterHandler、ResultSetHandler 以及 StatementHandler。
  • method: 攔截器所攔截接口中的方法名,也就是前面四個接口中的方法名,接口和方法要對應上。
  • args: 攔截器所攔截方法的參數類型,通過方法名和參數類型可以鎖定唯一一個方法。

一個簡單的簽名可能像下面這樣:

@Intercepts(@Signature(
        type = ResultSetHandler.class,
        method = "handleResultSets",
        args = {Statement.class}
))
public class CamelInterceptor implements Interceptor {
    //...
}

3.被攔截的對象

根據前面的介紹,被攔截的對象主要有如下四個:

Executor

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  int update(MappedStatement ms, Object parameter) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

  List<BatchResult> flushStatements() throws SQLException;

  void commit(boolean required) throws SQLException;

  void rollback(boolean required) throws SQLException;

  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  boolean isCached(MappedStatement ms, CacheKey key);

  void clearLocalCache();

  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

  Transaction getTransaction();

  void close(boolean forceRollback);

  boolean isClosed();

  void setExecutorWrapper(Executor executor);

}

各方法含義分別如下:

  • update:該方法會在所有的 INSERT、 UPDATE、 DELETE 執行時被調用,如果想要攔截這些操作,可以通過該方法實現。
  • query:該方法會在 SELECT 查詢方法執行時被調用,方法參數攜帶了很多有用的信息,如果需要獲取,可以通過該方法實現。
  • queryCursor:當 SELECT 的返回類型是 Cursor 時,該方法會被調用。
  • flushStatements:當 SqlSession 方法調用 flushStatements 方法或執行的接口方法中帶有 @Flush 注解時該方法會被觸發。
  • commit:當 SqlSession 方法調用 commit 方法時該方法會被觸發。
  • rollback:當 SqlSession 方法調用 rollback 方法時該方法會被觸發。
  • getTransaction:當 SqlSession 方法獲取數據庫連接時該方法會被觸發。
  • close:該方法在懶加載獲取新的 Executor 后會被觸發。
  • isClosed:該方法在懶加載執行查詢前會被觸發。

ParameterHandler

public interface ParameterHandler {

  Object getParameterObject();

  void setParameters(PreparedStatement ps) throws SQLException;

}

各方法含義分別如下:

  • getParameterObject:在執行存儲過程處理出參的時候該方法會被觸發。
  • setParameters:設置 SQL 參數時該方法會被觸發。

ResultSetHandler

public interface ResultSetHandler {

  <E> List<E> handleResultSets(Statement stmt) throws SQLException;

  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

  void handleOutputParameters(CallableStatement cs) throws SQLException;

}

各方法含義分別如下:

  • handleResultSets:該方法會在所有的查詢方法中被觸發(除去返回值類型為 Cursor的查詢方法),一般來說,如果我們想對查詢結果進行二次處理,可以通過攔截該方法實現。
  • handleCursorResultSets:當查詢方法的返回值類型為 Cursor時,該方法會被觸發。
  • handleOutputParameters:使用存儲過程處理出參的時候該方法會被調用。

StatementHandler

public interface StatementHandler {

  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;

  void parameterize(Statement statement)
      throws SQLException;

  void batch(Statement statement)
      throws SQLException;

  int update(Statement statement)
      throws SQLException;

  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;

  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;

  BoundSql getBoundSql();

  ParameterHandler getParameterHandler();

}

各方法含義分別如下:

  • prepare:該方法在數據庫執行前被觸發。
  • parameterize:該方法在 prepare 方法之后執行,用來處理參數信息。
  • batch:如果 MyBatis 的全劇配置中配置了 defaultExecutorType=”BATCH”,執行數據操作時該方法會被調用。
  • update:更新操作時該方法會被觸發。
  • query:該方法在 SELECT 方法執行時會被觸發。
  • queryCursor:該方法在 SELECT 方法執行時,并且返回值為 Cursor 時會被觸發。

在開發一個具體的插件時,我們應當根據自己的需求來決定到底攔截哪個方法。

4.開發分頁插件

4.1 內存分頁

MyBatis 中提供了一個不太好用的內存分頁功能,就是一次性把所有數據都查詢出來,然后在內存中進行分頁處理,這種分頁方式效率很低,基本上沒啥用,但是如果我們想要自定義分頁插件,就需要對這種分頁方式有一個簡單了解。

內存分頁的使用方式如下,首先在 Mapper 中添加 RowBounds 參數,如下:

public interface UserMapper {
    List<User> getAllUsersByPage(RowBounds rowBounds);
}

然后在 XML 文件中定義相關 SQL:

<select id="getAllUsersByPage" resultType="org.javaboy.mybatis03.model.User">
    select * from user
</select>

可以看到,在 SQL 定義時,壓根不用管分頁的事情,MyBatis 會查詢到所有的數據,然后在內存中進行分頁處理。

Mapper 中方法的調用方式如下:

@Test
public void test3() {
    UserMapper userMapper = sqlSessionFactory.openSession().getMapper(UserMapper.class);
    RowBounds rowBounds = new RowBounds(1,2);
    List<User> list = userMapper.getAllUsersByPage(rowBounds);
    for (User user : list) {
        System.out.println("user = " + user);
    }
}

構建 RowBounds 時傳入兩個參數,分別是 offset 和 limit,對應分頁 SQL 中的兩個參數。也可以通過 RowBounds.DEFAULT 的方式構建一個 RowBounds 實例,這種方式構建出來的 RowBounds 實例,offset 為 0,limit 則為 Integer.MAX_VALUE,也就相當于不分頁。

這就是 MyBatis 中提供的一個很不實用的內存分頁功能。

了解了 MyBatis 自帶的內存分頁之后,接下來我們就可以來看看如何自定義分頁插件了。

4.2 自定義分頁插件

首先要聲明一下,這里松哥帶大家自定義 MyBatis 分頁插件,主要是想通過這個東西讓小伙伴們了解自定義 MyBatis 插件的一些條條框框,了解整個自定義插件的流程,分頁插件并不是我們的目的,自定義分頁插件只是為了讓大家的學習過程變得有趣一些而已。

接下來我們就來開啟自定義分頁插件之旅。

首先我們需要自定義一個 RowBounds,因為 MyBatis 原生的 RowBounds 是內存分頁,并且沒有辦法獲取到總記錄數(一般分頁查詢的時候我們還需要獲取到總記錄數),所以我們自定義 PageRowBounds,對原生的 RowBounds 功能進行增強,如下:

public class PageRowBounds extends RowBounds {
    private Long total;

    public PageRowBounds(int offset, int limit) {
        super(offset, limit);
    }

    public PageRowBounds() {
    }

    public Long getTotal() {
        return total;
    }

    public void setTotal(Long total) {
        this.total = total;
    }
}

可以看到,我們自定義的 PageRowBounds 中增加了 total 字段,用來保存查詢的總記錄數。

接下來我們自定義攔截器 PageInterceptor,如下:

@Intercepts(@Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
))
public class PageInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameterObject = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        if (rowBounds != RowBounds.DEFAULT) {
            Executor executor = (Executor) invocation.getTarget();
            BoundSql boundSql = ms.getBoundSql(parameterObject);
            Field additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
            additionalParametersField.setAccessible(true);
            Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
            if (rowBounds instanceof PageRowBounds) {
                MappedStatement countMs = newMappedStatement(ms, Long.class);
                CacheKey countKey = executor.createCacheKey(countMs, parameterObject, RowBounds.DEFAULT, boundSql);
                String countSql = "select count(*) from (" + boundSql.getSql() + ") temp";
                BoundSql countBoundSql = new BoundSql(ms.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject);
                Set<String> keySet = additionalParameters.keySet();
                for (String key : keySet) {
                    countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
                }
                List<Object> countQueryResult = executor.query(countMs, parameterObject, RowBounds.DEFAULT, (ResultHandler) args[3], countKey, countBoundSql);
                Long count = (Long) countQueryResult.get(0);
                ((PageRowBounds) rowBounds).setTotal(count);
            }
            CacheKey pageKey = executor.createCacheKey(ms, parameterObject, rowBounds, boundSql);
            pageKey.update("RowBounds");
            String pageSql = boundSql.getSql() + " limit " + rowBounds.getOffset() + "," + rowBounds.getLimit();
            BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameterObject);
            Set<String> keySet = additionalParameters.keySet();
            for (String key : keySet) {
                pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
            }
            List list = executor.query(ms, parameterObject, RowBounds.DEFAULT, (ResultHandler) args[3], pageKey, pageBoundSql);
            return list;
        }
        //不需要分頁,直接返回結果
        return invocation.proceed();
    }

    private MappedStatement newMappedStatement(MappedStatement ms, Class<Long> longClass) {
        MappedStatement.Builder builder = new MappedStatement.Builder(
                ms.getConfiguration(), ms.getId() + "_count", ms.getSqlSource(), ms.getSqlCommandType()
        );
        ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), ms.getId(), longClass, new ArrayList<>(0)).build();
        builder.resource(ms.getResource())
                .fetchSize(ms.getFetchSize())
                .statementType(ms.getStatementType())
                .timeout(ms.getTimeout())
                .parameterMap(ms.getParameterMap())
                .resultSetType(ms.getResultSetType())
                .cache(ms.getCache())
                .flushCacheRequired(ms.isFlushCacheRequired())
                .useCache(ms.isUseCache())
                .resultMaps(Arrays.asList(resultMap));
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        return builder.build();
    }
}

這是我們今天定義的核心代碼,涉及到的知識點松哥來給大家一個一個剖析。

  1. 首先通過 @Intercepts 注解配置攔截器簽名,從 @Signature 的定義中我們可以看到,攔截的是 Executor#query 方法,該方法有一個重載方法,通過 args 指定了方法參數,進而鎖定了重載方法(實際上該方法的另一個重載方法我們沒法攔截,那個是 MyBatis 內部調用的,這里不做討論)。
  2. 將查詢操作攔截下來之后,接下來我們的操作主要在 PageInterceptor#intercept 方法中完成,該方法的參數重包含了攔截對象的諸多信息。
  3. 通過 invocation.getArgs() 獲取攔截方法的參數,獲取到的是一個數組,正常來說這個數組的長度為 4。數組第一項是一個 MappedStatement,我們在 Mapper.xml 中定義的各種操作節點和 SQL,都被封裝成一個個的 MappedStatement 對象了;數組第二項就是所攔截方法的具體參數,也就是你在 Mapper 接口中定義的方法參數;數組的第三項是一個 RowBounds 對象,我們在 Mapper 接口中定義方法時不一定使用了 RowBounds 對象,如果我們沒有定義 RowBounds 對象,系統會給我們提供一個默認的 RowBounds.DEFAULT;數組第四項則是一個處理返回值的 ResultHandler。
  4. 接下來判斷上一步提取到的 rowBounds 對象是否不為 RowBounds.DEFAULT,如果為 RowBounds.DEFAULT,說明用戶不想分頁;如果不為 RowBounds.DEFAULT,則說明用戶想要分頁,如果用戶不想分頁,則直接執行最后的 return invocation.proceed();,讓方法繼續往下走就行了。
  5. 如果需要進行分頁,則先從 invocation 對象中取出執行器 Executor、BoundSql 以及通過反射拿出來 BoundSql 中保存的額外參數(如果我們使用了動態 SQL,可能會存在該參數)。BoundSql 中封裝了我們執行的 Sql 以及相關的參數。
  6. 接下來判斷 rowBounds 是否是 PageRowBounds 的實例,如果是,說明除了分頁查詢,還想要查詢總記錄數,如果不是,則說明 rowBounds 可能是 RowBounds 實例,此時只要分頁即可,不用查詢總記錄數。
  7. 如果需要查詢總記錄數,則首先調用 newMappedStatement 方法構造出一個新的 MappedStatement 對象出來,這個新的 MappedStatement 對象的返回值是 Long 類型的。然后分別創建查詢的 CacheKey、拼接查詢的 countSql,再根據 countSql 構建出 countBoundSql,再將額外參數添加進 countBoundSql 中。最后通過 executor.query 方法完成查詢操作,并將查詢結果賦值給 PageRowBounds 中的 total 屬性。
  8. 接下來進行分頁查詢,有了第七步的介紹之后,分頁查詢就很簡單了,這里就不細說了,唯一需要強調的是,當我們啟動了這個分頁插件之后,MyBatis 原生的 RowBounds 內存分頁會變成物理分頁,原因就在這里我們修改了查詢 SQL。
  9. 最后將查詢結果返回。

在前面的代碼中,我們一共在兩個地方重新組織了 SQL,一個是查詢總記錄數的時候,另一個則是分頁的時候,都是通過 boundSql.getSql() 獲取到 Mapper.xml 中的 SQL 然后進行改裝,有的小伙伴在 Mapper.xml 中寫 SQL 的時候不注意,結尾可能加上了 ;,這會導致分頁插件重新組裝的 SQL 運行出錯,這點需要注意。松哥在 GitHub 上看到的其他 MyBatis 分頁插件也是一樣的,Mapper.xml 中 SQL 結尾不能有 ;

如此之后,我們的分頁插件就算是定義成功了。

5.測試

接下來我們對我們的分頁插件進行一個簡單測試。

首先我們需要在全局配置中配置分頁插件,配置方式如下:

<plugins>
    <plugin interceptor="org.javaboy.mybatis03.plugin.PageInterceptor"></plugin>
</plugins>

接下來我們在 Mapper 中定義查詢接口:

public interface UserMapper {
    List<User> getAllUsersByPage(RowBounds rowBounds);
}

接下來定義 UserMapper.xml,如下:

<select id="getAllUsersByPage" resultType="org.javaboy.mybatis03.model.User">
    select * from user
</select>

最后我們進行測試:

@Test
public void test3() {
    UserMapper userMapper = sqlSessionFactory.openSession().getMapper(UserMapper.class);
    List<User> list = userMapper.getAllUsersByPage(new RowBounds(1,2));
    for (User user : list) {
        System.out.println("user = " + user);
    }
}

這里在查詢時,我們使用了 RowBounds 對象,就只會進行分頁,而不會統計總記錄數。需要注意的時,此時的分頁已經不是內存分頁,而是物理分頁了,這點我們從打印出來的 SQL 中也能看到,如下:

可以看到,查詢的時候就已經進行了分頁了。

當然,我們也可以使用 PageRowBounds 進行測試,如下:

@Test
public void test4() {
    UserMapper userMapper = sqlSessionFactory.openSession().getMapper(UserMapper.class);
    PageRowBounds pageRowBounds = new PageRowBounds(1, 2);
    List<User> list = userMapper.getAllUsersByPage(pageRowBounds);
    for (User user : list) {
        System.out.println("user = " + user);
    }
    System.out.println("pageRowBounds.getTotal() = " + pageRowBounds.getTotal());
}

此時通過 pageRowBounds.getTotal() 方法我們就可以獲取到總記錄數。

6.小結

好啦,今天主要和小伙伴們分享了我們如何自己開發一個 MyBatis 插件,插件功能其實都是次要的,最主要是希望小伙伴們能夠理解 MyBatis 的工作流程。


責任編輯:武曉燕 來源: 江南一點雨
相關推薦

2024-03-05 18:27:43

2021-02-26 11:54:38

MyBatis 插件接口

2024-03-18 18:07:38

VSCode插件文件

2011-04-28 15:09:15

jQueryjqPlot

2011-05-27 08:41:26

JavascriptFirefox

2021-07-14 09:00:00

JavaFX開發應用

2011-05-03 15:59:00

黑盒打印機

2011-01-10 14:41:26

2025-05-07 00:31:30

2011-03-28 16:14:38

jQuery

2023-04-26 12:46:43

DockerSpringKubernetes

2022-12-07 08:42:35

2022-03-14 14:47:21

HarmonyOS操作系統鴻蒙

2022-07-27 08:16:22

搜索引擎Lucene

2022-01-08 20:04:20

攔截系統調用

2011-02-22 13:46:27

微軟SQL.NET

2021-12-28 08:38:26

Linux 中斷喚醒系統Linux 系統

2009-06-02 15:38:36

eclipse streclipse開發steclipse str

2021-11-24 16:02:57

鴻蒙HarmonyOS應用

2010-09-16 14:08:13

無線雙網
點贊
收藏

51CTO技術棧公眾號

国产精品视频分类| 无码免费一区二区三区免费播放 | 高清一区二区三区日本久| 强迫凌虐淫辱の牝奴在线观看| 女海盗2成人h版中文字幕| 日本一区二区免费在线| 5g国产欧美日韩视频| 日韩精品一区三区| 成人同人动漫免费观看| 日韩欧美美女一区二区三区| 成人综合视频在线| 黄色网页网址在线免费| www.欧美日韩国产在线| 国产精品99久久久久久久久久久久| 人与动物性xxxx| 加勒比色综合久久久久久久久| 色一情一乱一乱一91av| www.69av| 午夜在线视频| 91麻豆免费看片| 亚洲精品女av网站| 欧美性猛交xxxx乱大交hd | 中文字幕少妇一区二区三区| 熟女人妻一区二区三区免费看| 欧美日韩电影免费看| 亚洲欧美日韩国产一区二区三区| 精品在线不卡| www.国产视频| 欧美aaa在线| 91国在线精品国内播放| 2025国产精品自拍| 成人羞羞网站| 亚洲女人被黑人巨大进入| 成人高清在线观看视频| 视频一区在线免费看| 婷婷久久综合九色综合绿巨人 | 91av免费看| 中国女人真人一级毛片| 亚洲一区二区三区高清| 欧美日产国产成人免费图片| 毛片视频免费播放| 国产91一区| 亚洲激情视频网| 人妻互换一二三区激情视频| 国产aⅴ精品一区二区四区| 欧美午夜精品一区| 青青草av网站| 户外露出一区二区三区| 欧美日韩色婷婷| 熟女少妇在线视频播放| 日本一级理论片在线大全| 国产精品国产三级国产aⅴ原创 | 一级全黄裸体片| 国产一区二区高清在线| 欧美美女网站色| 999精彩视频| 巨胸喷奶水www久久久| 一本大道久久精品懂色aⅴ| 国产中文字幕在线免费观看| 91美女精品| 同产精品九九九| koreanbj精品视频一区| 色戒汤唯在线| 日本精品一区二区三区高清| 免费在线观看的毛片| 日韩av电影资源网| 欧美日韩高清一区| 亚洲制服中文字幕| 日韩精品一区二区三区中文字幕| 欧美成人免费网站| 久久午夜夜伦鲁鲁片| 欧美日韩一区二区三区在线电影 | 幼a在线观看| 成人欧美一区二区三区1314| 久久久成人精品一区二区三区 | 精品国产av一区二区| 国产成人精品www牛牛影视| 国产精品国色综合久久| 日韩porn| 国产精品女人毛片| 99久久久无码国产精品性色戒| 97影院秋霞午夜在线观看| 樱桃视频在线观看一区| 人人妻人人做人人爽| 亚洲优女在线| 精品污污网站免费看| 亚洲精品乱码久久久久久动漫| 中文字幕久久精品一区二区| 亚洲精品久久视频| www.99热| 欧美日韩亚洲三区| 欧美在线观看日本一区| 伊人久久成人网| 国产91精品一区二区| 久久久水蜜桃| gogo在线高清视频| 日韩欧美国产网站| 色婷婷一区二区三区在线观看| 91精品啪在线观看国产爱臀| 亚洲精品一区中文| 婷婷伊人五月天| 在线亚洲观看| 91精品视频专区| 五月天激情开心网| 亚洲天堂久久久久久久| 国产无限制自拍| 欧美美女被草| 亚洲韩国日本中文字幕| 少妇高潮惨叫久久久久| 宅男噜噜噜66一区二区| 成人免费网站在线| 精品视频二区| 亚洲午夜久久久久久久久电影网 | 亚洲巨乳在线观看| 草美女在线观看| 欧美日韩国产美女| 国产特黄级aaaaa片免| 欧美福利专区| 国产三级精品网站| 蜜桃视频在线免费| 亚洲图片欧美色图| 亚洲一二三不卡| 不卡一区2区| 欧美有码在线观看| 亚洲欧美黄色片| 1000精品久久久久久久久| 亚洲精品乱码久久久久久自慰| 97久久亚洲| 久久国产精彩视频| 中文字幕一二三四| 国产亚洲一区二区在线观看| 加勒比成人在线| 我要色综合中文字幕| 色青青草原桃花久久综合| 日本三级网站在线观看| 国产成人综合自拍| 综合国产精品久久久| 深夜视频一区二区| 亚洲精品网址在线观看| 日本午夜精品理论片a级app发布| 国产最新精品免费| a级黄色片网站| 日韩亚洲国产免费| 日韩少妇与小伙激情| 波多野结衣不卡| 久久婷婷成人综合色| 国产91xxx| 欧美黑白配在线| 性色av香蕉一区二区| 丰满肉嫩西川结衣av| 亚洲精品国产一区二区精华液 | 懂色中文一区二区在线播放| 日本女人高潮视频| 国产美女亚洲精品7777| 久久久av网站| 99久久精品日本一区二区免费| 国产精品久久久久久久久晋中| 亚洲天堂网一区| 91影院成人| 91精品啪aⅴ在线观看国产| 欧美18hd| 日韩久久免费av| 国产大片aaa| 91麻豆精品在线观看| 波多野结衣乳巨码无在线| 卡通动漫国产精品| 日本高清不卡在线| 91网在线播放| 日韩欧美电影一区| 亚洲天堂av片| 国产欧美综合在线观看第十页| 国产免费人做人爱午夜视频| 国产一区日韩| 国产日韩欧美日韩| 91精品久久久久久粉嫩| 精品国产一区二区三区久久久蜜月 | 亚洲中文字幕在线一区| 中文字幕一区二区三中文字幕| 91视频福利网| 亚洲国产日韩在线| 日本一区二区三区在线视频 | 亚洲乱码视频| 欧美亚洲精品日韩| 国产精品国产三级在线观看| 久久久免费在线观看| 你懂的在线网址| 欧美老肥妇做.爰bbww| 日本a在线观看| 日本一区二区视频在线观看| 看看黄色一级片| 影院欧美亚洲| 亚洲精品国产精品国自产观看| 麻豆视频久久| 国产成人avxxxxx在线看| 久草免费在线| 亚洲欧美日韩网| 国产乱子伦精品无码码专区| 五月激情丁香一区二区三区| 中文字幕91视频| 久久综合久久99| 成人性生交视频免费观看| 亚洲三级观看| 91香蕉视频网址| 欧美黄色录像| 91在线免费观看网站| 小h片在线观看| 欧美精品在线第一页| 噜噜噜噜噜在线视频| 日韩免费电影网站| 中文字幕一区二区人妻痴汉电车| 亚洲国产cao| 日韩精品一区二区三区在线视频| 97超碰欧美中文字幕| 天天爽夜夜爽视频| 日韩高清不卡在线| 精品久久久久久久久久中文字幕| 国产精品国产一区| 日韩欧美一区二区三区四区五区| 凹凸av导航大全精品| 91日韩在线视频| 激情中国色综合| 日韩男女性生活视频| 理论片午夜视频在线观看| 久久色精品视频| av电影在线网| 亚洲网在线观看| 欧美偷拍视频| 亚洲精品电影在线| 亚洲国产精品欧美久久| 91精品国产一区二区人妖| 日本一本在线观看| 日韩欧美在线视频免费观看| 久久久久成人精品无码| 亚洲欧美日韩系列| 国产成人免费在线观看视频| 久久久久国产精品麻豆ai换脸| 日韩av手机在线播放| 成人精品视频一区| 国产一精品一aⅴ一免费| 国产一区二区不卡| www.久久com| 国产一区二区按摩在线观看| 自拍偷拍一区二区三区四区| 人人爽香蕉精品| 999精彩视频| 久久超级碰视频| 99国产精品久久久久久| 久久精品国产第一区二区三区| 男人操女人免费| 久久裸体视频| 爱情岛论坛成人| 日韩av一二三| 日韩在线一区视频| 精品综合免费视频观看| 亚洲欧美天堂在线| 国产精品影视天天线| www.桃色.com| 国产成人8x视频一区二区 | 亚洲资源在线播放| 91精品国产综合久久蜜臀| 国产精品久久久久久无人区| 欧美一区二区二区| 亚洲精品久久久久久久久久| 精品国产99国产精品| 视频福利在线| 国产一区二区三区欧美| 第九色区av在线| 另类美女黄大片| 大黄网站在线观看| 欧美一级黑人aaaaaaa做受| sese综合| 国产九九精品视频| 97久久综合精品久久久综合| 久久福利电影| 日韩成人精品一区| 国产午夜精品视频一区二区三区| 一区在线免费观看| 妞干网在线免费视频| 老鸭窝一区二区久久精品| www.成人黄色| www.久久久久久久久| 97在线观看免费视频| 中文字幕佐山爱一区二区免费| 久久国产精品波多野结衣av| 午夜精品福利一区二区三区蜜桃| 久久久国产免费| 日韩欧美电影一二三| 六十路在线观看| 美女久久久久久久久久久| h片在线观看下载| 日产精品99久久久久久| **精品中文字幕一区二区三区| 超碰97国产在线| 精品国产乱码久久久| 久久香蕉视频网站| 天堂久久久久va久久久久| 亚洲丝袜在线观看| 久久女同性恋中文字幕| 中文字幕亚洲欧美日韩| 亚洲国产精品久久久久婷婷884 | 欧美一级欧美三级| 久久天堂电影| 欧美国产日本在线| 日本黄色一区| 国模精品一区二区三区| 日韩在线观看| 黄www在线观看| 国产91丝袜在线播放| 日韩欧美在线视频播放| 欧美性xxxxxx| 亚洲精品免费在线观看视频| 中文字幕视频在线免费欧美日韩综合在线看 | 国产成a人亚洲精v品在线观看| 日本免费新一区视频| 国产黑丝在线观看| 亚洲人成7777| 亚洲一区二区视频在线播放| 亚洲国模精品私拍| av电影免费在线观看| 国产精品久久久久久久久男| 欧洲精品一区| 九色自拍视频在线观看| 国产专区欧美精品| 国产精品18在线| 欧美在线三级电影| 天天综合网在线| 欧美高清激情视频| 日韩欧美中文字幕一区二区三区 | av片在线免费观看| 日本伊人精品一区二区三区介绍| 亚洲一二av| 99热这里只有精品免费| 激情久久久久久久久久久久久久久久| 欧美大波大乳巨大乳| 欧美日韩在线视频观看| 隣の若妻さん波多野结衣| 另类色图亚洲色图| 少妇高潮一区二区三区99| 日韩福利影院| 日韩在线卡一卡二| av男人的天堂av| 在线看日韩精品电影| 黄色av网站在线| 国产精品av在线播放| 久久不见久久见国语| 热久久精品国产| 久久精品男人天堂av| 无码人妻aⅴ一区二区三区有奶水| 亚洲精品福利免费在线观看| 国产福利在线免费观看| 痴汉一区二区三区| 亚洲欧洲视频| 亚洲av无码成人精品国产| 香蕉久久一区二区不卡无毒影院| 欧美 日韩 国产 成人 在线| 欧美精品久久久久| 米奇精品关键词| 欧美色图另类小说| 久久久影视传媒| 欧美性受xxx黑人xyx性爽| 日韩在线视频观看| 精品视频一区二区三区在线观看| 欧美少妇一区二区三区| 丁香六月久久综合狠狠色| 亚洲一区欧美在线| 亚洲视频在线视频| 青青久久精品| 欧美一级中文字幕| 成人av电影免费在线播放| 久久亚洲天堂网| 一本色道久久88综合日韩精品| 激情亚洲小说| 福利在线一区二区| 久久―日本道色综合久久| 五月婷婷六月婷婷| 久久网福利资源网站| h视频久久久| 久久精品网站视频| 国产精品成人一区二区艾草| 亚洲第一成人av| 人人爽久久涩噜噜噜网站| 日韩精品看片| 国产精品99久久久精品无码| 午夜精品久久久久久久久久久| 久草在线青青草| 成人免费在线视频网站| 亚洲第一在线| 亚洲av毛片基地| 欧美成人女星排名| 麻豆精品蜜桃| 国产xxxx振车| 亚洲国产成人自拍| www.久久伊人| 国产精品国语对白| 国产精品chinese| 久久国产柳州莫菁门| 欧美r级电影在线观看| 国产成人精品一区二三区在线观看| 超碰在线免费观看97| 91网站视频在线观看|