Mybatis使用之分页
一:简介
注:示例基于mysql数据库。Oracle可以自行测试。
使用Mybatis分页主要有两种方式、一种是将分页参数传递到配置文件中、在写sql的时候就做分页。另一种是使用Mybatis的拦截器拦截需要分页的sql语句、重新组装成具有分页功能的sql语句。
分页查询的关键在于查询时需要告诉数据库从第几条开始取、取多少条记录。也就是常用到Page对象(一般是方便传递参数、自己构建的实体类)的pageNumer、pageSize两个主要参数。至于Page对象的总记录数等是需要带到前台构造数据展示表格时使用的参数。
二:传递参数形式的分页
2.1 关键点
传递参数形式的分页就是将分页信息以参数的形式传递到映射文件中、这样就可以在编写具体的sql语句时实现分页功能。这种方式的重点在于一般的查询语句都会带有一个或者多个查询参数、而‘select‘标签的parameterType只能接收一个值。所以需要我们将查询参数与分页参数组装成一个Map作为参数传递到映射语句中。
不过前台展示所需要的数据总数还需要另写一条count查询语句来获取。多多少少会有些不便。
2.2 具体代码
映射文件:
<select id="selectAuthorWithPageParam"resultType="author" parameterType="hashMap"> SELECT t.id, t.username, t.password, t.email, t.bio, t.favourite_section favouriteSection FROM author t WHERE t.username = {username} AND t.password = {password} limit {page.dbIndex},{page.dbNumber} </select>
映射接口:
List<Author> selectAuthorWithPageParam(Map<String, Object>map);
测试代码:
@Test public void testSelectAuthorWithPageParam() throws Exception { Page page = new Page(); page.count(); Map<String, Object> map = new HashMap<>(); map.put("page", page); map.put("username", "alien"); map.put("password","alien"); List<Author> authors =this.authorMapper.selectAuthorWithPageParam(map); Assert.assertEquals(5, authors.size()); }
三:拦截器分页
3.1 关键点
拦截器实现分页的关键之处在于、在需要分页的sql语句执行之前、拦截下来并改造成具有分页功能的sql语句(还可以查询一下总数、设置到Page实体类中供前台展示数据时使用)、然后继续执行。
3.2 具体代码
说起来很简单、但是短短的一句话却需要理解许多东西才能达到目的。这里只挑一些重点步骤、具体拦截器工作原理、执行过程会在Mybatis深入过程详细分析。
从使用角度来考虑过程如下(***对所有映射语句id以‘ByPage‘结尾的做分页处理***):
3.2.1 Page实体类:
package org.alien.mybatis.samples.entity; /** *Created by andy on 5/25/2015.<br> *Version 1.0-SNAPSHOT<br> */ @SuppressWarnings("unused") public class Page { /** * 总条数 */ private int totalNumber; /** * 当前第几页 */ private int currentPage; /** * 总页数 */ private int totalPage; /** * 每页显示条数 */ private int pageNumber = 5; /** * 数据库中limit的参数,从第几条开始取 */ private int dbIndex; /** * 数据库中limit的参数,一共取多少条 */ private int dbNumber; /** * Oracle 起始记录行号 */ private int rowNum; /** * Oracle 结束记录行号 */ private int rn; /** * 根据当前对象中属性值计算并设置相关属性值 */ public void count() { // 计算总页数 int totalPageTemp = this.totalNumber / this.pageNumber; int plus = (this.totalNumber % this.pageNumber) == 0 ? 0 : 1; totalPageTemp = totalPageTemp + plus; if(totalPageTemp <= 0) { totalPageTemp = 1; } this.totalPage = totalPageTemp; // 设置当前页数 // 总页数小于当前页数,应将当前页数设置为总页数 if(this.totalPage < this.currentPage) { this.currentPage = this.totalPage; } // 当前页数小于1设置为1 if(this.currentPage < 1) { this.currentPage = 1; } // 设置limit的参数 this.dbIndex = (this.currentPage - 1) * this.pageNumber; this.dbNumber = this.pageNumber; } public int getTotalNumber() { return totalNumber; } public void setTotalNumber(int totalNumber) { this.totalNumber = totalNumber; this.count(); } public int getCurrentPage() { return currentPage; } public void setCurrentPage(int currentPage) { this.currentPage = currentPage; } public int getTotalPage() { return totalPage; } public void setTotalPage(int totalPage) { this.totalPage = totalPage; } public int getPageNumber() { return pageNumber; } public void setPageNumber(int pageNumber) { this.pageNumber = pageNumber; this.count(); } public int getDbIndex() { return dbIndex; } public void setDbIndex(int dbIndex) { this.dbIndex = dbIndex; } public int getDbNumber() { return dbNumber; } public void setDbNumber(int dbNumber) { this.dbNumber = dbNumber; } public int getRn() { return (this.getCurrentPage() + 1) * this.getPageNumber(); } public void setRn(int rn) { this.rn = rn; } public int getRowNum() { return this.getCurrentPage() * this.getPageNumber(); } public void setRowNum(int rowNum) { this.rowNum = rowNum; }
3.2.2 拦截器类:
packageorg.alien.mybatis.samples.interceptor; importorg.alien.mybatis.samples.entity.Page; importorg.apache.ibatis.executor.parameter.ParameterHandler; importorg.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; importorg.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; importorg.apache.ibatis.reflection.MetaObject; importorg.apache.ibatis.reflection.SystemMetaObject; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.Map; import java.util.Properties; /** *Page interceptor. *Support oracle and mysql. *Created by andy on 5/25/2015.<br> *Version 1.0-SNAPSHOT<br> */ @Intercepts({@Signature(type =StatementHandler.class, method = "prepare", args ={Connection.class})}) public class PageInterceptor implementsInterceptor { private String dialect; @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler =(StatementHandler) invocation.getTarget(); MetaObject metaObject = MetaObject.forObject(statementHandler,SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY); MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement"); String sqlId = mappedStatement.getId(); //intercept select sql witch end with "ByPage". if (sqlId.matches(".+ByPage$")) { BoundSql boundSql = (BoundSql)metaObject.getValue("delegate.boundSql"); String sql = boundSql.getSql(); ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler"); Map<?, ?> paramMap = (Map<?, ?>)parameterHandler.getParameterObject(); Page page = (Page) paramMap.get("page"); //set count Connection connection = (Connection) invocation.getArgs()[0]; String countSql = "select count(1) from (" + sql + ")a"; PreparedStatement preparedStatement =connection.prepareStatement(countSql); parameterHandler.setParameters(preparedStatement); ResultSet resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { page.setTotalNumber(resultSet.getInt(1)); } //construct record limit sql by dialect String pageSql; if ("oracle".equals(dialect.toLowerCase())) { pageSql = "select * from" + "(select a.*,rownum rn from (" + sql + ") a where rownum >= " +page.getRowNum() + ") " + "rn < " +page.getRn(); } else if ("mysql".equals(dialect.toLowerCase())) { pageSql = sql + " limit" + page.getDbIndex() + "," + page.getDbNumber(); } else { pageSql = sql; } metaObject.setValue("delegate.boundSql.sql", pageSql); } return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { this.dialect = properties.getProperty("dialect"); } }
3.2.3 mybatis.xml总配置文件中注册拦截器
***注意Mybatis配置文件各个元素的顺序!***
<plugins> <plugin interceptor="org.alien.mybatis.samples.interceptor.PageInterceptor"> <!--database dialect, only support mysql and oracle--> <property name="dialect" value="mysql"/> </plugin> </plugins>
3.2.4 映射文件语句
<select id="selectAuthorByPage" resultType="author"parameterType="hashMap"> SELECT t.id, t.username, t.password, t.email, t.bio, t.favourite_section favouriteSection FROM author t WHERE t.username = {username} AND t.password = {password} </select>
3.2.4 映射接口方法
List<Author> selectAuthorByPage(Map<String, Object> map);
3.2.4 测试方法
@Test public void testSelectAuthorByPage() throws Exception { Page page = new Page(); Map<String, Object> map = new HashMap<>(); map.put("page", page); List<Author> authors = this.authorMapper.selectAuthorByPage(map); Assert.assertEquals(5, authors.size()); }
三:补充
更多内容:Mybatis 目录
篇幅有限、仅仅说明了拦截器的实现过程、原理及代码都没有详细说明、这些会在Mybatis深入中详细解析。