您的位置:首页 > 数据库

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代码分页

改造后的版本

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中
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐