關(guān)于RowBounds分頁(yè)原理、RowBounds的坑記錄
背景說(shuō)明
項(xiàng)目中經(jīng)常會(huì)使用分頁(yè)查詢(xún),有次使用了RowBounds進(jìn)行分頁(yè),因?yàn)楹芏鄨?chǎng)景或網(wǎng)上也看到很多這樣的寫(xiě)法,所以我也在項(xiàng)目中使用了該類(lèi)進(jìn)行分頁(yè)。
但是有次線上卻拋了異常,由此引發(fā)了對(duì)RowBounds原理的探究。
RowBounds是將所有符合條件的數(shù)據(jù)全都查詢(xún)到內(nèi)存中,然后在內(nèi)存中對(duì)數(shù)據(jù)進(jìn)行分頁(yè),若數(shù)據(jù)量大,千萬(wàn)別使用RowBounds
如我們寫(xiě)的sql語(yǔ)句:
select * from user where id>0 limit 0,10
RowBounds會(huì)將id>0的所有數(shù)據(jù)全都加載到內(nèi)存中,然后截取前10行,若id>0有100萬(wàn)條,則100萬(wàn)條數(shù)據(jù)都會(huì)加載到內(nèi)存中,從而造成內(nèi)存OOM。
一:RowBounds分頁(yè)原理
Mybatis可以通過(guò)傳遞RowBounds對(duì)象,來(lái)進(jìn)行數(shù)據(jù)庫(kù)數(shù)據(jù)的分頁(yè)操作,然而遺憾的是,該分頁(yè)操作是對(duì)ResultSet結(jié)果集進(jìn)行分頁(yè),也就是人們常說(shuō)的邏輯分頁(yè),而非物理分頁(yè)(物理分頁(yè)當(dāng)然就是我們?cè)趕ql語(yǔ)句中指定limit和offset值)。
RowBounds源碼如下:
public class RowBounds { ? /* 默認(rèn)offset是0**/ ? public static final int NO_ROW_OFFSET = 0; ? /* 默認(rèn)Limit是int的最大值,因此它使用的是邏輯分頁(yè)**/ ? public static final int NO_ROW_LIMIT = Integer.MAX_VALUE; ? public static final RowBounds DEFAULT = new RowBounds(); ? private int offset; ? private int limit; ? public RowBounds() { ? ? this.offset = NO_ROW_OFFSET; ? ? this.limit = NO_ROW_LIMIT; ? } ? public RowBounds(int offset, int limit) { ? ? this.offset = offset; ? ? this.limit = limit; ? } ? public int getOffset() { ? ? return offset; ? } ? public int getLimit() { ? ? return limit; ? } }
對(duì)數(shù)據(jù)庫(kù)數(shù)據(jù)進(jìn)行分頁(yè),依靠offset和limit兩個(gè)參數(shù),表示從第幾條開(kāi)始,取多少條。也就是人們常說(shuō)的start,limit。
下面看看Mybatis的如何進(jìn)行分頁(yè)的。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleRowValuesForSimpleResultMap()方法源碼。
Mybatis中使用RowBounds實(shí)現(xiàn)分頁(yè)的大體思路:
先取出所有數(shù)據(jù),然后游標(biāo)移動(dòng)到offset位置,循環(huán)取limit條數(shù)據(jù),然后把剩下的數(shù)據(jù)舍棄。
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) ? ? ? throws SQLException { ? DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>(); //跳過(guò)RowBounds設(shè)置的offset值 ? ?skipRows(rsw.getResultSet(), rowBounds); //判斷數(shù)據(jù)是否小于limit,如果小于limit的話就不斷的循環(huán)取值 ? ?while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { ? ? ?ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); ? ? ?Object rowValue = getRowValue(rsw, discriminatedResultMap); ? ? ?storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); ? ?} } private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) throws SQLException { ?? ?//判斷數(shù)據(jù)是否小于limit,小于返回true ? ? return !context.isStopped() && context.getResultCount() < rowBounds.getLimit(); } ? //跳過(guò)不需要的行,應(yīng)該就是rowbounds設(shè)置的limit和offset ? private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException { ? ? if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) { ? ? ? if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) { ? ? ? ? rs.absolute(rowBounds.getOffset()); ? ? ? } ? ? } else { ?? ? ?//跳過(guò)RowBounds中設(shè)置的offset條數(shù)據(jù),只能逐條滾動(dòng)到指定位置 ? ? ? for (int i = 0; i < rowBounds.getOffset(); i++) { ? ? ? ? rs.next(); ? ? ? } ? ? } }
二:RowBounds的使用
原理:攔截器。
使用方法:
RowBounds:在dao.java中的方法中傳入RowBounds對(duì)象。
2.1:引入依賴(lài)
<dependency> ?? ?<groupId>org.mybatis</groupId> ?? ?<artifactId>mybatis</artifactId> ?? ?<version>3.5.6</version> </dependency>
2.2:service層
import org.apache.ibatis.session.RowBounds; public class UserServiceImpl implements UserService { ?? ?@Autowired ? ? private UserDao userDao; ? ?? ? ? @Override ? ? public Map<String, Object> queryUserList(String currentPage, String pageSize) { ? ? ? ? //查詢(xún)數(shù)據(jù)總條數(shù) ? ? ? ? int total = userDao.queryCountUser(); ? ? ? ? //返回結(jié)果集 ? ? ? ? Map<String,Object> resultMap = new HashMap<String,Object>(); ? ? ? ?? ? ? ? ? resultMap.put("total", total); ? ? ? ? //總頁(yè)數(shù) ? ? ? ? int totalpage = (total + Integer.parseInt(pageSize) - 1) / Integer.parseInt(pageSize); ? ? ? ? resultMap.put("totalpage", totalpage); ? ? ? ?? ? ? ? ? //數(shù)據(jù)的起始行 ? ? ? ? int offset = (Integer.parseInt(currentPage)-1)*Integer.parseInt(pageSize); ? ? ? ? RowBounds rowbounds = new RowBounds(offset, Integer.parseInt(pageSize)); ? ? ? ? //用戶(hù)數(shù)據(jù)集合 ? ? ? ? List<Map<String, Object>> userList = userDao.queryUserList(rowbounds); ? ? ? ?? ? ? ? ? resultMap.put("userList", userList); ? ? ? ?? ? ? ? ? return resultMap; ? ? } }
2.3:dao層
import org.apache.ibatis.session.RowBounds; public interface UserDao { ? ?? ? ? public int queryCountUser(); ? ? ? //查詢(xún)用戶(hù)總數(shù) ? ? public List<Map<String, Object>> queryUserList(RowBounds rowbounds); ? ?//查詢(xún)用戶(hù)列表 }
2.4:mapper.xml
mappep.xml里面正常配置,不用對(duì)rowBounds任何操作。
mybatis的攔截器自動(dòng)操作rowBounds進(jìn)行分頁(yè)。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.test.mapper.UserDao"> ? ?? ? ? <!-- 查詢(xún)用戶(hù)總數(shù) --> ? ? <select id="queryCountUser" resultType="java.lang.Integer"> ? ? ? ? select count(1) from user ? ? </select> ? ?? ? ? <!-- 查詢(xún)用戶(hù)列表 --> ? ? <select id="queryUserList" resultType="java.util.Map"> ? ? ? ? select * from user ? ? </select> ? ?? </mapper>
三:RowBounds的坑
2021-11-19 15:15:14.933 ERROR [task-10] [org.springframework.transaction.interceptor.TransactionInterceptor] Application exception overridden by rollback exception
org.springframework.dao.TransientDataAccessResourceException:
— Error querying database. Cause: java.sql.SQLException: Java heap space
— The error may exist in com/test/mapper/InfoRecordMapper.java (best guess)
– The error may involve com.test.mapper.InfoRecordMapper.selectByExampleAndRowBounds-Inline
— The error occurred while setting parameters
RowBounds是將所有符合條件的數(shù)據(jù)全都查詢(xún)到內(nèi)存中,然后在內(nèi)存中對(duì)數(shù)據(jù)進(jìn)行分頁(yè)
如我們查詢(xún)user表中id>0的數(shù)據(jù),然后分頁(yè)查詢(xún)sql如下:
select * from user where id >0 limit 3,10
但使用RowBounds后,會(huì)將id>0的所有數(shù)據(jù)都加載到內(nèi)存中,然后跳過(guò)offset=3條數(shù)據(jù),截取10條數(shù)據(jù)出來(lái),若id>0的數(shù)據(jù)有100萬(wàn),則100w數(shù)據(jù)都會(huì)被加載到內(nèi)存中,從而造成內(nèi)存OOM。
所以當(dāng)數(shù)據(jù)量非常大時(shí),一定要慎用RowBounds類(lèi)。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(35)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你2021-07-07File的API和常用方法詳解_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了File的API和常用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05為何修改equals方法時(shí)還要重寫(xiě)hashcode方法的原因分析
這篇文章主要介紹了為何修改equals方法時(shí)還要重寫(xiě)hashcode方法的原因分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Springboot2 集成 druid 加密數(shù)據(jù)庫(kù)密碼的配置方法
這篇文章給大家介紹Springboot2 集成 druid 加密數(shù)據(jù)庫(kù)密碼的配置方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-07-07java實(shí)現(xiàn)圖像轉(zhuǎn)碼為字符畫(huà)的方法
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)圖像轉(zhuǎn)碼為字符畫(huà)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03SpringBoot單點(diǎn)登錄實(shí)現(xiàn)過(guò)程詳細(xì)分析
這篇文章主要介紹了SpringBoot單點(diǎn)登錄實(shí)現(xiàn)過(guò)程,單點(diǎn)登錄英文全稱(chēng)Single?Sign?On,簡(jiǎn)稱(chēng)就是SSO。它的解釋是:在多個(gè)應(yīng)用系統(tǒng)中,只需要登錄一次,就可以訪問(wèn)其他相互信任的應(yīng)用系統(tǒng)2022-12-12Java Filter 過(guò)濾器詳細(xì)介紹及實(shí)例代碼
Filter也稱(chēng)之為過(guò)濾器,它是Servlet技術(shù)中最實(shí)用的技術(shù),本文章WEB開(kāi)發(fā)人員通過(guò)Filter技術(shù),對(duì)web服務(wù)器管理的所有web資源進(jìn)行攔截,從而實(shí)現(xiàn)一些特殊的功能,本文章將向大家介紹Java 中的 Filter 過(guò)濾器,需要的朋友可以參考一下2016-12-12SpringBoot org.springframework.beans.factory.Unsatisfie
本文主要介紹了SpringBoot org.springframework.beans.factory.UnsatisfiedDependencyException依賴(lài)注入異常,文中通過(guò)示例代碼介紹的很詳細(xì),具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02