精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

成年人看的免费视频| 男女私大尺度视频| 99热这里只有精品3| 欧美激情1区2区| 亚洲国产91色在线| 日韩av黄色网址| 国产私人尤物无码不卡| 精品在线播放午夜| 欧美激情视频在线免费观看 欧美视频免费一 | 欧美一级大片在线免费观看| 亚洲色成人网站www永久四虎 | a级影片在线| 99热在这里有精品免费| 国产精品久久久久久久久久久久久久| 性色av无码久久一区二区三区| 美女午夜精品| 538prom精品视频线放| 精品这里只有精品| 国产黄网站在线观看| www欧美成人18+| 91久久极品少妇xxxxⅹ软件 | 黄网站在线观| 国产精品天美传媒沈樵| 国产午夜精品一区| 国产精品欧美亚洲| 日韩影院在线观看| 97在线视频一区| 国产十六处破外女视频| 日本不卡二三区| 精品国产三级电影在线观看| 日本高清一区二区视频| 色香欲www7777综合网| 亚洲成人你懂的| 成年丰满熟妇午夜免费视频| 91社区在线| 久久新电视剧免费观看| 国产精品入口免费| 国产免费无遮挡| 免费精品视频在线| 国产99视频在线观看| 国产又色又爽又黄的| 亚洲午夜一级| 久热精品视频在线| 天堂а√在线中文在线鲁大师| 久久99精品久久久久久园产越南| 欧美精品一区二区久久婷婷 | 精品视频成人| 欧美精品精品一区| 日韩爱爱小视频| 懂色aⅴ精品一区二区三区| 欧美性xxxxx极品| 日韩少妇内射免费播放18禁裸乳| 免费在线观看av电影| 亚洲男人电影天堂| 亚洲区成人777777精品| 搞黄网站在线观看| 亚洲摸摸操操av| 日韩中文字幕亚洲精品欧美| 黄色网在线看| 一个色在线综合| 国产av熟女一区二区三区| 人交獸av完整版在线观看| 一区二区在线免费观看| 欧美性猛交内射兽交老熟妇| 欧美韩日亚洲| 亚洲成人福利片| 国产欧美高清在线| 亚洲电影有码| 91精品蜜臀在线一区尤物| 波多野结衣免费观看| 日韩一区网站| 亚洲国产一区二区三区在线观看| 成年人的黄色片| 亚洲精品亚洲人成在线观看| 亚洲免费精彩视频| 成人在线手机视频| 91tv官网精品成人亚洲| 色综合男人天堂| 亚洲黄色小说图片| 欧美96一区二区免费视频| 成人精品在线观看| 亚洲精品久久久久久动漫器材一区| 不卡一区二区在线| 欧美日韩一区二区视频在线观看 | 亚洲香蕉在线视频| 国产高清一区日本| 久久大片网站| 午夜在线视频| 亚洲一区二区欧美| 国模杨依粉嫩蝴蝶150p| 亚洲欧美在线人成swag| 精品国产青草久久久久福利| 国产一二三四五区| 亚洲国产一区二区三区在线播放| 国语自产精品视频在线看一大j8 | 蜜桃狠狠色伊人亚洲综合网站| 黄色片视频在线观看| 亚洲色图视频网| jizzjizzxxxx| 在线免费成人| 亚洲精品一区av在线播放| 自拍偷拍第9页| 国产日韩欧美一区二区三区在线观看| 国产精品久久久久影院日本 | 中文字幕在线播放网址| 一本大道av伊人久久综合| 中文字幕视频三区| 久久av综合| 欧美精品久久久久久久久久| 国产99久久久久久免费看| 懂色av一区二区在线播放| 日韩经典在线视频| 黄页网站在线观看免费| 777xxx欧美| 四虎永久免费在线观看| 亚洲性视频h| 成人欧美一区二区三区在线 | 久久男人的天堂| 中文字幕欧美人妻精品| 99久久精品国产麻豆演员表| 强伦女教师2:伦理在线观看| 国产精品迅雷| 亚洲大胆美女视频| 91狠狠综合久久久| 日韩电影在线一区二区三区| 精品91免费| 怡红院红怡院欧美aⅴ怡春院| 欧美丝袜第三区| 成人影视免费观看| 亚洲第一伊人| αv一区二区三区| 黄色网页在线看| 欧美日韩中文字幕一区二区| 性欧美丰满熟妇xxxx性仙踪林| 国产一区二区三区自拍| 亚洲一区二区三区乱码aⅴ蜜桃女| 国产视频第一页在线观看| 欧美日韩国内自拍| 99热超碰在线| 亚洲私人影院| 成人黄动漫网站免费| 在线中文字幕-区二区三区四区| 欧美日韩亚洲国产综合| 美国美女黄色片| 免费久久99精品国产| 小说区图片区图片区另类灬| 一区二区视频免费完整版观看| 国产婷婷成人久久av免费高清| 成人午夜视频精品一区| 99久久精品99国产精品| 欧美牲交a欧美牲交aⅴ免费下载| 日韩母乳在线| 日本成人精品在线| 黄色毛片在线观看| 在线精品视频免费播放| 欧美做受高潮6| 奇米影视一区二区三区| 亚洲二区三区四区| 亚洲国产精选| 久久综合国产精品台湾中文娱乐网| 亚洲中文字幕一区二区| 亚洲欧美日韩国产中文在线| 日本美女久久久| 伊人成年综合电影网| 好看的日韩精品| 成人欧美大片| 在线电影中文日韩| 一道本在线视频| 亚洲欧美韩国综合色| 国产xxx在线观看| 一本色道88久久加勒比精品| 蜜桃导航-精品导航| 成人看片在线观看| 久久精品人人爽| 欧美 日韩 中文字幕| 欧美日韩精品二区| 久久久久无码精品国产sm果冻 | 欧美激情第二页| 国产一区二区久久久| 激情黄产视频在线免费观看| 国产一区二区三区精品久久久| 一卡二卡三卡在线观看| 亚洲成人精品一区| 在线观看日本中文字幕| 国产一区二区三区四区五区美女| 久青草视频在线播放| 国产精品日韩精品中文字幕| 成人国产在线视频| 免费高潮视频95在线观看网站| 亚洲最新av在线网站| 国产欧美久久久| 亚洲综合色成人| 亚洲国产精品自拍视频| 蜜桃视频一区二区| 国产精品久久..4399| 久久国产亚洲精品| 国内精品视频在线播放| 欧美97人人模人人爽人人喊视频| 欧美精品激情在线| jizz在线观看中文| 亚洲精品福利在线观看| 国产精品主播一区二区| 色综合久久中文字幕综合网| 欧美爱爱小视频| 国产精品视频看| 黄色录像a级片| 国产精品资源站在线| 国产精品亚洲二区在线观看| 欧美日一区二区三区在线观看国产免| 日韩精品不卡| 人人精品亚洲| 91九色对白| 亚洲狼人在线| 国产精品扒开腿做| 国产精品xx| 欧美夫妻性生活xx| 欧美成人三区| 亚洲天堂第二页| 无码国产精品高潮久久99| 日韩欧美高清一区| 一二三区在线播放| 欧美综合在线视频| 国产精品久久久久久久久久久久久久久久久 | 每日更新成人在线视频| 免费看日b视频| 久久精品影视| 香蕉久久免费影视| 精品国产a一区二区三区v免费| 国产欧美丝袜| 91精品啪在线观看国产手机| 91亚洲精品在线| 久久精品97| 国产精品成人播放| 免费观看一级欧美片| 性欧美亚洲xxxx乳在线观看| 欧美人与动牲性行为| 欧美精品做受xxx性少妇| 欧美性videos| 久久精品亚洲精品| 在线日本中文字幕| 色播久久人人爽人人爽人人片视av| 无码国产精品一区二区色情男同 | 欧美日韩久久一区| 久久久久亚洲视频| 在线免费观看视频一区| 亚洲中文一区二区| 在线观看三级视频欧美| 午夜精品一区二| 色乱码一区二区三区88| 天天干天天操天天操| 日本高清不卡一区| 国产成人精品一区二区色戒| 欧美在线短视频| 中文字幕永久在线视频| 欧美绝品在线观看成人午夜影视| 中文天堂在线视频| 欧美日高清视频| 99在线观看免费| 欧美mv日韩mv国产网站app| 亚洲乱色熟女一区二区三区| 欧美成人在线直播| 少妇精品视频一区二区 | 触手亚洲一区二区三区| 国产亚洲精品美女| 日本精品一区二区三区在线播放| 日韩在线观看免费av| 国产精品刘玥久久一区| 欧美激情免费看| 竹内纱里奈兽皇系列在线观看| 国产成人aa精品一区在线播放 | 国产午夜久久| 亚洲中文字幕久久精品无码喷水| 美女在线视频一区| 久久久久亚洲av片无码v| 成人动漫在线一区| 国产精品高清无码在线观看| 国产精品传媒在线| 久久久久久久久久久97| 日韩欧美亚洲综合| 91久久精品无码一区二区| 日韩欧美视频一区| 欧美孕妇孕交xxⅹ孕妇交| 一区二区三区视频免费| 色呦呦久久久| 日本精品久久中文字幕佐佐木| 日本a人精品| 国内成+人亚洲| 日韩精品不卡一区二区| 日本熟妇人妻xxxx| 日韩av一区二区在线影视| 深夜福利网站在线观看| 久久综合色鬼综合色| 91社区视频在线观看| 亚洲一区二区视频在线观看| 波多野结衣电车痴汉| 欧美成人伊人久久综合网| 女人天堂在线| 欧美伦理91i| 欧美极品免费| 国产传媒一区| 日韩免费久久| 妺妺窝人体色www在线小说| 久久国产乱子精品免费女| 成熟妇人a片免费看网站| 国产精品伦理在线| 天天做天天爱夜夜爽| 宅男在线国产精品| 黄色在线小视频| 欧美日韩国产成人在线观看| 国产精品66| 日韩aⅴ视频一区二区三区| 黄色欧美日韩| 一级网站在线观看| 国产色一区二区| 日本在线观看中文字幕| 欧美一区二区三区免费视频| 狠狠色伊人亚洲综合网站l| 高清欧美一区二区三区| 国产视频网站一区二区三区| 先锋影音日韩| 久久久久久自在自线| 理论片大全免费理伦片| 一区二区在线观看免费视频播放| 中文字幕乱码视频| 亚洲午夜精品视频| 这里有精品可以观看| 国产欧美一区二区三区不卡高清| 午夜电影亚洲| 欧美污在线观看| 国产精品高潮呻吟久久| 欧美成人一区二区视频| 亚洲图中文字幕| 欧美成a人片在线观看久| 欧美一区国产一区| 亚洲免费中文| 无码人妻aⅴ一区二区三区| 午夜视黄欧洲亚洲| 亚洲第一成年人网站| 欧美另类暴力丝袜| 日本亚州欧洲精品不卡| 国产资源第一页| 国产精品18久久久久久久久| 欧美性猛交xxxxx少妇| 欧美一级一区二区| av毛片在线| 99国产在线| 欧美性久久久| 在线观看一区二区三区四区| 亚洲综合色婷婷| 国产精品爽爽久久久久久| www.欧美免费| 精品一区二区三区亚洲| 综合视频在线观看| 国产在线播精品第三| 99精品久久久久| 亚洲黄在线观看| 无码小电影在线观看网站免费| 久久99精品久久久久子伦 | 国产精品区在线| 亚洲天堂久久久久久久| 精品人妻一区二区三区浪潮在线 | 国产情侣在线视频| 日韩精品视频三区| 美女一区网站| 亚洲精品一区二区三区av| 精品无人区卡一卡二卡三乱码免费卡| 国产传媒免费在线观看| 精品区一区二区| 在线观看爽视频| 亚洲va韩国va欧美va精四季| 久久电影网电视剧免费观看| 波多野结衣爱爱视频| 欧美精品一区二区久久久| 吉吉日韩欧美| 一区二区在线观| 国产aⅴ精品一区二区三区色成熟| 国产精品111| 一本色道久久88精品综合| 日韩色性视频| 波多野结衣综合网| 中文字幕第一页久久| 午夜免费福利视频| 日韩av高清不卡| 91成人看片| 国产毛片毛片毛片毛片毛片毛片| 欧美日韩成人综合在线一区二区| 黄网站在线观| 一本久道久久综合狠狠爱亚洲精品| 国产剧情av麻豆香蕉精品| 亚洲第一在线播放| 久久久精品电影| 婷婷成人综合| 亚洲一二区在线观看| 欧美日韩另类字幕中文| 黄色网在线播放| 欧美成ee人免费视频| 国产精品2024| 波多野结衣在线观看一区| 欧美精品www| 日韩久久综合|