总结一下:分页的几种办法
以mysql为例,做分页的方法,目前我总结了3种。
第一种分页:采用Query类和PageUtils类做出分页,sql用limit获取条数
第一步:Query 类的作用是对传入的分页参数做处理,并计算出当前页的起始和结束条数
@Data
public class Query extends LinkedHashMap<String, Object> {
private static final long serialVersionUID = 1L;
//当前页码
private int page;
//每页条数
private int limit;
public Query(Map<String, Object> params){
this.putAll(params);
//分页参数
this.page = Integer.parseInt(params.get("page").toString());
this.limit = Integer.parseInt(params.get("limit").toString());
this.put("offset", (page - 1) * limit);
this.put("page", page);
this.put("limit", limit);
}
}
第二步:根据查询的结果计算总页数
@Data
public class PageUtils implements Serializable {
private static final long serialVersionUID = 1L;
//总记录数
private int totalCount;
//每页记录数
private int pageSize;
//总页数
private int totalPage;
//当前页数
private int currPage;
//列表数据
private List<?> list;
/**
* 分页
* @param list 列表数据
* @param totalCount 总记录数
* @param pageSize 每页记录数
* @param currPage 当前页数
*/
public PageUtils(List<?> list, int totalCount, int pageSize, int currPage) {
this.list = list;
this.totalCount = totalCount;
this.pageSize = pageSize;
this.currPage = currPage;
this.totalPage = (int)Math.ceil((double)totalCount/pageSize);
}
}
第三步: java代码中使用分页
//对传入分页的start、limit作处理,计算出要查看也页的起始结束条数分别是多少
Query query = new Query(params);
//去数据库查询,带上计算好的分页数据(即limit所需参数)
List<Bug> list = bugService.queryList(query);
//查询总条数
int total = bugService.queryTotal(query);
//根据总条数、当前页、等数据调用PageUtils 计算出总页数
PageUtils pageUtil = new PageUtils(list, total, query.getLimit(), query.getPage());
return R.ok().put("page", pageUtil).put("total", total);
查询sql的写法:
<if test="offset != null and limit != null">
limit #{offset}, #{limit}
</if>
这个方法简单明了,接近原生,更好理解原理,缺点是代码多,要单独查总记录数
第二种分页:采用插件pagehelper(前面博客有讲,这里简单过一下)
第一步:导包
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
第二步:配置yml
pagehelper:
#指定数据库 可以不配置,插件会自动检测数据库的类型
helper-dialect: mysql
#分页合理化参数,默认值为false,当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。
reasonable: true
#分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页
support-methods-arguments: true
#用于从对象中根据属性名取值 可以配置pageNum,pageSize,count,pageSizeZero,reasonable。不配置映射的用默认值。
params: count=countSql
第三步:分页(只列出关键代码)
int start = Integer.parseInt(String.valueOf(map.get("start")));
int limit = Integer.parseInt(String.valueOf(map.get("limit")));
PageHelper.startPage(start,limit);
PageInfo<HonorEntity> pageInfo = new PageInfo<>(pageHelperServiceImpl.pageHonor());
这样查出来的代码分页的参数齐全的。
不需要单独查总数,简单易用。
注意:
使用spring boot2整合 pagehelper-spring-boot-starter必须排除一下依赖
因为pagehelper-spring-boot-starter也已经在pom依赖了mybatis与mybatis-spring
所以会与mybatis-plus-boot-starter中的mybatis与mybatis-spring发生冲突
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.version}</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
深坑再现: 最近遇到使用pageHelper总数不正确的问题,总是随前端传的分页参数变化而变化。原因是:
在PageInfo<HonorEntity> pageInfo = new PageInfo<>(pageHelperServiceImpl.pageHonor());
pageInfo
中的数据又做了流式数据处理导致获取到的list的size.(比如这里的a就是真实去数据库查询的list集合)
参看pageHelper的底层:PageSerializable类的构造
public PageSerializable(List list) {
this.list = list;
if (list instanceof Page) {
this.total = ((Page)list).getTotal();
} else {
this.total = (long)list.size();
}
}
即:本来pageInfo 是Page类型,里面封装了total,如果做了其他处理就成了List类型即,total
获取到的是list的大小了。
第三种:mybatis-plus自带的分页
在项目使用mybatis-plus时使用它自己的分页即可。
第一步:导入mybatis-plus的包(以后项目尽量都导它,因为它和mybatis兼容的,用不用都可以)
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.2</version>
</dependency>
第二步:写一个关于分页的配置类(只有中间那个paginationInterceptor是关于分页的,这里是官网写法,最简单就是直接返回对象,其他都不用配置)
@Configuration
@EnableTransactionManagement
public class MybatisPlusConfig {
/**
* 注册乐观锁
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
/**
*分页配置
**/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
/**
* SQL执行效率插件
*/
@Bean
@Profile({"dev","test"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
//mysql执行的最大时间,如果超过1毫秒则不执行,并抛出异常
performanceInterceptor.setMaxTime(1000);
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
}
第三步:代码中的使用(这里是在实现类调用dao层的方法)
public R pageNationQuery(int start,int limit) {
IPage<LiaochaoUser> page = new Page<>(start,limit);
page = liaochaoUserMapper.selectPage(page, null);
return R.ok("查询成功").put("data",page);
}
总结:
三种方法都可以,都不错,第一种虽然代码多但是它轻量啊,群智曾使用。
第二种,方便简单,当初傻傻找查总记录数是哪个sql,始终找不到,联通曾使用。
第三种是以后的趋势,是mybatis-plus的附属功能,网络态势项目使用。
高效查询是否存在和高效分页
1、查询是否存在还在用count(*)吗? out了!
count(0)
:将返回表格中所有存在的行的总数包括值为null的行count(1)
:会统计包括null值的所有符合条件的字段的条数。
count(0)count(1) count(*
) 都会返回null的行
count(字段)
:将返回表格中除去null以外的所有行的总数(有默认值的列也会被计入)
按效率排序的话,count(字段)<count(主键 id)<count(1)≈count(*)
高效查询是否存在:
SELECT 1 FROM sys_user WHERE id = 1000009 LIMIT 1
SQL不再使用count,而是改用LIMIT 1,让数据库查询时遇到一条就返回,不要再继续查找还有多少条了
业务代码中直接判断是否非空即可,存在会返回1,不存在会返回null
2、高效分页
传统分页:select * from table limit 10000,10;
偏移量越大则越慢
推荐分页:select * from table WHERE id>=23434 limit 11;
limit #{offset}, #{limit}后的两个参数是:
#{offset}起始页,它是从0开始的,第一页就是取0
#{limit}每页条数
如果只查询前几条的话,分页参数只需要一个即可
例如:select * from t_user limit 5
它代表查询表中前5条