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

手把手教你開發 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
  }

}

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

  • intercept:這個就是具體的攔截方法,我們自定義 MyBatis 插件時,一般都需要重寫該方法,我們插件所完成的工作也都是在該方法中完成的。
  • plugin:這個方法的參數 target 就是攔截器要攔截的對象,一般來說我們不需要重寫該方法。Plugin.wrap 方法會自動判斷攔截器的簽名和被攔截對象的接口是否匹配,如果匹配,才會通過動態代理攔截目標對象。
  • 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();
    }
}

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

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

在前面的代碼中,我們一共在兩個地方重新組織了 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-04-02 08:58:13

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

2011-05-03 15:59:00

黑盒打印機

2011-01-10 14:41:26

2025-05-07 00:31:30

2021-07-14 09:00:00

JavaFX開發應用

2011-03-28 16:14:38

jQuery

2011-02-22 13:46:27

微軟SQL.NET

2021-12-28 08:38:26

Linux 中斷喚醒系統Linux 系統

2023-04-26 12:46:43

DockerSpringKubernetes

2022-03-14 14:47:21

HarmonyOS操作系統鴻蒙

2022-12-07 08:42:35

2022-07-27 08:16:22

搜索引擎Lucene

2022-01-08 20:04:20

攔截系統調用

2021-11-24 16:02:57

鴻蒙HarmonyOS應用

2009-06-02 15:38:36

eclipse streclipse開發steclipse str

2020-04-14 10:20:12

MySQL數據庫死鎖
點贊
收藏

51CTO技術棧公眾號

刘亦菲毛片一区二区三区| 在哪里可以看毛片| а√在线天堂官网| 久久久国产精品午夜一区ai换脸| 国产精品福利久久久| 日韩一区二区三区四区视频| 日韩免费一级| 91久久精品一区二区| 最新欧美日韩亚洲| 图片区 小说区 区 亚洲五月| 亚洲一区二区免费看| 上原亚衣av一区二区三区| 任你躁av一区二区三区| 素人一区二区三区| 一区二区三区四区高清精品免费观看 | 国产污片在线观看| 欧美日中文字幕| 精品久久99ma| 黄色永久免费网站| 日本不卡1234视频| 一区二区三区.www| 亚洲国产欧美日韩| 头脑特工队2免费完整版在线观看| 久久精品72免费观看| 91国内免费在线视频| 我要看黄色一级片| 国产日产一区| 日韩精品在线观看网站| 男插女视频网站| 欧美激情不卡| 在线观看av不卡| 国产视频一视频二| 久久一卡二卡| 亚洲精品中文字幕乱码三区| 一本一本久久a久久精品综合妖精| 日本午夜在线| 波多野结衣亚洲一区| 444亚洲人体| 一区二区三区免费观看视频| 丝袜美腿亚洲色图| 欧美最猛性xxxxx免费| 亚洲国产精一区二区三区性色| 羞羞答答成人影院www| 一区二区在线视频| 97在线观看免费视频| 美女亚洲一区| 亚洲色图综合网| 国产精品1000部啪视频| 日韩av不卡一区| 亚洲精品美女在线| 国产精品久久不卡| 欧洲亚洲视频| 日韩美女av在线| 久久亚洲AV成人无码国产野外| 日韩高清影视在线观看| 日韩大片在线观看视频| 人人妻人人澡人人爽人人精品| 国产伦精品一区二区三区免费优势 | 久久激情视频免费观看| 黑人狂躁日本娇小| 五月久久久综合一区二区小说| 最近2019年中文视频免费在线观看| 中文字幕免费在线看线人动作大片| 国产成人精品三级高清久久91| 国产丝袜一区视频在线观看| 在线观看日本中文字幕| 精品国产精品国产偷麻豆| 中文字幕精品一区二区精品| 天天操天天摸天天舔| 天天影视欧美综合在线观看| 久久不射电影网| 久久久久久久久艹| 日韩视频不卡| 国产精品三级久久久久久电影| 在线观看免费观看在线| 国产一区二区三区在线观看免费 | 亚洲最大成人网色| 肥臀熟女一区二区三区| 99久久精品国产导航| 麻豆91av| 男人的天堂在线视频免费观看| 亚洲天堂成人在线观看| 欧美亚洲黄色片| 亚洲女同av| 欧美剧情片在线观看| 亚洲精品一区二区18漫画| 日本成人中文| 精品国产欧美一区二区三区成人| 国产一级大片在线观看| 日韩精品一区第一页| 成人乱色短篇合集| 色综合免费视频| 欧美激情一区三区| 日本一本中文字幕| 亚洲成人va| 精品国产在天天线2019| 国产精品久久久久无码av色戒| 欧美黄色大片在线观看| 午夜精品久久久久久久99热浪潮| 欧美男人亚洲天堂| 国产激情一区二区三区桃花岛亚洲| 国模精品一区二区三区| 日韩免费网站| 欧美性xxxx极品高清hd直播| av噜噜在线观看| 免费看成人人体视频| 久久精品国产69国产精品亚洲 | 日韩在线一卡二卡| 亚洲最黄网站| 亚洲综合日韩在线| 第九色区av在线| 午夜国产精品一区| 欧美专区第二页| 精品久久久久久久久久久aⅴ| 欧美黄色www| 91精品国产乱码久久| 91蜜桃传媒精品久久久一区二区| 天天干天天色天天爽| 日韩一级二级| 亚洲免费人成在线视频观看| 久久久精品国产sm调教网站| 美女一区二区久久| 欧美中日韩一区二区三区| segui88久久综合9999| 91精品国产高清一区二区三区蜜臀 | 国产成人亚洲精品无码h在线| 亚洲国产精品免费视频| 最新国产成人av网站网址麻豆| 久久久久亚洲av成人毛片韩| 成年人午夜久久久| 欧美一区二区激情| 久久伊人影院| 久久中文字幕在线视频| 91禁在线观看| 最新国产精品久久精品| 亚洲视频第二页| 精品国产乱码| 国产精品电影在线观看| 欧美日韩国产中文字幕在线| 午夜视频在线观看一区二区| 少妇伦子伦精品无吗| 欧美精品aa| av一区二区三区免费| 国产在线观看a视频| 欧美精品乱码久久久久久| 国产aaaaaaaaa| 开心九九激情九九欧美日韩精美视频电影| 日本午夜精品一区二区三区| 国产免费不卡| 国产一区二区三区在线观看视频| 午夜精品免费观看| 国产欧美日韩三级| 四季av一区二区三区| 色婷婷亚洲mv天堂mv在影片| 国产欧美va欧美va香蕉在线 | 欧美极品在线播放| 成人毛片视频免费看| 亚洲一二三级电影| 中文字幕精品久久久| 亚洲一区国产一区| 日本三级中国三级99人妇网站| 欧美123区| 久久精品成人动漫| 动漫av一区二区三区| 精品国产91久久久久久| 亚洲国产精品成人综合久久久| 亚洲综合二区| 日韩视频专区| 激情不卡一区二区三区视频在线| 欧美高清第一页| 亚洲人成色777777精品音频| 色www精品视频在线观看| 成年人视频软件| 国产成人久久精品77777最新版本 国产成人鲁色资源国产91色综 | 天天操天天干天天爱| 色综合色综合色综合色综合色综合 | 亚洲成人最新网站| 国产精品大全| 亚洲精品一级二级| 久久夜色精品国产| 免费av网站在线播放| 色先锋资源久久综合| 日本 欧美 国产| gogo大胆日本视频一区| 91国产精品视频在线观看| 亚洲电影影音先锋| 久久精品ww人人做人人爽| 韩国理伦片久久电影网| 欧美大片免费观看在线观看网站推荐| 亚洲欧美日韩免费| 欧美人与禽zozo性伦| 久久9999久久免费精品国产| 国产夜色精品一区二区av| 午夜影院免费观看视频| 久久性色av| 996这里只有精品| 蜜乳av综合| 成人av免费电影| 2019年精品视频自拍| 久久久女女女女999久久| 成年人视频在线看| 亚洲激情小视频| 国产一区二区自拍视频| 欧美日韩国产中文字幕| 中文字幕电影av| 国产女同互慰高潮91漫画| 色哟哟无码精品一区二区三区| 日韩精品欧美精品| 日韩五码在线观看| 一区二区免费不卡在线| 色999日韩自偷自拍美女| 都市激情亚洲| 91中文字幕在线观看| 色豆豆成人网| 欧美亚洲国产成人精品| 丝袜在线视频| xvideos成人免费中文版| 青青青手机在线视频观看| 精品国产乱码久久久久久影片| 亚洲无码精品在线观看| 一本一道波多野结衣一区二区 | 欧美在线视频日韩| 欧美亚洲精品天堂| 亚洲大片精品永久免费| 久草综合在线视频| 亚洲视频狠狠干| www中文在线| 欧美国产一区二区| a级片在线观看| 久久一日本道色综合| 国产成人精品无码片区在线| 国产精品123| 国产成人av免费观看| 狠狠色丁香婷婷综合| 特黄视频免费观看| 六月丁香婷婷色狠狠久久| 北条麻妃视频在线| 媚黑女一区二区| 成年人观看网站| 亚洲综合精品| 国产精品-区区久久久狼| 制服诱惑一区二区| 日本福利视频在线| 国产视频一区三区| 激情深爱综合网| 国产精品久久久久久久免费软件 | 日韩精品有码在线观看| 日本高清视频www| 亚洲国产精品久久91精品| 好吊色在线观看| 日韩电视剧在线观看免费网站| 五月婷婷综合久久| 亚洲欧美另类人妖| 国产小视频在线观看| 在线视频亚洲欧美| 日本在线免费看| 九九热最新视频//这里只有精品 | 国产69久久| 丝袜美腿亚洲一区二区| 国产丝袜在线| 久久久久久久久久久人体 | 国产精品www994| 国产精品国产亚洲精品看不卡| 国产精品最新自拍| 亚洲人辣妹窥探嘘嘘| 精品一区二区三区在线观看| 久久精品一二三四| 成人黄页毛片网站| 美女脱光内衣内裤| 成人欧美一区二区三区在线播放| 午夜69成人做爰视频| 图片区小说区国产精品视频| 亚洲欧美综合另类| 欧美精品久久一区| 黄色av网址在线| 亚洲日韩中文字幕| av小次郎在线| 欧洲精品在线视频| 国产精品高清一区二区| 国产精品久久久久av福利动漫| 自拍偷拍欧美一区| 中日韩在线视频| 国产日韩欧美一区在线 | 给我免费播放片在线观看| 久久久久久婷| 免费成人黄色大片| 成年人午夜久久久| 久久成人小视频| 欧美日韩国产专区| 国产精品高潮呻吟av| 亚洲激情视频网| 国产最新在线| 国产ts一区二区| 综合激情五月婷婷| 午夜一区二区三视频在线观看| 欧美激情91| 亚洲福利精品视频| www.日韩精品| 成人在线观看小视频| 色婷婷精品久久二区二区蜜臀av | 日韩精品一级| 日本一区二区三区免费观看| 欧美午夜不卡| 色免费在线视频| 久久综合狠狠综合| 欧美日韩大片在线观看| 欧美色视频一区| 亚洲aaaaaaa| 国内精品久久久久久久| 91麻豆精品一二三区在线| 欧美日韩电影一区二区| 韩日欧美一区| 日韩欧美理论片| 国产精品久久久久久久久免费桃花 | 日韩三级电影网址| 91九色在线porn| 日韩av片电影专区| 久久99精品久久久久久欧洲站| 四虎永久免费网站| 美女网站色91| 亚洲欧美va天堂人熟伦| 欧美日韩中文在线| 深爱激情五月婷婷| 欧美大片免费观看在线观看网站推荐| 欧美一级做一级爱a做片性| 欧美中日韩免费视频| 性久久久久久| 捆绑凌虐一区二区三区| 亚洲一区电影777| 亚洲av综合色区无码一区爱av| 久久精品国产免费观看| 欧美亚洲综合视频| 五月天亚洲综合情| 日韩不卡一区二区三区 | 精品国产aⅴ| 国产一级不卡毛片| 国产视频一区不卡| 国产99免费视频| 国产小视频91| 成人高清一区| 中文字幕不卡每日更新1区2区| 免播放器亚洲一区| 成人黄色短视频| 欧美精品xxxxbbbb| 成人影院在线观看| 91成人伦理在线电影| 欧美区国产区| 极品白嫩的小少妇| 午夜欧美一区二区三区在线播放| 亚洲国产欧美另类| 午夜精品www| 免费观看久久av| 青青草av网站| 中文字幕一区在线观看视频| 国产又爽又黄免费软件| 欧美成人一区在线| 国产成人一二片| 91视频最新入口| 国产欧美精品一区aⅴ影院| 中文无码av一区二区三区| 久久精品99久久久久久久久 | 亚洲精品粉嫩美女一区| 亚洲国产高清国产精品| 激情久久五月天| 欧美另类视频在线观看| 日韩精品在线免费观看| 99只有精品| 九一免费在线观看| 99精品视频在线播放观看| 91在线视频免费播放| 久久久999成人| 91成人噜噜噜在线播放| 熟妇人妻va精品中文字幕| 国产精品久久久99| 亚洲精品喷潮一区二区三区| 2019中文在线观看| 国产精品成人a在线观看| 白丝校花扒腿让我c| 色综合天天综合狠狠| 日本亚洲精品| 国产有色视频色综合| 视频在线观看一区二区三区| 国产传媒免费在线观看| 亚洲国产一区自拍| 日韩城人网站| 激情深爱综合网| 亚洲视频图片小说| 男女视频在线观看免费| 91色精品视频在线| 久久精品在线| 久久在线视频精品| 在线观看欧美成人| 国产成人精品福利| 亚洲欧美日本一区二区| 精品国产乱码久久久久久天美 | 手机在线不卡av| 国产日韩在线视频| 亚洲欧美日韩国产一区| 欧美成人片在线观看| 国产亚洲免费的视频看|