精品欧美一区二区三区在线观看 _久久久久国色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)

www.久久av.com| 韩日午夜在线资源一区二区| 色婷婷国产精品免| 福利一区三区| 亚洲电影在线播放| 久久亚洲综合网| 一二三四区在线| 亚洲私人影院| 国产一区二区日韩| 日本成人xxx| 色在线免费观看| 国产精品高潮呻吟| 国产伦精品一区二区三区视频免费| 99久在线精品99re8热| 久久美女视频| 亚洲大胆人体在线| 国产精品自拍视频在线| 国产精品原创| 中文字幕视频一区| 麻豆av一区| xxxx国产精品| 蜜臀av一区二区| 91av在线免费观看视频| 免费在线观看a级片| 色先锋久久影院av| 欧美一区三区四区| 99久久国产宗和精品1上映| www.欧美日本韩国| 国产午夜精品一区二区三区嫩草| 99国产高清| 亚洲视频在线免费播放| 亚洲少妇在线| 久久久久久久久久国产精品| 成人欧美一区二区三区黑人一| 日本国产精品| 日韩欧美在线观看一区二区三区| 已婚少妇美妙人妻系列| av在线最新| 一区二区三区在线免费视频| 亚洲一区二区精品在线观看| 欧美zozo| 99re6这里只有精品视频在线观看| 92看片淫黄大片看国产片| 老熟妇一区二区三区| 日韩一级欧洲| 久久久久久国产免费| 私库av在线播放| 99久久99视频只有精品| 中文字幕欧美日韩| 99久久久无码国产精品衣服| 久久99国产精品视频| 亚洲精品久久久久久久久久久久 | 黄网站免费久久| 国产欧美精品在线播放| 中文字幕免费高清在线观看| 日韩精品成人一区二区在线| 国产激情综合五月久久| 337p粉嫩色噜噜噜大肥臀| 丝袜脚交一区二区| 2018国产精品视频| 国产三级av片| 天堂一区二区在线| 国产精品丝袜高跟| 91国产免费视频| 国产精品888| av资源站久久亚洲| 日本加勒比一区| 91免费在线视频观看| 欧美成ee人免费视频| 久久久久久女乱国产| 欧美经典三级视频一区二区三区| 日韩精品欧美一区二区三区| 国产色在线 com| 国产精品一二三四五| 91欧美精品成人综合在线观看| 亚洲在线免费观看视频| 毛片一区二区三区| 国产拍精品一二三| 中文字幕日韩国产| 国产一区二区三区黄视频 | 91亚洲精品一区二区乱码| 国产伦精品一区二区三区在线| av加勒比在线| 丰满放荡岳乱妇91ww| 国产精品福利视频| 天堂网av2014| 久久久91精品国产一区二区精品| 欧美亚洲免费在线| 中文日本在线观看| 中文天堂在线一区| 国产又粗又长又爽视频| av资源在线播放| 亚洲成人激情综合网| 欧美一级在线看| 在线一区视频观看| 欧美片在线播放| 中国特级黄色片| 亚洲国产合集| 视频直播国产精品| 亚洲综合图片一区| 夜夜精品视频| 国产欧美精品在线| 狠狠人妻久久久久久综合麻豆| 91在线视频免费观看| 日本一区高清在线视频| 日本高清视频在线播放| 一区二区三区视频在线看| 午夜免费福利小电影| 992tv国产精品成人影院| 精品裸体舞一区二区三区| 成人免费毛片糖心| 亚洲最新av| 欧美激情第99页| 欧美超碰在线观看| 国产精品综合一区二区三区| 国产一区免费在线观看| 一区二区三区视频网站| 午夜精品一区二区三区三上悠亚| 中文字幕一区二区三区四区在线视频| 久久国内精品| 亚洲视频日韩精品| 久久久久久久9999| 日本不卡高清视频| 国产91视觉| 超碰在线国产| 亚洲不卡一区二区三区| 午夜免费看毛片| 日韩三区视频| 欧美成人小视频| 一卡二卡三卡在线| www激情久久| 日本大片免费看| 欧洲亚洲精品| 精品亚洲一区二区三区四区五区| 国产尤物在线播放| 日韩影院免费视频| 精品国产免费人成电影在线观... 精品国产免费久久久久久尖叫 | 97av视频在线观看| 成人豆花视频| xxav国产精品美女主播| caoporn国产| 成人午夜精品一区二区三区| 在线观看欧美一区| 欧美1级2级| 亚洲精品国产免费| 久久一区二区三| 韩国理伦片一区二区三区在线播放 | 日本精品在线免费观看| 日韩成人午夜精品| 久久精品二区| 国产夫妻在线播放| 欧美成人video| 69av视频在线| 国内精品视频666| 午夜精品短视频| av老司机免费在线| 亚洲激情视频在线| 国产中文字字幕乱码无限| 国产精品亚洲第一区在线暖暖韩国 | 国产一区二区av在线| 最新日韩中文字幕| 日韩黄色片网站| 国产香蕉久久精品综合网| www.浪潮av.com| 欧洲亚洲视频| 97av在线视频| 日批视频在线播放| 亚洲永久精品大片| 国产a级黄色片| 91久久久久| 精品乱码一区二区三区| 国产伦理精品| 亚洲欧美激情另类校园| 亚洲色成人www永久网站| 欧美国产激情二区三区| 91人人澡人人爽人人精品| 精品视频免费| 91精品久久久久久久久| 麻豆av在线免费看| 日韩一区二区在线观看视频播放| 黑人巨大精品一区二区在线| 国产福利一区二区三区视频| 国产精品igao激情视频| a级日韩大片| 久久久中精品2020中文| 奇米影视888狠狠狠777不卡| 色国产精品一区在线观看| 永久免费毛片在线观看| 久久er精品视频| 9191国产视频| 欧美激情极品| 国产精品久久久久久亚洲调教| 爱久久·www| 欧美变态口味重另类| 香蕉免费毛片视频| 国产人成亚洲第一网站在线播放| 少妇网站在线观看| 午夜欧美视频| 欧美日韩国产精品一卡| 激情欧美一区二区三区黑长吊| 久久国产精品久久久| 青春有你2免费观看完整版在线播放高清 | 樱花www成人免费视频| 伊色综合久久之综合久久| 91精品91久久久久久| 岛国在线视频| 日韩欧美亚洲一区二区| 久久精品国产av一区二区三区| 国产女人水真多18毛片18精品视频 | 欧美日韩国产高清一区| 国产精品.www| 国产精品高潮呻吟| 800av在线播放| 美女一区二区视频| 日韩小视频在线播放| 久久香蕉国产| 翡翠波斯猫1977年美国| 日本成人一区二区| 2023亚洲男人天堂| jizzjizz亚洲| 揄拍成人国产精品视频| 日韩一级在线播放| 欧美精选午夜久久久乱码6080| 99精品视频99| 亚洲国产精品久久久久秋霞影院 | 亚洲素人一区二区| aaaaa一级片| 国产成人精品aa毛片| 精品日韩久久久| 国产一区91| 成人国产一区二区三区| 日本久久综合| 蜜桃精品久久久久久久免费影院| 国产区一区二| 国产精品网红直播| 芒果视频成人app| 欧美精品福利视频| 在线观看h片| 中文字幕精品www乱入免费视频| 欧美在线 | 亚洲| 欧美一级艳片视频免费观看| 亚洲午夜在线播放| 色综合久久综合| 国产成人无码精品| 亚洲综合免费观看高清完整版| 国产探花在线免费观看| 国产精品女主播av| 一区二区三区伦理片| 97国产一区二区| 国产性生活毛片| www.亚洲国产| 熟妇高潮一区二区| 国产黑丝在线一区二区三区| 古装做爰无遮挡三级聊斋艳谭| 日韩精品久久久久久| 久久无码高潮喷水| 99视频在线精品国自产拍免费观看| 韩日视频在线观看| 狠狠色综合网| 日韩美女爱爱视频| 精品1区2区3区4区| 日本丰满少妇xxxx| 99精品免费| 国产91在线免费| 老司机久久99久久精品播放免费| 久久网站免费视频| 国产亚洲一级| 国产视频一区二区三区在线播放 | 欧美三级午夜理伦三级富婆| 日韩二区在线观看| 熟妇人妻无乱码中文字幕真矢织江 | 国产在线日韩欧美| ass极品水嫩小美女ass| 国产福利一区在线| 久草免费资源站| av亚洲精华国产精华精华| 亚洲狠狠婷婷综合久久久久图片| 91丝袜高跟美女视频| b站大片免费直播| 国产精品女同互慰在线看| 久久久国产一级片| 一区二区三区**美女毛片| 国产乡下妇女做爰毛片| 欧美日韩亚洲精品一区二区三区 | 色妞www精品视频| 亚洲欧美精品一区二区三区| 欧美日韩一级视频| 国产人妻精品一区二区三| 欧美sm极限捆绑bd| 三级在线观看| 色偷偷91综合久久噜噜| 污视频网站在线免费| 欧美综合激情网| 九九久久国产| 国产精品一区二区三区不卡| 台湾亚洲精品一区二区tv| 日本一区二区不卡高清更新| 久久精品青草| av免费观看国产| 久久精品国产精品亚洲红杏| 日本亚洲一区二区三区| 99久久精品免费| 奇米网一区二区| 亚洲一区二区视频在线| 亚洲图片欧美在线| 欧美成人一级视频| 国产免费a∨片在线观看不卡| 久久视频中文字幕| 日本不卡1234视频| 国产区精品视频| 99久久免费精品国产72精品九九| 午夜欧美一区二区三区免费观看| 亚洲欧美伊人| 青青青在线视频免费观看| 国产精品白丝av| 国产精品久久免费观看| 亚洲一卡二卡三卡四卡无卡久久| 亚洲一区中文字幕永久在线| 亚洲精品美女久久| 欧美精品hd| 日韩av成人在线| 动漫av一区| 伊人久久青草| 免费在线欧美视频| 精品无码国产一区二区三区51安| 国产精品久久毛片a| 日本中文字幕在线免费观看| 6080yy午夜一二三区久久| 欧美扣逼视频| 97精品国产97久久久久久春色| 日韩免费va| 欧美日韩国产综合在线| 激情综合久久| 久久人人爽人人片| 国产精品久久久久久久久晋中| 国产精品乱子伦| 欧美mv日韩mv亚洲| 美女国产在线| 国产精品色视频| 久久91成人| 99精品人妻少妇一区二区| 不卡一区二区三区四区| 久草网视频在线观看| 69堂精品视频| 免费网站免费进入在线| 国产成人激情视频| 蜜乳av综合| 色七七在线观看| 久久九九影视网| 亚洲影院在线播放| 日韩成人免费视频| 日本在线影院| 久久久综合亚洲91久久98| 最新亚洲激情| 国产午夜在线一区二区三区| 精品国产乱码久久久久久天美| 国产探花精品一区二区| 久久天天躁狠狠躁夜夜爽蜜月| 日韩五码电影| 亚洲图片欧洲图片日韩av| 天堂资源在线中文精品| 欧日韩不卡视频| 欧美日韩不卡在线| 午夜视频在线免费观看| 国产日韩欧美电影在线观看| 欧美h版在线| 国产在线观看免费播放| 亚洲二区在线观看| 色综合成人av| 国产福利视频一区| 欧美丝袜丝交足nylons172| 99精品999| 亚洲一区二区在线视频| 天堂中文资源在线观看| 欧洲精品久久久| 国产精品一区二区av交换| 人人干人人干人人| 亚洲三级免费观看| www.蜜臀av.com| 97精品欧美一区二区三区| 日韩在线黄色| 九一精品久久久| 亚洲黄色尤物视频| 日本韩国在线观看| 日韩av免费一区| 99re6这里只有精品| 久久久久久久久久影视| 丁香五六月婷婷久久激情| 国产精品天堂| 91色p视频在线| 激情婷婷亚洲| 波多野吉衣中文字幕| 欧美日本在线观看| av在线小说| 天堂社区 天堂综合网 天堂资源最新版 | 无码国产精品96久久久久| 日韩av手机在线看| 久久久久蜜桃| 亚洲综合自拍网| 欧美日韩在线播放一区|