一个完整的Mybatis分页解决方案

【布景】

号码大全项目结构是 SpringMVC+Mybatis, 需求是想选用自定义的分页标签,关键词挖掘工具一起,要尽量少的影响事务程序开发的。

假如你已经运用了JS结构( 如:Ext,EasyUi等)自带的分页机能,这篇文章帮助能够不大,因为JS结构供给了固定的接口。

【关于疑问】

大多数分页器会运用在查询页面,要思考以下疑问:

1)分页时是要随时带有近来一次查询条件

2)不能影响现有的sql,相似aop的作用

3)mybatis供给了通用的阻拦接口,要挑选恰当的阻拦办法和时点

4)尽量少的影响现有service等接口

【关于依赖库】

Google Guava    作为根底工具包

Commons JXPath  用于目标查询  (1/23日版改进后,不再需求)

Jackson  向前台传送Json格局数据变换用

【关于适用数据库】

如今只适用mysql

(假如需求用在其他数据库可参阅 paginator的Dialect有些,改动都不大)

首先是Page类,对比简略,保留分页有关的一切信息,触及到分页算法。尽管“其貌不扬”,但很重要。后边会看到这个page类目标会以“信使”的身份出如今悉数与分页有关的当地。

Java代码  保藏代码

/**

* 封装分页数据

*/

import java.util.List;

import java.util.Map;

import org.codehaus.jackson.map.ObjectMapper;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import com.谷歌.common.base.Joiner;

import com.谷歌.common.collect.Lists;

import com.谷歌.common.collect.Maps;

public class Page {

private static final Logger logger = LoggerFactory.getLogger(Page.class);

private static ObjectMapper mapper = new ObjectMapper();

public static String DEFAULT_PAGESIZE = "10";

private int pageNo;          //当时页码

private int pageSize;        //每页行数

private int totalRecord;      //总记载数

private int totalPage;        //总页数

private Map params;  //查询条件

private Map paramLists;  //数组查询条件

private String searchUrl;      //Url地址

private String pageNoDisp;       //能够显现的页号(分隔符"|",总页数改变时更新)

private Page() {

pageNo = 1;

pageSize = Integer.valueOf(DEFAULT_PAGESIZE);

totalRecord = 0;

totalPage = 0;

params = Maps.newHashMap();

paramLists = Maps.newHashMap();

searchUrl = "";

pageNoDisp = "";

}

public static Page newBuilder(int pageNo, int pageSize, String url){

Page page = new Page();

page.setPageNo(pageNo);

page.setPageSize(pageSize);

page.setSearchUrl(url);

return page;

}

/**

* 查询条件转JSON

*/

public String getParaJson(){

Map map = Maps.newHashMap();

for (String key : params.keySet()){

if ( params.get(key) != null  ){

map.put(key, params.get(key));

}

}

String json="";

try {

json = mapper.writeValueAsString(map);

} catch (Exception e) {

logger.error("变换JSON失利", params, e);

}

return json;

}

/**

* 数组查询条件转JSON

*/

public String getParaListJson(){

Map map = Maps.newHashMap();

for (String key : paramLists.keySet()){

Listlists = paramLists.get(key);

if ( lists != null && lists.size()>0 ){

map.put(key, lists);

}

}

String json="";

try {

json = mapper.writeValueAsString(map);

} catch (Exception e) {

logger.error("变换JSON失利", params, e);

}

return json;

}

/**

* 总件数改变时,更新总页数并核算显现款式

*/

private void refreshPage(){

//总页数核算

totalPage = totalRecord%pageSize==0 ? totalRecord/pageSize : (totalRecord/pageSize + 1);

//防止超出最末页(阅览途中数据被删去的状况)

if ( pageNo > totalPage && totalPage!=0){

pageNo = totalPage;

}

pageNoDisp = computeDisplayStyleAndPage();

}

/**

* 核算页号显现款式

*  这儿完结以下的分页款式("[]"代表当时页号),可依据项目需求调整

*   [1],2,3,4,5,6,7,8..12,13

*   1,2..5,6,[7],8,9..12,13

*   1,2..6,7,8,9,10,11,12,[13]

*/

private String computeDisplayStyleAndPage(){

ListpageDisplays = Lists.newArrayList();

if ( totalPage <= 11 ){

for (int i=1; i<=totalPage; i++){

pageDisplays.add(i);

}

}else if ( pageNo < 7 ){

for (int i=1; i<=8; i++){

pageDisplays.add(i);

}

pageDisplays.add(0);// 0 表明 省掉有些(下同)

pageDisplays.add(totalPage-1);

pageDisplays.add(totalPage);

}else if ( pageNo> totalPage-6 ){

pageDisplays.add(1);

pageDisplays.add(2);

pageDisplays.add(0);

for (int i=totalPage-7; i<=totalPage; i++){

pageDisplays.add(i);

}

}else{

pageDisplays.add(1);

pageDisplays.add(2);

pageDisplays.add(0);

for (int i=pageNo-2; i<=pageNo+2; i++){

pageDisplays.add(i);

}

pageDisplays.add(0);

pageDisplays.add(totalPage-1);

pageDisplays.add(totalPage);

}

return Joiner.on("|").join(pageDisplays.toArray());

}

public int getPageNo() {

return pageNo;

}

public void setPageNo(int pageNo) {

this.pageNo = pageNo;

}

public int getPageSize() {

return pageSize;

}

public void setPageSize(int pageSize) {

this.pageSize = pageSize;

}

public int getTotalRecord() {

return totalRecord;

}

public void setTotalRecord(int totalRecord) {

this.totalRecord = totalRecord;

refreshPage();

}

public int getTotalPage() {

return totalPage;

}

public void setTotalPage(int totalPage) {

this.totalPage = totalPage;

}

public Map getParams() {

return params;

}

public void setParams(Map params) {

this.params = params;

}

public Map getParamLists() {

return paramLists;

}

public void setParamLists(Map paramLists) {

this.paramLists = paramLists;

}

public String getSearchUrl() {

return searchUrl;

}

public void setSearchUrl(String searchUrl) {

this.searchUrl = searchUrl;

}

public String getPageNoDisp() {

return pageNoDisp;

}

public void setPageNoDisp(String pageNoDisp) {

this.pageNoDisp = pageNoDisp;

}

}

然后是最中心的阻拦器了。触及到了mybatis的中心功用,期间阅览许多mybatis源码几经修正重构,辛苦自不必说。

中心思维是将阻拦到的select句子,改装成select count(*)句子,履行之得到,总数据数。再依据page中的当时页号算出limit值,拼接到select句子后。

为简化代码运用了Commons JXPath 包,做目标查询。

Java代码  保藏代码

/**

* 分页用阻拦器

*/

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.util.Properties;

import org.apache.commons.jxpath.JXPathContext;

import org.apache.commons.jxpath.JXPathNotFoundException;

import org.apache.ibatis.executor.Executor;

import org.apache.ibatis.executor.parameter.DefaultParameterHandler;

import org.apache.ibatis.mapping.BoundSql;

import org.apache.ibatis.mapping.MappedStatement;

import org.apache.ibatis.mapping.MappedStatement.Builder;

import org.apache.ibatis.mapping.ParameterMapping;

import org.apache.ibatis.mapping.SqlSource;

import org.apache.ibatis.plugin.Interceptor;

import org.apache.ibatis.plugin.Intercepts;

import org.apache.ibatis.plugin.Invocation;

import org.apache.ibatis.plugin.Plugin;

import org.apache.ibatis.plugin.Signature;

import org.apache.ibatis.session.ResultHandler;

import org.apache.ibatis.session.RowBounds;

@Intercepts({@Signature(type=Executor.class,method="query",args={ MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class })})

public class PageInterceptor implements Interceptor{

public Object intercept(Invocation invocation) throws Throwable {

//当时环境 MappedStatement,BoundSql,及sql获得

MappedStatement mappedStatement=(MappedStatement)invocation.getArgs()[0];

Object parameter = invocation.getArgs()[1];

BoundSql boundSql = mappedStatement.getBoundSql(parameter);

String originalSql = boundSql.getSql().trim();

Object parameterObject = boundSql.getParameterObject();

//Page目标获取,“信使”抵达阻拦器!

Page page = searchPageWithXpath(boundSql.getParameterObject(),".","page","*/page");

if(page!=null ){

//Page目标存在的场合,开端分页处置

String countSql = getCountSql(originalSql);

Connection connection=mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection()  ;

PreparedStatement countStmt = connection.prepareStatement(countSql);

BoundSql countBS = copyFromBoundSql(mappedStatement, boundSql, countSql);

DefaultParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, countBS);

parameterHandler.setParameters(countStmt);

ResultSet rs = countStmt.executeQuery();

int totpage=0;

if (rs.next()) {

totpage = rs.getInt(1);

}

rs.close();

countStmt.close();

* 依据原Sql句子获取对应的查询总记载数的Sql句子

*/

private String getCountSql(String sql) {

return "SELECT COUNT(*) FROM (" + sql + ") aliasForPage";

}

public class BoundSqlSqlSource implements SqlSource {

BoundSql boundSql;

public BoundSqlSqlSource(BoundSql boundSql) {

this.boundSql = boundSql;

}

public BoundSql getBoundSql(Object parameterObject) {

return boundSql;

}

}

public Object plugin(Object arg0) {

return Plugin.wrap(arg0, this);

}

public void setProperties(Properties arg0) {

}

}

到展示层总算能够轻松些了,运用了文件标签来简化前台开发。

选用暂时表单提交,CSS运用了Bootstrap。

Html代码  保藏代码

<%@tag pageEncoding="UTF-8"%>

<%@ attribute name="page" type="cn.com.intasect.ots.common.utils.Page" required="true"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<%

int current =  page.getPageNo();

int begin = 1;

int end = page.getTotalPage();

request.setAttribute("current", current);

request.setAttribute("begin", begin);

request.setAttribute("end", end);

request.setAttribute("pList", page.getPageNoDisp());

%>

<% if (current!=1 && end!=0){%>

首页

前页

<%}else{%>

首页

前页

<%} %>

${pNo}

${pNo}

<% if (current

后页

末页

<%}else{%>

后页

末页

<%} %>

留意“信使”在这儿使出了浑身解数,7个首要的get办法悉数用上了。

Java代码  保藏代码

page.getPageNo()        //当时页号

page.getTotalPage()     //总页数

page.getPageNoDisp()    //能够显现的页号

page.getParaJson()      //查询条件

page.getParaListJson()  //数组查询条件

page.getPageSize()      //每页行数

page.getSearchUrl()     //Url地址(作为action称号)

到这儿三个中心模块完结了。然后是阻拦器的注册。

【阻拦器的注册】

需求在mybatis-config.xml 中参加阻拦器的装备

Java代码  保藏代码

【有关代码修正】

首先是后台代码的修正,Controller层因为触及到查询条件,需求修正的内容较多。

1)入参需添加 pageNo,pageSize 两个参数

2)依据pageNo,pageSize 及你的相对url结构page目标。(

3)最重要的是将你的其他入参(查询条件)保留到page中

4)Service层的办法需求带着page这个目标(终究意图是传递到sql履行的入参,让阻拦器识别出该sql需求分页,一起传递页号)

5)将page目标传回Mode中

修正前

Java代码  保藏代码

@RequestMapping(value = "/user/users")

public String list(

@ModelAttribute("name") String name,

@ModelAttribute("levelId") String levelId,

@ModelAttribute("subjectId") String subjectId,

Model model) {

model.addAttribute("users",userService.selectByNameLevelSubject(

name, levelId, subjectId));

return USER_LIST_JSP;

}

修正后

Java代码  保藏代码

@RequestMapping(value = "/user/users")

public String list(

@RequestParam(required = false, defaultValue = "1") int pageNo,

@RequestParam(required = false, defaultValue = "5") int pageSize,

@ModelAttribute("name") String name,

@ModelAttribute("levelId") String levelId,

@ModelAttribute("subjectId") String subjectId,

Model model) {

// 这儿是“信使”诞生之地,一出生就加载了许多重要信息!

Page page = Page.newBuilder(pageNo, pageSize, "users");

page.getParams().put("name", name);           //这儿再保留查询条件

page.getParams().put("levelId", levelId);

page.getParams().put("subjectId", subjectId);

-        indexRead arguments from command-line "http://www.shoudashou.com"

-        indexRead arguments from command-line "http://www.4lunwen.cn"

-        indexRead arguments from command-line "http://www.zx1234.cn"

-        indexRead arguments from command-line "http://www.penbar.cn"

-        indexRead arguments from command-line "http://www.whathappy.cn"

-        indexRead arguments from command-line "http://www.lunjin.net"

-        indexRead arguments from command-line "http://www.ssstyle.cn"

-        indexRead arguments from command-line "http://www.91fish.cn"

-        indexRead arguments from command-line "http://www.fanselang.com"

model.addAttribute("users",userService.selectByNameLevelSubject(

name, levelId, subjectId, page));

model.addAttribute("page", page);             //这儿将page回来前台

return USER_LIST_JSP;

}

留意pageSize的缺省值决议该分页的每页数据行数 ,实践项目更通用的办法是运用装备文件指定。

Service层

阻拦器能够主动识别在Map或Bean中的Page目标。

假如运用Bean需求在里面添加一个page项目,Map则对比简略,以下是比如。

Java代码  保藏代码

@Override

public ListselectByNameLevelSubject(String name, String levelId, String subjectId, Page page) {

Map map = Maps.newHashMap();

levelId = DEFAULT_SELECTED.equals(levelId)?null: levelId;

subjectId = DEFAULT_SELECTED.equals(subjectId)?null: subjectId;

if (name != null && name.isEmpty()){

name = null;

}

map.put("name", name);

map.put("levelId", levelId);

map.put("subjectId", subjectId);

map.put("page", page);             //MAP的话加这一句就OK

return userMapper.selectByNameLevelSubject(map);

}

前台页面方面,因为运用了标签,在恰当的方位加一句就够了。

Html代码  保藏代码

“信使”page在这儿进入标签,让分页按钮终究展示。

至此,无需修正一句sql,完结分页主动化。

【作用图】

【总结】

如今回过头来看下最开端提出的几个疑问:

1)分页时是要随时带有近来一次查询条件

答复:在改造Controller层时,经过将提交参数设置到 Page目标的 Map params(单个根本型参数) 和 Map paramLists(数组根本型)处理。

趁便提一下,比如中没有触及参数是Bean的状况,实践运用中大概对比常见。简略的办法是将Bean变换层Map后参加到params。

2)不能影响现有的sql,相似aop的作用

答复:运用Mybatis供给了 Interceptor 接口,阻拦后面目一新去的件数并核算limit值,天然能神不知鬼不觉。

3)mybatis供给了通用的阻拦接口,要挑选恰当的阻拦办法和时点

答复:@Signature(method = "query", type = Executor.class, args = {  MappedStatement.class, Object.class, RowBounds.class,  ResultHandler.class }) 只阻拦查询句子,其他增修正查不会影响。

4)尽量少的影响现有service等接口

答复:这个自认为本计划做的还不够好,首要是Controller层改造上,感觉代码量还对比大。假如有有识者晓得更好的计划还请多指导。

【遗留疑问】

1)一个“显着”的功能疑问,是每次检索前都要去 select count(*)一次。在许多时分(数据改变不是格外敏感的场景)是不必要的。调整也不难,先Controller参数添加一个 totalRecord 总记载数 ,在稍加修正一下Page有关代码即可。

2)要排序怎么办?这篇文章并未评论排序,但是办法是相似的。以上面代码为根底,能够较容易地完结一个通用的排序标签。

===================================== 分割线 (1/8)=======================================

关于Controller层需求将入参传入Page目标的疑问已经进行了改进,思路是主动从HttpServletRequest 类中获取入残,减低了分页代码的侵入性,具体参看文章 http://duanhengbin.iteye.com/blogs/2001142

===================================== 分割线 (1/23)=======================================

再次改进,运用ThreadLocal类封装Page目标,让Service层等无需传Page目标,减小了侵入性。阻拦器也省去了查找Page目标的动作,功能也一起改进。全体代码改动不大。

===================================== 分割线 (2/21)=======================================

今天对比闲,趁便聊下这个分页的终究版,当然历来只有不断改变的需求,没有完满的计划,这儿所说的终究版本来是一个优化后的“零侵入”的计划。为防止代码紊乱仍是只介绍思路。在上一个版本(1/23版)根底上有两点改动:

一是添加一个装备文件,按Url 装备初始的每页行数。如下面这样(pagesize 指的是每页行数):

Xml代码  保藏代码

二是添加一个过滤器,并将剩余的位于Control类中 唯一侵入性的分页有关代码移入过滤器。发现当时的 Url  在装备文件中有匹配是就结构Page目标,并参加到Response中。

运用终究版后,关于开发者需求分页时,只要在装备文件中加一行,并在前端页面上加一个分页标签即可,其他代码,SQL等都不需求任何改动,能够说简化到了极限。

一个完整的Mybatis分页解决方案

时间: 2024-10-18 12:48:56

一个完整的Mybatis分页解决方案的相关文章

分享一个完整的Mybatis分页解决方案

Mybatis 的物理分页是应用中的一个难点,特别是配合检索和排序功能叠加时更是如此. 我在最近的项目中开发了这个通用分页器,过程中参考了站内不少好文章,阅读源码帮助更大minglisoft.cn/technology [背景] 项目框架是 SpringMVC+Mybatis, 需求是想采用自定义的分页标签,同时,要尽量少的影响业务程序开发的.如果你已经使用了JS框架( 如:Ext,EasyUi等)自带的分页机能,是属于前端分页,不在本文讨论范围. [关于问题] 大多数分页器会使用在查询页面,要

很好用的mybatis分页解决方案

分页如果写在SQL脚本中,将会大大影响我们后续数据库的迁移难度.mybatis的分页一般是自己实现一个mybatis的拦截器,然后根据某些特定的条件开启分页,对原有SQL进行改造. 正在我对mybatis的拦截器进行研究的时候从网上找到了一个很好的分页插件,主页地址是 https://github.com/pagehelper/Mybatis-PageHelper 已经实现了拦截器/多种数据库的适配 基本上很方便的即可集成到我们的项目中.集成方式: 1.使用maven引入jar包: <depen

Mybatis分页解决方案

转自:  http://blog.csdn.net/fover717/article/details/8334209 http://www.cnblogs.com/zemliu/archive/2013/08/07/3242966.html http://fhd001.iteye.com/blog/1121189 参考:http://blog.csdn.net/isea533/article/details/23831273 http://blog.csdn.net/hupanfeng/arti

一个完整的mybatis项目,包含增删改查

1.导入jar包,导入相关配置文件,均在自己博客园的文件中 编写mybatis.xml文件 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "./mybatis-3-config.dtd"> <configuration> &l

OpManager12——一个完整的网络管理解决方案

OpManager12--一个完整的网络管理解决方案完整性是衡量一个网络管理解决方案的重要标准.以往单纯的设备性能管理和可用性管理已经不能满足当今网络管理需要了.就是这样, OpManager12应运而生了.在这个版本的OpManager帮助下,卓豪(Zoho)提供了完整的网络解决方案.Opmanager12除了基本的网络基础架构管理外还集成了以下主要功能模块:配置管理防火墙日志分析IP地址管理交换机端口管理网络带宽管理和流量分析存储设备管理OpManager12的一大优势在于把以上所有的功能模

Mybatis分页插件

Mybatis分页插件 - PageHelper说明 如果你也在用Mybatis,建议尝试该分页插件,这个一定是最方便使用的分页插件. 该插件目前支持Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库分页. 点击提交BUG 版本说明 最新版本为3.7.5 PageInfo中的judgePageBoudary方法修改: isLastPage = pageNum == pages && pageNum != 1; //改为 isLastPage

Mybatis分页插件2.0版本发布

项目地址:http://git.oschina.net/free/Mybatis_PageHelper 分页插件示例: http://blog.csdn.net/isea533/article/details/24700339 v2.0更新内容: 支持Mybatis缓存,count和分页同时支持(二者同步) 修改拦截器签名,拦截Executor,签名如下: @Intercepts(@Signature(type = Executor.class, method = "query", a

MyBatis学习总结(17)——Mybatis分页插件PageHelper

如果你也在用Mybatis,建议尝试该分页插件,这一定是最方便使用的分页插件. 分页插件支持任何复杂的单表.多表分页,部分特殊情况请看重要提示. 想要使用分页插件?请看如何使用分页插件. 物理分页 该插件目前支持以下数据库的物理分页: Oracle Mysql MariaDB SQLite Hsqldb PostgreSQL DB2 SqlServer(2005,2008) Informix H2 SqlServer2012 配置dialect属性时,可以使用小写形式: oracle,mysql

springboot +mybatis分页插件PageHelper

1.问题描述 JAVA界ORM的两位大佬Hibernate和Mybatis,hb自带分页(上手挺快,以前用了好几年hb,后期运维及优化快疯了),mybatis没有分页功能,需要借助第三方插件来完成,比较流行的三方框架:PageHelper,今天结合springboot做下介绍,直接贴线上配置,保证可用(如有遗漏,朋友们可以指正下). 2. 解决方案 2.1 配置项目pom.xml <!--分页--> <dependency> <groupId>com.github.pa