關(guān)于RowBounds分頁(yè)原理、RowBounds的坑記錄
背景說(shuō)明
項(xiàng)目中經(jīng)常會(huì)使用分頁(yè)查詢,有次使用了RowBounds進(jìn)行分頁(yè),因?yàn)楹芏鄨?chǎng)景或網(wǎng)上也看到很多這樣的寫法,所以我也在項(xiàng)目中使用了該類進(jìn)行分頁(yè)。
但是有次線上卻拋了異常,由此引發(fā)了對(duì)RowBounds原理的探究。
RowBounds是將所有符合條件的數(shù)據(jù)全都查詢到內(nèi)存中,然后在內(nèi)存中對(duì)數(shù)據(jù)進(jìn)行分頁(yè),若數(shù)據(jù)量大,千萬(wàn)別使用RowBounds
如我們寫的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:引入依賴
<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) {
? ? ? ? //查詢數(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));
? ? ? ? //用戶數(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(); ? ? ? //查詢用戶總數(shù)
? ? public List<Map<String, Object>> queryUserList(RowBounds rowbounds); ? ?//查詢用戶列表
}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"> ? ?? ? ? <!-- 查詢用戶總數(shù) --> ? ? <select id="queryCountUser" resultType="java.lang.Integer"> ? ? ? ? select count(1) from user ? ? </select> ? ?? ? ? <!-- 查詢用戶列表 --> ? ? <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ù)全都查詢到內(nèi)存中,然后在內(nèi)存中對(duì)數(shù)據(jù)進(jìn)行分頁(yè)
如我們查詢user表中id>0的數(shù)據(jù),然后分頁(yè)查詢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類。
總結(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-07
File的API和常用方法詳解_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了File的API和常用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
為何修改equals方法時(shí)還要重寫hashcode方法的原因分析
這篇文章主要介紹了為何修改equals方法時(shí)還要重寫hashcode方法的原因分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
Springboot2 集成 druid 加密數(shù)據(jù)庫(kù)密碼的配置方法
這篇文章給大家介紹Springboot2 集成 druid 加密數(shù)據(jù)庫(kù)密碼的配置方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-07-07
java實(shí)現(xiàn)圖像轉(zhuǎn)碼為字符畫的方法
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)圖像轉(zhuǎn)碼為字符畫的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03
SpringBoot單點(diǎn)登錄實(shí)現(xiàn)過(guò)程詳細(xì)分析
這篇文章主要介紹了SpringBoot單點(diǎn)登錄實(shí)現(xiàn)過(guò)程,單點(diǎn)登錄英文全稱Single?Sign?On,簡(jiǎn)稱就是SSO。它的解釋是:在多個(gè)應(yīng)用系統(tǒng)中,只需要登錄一次,就可以訪問(wèn)其他相互信任的應(yīng)用系統(tǒng)2022-12-12
Java Filter 過(guò)濾器詳細(xì)介紹及實(shí)例代碼
Filter也稱之為過(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-12
SpringBoot org.springframework.beans.factory.Unsatisfie
本文主要介紹了SpringBoot org.springframework.beans.factory.UnsatisfiedDependencyException依賴注入異常,文中通過(guò)示例代碼介紹的很詳細(xì),具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02

