1. Requirements:
when we use the sql like "select * from targetTable", we get all records of the table,
but we usually just need one page of records(about 10 records).
so we need to change the sql sentences.
2. Solution(Mybatis Physical Pagination):
2.1 Mybatis help us to reduce the difficult of operating database, its
interceptor is a good tool to change the original SQL sentence.
Let's begin!
2.2 Page Interceptor
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.LogFactory; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; 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.reflection.MetaObject; import org.apache.ibatis.reflection.factory.DefaultObjectFactory; import org.apache.ibatis.reflection.factory.ObjectFactory; import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory; import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory; import org.apache.ibatis.scripting.defaults.DefaultParameterHandler; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.RowBounds; /** * 通过拦截<code>StatementHandler</code>的<code>prepare</code>方法,重写sql语句实现物理分页。 * */ @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})}) public class PageInterceptor implements Interceptor { private static final Log logger = LogFactory.getLog(PageInterceptor.class); private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory(); private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory(); private static String defaultDialect = "oracle"; // 数据库类型(默认为mysql) private static String defaultPageSqlId = ".*Page$"; // 需要拦截的ID(正则匹配) private static String dialect = "oracle"; // 数据库类型(默认为mysql) private static String pageSqlId = ""; // 需要拦截的ID(正则匹配) public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY); // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环可以分离出最原始的的目标类) while (metaStatementHandler.hasGetter("h")) { Object object = metaStatementHandler.getValue("h"); metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY); } // 分离最后一个代理对象的目标类 while (metaStatementHandler.hasGetter("target")) { Object object = metaStatementHandler.getValue("target"); metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY); } Configuration configuration = (Configuration) metaStatementHandler.getValue("delegate.configuration"); dialect = configuration.getVariables().getProperty("dialect"); if (null == dialect || "".equals(dialect)) { logger.warn("Property dialect is not setted,use default 'oracle' "); dialect = defaultDialect; } pageSqlId = configuration.getVariables().getProperty("pageSqlId"); if (null == pageSqlId || "".equals(pageSqlId)) { logger.warn("Property pageSqlId is not setted,use default '.*Page$' "); pageSqlId = defaultPageSqlId; } MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement"); // 只重写需要分页的sql语句。通过MappedStatement的ID匹配,默认重写以Page结尾的MappedStatement的sql if (mappedStatement.getId().matches(pageSqlId)) { BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql"); Object parameterObject = boundSql.getParameterObject(); if (parameterObject == null) { throw new NullPointerException("parameterObject is null!"); } else { PageParameter page = (PageParameter) metaStatementHandler .getValue("delegate.boundSql.parameterObject.page");//此处在mysql数据源时取到的对象page的pageno一直为1,可以通过改这里的逻辑取到pageNo String sql = boundSql.getSql(); // 重写sql String pageSql = buildPageSql(sql, page); metaStatementHandler.setValue("delegate.boundSql.sql", pageSql); // 采用物理分页后,就不需要mybatis的内存分页了,所以重置下面的两个参数 metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET); metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT); } } // 将执行权交给下一个拦截器 return invocation.proceed(); } /** * 根据数据库类型,生成特定的分页sql * * @param sql * @param page * @return */ private String buildPageSql(String sql, PageParameter page) { if (page != null) { StringBuilder pageSql = new StringBuilder(); if ("mysql".equals(dialect)) { pageSql = buildPageSqlForMysql(sql, page); } else if ("oracle".equals(dialect)) { pageSql = buildPageSqlForOracle(sql, page); } else { return sql; } return pageSql.toString(); } else { return sql; } } /** * mysql的分页语句 * * @param sql * @param page * @return String */ public StringBuilder buildPageSqlForMysql(String sql, PageParameter page) { StringBuilder pageSql = new StringBuilder(100); String beginrow = String.valueOf((page.getPageNo() - 1) * page.getPageSize()); pageSql.append(sql); pageSql.append(" limit " + beginrow + "," + page.getPageSize()); return pageSql; } /** * oracle的分页 * * @param sql * @param page * @return String */ public StringBuilder buildPageSqlForOracle(String sql, PageParameter page) { StringBuilder pageSql = new StringBuilder(100); String beginrow = String.valueOf((page.getPageNo() - 1) * page.getPageSize()); String endrow = String.valueOf(page.getPageNo() * page.getPageSize()); pageSql.append("select * from ( select temp.*, rownum row_id from ( "); pageSql.append(sql); pageSql.append(" ) temp where rownum <= ").append(endrow); pageSql.append(") where row_id > ").append(beginrow); return pageSql; } public Object plugin(Object target) { // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的次数 if (target instanceof StatementHandler) { return Plugin.wrap(target, this); } else { return target; } } public void setProperties(Properties properties) { } }
2.3 Page Object
/** * 分页参数类 * */ public class PageParameter { public static final int DEFAULT_PAGE_SIZE = 10; private int pageSize; private int pageNo; private int prePage; private int nextPage; private int totalPage; private int totalCount; public PageParameter() { this.pageNo = 1; this.pageSize = DEFAULT_PAGE_SIZE; } /** * get method for reflect * @return */ public PageParameter getPage(){ return new PageParameter(); } /** * * @param currentPage * @param pageSize */ public PageParameter(int pageNo, int pageSize) { this.pageNo = pageNo; this.pageSize = pageSize; } 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 getPrePage() { return prePage; } public void setPrePage(int prePage) { this.prePage = prePage; } public int getNextPage() { return nextPage; } public void setNextPage(int nextPage) { this.nextPage = nextPage; } public int getTotalPage() { return totalPage; } public void setTotalPage(int totalPage) { this.totalPage = totalPage; } public int getTotalCount() { return totalCount; } public void setTotalCount(int totalCount) { this.totalCount = totalCount; } }
2.4 MVC Controller
import org.apache.commons.io.filefilter.FalseFileFilter; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; import javax.annotation.Resource; import com.cdv.ppms.core.feature.orm.mybatis.PageParameter; import com.cdv.ppms.web.model.ObjProgramWithBLOBs; import com.cdv.ppms.web.service.ProgramService; import com.cdv.ppms.web.service.impl.ProgramServiceImpl; /** * 节目管理 * @author rocky * */ @Controller @RequestMapping("/program/") public class ProgramController { @Resource private ProgramService programService; @RequestMapping("uneditedProgram") public String uneditedProgram(@RequestParam(required=false) Integer pageNo , Model model){ PageParameter page = new PageParameter(); if(pageNo!=null && pageNo>1){ page.setPageNo(pageNo); } List<ObjProgramWithBLOBs> programList = programService.selectProgramListPage(page); int totalCount = programService.selectTotalCount(); page.setTotalCount(totalCount); page.setTotalPage(totalCount%page.getPageSize()==0 ? totalCount/page.getPageSize() : totalCount/page.getPageSize()+1); model.addAttribute("programList", programList); model.addAttribute("page", page); return "/program/program_unedited"; } }
2.5 JSP page
<% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %>
<div class="row"> <div class="gigantic pagination"> <a href="#" class="first" data-action="first">«</a> <a href="#" class="previous" data-action="previous">‹</a> <input type="text" readonly="readonly" data-max-page="40" /> <a href="#" class="next" data-action="next">›</a> <a href="#" class="last" data-action="last">»</a> </div> </div>
<form method="post" id="pageForm" name="pageForm"
action="<%=path%>/finished_task.action">
<input input="hidden" id="pageNo" name="pageNo" value="${page.pageNo }" />
</form> <link rel="stylesheet" href="<%=path %>/static/css/jqpagination.css"/> <link rel="stylesheet" href="<%=path %>/static/css/demo.css"/> <script src="<%=path %>/static/script/jquery.jqpagination.min.js"></script> <script> var path = '<%=path %>'; var pageNo = ${page.pageNo}; var totalPage = ${page.totalPage}; $('.pagination').jqPagination({ link_string : path+'/rest/program/uneditedProgram?pageNo={page_number}', current_page: pageNo, //设置当前页 默认为1 max_page : totalPage, //设置最大页 默认为1 page_string : '当前第{current_page}页,共{max_page}页', paged : function(page) { console.log(page); $("#pageNo").val(page); $("#pageForm").submit(); } }); </script>
2.6 mybatis config file
<plugins> <!--Paging Interceptor --> <plugin interceptor="com.cdv.ppms.core.feature.orm.mybatis.PageInterceptor"/> </plugins>
2.7 jqPagination Plugin
https://github.com/beneverard/jqPagination