拦截器分页的原理:
分页拦截器,用于拦截需要进行分页查询的操作,然后对其进行分页处理。 利用拦截器实现Mybatis分页的原理: 要利用JDBC对数据库进行操作就必须要有一个对应的Statement对象 ,Mybatis在执行Sql语句前就会产生一个包含Sql语句的Statement对象,而且对应的Sql语句 是在Statement之前产生的,所以我们就可以在它生成Statement之前对用来生成Statement的Sql语句下手。在Mybatis中Statement语句是通过RoutingStatementHandler对象的prepare方法生成的。所以利用拦截器实现Mybatis分页的一个思路就是拦截StatementHandler接口的prepare方法,然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句,之后再调用StatementHandler对象的prepare方法,即调用invocation.proceed()。对于分页而言,在拦截器里面我们还需要做的一个操作就是统计满足当前条件的记录一共有多少,这是通过获取到了原始的Sql语句后,把它改为对应的统计语句再利用Mybatis封装好的参数和设置参数的功能把Sql语句中的参数进行替换,之后再执行查询记录数的Sql语句进行总记录数的统计。
首先在src下建立mybatis配置文件 ,这里只是用来配置拦截器数据库类型。mybatis拦截器翻页支持oracle和mysql数据库。
mybatis-config.xml:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 拦截器翻页时使用 支持oracle 和 mysql 数据库 通过databaseType来区分 --> <plugins> <plugin interceptor="com.etoak.page.PageInterceptor"> <property name="databaseType" value="mysql"/> </plugin> </plugins> </configuration>
然后在spring配置文件中添加mybatis配置文件指向。
即在applicationContext.xml中找到bean ssf ,修改如下:
<bean id="ssf" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- mybatis配置文件 --> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="dataSource" ref="ds"> </property> </bean>
src下建立包com.etoak.page ,增加如下3个工具类:
拦截器PageInterceptor.java
@Intercepts({ @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
public class PageInterceptor implements Interceptor {
private String databaseType;// 数据库类型,不同的数据库有不同的分页方法
/** * 拦截后要执行的方法 */
public Object intercept(Invocation invocation) throws Throwable {
final RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget(); // 通过反射获取到当前RoutingStatementHandler对象的delegate属性
final StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate");// 获取到当前StatementHandler的boundSql,这里不管是调用handler.getBoundSql()还是直接调用delegate.getBoundSql()结果是一样的,因为之前已经说过了
// RoutingStatementHandler实现的所有StatementHandler接口方法里面都是调用的delegate对应的方法。
final BoundSql boundSql = delegate.getBoundSql(); // 拿到当前绑定Sql的参数对象,就是我们在调用对应的Mapper映射语句时所传入的参数对象
final Object obj = boundSql.getParameterObject(); // 这里我们简单的通过传入的是Page对象就认定它是需要进行分页操作的。
if (obj instanceof SearchPageUtil) {
final SearchPageUtil page = (SearchPageUtil) obj; // 通过反射获取delegate父类BaseStatementHandler的mappedStatement属性
// MappedStatement mappedStatement = (MappedStatement)
// ReflectUtil.getFieldValue(delegate, "mappedStatement");
// 拦截到的prepare方法参数是一个Connection对象
// Connection connection = (Connection) invocation.getArgs()[0];
// 获取当前要执行的Sql语句,也就是我们直接在Mapper映射语句中写的Sql语句
final String sql = boundSql.getSql();
// 给当前的page参数对象设置总记录数 影响性能
// this.setTotalRecord(page, mappedStatement, connection);
// 获取分页Sql语句
final String pageSql = this.getPageSql(page, sql); // 利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
ReflectUtil.setFieldValue(boundSql, "sql"</span>, pageSql);
}
return</span> invocation.proceed();
}
/** * 拦截器对应的封装原始对象的方法 */
public Object plugin(Object target) { return Plugin.wrap(target, this); }
/** * 设置注册拦截器时设定的属性 */
public void setProperties(Properties properties) {
this.databaseType = properties.getProperty("databaseType");
}
/** * 根据page对象获取对应的分页查询Sql语句,这里只做了两种数据库类型,Mysql和Oracle 其它的数据库都 没有进行分页 * *
@param page
@param sql
@return*/
private String getPageSql(SearchPageUtil page, String sql) {
final StringBuffer sqlBuffer = new StringBuffer(sql);
if ("mysql".equalsIgnoreCase(databaseType)) {
return getMysqlPageSql(page, sqlBuffer);
} else
if ("oracle".equalsIgnoreCase(databaseType)) {
return getOraclePageSql(page, sqlBuffer);
}
return sqlBuffer.toString();
}
/** * 获取Mysql数据库的分页查询语句 * *
@param page * 分页对象 *
@param sqlBuffer * 包含原sql语句的StringBuffer对象 *
@return Mysql数据库分页语句
*/
private String getMysqlPageSql(SearchPageUtil page, StringBuffer sqlBuffer) {
// 计算第一条记录的位置,Mysql中记录的位置是从0开始的。
// int offset = (page.getPage().getPageIndex() - 1)
// page.getPageSize();
sqlBuffer.append(" limit ").append(page.getStartRow()).append(",").append(page.getPageSize());
return sqlBuffer.toString();
}
/** * 获取Oracle数据库的分页查询语句 * *
@param page * 分页对象 *
@param sqlBuffer * 包含原sql语句的StringBuffer对象 *
@return Oracle数据库的分页查询语句 */
private String getOraclePageSql(SearchPageUtil page, StringBuffer sqlBuffer) {
// 计算第一条记录的位置,Oracle分页是通过rownum进行的,而rownum是从1开始的
final int offset = (page.getPage().getPageIndex() - 1) * page.getPageSize() + 1;
sqlBuffer.insert(0, "select u.*, rownum r from (").append(") u where rownum < ") .append(offset + page.getPageSize());
sqlBuffer.insert(0, "select * from (").append(") where r >= ").append(offset);
// 上面的Sql语句拼接之后大概是这个样子:
// select * from (select u.*, rownum r from (select * from t_user) u where rownum < 31) where r >= 16
return sqlBuffer.toString();
}
/** * 给当前的参数对象page设置总记录数 * *
@param page * Mapper映射语句对应的参数对象 *
@param mappedStatement * Mapper映射语句 *
@param connection * 当前的数据库连接 */
private void setTotalRecord(SearchPageUtil page, MappedStatement mappedStatement, Connection connection) {
// 获取对应的BoundSql,这个BoundSql其实跟我们利用StatementHandler获取到的BoundSql是同一个对象。
// delegate里面的boundSql也是通过mappedStatement.getBoundSql(paramObj)方法获取到的。
final BoundSql boundSql = mappedStatement.getBoundSql(page);
// 获取到我们自己写在Mapper映射语句中对应的Sql语句
final String sql = boundSql.getSql();
// 通过查询Sql语句获取到对应的计算总记录数的sql语句
final String countSql = this.getCountSql(sql);
// 通过BoundSql获取对应的参数映射
final List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// 利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象page建立查询记录数对应的BoundSql对象。
final BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page);
// 通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象
final ParameterHandler parameterHandler = new org.apache.ibatis.executor.parameter.DefaultParameterHandler(mappedStatement, page, countBoundSql);
// 通过connection建立一个countSql对应的PreparedStatement对象。
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = connection.prepareStatement(countSql);
// 通过parameterHandler给PreparedStatement对象设置参数
parameterHandler.setParameters(pstmt);
// 之后就是执行获取总记录数的Sql语句和获取结果了。
rs = pstmt.executeQuery();
if (rs.next()) {
final int totalRecord = rs.getInt(1); // 给当前的参数page对象设置总记录数
page.getPage().setRowTotal(totalRecord);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
}
catch (SQLException e) {
e.printStackTrace();
}
}
}
/** * 根据原Sql语句获取对应的查询总记录数的Sql语句 * *
@param sql *
@return*/
private String getCountSql(String sql) {
final int index = sql.indexOf("from");
return"select count(*) " + sql.substring(index);
}
/** * 利用反射进行操作的一个工具类 * */
private static class ReflectUtil {
/** * 利用反射获取指定对象的指定属性 * *
@param obj * 目标对象 *
@param fieldName * 目标属性 *
@return 目标属性的值 */
public static Object getFieldValue(Object obj, String fieldName) {
Object result = null;
final Field field = ReflectUtil.getField(obj, fieldName);
if (field != null) {
field.setAccessible(true);
try {
result = field.get(obj);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return result;
}
/** * 利用反射获取指定对象里面的指定属性 * *
@param obj * 目标对象 *
@param fieldName * 目标属性 *
@return 目标字段 */
private static Field getField(Object obj, String fieldName) {
Field field = null;
for (Class<?> clazz = obj.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
try {
field = clazz.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e) {
// 这里不用做处理,子类没有该字段可能对应的父类有,都没有就返回null。
}
}
return field;
}
/** * 利用反射设置指定对象的指定属性为指定的值 * *
@param obj * 目标对象 *
@param fieldName * 目标属性 *
@param fieldValue * 目标值 */
public static void setFieldValue(Object obj, String fieldName, String fieldValue) {
final Field field = ReflectUtil.getField(obj, fieldName);
if (field != null) {
try {
field.setAccessible(rue); field.set(obj, fieldValue);
}catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
SearchPageUtil.java
public class SearchPageUtil {
// 查询对象
private Object object;
private String[] orderBys; // 排序字段
private int startRow; // 开始行
private int pageSize;//页数
private String filter;// 条件字符串
private String orderBy; // 排序字符串
// 分页类
private Page page;
public Object getObject() { return object; }
public void setObject(Object object) { this.object = object; }
public int getStartRow() { return startRow; }
public void setStartRow(int startRow) { this.startRow = startRow; }
public String getFilter() { return filter; } public int getPageSize() { return pageSize; }
public void setPageSize (int pageSize) { this.pageSize = pageSize; }
public void setFilter(String filter) { this.filter = filter; }
public String[] getOrderBys() { return orderBys; }
public void setOrderBys(String[] orderBys) { this.orderBys = orderBys; }
public String getOrderBy() { return orderBy; }
public void setOrderBy(String orderBy) { this.orderBy = orderBy; }
public Page getPage() { return page; }
public void setPage(Page page) { this.startRow = (page.getPageIndex() - 1) * page.getPageSize();
this.pageSize = page.getPageSize();
this.page = page;
}
}
Page.java
public class Page {
// 第几页
private int pageIndex; // 每页大小
private int pageSize; // 总数
private int rowTotal; // 总共多少页
private int pageTotal;
public Page(int pageIndex, int pageSize, int rowTotal) {
this.pageIndex = pageIndex;
this.pageSize = pageSize;
this.rowTotal = rowTotal;
}
public int getRowTotal() { return rowTotal; }
public void setRowTotal(int rowTotal) { this.rowTotal = rowTotal; }
public int getPageIndex() { return pageIndex; }
public void setPageIndex(int pageIndex) { this.pageIndex = pageIndex; }
public int getPageSize() { return pageSize; }
public void setPageSize(int pageSize) { this.pageSize = pageSize; }
public int getPageTotal() {
int pageTotal = 0;
if (rowTotal % pageSize == 0) {
pageTotal = this.rowTotal / this.pageSize;
} else {
pageTotal = rowTotal / pageSize + 1;
}
return pageTotal;
}
public void setPageTotal(int pageTotal) {
this.pageTotal = pageTotal;
} }
修改StudentDaoIF.java中的selectAllStudents方法
/** * 根据条件查询 *
@param searchPageUtil *
@return */
@Select("select * from student")
@Results({
@Result(column = "id" , property = "id"),
@Result(column = "name" , property = "name"),
@Result(column = "password", property = "password")
})
public List<Student> selectAllStudents(SearchPageUtil searchPageUtil);
修改service层方法:
@Override
public List<Student> selectAllStudents(Student stu,Page page) {
SearchPageUtil searchPageUtil = new SearchPageUtil();
String a[] = { "name desc", "id asc" };
searchPageUtil.setOrderBys(a);
searchPageUtil.setPage(page);
searchPageUtil.setObject(stu);
final List list = dao.selectAllStudents(searchPageUtil);
return list; }
注:我们可以看到 这里可以使用searchPageUtil工具类中的方法进行拼接sql
controller层:
private Page page;
private int pageIndex = 1;
private int pageSize = 10;
private int total = 0;
@RequestMapping("/ShowStudents.do")
@ResponseBody
public Map showStudents(HttpServletRequest request,HttpServletResponse response) throws Exception{
pageIndex = Integer.parseInt(request.getParameter("page"));
total = dao.StudentCount();
page = new Page(pageIndex,pageSize,total);
pageSize = Integer.parseInt(request.getParameter("rows"));
Map map = new HashMap();
List<Student> students = dao.selectAllStudents(stu,page);
map.put("rows",students);
map.put("total", total);
System.out.println(map);
return map;
}
注:使用easyui一定要返回rows、total两个参数
本文介绍如何使用Mybatis拦截器实现分页查询功能,包括配置拦截器、编写拦截器类以及实现分页逻辑的具体步骤,适用于数据库类型为Oracle和MySQL。
1764





