这个拦截器比较复杂,是通过判断传入的参数有page对象就认定它是需要分页的。
1.首先,自定义一个分页拦截器
package com.jd.controller.interceptor;
import com.jd.base.entity.Page;
import com.jd.util.page.ReflectUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.binding.MapperMethod;
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 org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import javax.xml.bind.PropertyException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
/**
* 分页拦截器,用于拦截需要进行分页查询的操作,然后对其进行分页处理。 利用拦截器实现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语句进行总记录数的统计。
*/
@Intercepts({ @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
@SuppressWarnings("rawtypes")
public class PageInterceptor implements Interceptor {
private static String databaseType ="";// 数据库类型,不同的数据库有不同的分页方法
/**
* 拦截后要执行的方法
*/
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 params = boundSql.getParameterObject();
// 这里我们简单的通过传入的是Page对象就认定它是需要进行分页操作的。
Page page = null;
if (params instanceof Page) {
page = (Page) params;
params=null;
} else if (params instanceof MapperMethod.ParamMap) {
MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) params;
for (Object key : paramMap.keySet()) {
if (paramMap.get(key) instanceof Page) {
page = (Page) paramMap.get(key);
break;
}
}
}
if (page != null) {
// 通过反射获取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, (MapperMethod.ParamMap) params,
mappedStatement, connection);
// 获取分页Sql语句
String pageSql = this.getPageSql(page, sql);
// 利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
}
return invocation.proceed();
}
/**
* 拦截器对应的封装原始对象的方法
*/
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties p) {
databaseType = p.getProperty("databaseType");
if (StringUtils.isEmpty(databaseType)) {
try {
throw new PropertyException("databaseType is not found!");
} catch (PropertyException e) {
e.printStackTrace();
}
}
}
/**
* 根据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);
} else if ("sqlserver".equalsIgnoreCase(databaseType)) {
return getSqlserverPageSql(page, sqlBuffer);
}
return sqlBuffer.toString();
}
/**
* 获取Sqlserver2005或以上版本数据库的分页查询语句
*
* @param page
* 分页对象
* @param sqlBuffer
* 包含原sql语句的StringBuffer对象
* @return Mysql数据库分页语句
*/
private String getSqlserverPageSql(Page<?> page, StringBuffer sqlBuffer) {
// 计算第一条记录的位置,Sqlserver中记录的位置是从0开始的。
int startRowNum = (page.getPageNum() - 1) * page.getPageSize() + 1;
int endRowNum = startRowNum + page.getPageSize();
String sql = "select appendRowNum.row,* from (select ROW_NUMBER() OVER (order by (select 0)) AS row,* from ("
+ sqlBuffer.toString()
+ ") as innerTable"
+ ")as appendRowNum where appendRowNum.row >= "
+ startRowNum
+ " AND appendRowNum.row <= " + endRowNum;
return sql;
}
/**
* 获取Mysql数据库的分页查询语句
*
* @param page
* 分页对象
* @param sqlBuffer
* 包含原sql语句的StringBuffer对象
* @return Mysql数据库分页语句
*/
private String getMysqlPageSql(Page<?> page, StringBuffer sqlBuffer) {
// 计算第一条记录的位置,Mysql中记录的位置是从0开始的。
int offset = (page.getPageNum() - 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.getPageNum() - 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, MapperMethod.ParamMap params,
MappedStatement mappedStatement, Connection connection) {
// 获取对应的BoundSql,这个BoundSql其实跟我们利用StatementHandler获取到的BoundSql是同一个对象。
// delegate里面的boundSql也是通过mappedStatement.getBoundSql(paramObj)方法获取到的。
BoundSql boundSql = mappedStatement.getBoundSql(params);
// 获取到我们自己写在Mapper映射语句中对应的Sql语句
String sql = boundSql.getSql();
// 通过查询Sql语句获取到对应的计算总记录数的sql语句
String countSql = this.getCountSql(sql);
// 通过BoundSql获取对应的参数映射
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// 利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象page建立查询记录数对应的BoundSql对象。
BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql,parameterMappings, params);
// 通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象
ParameterHandler parameterHandler = new DefaultParameterHandler(
mappedStatement, params, 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) {
return "select count(*) from (" + sql + ") countRecord";
}
}
2.定义分页实体类
package com.jd.base.entity;
import java.util.List;
/**
* 对分页的基本数据进行封装
*/
public class Page<T> {
private int pageNum = 1;//页码,默认是第一页
private int pageSize = 5;//每页显示的记录数,默认是5
private int totalRecord;//总记录数
private int totalPage;//总页数
private List<T> results;//对应的当前页记录
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
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() {
if(null != results && results.size() == 0){
return null;
}
return results;
}
public void setResults(List<T> results) {
this.results = results;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Page [pageNum=").append(pageNum).append(", pageSize=")
.append(pageSize).append(", results=").append(results).append(
", totalPage=").append(totalPage).append(
", totalRecord=").append(totalRecord).append("]");
return builder.toString();
}
}
3.在配置文件中配置此拦截器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"
default-lazy-init="true">
<!-- 数据源配置, 使用应用服务器的数据库连接池 -->
<!--<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/${jndi.name}" />-->
<!-- 读取数据库配置文件 -->
<!-- 配置数据源 -->
<bean id="mysqlDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${mysql.driverClassName}"></property>
<property name="url" value="${mysql.url}"></property>
<property name="username" value="${mysql.username}"></property>
<property name="password" value="${mysql.password}"></property>
<property name="maxActive" value="${mysql.maxActive}"></property>
<property name="maxIdle" value="${mysql.maxIdle}"></property>
</bean>
<!-- MyBatis配置 -->
<bean id="mysqlSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="mysqlDataSource" />
<!-- 自动扫描entity目录, 省掉xml里的手工配置 -->
<property name="typeAliasesPackage" value="com.jd.dto" />
<!-- 显式指定Mapper文件位置 -->
<property name="mapperLocations">
<list>
<value>classpath:/mybatis/**/**/*Mapper.xml</value>
</list>
</property>
<property name="plugins"><array><ref bean="pagePlugin" /></array></property>
<!--<property name="configLocation" value="classpath:config/mybatis-config.xml"/>-->
</bean>
<!-- 分页配置-->
<bean id="pagePlugin" class="com.yscredit.sz.controller.interceptor.PageInterceptor">
<property name="properties">
<props>
<prop key="databaseType">mysql</prop>
</props>
</property>
</bean>
<!-- 分页配置end-->
<!-- 扫描basePackage下所有以@MyBatisRepository标识的 接口-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="mysqlSqlSessionFactory"/>
<property name="basePackage" value="com.sz.base.dao" />
</bean>
<!-- 配置事务管理器 -->
<bean id="mysqlTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="mysqlDataSource" />
</bean>
<!-- 拦截器方式配置事务 -->
<tx:advice id="mysqlTransactionAdvice" transaction-manager="mysqlTransactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="find*" propagation="SUPPORTS" />
<tx:method name="*" propagation="SUPPORTS" />
</tx:attributes>
</tx:advice>
<aop:config proxy-target-class="true">
<aop:pointcut id="mysqlTransactionPointcut" expression="execution(* com.yscredit.sz.service.*..*(..))" />
<aop:advisor pointcut-ref="mysqlTransactionPointcut" advice-ref="mysqlTransactionAdvice" />
</aop:config>
<tx:annotation-driven transaction-manager="mysqlTransactionManager" proxy-target-class="true" />
<bean id="zxJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name = "dataSource" ref="dataSource" />
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${zx.mysql.driverClassName}"></property>
<property name="url" value="${zx.mysql.url}"></property>
<property name="username" value="${zx.mysql.username}"></property>
<property name="password" value="${zx.mysql.password}"></property>
<property name="maxActive" value="${zx.mysql.maxActive}"></property>
<property name="maxIdle" value="${zx.mysql.maxIdle}"></property>
</bean>
</beans>
4.调用链示例
controller
public ResponseWrapper getUnreliableEntList(Page <EntFile> page){
ResponseWrapper responseWrapper = new ResponseWrapper();
Map<String,Object> map = new HashMap<>();
String platFromId = SecurityUtils.getSubject().getSession().getAttribute("platFromId").toString();
EntFile entFilep = new EntFile();
entFilep.setPlatFrom(platFromId);
try {
page = focusEntService.getUnreliableEntList(entFilep,page);
logger.info("分页查询成功");
map.put("code","0001");//查询成功
map.put("page",page);
responseWrapper.setData(map);
responseWrapper.setSuccess(true);
responseWrapper.setMessage("查询成功");
}catch (Exception e){
logger.info("分页查询失败");
map.put("code","0000");//查询失败
responseWrapper.setData(map);
responseWrapper.setSuccess(false);
responseWrapper.setMessage("查询失败");
}
return responseWrapper;
}
service
Page<EntFile> getUnreliableEntList(EntFile entFilep, Page<EntFile> page);
serviceImpl
public Page<EntFile> getUnreliableEntList(EntFile entFilep, Page<EntFile> page) {
page.setResults(entFileDao.getUnreliableEntList(entFilep,page));
return page;
}
dao
List<EntFile> getUnreliableEntList(@Param("param")EntFile entFilep, Page<EntFile> page);
mapper.xml
<select id="getUnreliableEntList" parameterType="java.lang.String" resultMap="resMap">
SELECT id,ent_name,regcap,esdate,frname
FROM ent_file e
WHERE plat_from = #{param.platFrom,jdbcType=VARCHAR}
AND label_dishonesty=1
AND e.delete_flag=0
</select>
5.注意,参数都要封装到对象里,不能用string,int等基本类型,因为在拦截器中获取参数时用的是getter,基本类型数据没有getter和setter
6.ReflectUtil
反射工具类
package com.yscredit.sz.util;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.Assert;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 利用反射进行操作的一个工具类
*/
public 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,
String 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();
}
}
}
/**
* 两者属性名一致时,拷贝source里的属性到dest里
*
* @return void
* @throws IllegalArgumentException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
@SuppressWarnings("unchecked")
public static void copyPorperties(Object dest, Object source) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class srcCla = source.getClass();
Field[] fsF = srcCla.getDeclaredFields();
for (Field s : fsF)
{
String name = s.getName();
Object srcObj = invokeGetterMethod(source, name);
try
{
BeanUtils.setProperty(dest, name, srcObj);
}
catch (Exception e){
e.printStackTrace();
}
}
}
/**
* 调用Getter方法.
* @throws InvocationTargetException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
public static Object invokeGetterMethod(Object target, String propertyName) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
String getterMethodName = "get" + StringUtils.capitalize(propertyName);
return invokeMethod(target, getterMethodName, new Class[] {},
new Object[] {});
}
/**
* 直接调用对象方法, 无视private/protected修饰符.
* @throws InvocationTargetException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
public static Object invokeMethod(final Object object,
final String methodName, final Class<?>[] parameterTypes,
final Object[] parameters) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Method method = getDeclaredMethod(object, methodName, parameterTypes);
if (method == null)
{
throw new IllegalArgumentException("Could not find method ["
+ methodName + "] parameterType " + parameterTypes
+ " on target [" + object + "]");
}
method.setAccessible(true);
return method.invoke(object, parameters);
}
/**
* 循环向上转型, 获取对象的DeclaredMethod.
*
* 如向上转型到Object仍无法找到, 返回null.
*/
protected static Method getDeclaredMethod(Object object, String methodName,
Class<?>[] parameterTypes)
{
Assert.notNull(object, "object不能为空");
for (Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass
.getSuperclass())
{
try{
return superClass.getDeclaredMethod(methodName, parameterTypes);
}
catch (NoSuchMethodException e)
{// NOSONAR
// Method不在当前类定义,继续向上转型
}
}
return null;
}
}