MyBatis分页
最早使用普通JDBC使用分页的时候,都是
SELECT * FROM students LIMIT num1,num2;
意思就是从students这个表中找第num1的记录开始往下的num2个记录。
每次分页都得计算总条数,然后计算页数等等。后来使用MyBatis后, 该如何进行分页呢?当然也可以用以上的方法进行传参,但是每次遇到分页都得重写一个,很麻烦。
实现MyBatis的拦截器实现
MyBatis拦截器是基于Java动态代理实现的。动态代理知识可参考http://blog.csdn.net/qq407388356/article/details/79313972。
页面实体类Page.java
首先写一个Page的pojo类来封装分页的一些数据信息:
public class Page { private int totalNumber;//总条数 private int currentPage;//当前第几页 private int totalPage;//总页数 private int pageNumber = 5;//每页显示条数 private int dbIndex;//数据库中limit的参数,从第几条开始取 private int dbNumber;//数据库中limit的参数,一共取多少条 /** * 根据当前对象中属性值计算并设置相关属性值 */ 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; } //设置totalNumber时候需要计算count public void setTotalNumber(int totalNumber) { this.totalNumber = totalNumber; this.count(); } //设置pageNumber时候需要计算count public void setPageNumber(int pageNumber) { this.pageNumber = pageNumber; this.count(); } ...//其他setter、getter略 }
mapper文件
在StudentsMapper.java接口中声明该方法:
List<Students> selectAllByPage(Map<String,Object>parameter);
在StudentsMapper.xml文件中配置对应id的sql语句,注意parameterType需要传入page对象的信息在该map中,其他查询信息也可放入该map中。
<select id="selectAllByPage"parameterType="java.util.Map" resultMap="BaseResultMap">
select id, name, age, hobby,classAndGradeId
from students
</select>
分页拦截器PageInterceptor.java
编写分页拦截器,实现org.apache.ibatis.plugin.Interceptor接口。
@Signature(type=StatementHandler.class,method="prepare",args={Connection.class}表示我们要拦截StatementHandler接口下面的prepare的方法,参数是Connection.class。因为该方法正是处理我们提交sql的方法,因此拦截后可以做修改(加入分页功能)。
根据拦截下来的SQL的id是否ByPage结尾(使用正则表达式匹配),这个可以在一个项目中统一规范哪些SQL需要分页。如果是,则进行SQL语句修改,通过参数中Page对象的信息进行分页查询。
具体代码如下:
/** * 分页拦截器 */ @Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class})}) public class PageInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = (StatementHandler)invocation.getTarget(); MetaObject metaObject = SystemMetaObject.forObject(statementHandler); MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement"); // 配置文件中SQL语句的ID String id = mappedStatement.getId(); if(id.matches(".+ByPage$")) { BoundSql boundSql = statementHandler.getBoundSql(); // 原始的SQL语句 String sql = boundSql.getSql(); // 查询总条数的SQL语句 String countSql = "select count(*) from (" + sql + ")a"; Connection connection = (Connection)invocation.getArgs()[0]; PreparedStatement countStatement = connection.prepareStatement(countSql); ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler"); parameterHandler.setParameters(countStatement); ResultSet rs = countStatement.executeQuery(); Map<?,?> parameter = (Map<?,?>)boundSql.getParameterObject(); Page page = (Page)parameter.get("page"); if(rs.next()) { page.setTotalNumber(rs.getInt(1)); } // 改造后带分页查询的SQL语句 String pageSql = sql + " limit " + page.getDbIndex() + "," + page.getDbNumber(); 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) { } }
添加插件mybatis-config.xml
在mybatis的配置文件中添加插件配置,也就是我们刚刚编写的那个实现Interceptor接口的类。
<plugins> <plugin interceptor="com.seu.fn.interceptor.PageInterceptor"> </plugin> </plugins>
测试
参数中需要在map中传入“page”对象,如果需要同时也可以map.put()其他的查询条件。
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration({"classpath:spring/applicationContext-dao.xml"}) public class StudentsMapperTest { @Autowired StudentsMapper studentsMapper; @Test public void selectAllByPage(){ Map<String,Object> map = new HashMap<>(); Page page = new Page(); page.setCurrentPage(2); map.put("page",page); List<Students> list = studentsMapper.selectAllByPage(map); for (Students s:list){ System.out.println(s); } } }
测试输出:
Students{id='06', name='name6', age=null,hobby='null', classandgradeid='1'}
Students{id='07', name='name7', age=null,hobby='null', classandgradeid='1'}
Students{id='08', name='name8', age=null,hobby='null', classandgradeid='1'}
Students{id='09', name='name9', age=null,hobby='null', classandgradeid='1'}
Students{id='10', name='name10', age=null,hobby='null', classandgradeid='1'}
使用PageHelper插件实现
PageHelper插件用起来比较方便,不用改自己的代码。该方法其实也是基于拦截器实现的,只是用起来更方便,不用自己实现Interceptor接口,只需要在返回List<?>的select方法前调用PageHelper.startPage(int,int)方法,即可。
pom.xml依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
在mybatis-config.xml中配置拦截器插件(同上):
<plugins> <!-- com.github.pagehelper为PageHelper类所在包名 --> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- 设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库可以自动识别,不用再配置下面的property--> <!--<property name="dialect" value="mysql"/>--> </plugin> </plugins>
mapper文件:
在StudentsMapper.java接口中声明该方法(不需要传page信息参数)。
List<Students> selectAll();
在StudentsMapper.xml文件中配置对应id的sql语句,普通的SQL语句配置。
<select id="selectAll"resultMap="BaseResultMap">
select id, name, age, hobby,classAndGradeId
from students
</select>
如何使用:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration({"classpath:spring/applicationContext-dao.xml"}) public class StudentsMapperTest { @Autowired StudentsMapper studentsMapper; @Test public void selectAll() { //获取第2页,5条内容,默认查询总数count PageHelper.startPage(2, 5); //紧跟着的第一个select方法会被分页 List<Students> list = studentsMapper.selectAll(); PageInfo page = new PageInfo(list); //PageInfo包含了非常全面的分页属性 System.out.println(page.getPageNum());//2 System.out.println(page.getPageSize());//5 System.out.println(page.getStartRow());//6 System.out.println(page.getEndRow());//10 System.out.println(page.getTotal());//15 System.out.println(page.getPages());//3 System.out.println(page.isIsFirstPage());//false System.out.println(page.isIsLastPage());//false System.out.println(page.isHasPreviousPage());//true System.out.println(page.isHasNextPage());//true for (Students s:list){ System.out.println(s); } } }
测试输出:
Students{id='06', name='name6', age=null,hobby='null', classandgradeid='1'}
Students{id='07', name='name7', age=null,hobby='null', classandgradeid='1'}
Students{id='08', name='name8', age=null,hobby='null', classandgradeid='1'}
Students{id='09', name='name9', age=null,hobby='null', classandgradeid='1'}
Students{id='10', name='name10', age=null,hobby='null', classandgradeid='1'}
和上面结果相同,这种方法是使用第三方插件(实现原理都相同),比较方便,容易上手。