Mybatis使用拦截器自动分页实现/使用反射替换sql代码分页
2015-05-14 16:20
681 查看
本文章内容代码来自互联网,扒拉下来使用无参数的分页成功,但是带参数的分页就抛出异常。本代码解决了该异常。使用mysql成功。oracle 没有环境。没有测试。这个是改造之后的,最后有详细注释的版本。
MyBatis允许开发者在StatementHandler、ResultSetHandler、ParameterHandler以及Executor插入自己想执行的代码,使用plugin是将你插件放入到MyBatis的插件集合中去。
1、使用拦截到的对象中的参数,我们按照正常的初始化的调用过程需要用到的参数来创建BoundSql,ParameterHandler 等步骤,把正常提交的sql改造成查询总数的sql语句。给分页参数赋值。
2、把目标sql改造成分页sql,使用反射把提交的sql替换。把提交的page对象替换成与mapper.xml中对应的参数
由于page对象是引用类型的。所以里面的分页参数值就有了,根据调用的mapper方法也得到了分页后的结果
(ThreadLocal版本)Mybatis使用拦截器自动分页实现/使用反射替换sql代码分页
未分页的方法:mapper.xml
未分页mapper.xml中selectByPid所对应的mapper接口
不分页,调用方式
如果要把mapper.xml中对应的selectByPid 加上分页的功能,可如下操作:
1、在mapper接口中添加同名方法,但是参数是固定的Page
2.客户端调用方式
由于 page对象更改了泛型,在上面版本中与page对象相关的都要进行修改:修改如下:
1、在mapper接口中添加同名方法,但是参数是固定的Page
2.客户端调用方式
未分页mapper.xml中selectByPidTest所对应的mapper接口
不分页,调用方式
如果要把mapper.xml中对应的selectByPid 加上分页的功能,可如下操作:
1、在mapper接口中添加同名方法,但是参数是固定的Page
2.客户端调用方式
简单总结下思路:
MyBatis允许开发者在StatementHandler、ResultSetHandler、ParameterHandler以及Executor插入自己想执行的代码,使用plugin是将你插件放入到MyBatis的插件集合中去。
1、使用拦截到的对象中的参数,我们按照正常的初始化的调用过程需要用到的参数来创建BoundSql,ParameterHandler 等步骤,把正常提交的sql改造成查询总数的sql语句。给分页参数赋值。
2、把目标sql改造成分页sql,使用反射把提交的sql替换。把提交的page对象替换成与mapper.xml中对应的参数
由于page对象是引用类型的。所以里面的分页参数值就有了,根据调用的mapper方法也得到了分页后的结果
(ThreadLocal版本)Mybatis使用拦截器自动分页实现/使用反射替换sql代码分页
改造后的版本
Page对象定义
package org.zq.beans.intf; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 分页返回结果对象 * @param <T> */ public class Page<T> { private int pageNo = 1;//页码,默认是第一页 private int pageSize = 10;//每页显示的记录数,默认是10 private int totalRecord;//总记录数 private int totalPage;//总页数 private List<T> results;//对应的当前页记录 private Map<String, Object> params = new HashMap<String, Object>();//其他的参数我们把它分装成一个Map对象 private boolean hasNextPage; /** * 是否有下一页 * @return */ public boolean hasNextPage(){ return totalPage - pageNo != 0; } public boolean isHasNextPage() { hasNextPage = this.hasNextPage(); return this.hasNextPage; } 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; //在设置总页数的时候计算出对应的总页数,在下面的三目运算中加法拥有更高的优先级,所以最后可以不加括号。 int totalPage = totalRecord%pageSize==0 ? totalRecord/pageSize : totalRecord/pageSize + 1; this.setTotalPage(totalPage); } public int getTotalPage() { return totalPage; } public void setTotalPage(int totalPage) { this.totalPage = totalPage; } public List<T> getResults() { return results; } public void setResults(List<T> results) { this.results = results; } public Map<String, Object> getParams() { return params; } public void setParams(Map<String, Object> params) { this.params = params; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Page [pageNo=").append(pageNo).append(", pageSize=") .append(pageSize).append(", results=").append(results).append( ", totalPage=").append(totalPage).append( ", totalRecord=").append(totalRecord).append( ",hasNextPage").append(this.hasNextPage()).append("]"); return builder.toString(); } }
Interceptor拦截器实现
package org.zq.core.common.util.page; import org.apache.ibatis.executor.parameter.DefaultParameterHandler; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.executor.statement.RoutingStatementHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.plugin.*; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import java.util.Properties; /** * Created by zhuqiang on 2015/5/14. */ @Intercepts({ @Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class})}) //标识拦截StatementHandler public class MyBatisPageInterceptor implements Interceptor { private String databaseType = "mysql";//数据库类型,不同的数据库有不同的分页方法,默认mysql,在拦截器注入的时候可改变支持分页的数据库 /** * 拦截后要执行的方法, * 在拦截之前,也会经过下面改造获取总数的sql类似的步骤。在BaseStatementHandler中,创建parameterHandler和resultSetHandler等 */ @Override public Object intercept(Invocation invocation) throws Throwable { RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget(); StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate"); BoundSql boundSql = delegate.getBoundSql();//获取该mapper映射的参数信息和传递的参数信息 Object obj = boundSql.getParameterObject(); //获得传递的参数 if (obj instanceof Page<?>) { //只拦截参数是page的mapper方法 Page<?> page = (Page<?>) obj; MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement"); //获取mapper方法与xml的映射信息 Connection connection = (Connection) invocation.getArgs()[0]; String sql = boundSql.getSql(); this.setTotalRecord(page, mappedStatement, connection); String pageSql = this.getPageSql(page, sql); ReflectUtil.setFieldValue(boundSql, "sql", pageSql); //把客户端提交的sql语句替换成分页sql语句 ReflectUtil.setFieldValue(boundSql, "parameterObject", page.getParams()); //把分页mapper方法中提交的参数,替换成未分页的mapper。xml中映射的参数 ParameterHandler parameterHandler = delegate.getParameterHandler(); ReflectUtil.setFieldValue(parameterHandler, "parameterObject", page.getParams()); //把分页mapper方法中提交的参数,替换成未分页的mapper。xml中映射的参数 } return invocation.proceed(); } /** * 拦截器对应的封装原始对象的方法,拦截之前先执行此方法 */ @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } /** * 设置注册拦截器时设定的属性,启动容器初始化的时候就赋值了 */ @Override public void setProperties(Properties properties) { this.databaseType = properties.getProperty("databaseType"); } /** * 根据page对象获取对应的分页查询Sql语句,这里只做了两种数据库类型,Mysql和Oracle * 其它的数据库都 没有进行分页 * * @param page 分页对象 * @param sql 原sql语句 * @return */ private String getPageSql(Page<?> page, String sql) { 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(Page<?> page, StringBuffer sqlBuffer) { //计算第一条记录的位置,Mysql中记录的位置是从0开始的。 int offset = (page.getPageNo() - 1) * page.getPageSize(); sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize()); return sqlBuffer.toString(); } /** * 获取Oracle数据库的分页查询语句 * * @param page 分页对象 * @param sqlBuffer 包含原sql语句的StringBuffer对象 * @return Oracle数据库的分页查询语句 */ private String getOraclePageSql(Page<?> page, StringBuffer sqlBuffer) { //计算第一条记录的位置,Oracle分页是通过rownum进行的,而rownum是从1开始的 int offset = (page.getPageNo() - 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(Page<?> page, MappedStatement mappedStatement, Connection connection) { BoundSql boundSql = mappedStatement.getBoundSql(page); String sql = boundSql.getSql(); String countSql = this.getCountSql(sql); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();//获取映射需要的参数 BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page.getParams());//根据配置,新的sql语句。和sql对应的参数创建对象 ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page.getParams(), countBoundSql);//StatementHandler、ResultSetHandler、ParameterHandler运行在这几个中插入自己的代码 PreparedStatement pstmt = null; ResultSet rs = null; try { pstmt = connection.prepareStatement(countSql); //创建一个 PreparedStatement 对象来将参数化的 SQL 语句发送到数据库。 parameterHandler.setParameters(pstmt); rs = pstmt.executeQuery(); if (rs.next()) { int totalRecord = rs.getInt(1); page.setTotalRecord(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) { int indexLo = sql.indexOf("from"); int indexUp = sql.indexOf("FROM"); int index = 0; if (indexLo != -1) { index = indexLo; } if (indexUp != -1) { index = indexUp; } return "select count(1) " + sql.substring(index); } /** * 利用反射进行操作的一个工具类 */ private static class ReflectUtil { /** * 利用反射获取指定对象的指定属性 * * @param obj 目标对象 * @param fieldName 目标属性 * @return 目标属性的值 */ public static Object getFieldValue(Object obj, String fieldName) { Object result = null; 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, Object fieldValue) { Field field = ReflectUtil.getField(obj, fieldName); if (field != null) { try { field.setAccessible(true); field.set(obj, fieldValue); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } }
加载拦截器配置
<!-- MyBatis配置 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:mybatis-configuration.xml" /> <property name="plugins"> <bean class="org.zq.core.common.util.page.MyBatisPageInterceptor"> <property name="properties"> <!-- 指定是什么类型的数据库--> <map> <entry key="databaseType" value="mysql"></entry> </map> </property> </bean> </property> </bean>
测试
参数类型只能为map示例
以下为按父id查询区域的配置和mapper的方法未分页的方法:mapper.xml
<!--按父id查询--> <select id="selectByPid" parameterType="string" resultType="AreaDto"> SELECT AREAID, AREANAME, PID FROM PM_AREA WHERE PID =#{pid} AND DELFLAG = 'N' </select>
未分页mapper.xml中selectByPid所对应的mapper接口
/** * 按父id查询 * @param pid 父id * @return */ List<AreaDto> selectByPid(String pid);
不分页,调用方式
List<AreaDto> list = areaMapper.selectByPid("402881ea4b2f5279014b2f52aa3c0011");
如果要把mapper.xml中对应的selectByPid 加上分页的功能,可如下操作:
1、在mapper接口中添加同名方法,但是参数是固定的Page
<T>page对象
/** * 按父id查询:原始未分页接口 * @param pid 父id * @return */ List<AreaDto> selectByPid(String pid); /** *按父id查询,定义page参数,查询参数放置到page的map里面 */ List<AreaDto> selectByPid(Page<AreaDto> page);
2.客户端调用方式
Page<AreaDto> page = new Page<AreaDto>(); page.setPageNo(1); // 设置当前页 page.getParams().put("pid", "402881ea4b2f5279014b2f52aa3c0011"); //设置查询参数 List<AreaDto> result = areaMapper.selectByPid(page); //调用mapper 的查询分页方法 page.setResults(result); //设置值到page中 给客户端返回 page 对象
对于查询参数的优化改造描述:
上面的版本,查询参数必须放入map中,优化版本后,再提供了一个泛型查询参数。使用方法如下:由于 page对象更改了泛型,在上面版本中与page对象相关的都要进行修改:修改如下:
1、在mapper接口中添加同名方法,但是参数是固定的Page
<T,PT>page对象
/** * 按父id查询:原始未分页接口 * @param pid 父id * @return */ List<AreaDto> selectByPid(String pid); /** *查询参数使用 AreaDto 包装 */ List<AreaDto> selectByPid(Page<AreaDto,AreaDto> page);
2.客户端调用方式
Page<AreaDto,AreaDto> page = new Page<AreaDto,AreaDto>(); page.setPageNo(1); // 设置当前页 AreaDto paramsT = new AreaDto(); paramsT.setPid("402881ea4b2f5279014b2f52aa3c0011"); page.setParamsT(paramsT); List<AreaDto> result = areaMapper.selectByPid(page); //调用mapper 的查询分页方法 page.setResults(result); //设置值到page中 给客户端返回 page 对象
如果参数类型为 嵌套对象参数 查询语句;测试如下
未分页的方法:mapper.xml<!--按父id查询--> <select id="selectByPidTest" parameterType="AreaDto" resultType="AreaDto"> SELECT AREAID, AREANAME, PID FROM PM_AREA WHERE PID =#{test.pid} AND DELFLAG = 'N' </select>
未分页mapper.xml中selectByPidTest所对应的mapper接口
/** * 按父id查询 * @param areaDto.test.pid 父id * @return */ List<AreaDto> selectByPidTest(AreaDto areaDto);
不分页,调用方式
AreaDto paramsT = new AreaDto(); AreaDto test = new AreaDto(); test.setPid("402881ea4b2f5279014b2f52aa3c0011"); paramsT.setTest(test); List<AreaDto> result = areaMapper.selectByPidTest(paramsT);
如果要把mapper.xml中对应的selectByPid 加上分页的功能,可如下操作:
1、在mapper接口中添加同名方法,但是参数是固定的Page
<T,PT>page对象
List<AreaDto> selectByPidTest(AreaDto areaDto); List<AreaDto> selectByPidTest(Page<AreaDto,AreaDto> page);
2.客户端调用方式
Page<AreaDto,AreaDto> page = new Page<AreaDto,AreaDto>(); page.setPageNo(1); // 设置当前页 AreaDto paramsT = new AreaDto(); AreaDto test = new AreaDto(); test.setPid("402881ea4b2f5279014b2f52aa3c0011"); paramsT.setTest(test); page.setParamsT(paramsT); List<AreaDto> result = areaMapper.selectByPidTest(page); //调用mapper 的查询分页方法 page.setResults(result); //设置值到page中
详细注释的版本
本文章内容代码来自互联网,扒拉下来使用无参数的分页成功,但是带参数的分页就抛出异常。本代码解决了该异常。使用mysql成功。其他的待续改造Page对象定义
package org.zq.core.common.util.page; import java.util.HashMap; import java.util.List; import java.util.Map; public class Page<T> { private int pageNo = 1;//页码,默认是第一页 private int pageSize = 5;//每页显示的记录数,默认是15 private int totalRecord;//总记录数 private int totalPage;//总页数 private List<T> results;//对应的当前页记录 private Map<String, Object> params = new HashMap<String, Object>();//其他的参数我们把它分装成一个Map对象 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; //在设置总页数的时候计算出对应的总页数,在下面的三目运算中加法拥有更高的优先级,所以最后可以不加括号。 int totalPage = totalRecord%pageSize==0 ? totalRecord/pageSize : totalRecord/pageSize + 1; this.setTotalPage(totalPage); } public int getTotalPage() { return totalPage; } public void setTotalPage(int totalPage) { this.totalPage = totalPage; } public List<T> getResults() { return results; } public void setResults(List<T> results) { this.results = results; } public Map<String, Object> getParams() { return params; } public void setParams(Map<String, Object> params) { this.params = params; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Page [pageNo=").append(pageNo).append(", pageSize=") .append(pageSize).append(", results=").append(results).append( ", totalPage=").append(totalPage).append( ", totalRecord=").append(totalRecord).append("]"); return builder.toString(); } }
Interceptor拦截器实现
package org.zq.core.common.util.page; import org.apache.ibatis.executor.parameter.DefaultParameterHandler; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.executor.statement.RoutingStatementHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.plugin.*; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import java.util.Properties; /** * Created by zhuqiang on 2015/5/14. */ @Intercepts( { @Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class}) }) public class MyMyBatisPage implements Interceptor { private String databaseType = "mysql";//数据库类型,不同的数据库有不同的分页方法 /** * 拦截后要执行的方法 */ public Object intercept(Invocation invocation) throws Throwable { //对于StatementHandler其实只有两个实现类,一个是RoutingStatementHandler,另一个是抽象类BaseStatementHandler, //BaseStatementHandler有三个子类,分别是SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler, //SimpleStatementHandler是用于处理Statement的,PreparedStatementHandler是处理PreparedStatement的,而CallableStatementHandler是 //处理CallableStatement的。Mybatis在进行Sql语句处理的时候都是建立的RoutingStatementHandler,而在RoutingStatementHandler里面拥有一个 //StatementHandler类型的delegate属性,RoutingStatementHandler会依据Statement的不同建立对应的BaseStatementHandler,即SimpleStatementHandler、 //PreparedStatementHandler或CallableStatementHandler,在RoutingStatementHandler里面所有StatementHandler接口方法的实现都是调用的delegate对应的方法。 //我们在PageInterceptor类上已经用@Signature标记了该Interceptor只拦截StatementHandler接口的prepare方法,又因为Mybatis只有在建立RoutingStatementHandler的时候 //是通过Interceptor的plugin方法进行包裹的,所以我们这里拦截到的目标对象肯定是RoutingStatementHandler对象。 RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget(); //通过反射获取到当前RoutingStatementHandler对象的delegate属性 StatementHandler delegate = (StatementHandler)ReflectUtil.getFieldValue(handler, "delegate"); //获取到当前StatementHandler的 boundSql,这里不管是调用handler.getBoundSql()还是直接调用delegate.getBoundSql()结果是一样的,因为之前已经说过了 //RoutingStatementHandler实现的所有StatementHandler接口方法里面都是调用的delegate对应的方法。 BoundSql boundSql = delegate.getBoundSql(); //拿到当前绑定Sql的参数对象,就是我们在调用对应的Mapper映射语句时所传入的参数对象 Object obj = boundSql.getParameterObject(); //这里我们简单的通过传入的是Page对象就认定它是需要进行分页操作的。 if (obj instanceof Page<?>) { Page<?> page = (Page<?>) obj; //通过反射获取delegate父类BaseStatementHandler的mappedStatement属性 MappedStatement mappedStatement = (MappedStatement)ReflectUtil.getFieldValue(delegate, "mappedStatement"); //拦截到的prepare方法参数是一个Connection对象 Connection connection = (Connection)invocation.getArgs()[0]; //获取当前要执行的Sql语句,也就是我们直接在Mapper映射语句中写的Sql语句 String sql = boundSql.getSql(); //给当前的page参数对象设置总记录数 this.setTotalRecord(page, mappedStatement, connection); //获取分页Sql语句 String pageSql = this.getPageSql(page, sql); //利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句 ReflectUtil.setFieldValue(boundSql, "sql", pageSql); //*** zq :在框架取值的时候,会从两个地方取值。主要的是在 ParameterHandler 中,所以在这里。需要把参数给替换成map //org.apache.ibatis.reflection.MetaObject 类中 123 行 , return objectWrapper.get(prop); 取值是取ParameterHandler中的。所以需要替换掉里面的参数 ReflectUtil.setFieldValue(boundSql, "parameterObject", page.getParams()); ParameterHandler parameterHandler = delegate.getParameterHandler(); ReflectUtil.setFieldValue(parameterHandler, "parameterObject", page.getParams()); } return invocation.proceed(); } /** * 拦截器对应的封装原始对象的方法 */ public Object plugin(Object target) { return Plugin.wrap(target, this); } /** * 设置注册拦截器时设定的属性 */ public void setProperties(Properties properties) { // this.databaseType = properties.getProperty("databaseType"); this.databaseType = "mysql"; } /** * 根据page对象获取对应的分页查询Sql语句,这里只做了两种数据库类型,Mysql和Oracle * 其它的数据库都 没有进行分页 * * @param page 分页对象 * @param sql 原sql语句 * @return */ private String getPageSql(Page<?> page, String sql) { 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(Page<?> page, StringBuffer sqlBuffer) { //计算第一条记录的位置,Mysql中记录的位置是从0开始的。 int offset = (page.getPageNo() - 1) * page.getPageSize(); sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize()); return sqlBuffer.toString(); } /** * 获取Oracle数据库的分页查询语句 * @param page 分页对象 * @param sqlBuffer 包含原sql语句的StringBuffer对象 * @return Oracle数据库的分页查询语句 */ private String getOraclePageSql(Page<?> page, StringBuffer sqlBuffer) { //计算第一条记录的位置,Oracle分页是通过rownum进行的,而rownum是从1开始的 int offset = (page.getPageNo() - 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(Page<?> page, MappedStatement mappedStatement, Connection connection) { //获取对应的BoundSql,这个BoundSql其实跟我们利用StatementHandler获取到的BoundSql是同一个对象。 //delegate里面的boundSql也是通过mappedStatement.getBoundSql(paramObj)方法获取到的。 BoundSql boundSql = mappedStatement.getBoundSql(page); //获取到我们自己写在Mapper映射语句中对应的Sql语句 String sql = boundSql.getSql(); //通过查询Sql语句获取到对应的计算总记录数的sql语句 String countSql = this.getCountSql(sql); //通过BoundSql获取对应的参数映射 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); //利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象(page中的参数map)建立查询记录数对应的BoundSql对象。 //*** zq :在框架取值的时候,会从两个地方取值。主要的是在 ParameterHandler 中,所以在这里。需要把参数给替换成map BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page.getParams()); //*** zq :在框架取值的时候,会从两个地方取值。主要的是在 ParameterHandler 中,所以在这里。需要把参数给替换成map ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page.getParams(), countBoundSql); //通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象 // ParameterHandler parameterHandler = new 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()) { int totalRecord = rs.getInt(1); //给当前的参数page对象设置总记录数 page.setTotalRecord(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) { int indexLo = sql.indexOf("from"); int indexUp = sql.indexOf("FROM"); int index = 0; if(indexLo != -1){ index = indexLo; } if(indexUp != -1){ index = indexUp; } 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; Field field = ReflectUtil.getField(obj, fieldName); if (field != null) { field.setAccessible(true); try { result = field.get(obj); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block 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) { Field field = ReflectUtil.getField(obj, fieldName); if (field != null) { try { field.setAccessible(true); field.set(obj, fieldValue); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** * 利用反射设置指定对象的指定属性为指定的值 * @param obj 目标对象 * @param fieldName 目标属性 * @param fieldValue 目标值 */ public static void setFieldValue(Object obj, String fieldName, Object fieldValue) { Field field = ReflectUtil.getField(obj, fieldName); if (field != null) { try { field.setAccessible(true); field.set(obj, fieldValue); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
加载拦截器配置
<!-- MyBatis配置 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:mybatis-configuration.xml" /> <property name="plugins"> <!-- 以插件的形式提供拦截器--> <bean class="org.zq.core.common.util.page.MyMyBatisPage"></bean> </property> </bean>
使用方式:
假如入要查询城市:mapper.xml
<!--按父id查询--> <select id="selectByPid" parameterType="string" resultType="AreaDto"> SELECT AREAID, AREANAME, PID FROM PM_AREA WHERE PID =#{pid} AND DELFLAG = 'N' </select>
mapper接口
/** * 按父id查询 * @param pid 父id * @return */ List<AreaDto> selectByPid(String pid); //无分页方式,对应xml中的selectByPid语句 List<AreaDto> selectByPid(Page<AreaDto> page); //定义page参数,查询参数放置到page的map里面,把上面的无分页方式的参数放置到了page中。拦截器会拦截该方法,完成处理后返回结果
测试示例
Page<AreaDto> page = new Page<AreaDto>(); page.setPageNo(1); // 设置当前页 page.getParams().put("pid","402881ea4b2f5279014b2f52aa3c0011"); //设置查询参数 List<AreaDto> result = areaMapper.selectByPid(page); //调用mapper 的查询分页方法 page.setResults(result); //设置值到page中
相关文章推荐
- (ThreadLocal版本)Mybatis使用拦截器自动分页实现/使用反射替换sql代码分页
- maven+mybatis+mybatis-generator+sql server 2005自动生成代码,加上自定义分页插件和批量插入更新插件
- MyBatis精通之路之分页功能的实现(数组分页、sql分页、拦截器,RowBounds分页)
- Mybatis Generator Configuration Eclipse自动生成代码工具使用及实现基本crud
- Mybatis 使用Mapper接口的Sql动态代码方式进行CURD和分页查询
- 使用Auto Layout中的VFL(Visual format language)--代码实现自动布局
- 使用Auto Layout中的VFL(Visual format language)--代码实现自动布局
- iOS开发笔记--使用Auto Layout中的VFL(Visual format language)--代码实现自动布局
- MyBatis学习总结(9)——使用MyBatis Generator自动创建代码
- 【Java EE 学习 75 下】【数据采集系统第七天】【二进制运算实现权限管理】【使用反射初始化权限表】【权限捕获拦截器动态添加权限】
- Mybatis自动代码生成插件MybatisGenerator使用
- 通过Mybatis拦截器实现分页
- 使用 Sublime Text 做 Javascript 编辑器 - 集成 SublimeCodeIntel 实现代码智能提示及自动完成
- 使用 Sublime Text 做 Javascript 编辑器 - 集成 SublimeCodeIntel 实现代码智能提示及自动完成
- lua(实现代码(1)):使用lua将excel中的配置数据生成sql文件
- MyBatis 拦截器 (实现分页功能)
- Intellij IDEA 14中使用MyBatis-generator 自动生成MyBatis代码
- mansory使用记录&&NSLayoutConstraint-代码实现自动布局
- iOS开发笔记--使用Auto Layout中的VFL(Visual format language)--代码实现自动布局
- 使用Auto Layout中的VFL(Visual format language)--代码实现自动布局【转】