關(guān)于RowBounds分頁原理、RowBounds的坑記錄
背景說明
項(xiàng)目中經(jīng)常會(huì)使用分頁查詢,有次使用了RowBounds進(jìn)行分頁,因?yàn)楹芏鄨?chǎng)景或網(wǎng)上也看到很多這樣的寫法,所以我也在項(xiàng)目中使用了該類進(jìn)行分頁。
但是有次線上卻拋了異常,由此引發(fā)了對(duì)RowBounds原理的探究。
RowBounds是將所有符合條件的數(shù)據(jù)全都查詢到內(nèi)存中,然后在內(nèi)存中對(duì)數(shù)據(jù)進(jìn)行分頁,若數(shù)據(jù)量大,千萬別使用RowBounds
如我們寫的sql語句:
select * from user where id>0 limit 0,10
RowBounds會(huì)將id>0的所有數(shù)據(jù)全都加載到內(nèi)存中,然后截取前10行,若id>0有100萬條,則100萬條數(shù)據(jù)都會(huì)加載到內(nèi)存中,從而造成內(nèi)存OOM。
一:RowBounds分頁原理
Mybatis可以通過傳遞RowBounds對(duì)象,來進(jìn)行數(shù)據(jù)庫數(shù)據(jù)的分頁操作,然而遺憾的是,該分頁操作是對(duì)ResultSet結(jié)果集進(jìn)行分頁,也就是人們常說的邏輯分頁,而非物理分頁(物理分頁當(dāng)然就是我們?cè)趕ql語句中指定limit和offset值)。
RowBounds源碼如下:
public class RowBounds {
? /* 默認(rèn)offset是0**/
? public static final int NO_ROW_OFFSET = 0;
? /* 默認(rèn)Limit是int的最大值,因此它使用的是邏輯分頁**/
? 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ù)庫數(shù)據(jù)進(jìn)行分頁,依靠offset和limit兩個(gè)參數(shù),表示從第幾條開始,取多少條。也就是人們常說的start,limit。
下面看看Mybatis的如何進(jìn)行分頁的。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleRowValuesForSimpleResultMap()方法源碼。
Mybatis中使用RowBounds實(shí)現(xiàn)分頁的大體思路:
先取出所有數(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>();
//跳過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();
}
? //跳過不需要的行,應(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 {
?? ? ?//跳過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);
? ? ? ? //總頁數(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)行分頁。
<?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)行分頁
如我們查詢user表中id>0的數(shù)據(jù),然后分頁查詢sql如下:
select * from user where id >0 limit 3,10
但使用RowBounds后,會(huì)將id>0的所有數(shù)據(jù)都加載到內(nèi)存中,然后跳過offset=3條數(shù)據(jù),截取10條數(shù)據(jù)出來,若id>0的數(shù)據(jù)有100萬,則100w數(shù)據(jù)都會(huì)被加載到內(nèi)存中,從而造成內(nèi)存OOM。
所以當(dāng)數(shù)據(jù)量非常大時(shí),一定要慎用RowBounds類。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
解決mybatis 執(zhí)行mapper的方法時(shí)報(bào)空指針問題
這篇文章主要介紹了解決mybatis 執(zhí)行mapper的方法時(shí)報(bào)空指針問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Spring Security使用Lambda DSL配置流程詳解
Spring Security 5.2 對(duì) Lambda DSL 語法的增強(qiáng),允許使用lambda配置HttpSecurity、ServerHttpSecurity,重要提醒,之前的配置方法仍然有效。lambda的添加旨在提供更大的靈活性,但是用法是可選的。讓我們看一下HttpSecurity的lambda配置與以前的配置樣式相比2023-02-02
Java項(xiàng)目Guava包?HashMultimap使用及注意事項(xiàng)
guava基本上可以說是java開發(fā)項(xiàng)目中,大概率會(huì)引入的包,今天介紹的主角是一個(gè)特殊的容器HashMultmap,可以簡(jiǎn)單的將它的數(shù)據(jù)結(jié)構(gòu)理解為Map<K,?Set<V>>,今天主要介紹下基礎(chǔ)的知識(shí)點(diǎn)?HashMultmap級(jí)使用,感興趣的朋友一起看看吧2022-05-05
Java構(gòu)造方法 super 及自定義異常throw合集詳解用法
異常是程序中的一些錯(cuò)誤,但不是所有錯(cuò)誤都是異常,且錯(cuò)誤有時(shí)候是可以避免的,super可以理解為是指向自己超(父)類對(duì)象的一個(gè)指針,而這個(gè)超類指的是離自己最近的一個(gè)父類,構(gòu)造器也叫構(gòu)造方法、構(gòu)造函數(shù),是一種特殊類型的方法,負(fù)責(zé)類中成員變量(域)的初始化2021-10-10

