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

手把手教你開發(fā) MyBatis 插件

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

[[383827]]

 小伙伴們元宵節(jié)快樂,記得吃元宵哦~

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

其實(shí)自己動(dòng)手?jǐn)]一個(gè) MyBatis 插件并不難,今天松哥就把手帶大家擼一個(gè) MyBatis 插件!

1.MyBatis 插件接口

即使你沒開發(fā)過 MyBatis 插件,估計(jì)也能猜出來,MyBatis 插件是通過攔截器來起作用的,MyBatis 框架在設(shè)計(jì)的時(shí)候,就已經(jīng)為插件的開發(fā)預(yù)留了相關(guān)接口,如下:

  1. public interface Interceptor { 
  2.  
  3.   Object intercept(Invocation invocation) throws Throwable; 
  4.  
  5.   default Object plugin(Object target) { 
  6.     return Plugin.wrap(target, this); 
  7.   } 
  8.  
  9.   default void setProperties(Properties properties) { 
  10.     // NOP 
  11.   } 
  12.  

這個(gè)接口中就三個(gè)方法,第一個(gè)方法必須實(shí)現(xiàn),后面兩個(gè)方法都是可選的。三個(gè)方法作用分別如下:

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

2.MyBatis 攔截器簽名

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

這個(gè)就需要攔截器簽名來完成了!

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

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

一個(gè)簡單的簽名可能像下面這樣:

  1. @Intercepts(@Signature( 
  2.         type = ResultSetHandler.class, 
  3.         method = "handleResultSets"
  4.         args = {Statement.class} 
  5. )) 
  6. public class CamelInterceptor implements Interceptor { 
  7.     //... 

3.被攔截的對(duì)象

根據(jù)前面的介紹,被攔截的對(duì)象主要有如下四個(gè):

Executor

  1. public interface Executor { 
  2.  
  3.   ResultHandler NO_RESULT_HANDLER = null
  4.  
  5.   int update(MappedStatement ms, Object parameter) throws SQLException; 
  6.  
  7.   <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException; 
  8.  
  9.   <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException; 
  10.  
  11.   <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException; 
  12.  
  13.   List<BatchResult> flushStatements() throws SQLException; 
  14.  
  15.   void commit(boolean required) throws SQLException; 
  16.  
  17.   void rollback(boolean required) throws SQLException; 
  18.  
  19.   CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql); 
  20.  
  21.   boolean isCached(MappedStatement ms, CacheKey key); 
  22.  
  23.   void clearLocalCache(); 
  24.  
  25.   void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType); 
  26.  
  27.   Transaction getTransaction(); 
  28.  
  29.   void close(boolean forceRollback); 
  30.  
  31.   boolean isClosed(); 
  32.  
  33.   void setExecutorWrapper(Executor executor); 
  34.  

各方法含義分別如下:

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

ParameterHandler

  1. public interface ParameterHandler { 
  2.  
  3.   Object getParameterObject(); 
  4.  
  5.   void setParameters(PreparedStatement ps) throws SQLException; 
  6.  

各方法含義分別如下:

  • getParameterObject:在執(zhí)行存儲(chǔ)過程處理出參的時(shí)候該方法會(huì)被觸發(fā)。
  • setParameters:設(shè)置 SQL 參數(shù)時(shí)該方法會(huì)被觸發(fā)。

ResultSetHandler

  1. public interface ResultSetHandler { 
  2.  
  3.   <E> List<E> handleResultSets(Statement stmt) throws SQLException; 
  4.  
  5.   <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException; 
  6.  
  7.   void handleOutputParameters(CallableStatement cs) throws SQLException; 
  8.  

各方法含義分別如下:

  • handleResultSets:該方法會(huì)在所有的查詢方法中被觸發(fā)(除去返回值類型為 Cursor的查詢方法),一般來說,如果我們想對(duì)查詢結(jié)果進(jìn)行二次處理,可以通過攔截該方法實(shí)現(xiàn)。
  • handleCursorResultSets:當(dāng)查詢方法的返回值類型為 Cursor時(shí),該方法會(huì)被觸發(fā)。
  • handleOutputParameters:使用存儲(chǔ)過程處理出參的時(shí)候該方法會(huì)被調(diào)用。

StatementHandler

  1. public interface StatementHandler { 
  2.  
  3.   Statement prepare(Connection connectionInteger transactionTimeout) 
  4.       throws SQLException; 
  5.  
  6.   void parameterize(Statement statement) 
  7.       throws SQLException; 
  8.  
  9.   void batch(Statement statement) 
  10.       throws SQLException; 
  11.  
  12.   int update(Statement statement) 
  13.       throws SQLException; 
  14.  
  15.   <E> List<E> query(Statement statement, ResultHandler resultHandler) 
  16.       throws SQLException; 
  17.  
  18.   <E> Cursor<E> queryCursor(Statement statement) 
  19.       throws SQLException; 
  20.  
  21.   BoundSql getBoundSql(); 
  22.  
  23.   ParameterHandler getParameterHandler(); 
  24.  

各方法含義分別如下:

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

在開發(fā)一個(gè)具體的插件時(shí),我們應(yīng)當(dāng)根據(jù)自己的需求來決定到底攔截哪個(gè)方法。

4.開發(fā)分頁插件

4.1 內(nèi)存分頁

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

內(nèi)存分頁的使用方式如下,首先在 Mapper 中添加 RowBounds 參數(shù),如下:

  1. public interface UserMapper { 
  2.     List<User> getAllUsersByPage(RowBounds rowBounds); 

然后在 XML 文件中定義相關(guān) SQL:

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

可以看到,在 SQL 定義時(shí),壓根不用管分頁的事情,MyBatis 會(huì)查詢到所有的數(shù)據(jù),然后在內(nèi)存中進(jìn)行分頁處理。

Mapper 中方法的調(diào)用方式如下:

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

構(gòu)建 RowBounds 時(shí)傳入兩個(gè)參數(shù),分別是 offset 和 limit,對(duì)應(yīng)分頁 SQL 中的兩個(gè)參數(shù)。也可以通過 RowBounds.DEFAULT 的方式構(gòu)建一個(gè) RowBounds 實(shí)例,這種方式構(gòu)建出來的 RowBounds 實(shí)例,offset 為 0,limit 則為 Integer.MAX_VALUE,也就相當(dāng)于不分頁。

這就是 MyBatis 中提供的一個(gè)很不實(shí)用的內(nèi)存分頁功能。

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

4.2 自定義分頁插件

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

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

首先我們需要自定義一個(gè) RowBounds,因?yàn)?MyBatis 原生的 RowBounds 是內(nèi)存分頁,并且沒有辦法獲取到總記錄數(shù)(一般分頁查詢的時(shí)候我們還需要獲取到總記錄數(shù)),所以我們自定義 PageRowBounds,對(duì)原生的 RowBounds 功能進(jìn)行增強(qiáng),如下:

  1. public class PageRowBounds extends RowBounds { 
  2.     private Long total; 
  3.  
  4.     public PageRowBounds(int offset, int limit) { 
  5.         super(offset, limit); 
  6.     } 
  7.  
  8.     public PageRowBounds() { 
  9.     } 
  10.  
  11.     public Long getTotal() { 
  12.         return total; 
  13.     } 
  14.  
  15.     public void setTotal(Long total) { 
  16.         this.total = total; 
  17.     } 

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

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

  1. @Intercepts(@Signature( 
  2.         type = Executor.class, 
  3.         method = "query"
  4.         args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} 
  5. )) 
  6. public class PageInterceptor implements Interceptor { 
  7.     @Override 
  8.     public Object intercept(Invocation invocation) throws Throwable { 
  9.         Object[] args = invocation.getArgs(); 
  10.         MappedStatement ms = (MappedStatement) args[0]; 
  11.         Object parameterObject = args[1]; 
  12.         RowBounds rowBounds = (RowBounds) args[2]; 
  13.         if (rowBounds != RowBounds.DEFAULT) { 
  14.             Executor executor = (Executor) invocation.getTarget(); 
  15.             BoundSql boundSql = ms.getBoundSql(parameterObject); 
  16.             Field additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters"); 
  17.             additionalParametersField.setAccessible(true); 
  18.             Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql); 
  19.             if (rowBounds instanceof PageRowBounds) { 
  20.                 MappedStatement countMs = newMappedStatement(ms, Long.class); 
  21.                 CacheKey countKey = executor.createCacheKey(countMs, parameterObject, RowBounds.DEFAULT, boundSql); 
  22.                 String countSql = "select count(*) from (" + boundSql.getSql() + ") temp"
  23.                 BoundSql countBoundSql = new BoundSql(ms.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject); 
  24.                 Set<String> keySet = additionalParameters.keySet(); 
  25.                 for (String key : keySet) { 
  26.                     countBoundSql.setAdditionalParameter(key, additionalParameters.get(key)); 
  27.                 } 
  28.                 List<Object> countQueryResult = executor.query(countMs, parameterObject, RowBounds.DEFAULT, (ResultHandler) args[3], countKey, countBoundSql); 
  29.                 Long count = (Long) countQueryResult.get(0); 
  30.                 ((PageRowBounds) rowBounds).setTotal(count); 
  31.             } 
  32.             CacheKey pageKey = executor.createCacheKey(ms, parameterObject, rowBounds, boundSql); 
  33.             pageKey.update("RowBounds"); 
  34.             String pageSql = boundSql.getSql() + " limit " + rowBounds.getOffset() + "," + rowBounds.getLimit(); 
  35.             BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameterObject); 
  36.             Set<String> keySet = additionalParameters.keySet(); 
  37.             for (String key : keySet) { 
  38.                 pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key)); 
  39.             } 
  40.             List list = executor.query(ms, parameterObject, RowBounds.DEFAULT, (ResultHandler) args[3], pageKey, pageBoundSql); 
  41.             return list; 
  42.         } 
  43.         //不需要分頁,直接返回結(jié)果 
  44.         return invocation.proceed(); 
  45.     } 
  46.  
  47.     private MappedStatement newMappedStatement(MappedStatement ms, Class<Long> longClass) { 
  48.         MappedStatement.Builder builder = new MappedStatement.Builder( 
  49.                 ms.getConfiguration(), ms.getId() + "_count", ms.getSqlSource(), ms.getSqlCommandType() 
  50.         ); 
  51.         ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), ms.getId(), longClass, new ArrayList<>(0)).build(); 
  52.         builder.resource(ms.getResource()) 
  53.                 .fetchSize(ms.getFetchSize()) 
  54.                 .statementType(ms.getStatementType()) 
  55.                 .timeout(ms.getTimeout()) 
  56.                 .parameterMap(ms.getParameterMap()) 
  57.                 .resultSetType(ms.getResultSetType()) 
  58.                 .cache(ms.getCache()) 
  59.                 .flushCacheRequired(ms.isFlushCacheRequired()) 
  60.                 .useCache(ms.isUseCache()) 
  61.                 .resultMaps(Arrays.asList(resultMap)); 
  62.         if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) { 
  63.             StringBuilder keyProperties = new StringBuilder(); 
  64.             for (String keyProperty : ms.getKeyProperties()) { 
  65.                 keyProperties.append(keyProperty).append(","); 
  66.             } 
  67.             keyProperties.delete(keyProperties.length() - 1, keyProperties.length()); 
  68.             builder.keyProperty(keyProperties.toString()); 
  69.         } 
  70.         return builder.build(); 
  71.     } 

這是我們今天定義的核心代碼,涉及到的知識(shí)點(diǎn)松哥來給大家一個(gè)一個(gè)剖析。

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

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

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

5.測試

接下來我們對(duì)我們的分頁插件進(jìn)行一個(gè)簡單測試。

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

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

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

  1. public interface UserMapper { 
  2.     List<User> getAllUsersByPage(RowBounds rowBounds); 

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

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

最后我們進(jìn)行測試:

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

這里在查詢時(shí),我們使用了 RowBounds 對(duì)象,就只會(huì)進(jìn)行分頁,而不會(huì)統(tǒng)計(jì)總記錄數(shù)。需要注意的時(shí),此時(shí)的分頁已經(jīng)不是內(nèi)存分頁,而是物理分頁了,這點(diǎn)我們從打印出來的 SQL 中也能看到,如下:

 

可以看到,查詢的時(shí)候就已經(jīng)進(jìn)行了分頁了。

當(dāng)然,我們也可以使用 PageRowBounds 進(jìn)行測試,如下:

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

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

6.小結(jié)

好啦,今天主要和小伙伴們分享了我們?nèi)绾巫约洪_發(fā)一個(gè) MyBatis 插件,插件功能其實(shí)都是次要的,最主要是希望小伙伴們能夠理解 MyBatis 的工作流程。

本文轉(zhuǎn)載自微信公眾號(hào)「江南一點(diǎn)雨」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系江南一點(diǎn)雨公眾號(hào)。

 

責(zé)任編輯:武曉燕 來源: 江南一點(diǎn)雨
相關(guān)推薦

2024-04-02 08:58:13

2024-03-05 18:27:43

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開發(fā)應(yīng)用

2011-05-03 15:59:00

黑盒打印機(jī)

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-01-08 20:04:20

攔截系統(tǒng)調(diào)用

2022-12-07 08:42:35

2022-03-14 14:47:21

HarmonyOS操作系統(tǒng)鴻蒙

2022-07-27 08:16:22

搜索引擎Lucene

2011-02-22 13:46:27

微軟SQL.NET

2021-12-28 08:38:26

Linux 中斷喚醒系統(tǒng)Linux 系統(tǒng)

2009-06-02 15:38:36

eclipse streclipse開發(fā)steclipse str

2021-11-24 16:02:57

鴻蒙HarmonyOS應(yīng)用

2010-09-16 14:08:13

無線雙網(wǎng)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

zjzjzjzjzj亚洲女人| 日韩高清国产精品| 国产污片在线观看| 一区二区三区自拍视频| 亚洲女同ⅹxx女同tv| 99r国产精品视频| 国产成人自拍视频在线| 欧美人妖在线| 欧美精选一区二区| 久久人妻无码一区二区| 欧美一区二区三区成人片在线| 91久久黄色| 在线播放日韩av| 国产裸体视频网站| 欧美裸体视频| 国产精品福利在线播放| 国产成人免费电影| 91视频在线视频| 日韩精品中文字幕第1页| 91精品国产91热久久久做人人| av无码久久久久久不卡网站| 色视频在线观看福利| 日本欧美在线观看| 欧美精品video| 国产精品情侣呻吟对白视频| 伊人精品综合| 欧美亚洲自拍偷拍| 性一交一乱一伧国产女士spa| 理论在线观看| 东方欧美亚洲色图在线| 国产精品96久久久久久又黄又硬| 国产黄色片在线免费观看| 日韩成人午夜| 日韩欧美一级精品久久| 538任你躁在线精品免费| 草草视频在线| 日韩理论片中文av| 欧美一级爱爱| 亚洲黄色在线播放| 久久99这里只有精品| 91av成人在线| 欧美日韩在线观看成人| 亚洲视频分类| 精品国产乱码久久久久久浪潮| 一区二区三区国产免费| 岛国在线视频网站| 亚洲精选视频免费看| 日韩中文字幕av在线| 西西人体44www大胆无码| 韩国av一区二区三区| 日韩美女激情视频| 草久久免费视频| 久久久久国产| 日韩在线观看你懂的| 亚洲精品在线视频免费观看 | 欧美丰满少妇xxxx| 中文字幕 日本| 一区二区三区欧洲区| 欧美人与z0zoxxxx视频| 成人精品视频一区二区| xxx欧美xxx| 黄网站色欧美视频| 久久视频这里有精品| 深夜国产在线播放| 国产精品久久久久久久久久久免费看 | 国产精品一区二区三区免费视频| 影音先锋在线国产| 一区二区久久| 97精品在线观看| 日韩乱码在线观看| 91久久视频| 欧美激情一区二区三区高清视频| 欧美交换国产一区内射| 欧美激情视频一区二区三区在线播放 | 又色又爽又黄无遮挡的免费视频| 久久久久综合| 国产精品成人一区二区三区吃奶 | 欧洲国产伦久久久久久久| 日韩一级免费在线观看| 桃色一区二区| 欧美性欧美巨大黑白大战| 日日摸日日碰夜夜爽无码| 电影在线观看一区| 日韩人体视频一二区| 国产男女无遮挡| 国产精品字幕| 欧美久久久久免费| 日韩欧美色视频| 国产精品毛片久久久| 日韩成人在线网站| 免费毛片视频网站| 波多野结衣在线观看一区二区三区 | 一区二区三区 欧美| 成人综合日日夜夜| 精品国产乱码久久久久久免费| 国产老熟女伦老熟妇露脸| 久久综合影院| 精品激情国产视频| 久久久久无码精品国产| 午夜在线精品偷拍| 国产啪精品视频网站| 国产av一区二区三区| 99久久精品免费| 视频一区不卡| 手机av免费在线| 一本色道久久综合精品竹菊| 国产一区视频在线看| 日av在线播放中文不卡| 国产精品污视频| 91色porny| 激情图片qvod| 欧美色片在线观看| 欧美精品一区二区三区在线播放| 精品欧美一区二区久久久| 国内揄拍国内精品久久| 国产精品美女久久久免费| 男人天堂网在线视频| 国产精品亲子伦对白| 国产亚洲欧美在线视频| 欧一区二区三区| 中文字幕日韩欧美| 少妇太紧太爽又黄又硬又爽| 国产成人亚洲精品青草天美| 亚洲v国产v在线观看| 欧美日韩国产观看视频| 欧美变态口味重另类| 美国黄色特级片| 媚黑女一区二区| 国产伦精品一区二区三区高清版| 国内外激情在线| 欧美日韩一级视频| 国产精品毛片一区二区| 中文久久精品| 国产青春久久久国产毛片| 麻豆网站在线观看| 欧美性生交片4| 人妻精品久久久久中文| 免费国产自线拍一欧美视频| 痴汉一区二区三区| 91精品久久| 欧美一激情一区二区三区| 美国美女黄色片| 日av在线不卡| 亚洲国产精品一区二区第四页av| 粉嫩一区二区| 亚洲色图综合网| 无码人妻一区二区三区免费| 26uuu久久天堂性欧美| 亚洲自偷自拍熟女另类| 自拍亚洲一区| 青草青草久热精品视频在线网站| 无码国产伦一区二区三区视频| 夜色激情一区二区| 欧美日韩一区二区区别是什么| 一级欧洲+日本+国产| 国产综合在线观看视频| 91精彩视频在线观看| 欧亚洲嫩模精品一区三区| 成人小视频免费看| 日本aⅴ精品一区二区三区| 亚洲欧美日韩精品在线| 91麻豆精品| 久久99亚洲精品| 特级丰满少妇一级aaaa爱毛片| 亚洲国产成人tv| 蜜臀av一区二区三区有限公司| 国产欧美短视频| 欧洲一区二区在线| 一区二区三区无毛| 欧美大胆在线视频| 俄罗斯嫩小性bbwbbw| 午夜精品久久久久久久久久 | 99久久综合99久久综合网站| 日日鲁鲁鲁夜夜爽爽狠狠视频97 | 亚洲最大网站| 亚洲人成电影网站色www| 欧美视频xxxx| 亚洲伦理在线精品| 天堂www中文在线资源| 午夜综合激情| 亚洲三级一区| 99久久香蕉| 日本一区二区不卡| 老司机精品视频在线观看6| 欧美一区二区三区公司| 国产精彩视频在线| 国产精品系列在线| 91人人澡人人爽| 午夜在线精品偷拍| 日韩第一页在线观看| 盗摄系列偷拍视频精品tp| 日本精品久久久久久久| 黄色一级片在线观看| 亚洲电影免费观看| 影音先锋黄色网址| 午夜精品久久久| 91麻豆制片厂| 不卡的av网站| 日本免费色视频| 一区二区黄色| 国产又粗又大又爽的视频| 天堂资源在线亚洲| 亚洲一区久久久| 欧美精品总汇| 久久久免费观看| 3p在线观看| 亚洲精品国产精品国自产观看浪潮| 伊人网视频在线| 婷婷开心久久网| 好吊色视频在线观看| 国产日韩av一区| 性色av蜜臀av浪潮av老女人 | 国产精品扒开腿爽爽爽视频| 伊人在我在线看导航| 国产亚洲欧美日韩精品| 蜜臀av在线观看| 在线不卡a资源高清| 亚洲欧美精品一区二区三区| 一区二区三区免费观看| av资源在线免费观看| 久久免费视频一区| 国产性生活毛片| 国产成人在线网站| 性生生活大片免费看视频| 久久夜色精品| 欧美大片在线播放| 国语精品一区| 色爽爽爽爽爽爽爽爽| 日韩欧美一区免费| 欧美一区激情视频在线观看| 麻豆视频一区| 肥熟一91porny丨九色丨| 国产精品18| 国产精品网红福利| 激情开心成人网| 欧美怡春院一区二区三区| 人人澡人人添人人爽一区二区| 久久久国产视频| 国产在线1区| 色多多国产成人永久免费网站| 精品视频二区| 亚洲欧美在线看| 日夜干在线视频| 亚洲激情视频在线观看| 高清国产mv在线观看| 日韩精品中文字幕在线不卡尤物| 国产精品一区二区av白丝下载| 欧美日韩国产成人在线91| 最近中文在线观看| 精品视频在线免费观看| 亚洲一区二区三区网站| 欧美区一区二区三区| 一级黄在线观看| 91精品国产综合久久久久久久 | 欧美精品乱码久久久久久按摩| 一级淫片免费看| 欧美精品一二三| 国产三级按摩推拿按摩| 日韩欧美国产综合一区| 黄色福利在线观看| 日韩电视剧在线观看免费网站| 午夜影院免费体验区| 亚洲欧美制服第一页| av电影在线网| 久久久国产在线视频| 日本理论片午伦夜理片在线观看| 欧美黑人巨大精品一区二区| 成人影音在线| 欧美在线视频网| jizz亚洲女人高潮大叫| 成人a在线观看| 亚洲超碰在线观看| 精品综合久久久| 精品日韩欧美一区| 亚洲AV无码成人精品一区| 黄页网站一区| 亚洲成熟丰满熟妇高潮xxxxx| 日韩高清在线观看| 一级黄色高清视频| 成人av网站在线观看免费| 国产精品无码一区二区三区| 国产精品嫩草久久久久| 久久久久99精品成人片毛片| 精品久久久久人成| 一二三四区视频| 亚洲精品一区二区三区99| 免费在线黄色影片| 久久中文字幕在线| 第一福利在线视频| 国产在线视频91| 麻豆精品少妇| 中文字幕人成一区| 日韩一区二区免费看| 69久久久久久| 成人精品国产福利| 奇米网一区二区| 亚洲成精国产精品女| 在线观看亚洲国产| 亚洲精品久久久久中文字幕二区| av网站无病毒在线| 7m第一福利500精品视频| 一区二区三区无毛| 欧美一区二区在线视频观看| 欧美精品大片| 免费看国产黄色片| 99精品视频中文字幕| 秋霞欧美一区二区三区视频免费| 精品久久久久久中文字幕一区奶水| 91禁在线观看| 亚洲精品一区二三区不卡| 性网站在线观看| 国产精品网站入口| 亚洲精品国产动漫| 成人黄色大片网站| 国产精品综合二区| 久久精品国产亚洲AV成人婷婷| 精品动漫一区二区| wwwav在线播放| 日韩网站免费观看高清| 欧美国产大片| 久久久久网址| 亚洲午夜极品| 国产又粗又猛大又黄又爽| 中日韩av电影| 精品一区二三区| 日韩电影中文字幕av| 美女网站视频在线| 91影视免费在线观看| 欧美综合一区| 蜜桃免费在线视频| 久久日韩精品一区二区五区| 99视频在线看| 精品国产乱码久久久久久老虎 | 国产高清精品软件丝瓜软件| 精品国产一区久久久| av亚洲一区二区三区| 欧美日韩电影一区二区| 国产精品嫩草99av在线| 日本一卡二卡在线| 亚洲香蕉伊在人在线观| 99久久精品国产一区色| 操日韩av在线电影| 91国产一区| 免费看啪啪网站| 美女任你摸久久| 国产免费嫩草影院| 欧美精品99久久久**| 黄色成人在线观看| 国产欧美精品一区二区三区介绍| 日韩在线视频精品| 中文字幕亚洲欧洲| 亚洲天天做日日做天天谢日日欢| ,一级淫片a看免费| 久久久99免费视频| 9999久久久久| 成人免费视频91| 26uuu精品一区二区| 无码人妻一区二区三区线| 亚洲欧美激情精品一区二区| 桃子视频成人app| 视频一区不卡| 国产一区中文字幕| 久久久久成人片免费观看蜜芽| 精品国免费一区二区三区| 国产资源在线观看入口av| 免费99视频| 日本伊人午夜精品| 精品无码一区二区三区蜜臀| 日韩一区二区三| 麻豆视频在线观看免费网站黄| 欧美 日韩 国产在线| 久久aⅴ国产欧美74aaa| 久久无码精品丰满人妻| 精品亚洲va在线va天堂资源站| 成人高清一区| 神马午夜伦理影院| 91视频www| 亚洲系列在线观看| 欧美黑人视频一区| 欧美女优在线视频| 一级网站在线观看| 色999韩欧美国产综合俺来也| 日韩视频永久免费| 91禁在线看| 西游记1978| 成人午夜在线播放| 精品黑人一区二区三区| 久热国产精品视频| 欧美成人专区| 女人高潮一级片| 天涯成人国产亚洲精品一区av| 番号集在线观看| 99国产视频在线| 丝袜美腿一区二区三区| 日韩精品一区二区亚洲av性色 | 亚洲天堂一区二区在线观看| 五月婷婷激情综合网| 色视频在线免费观看| 精品国产一区二区三区麻豆小说 | 噜噜噜在线观看播放视频|