Mybatis拦截器分页

来源:互联网 发布:乌鲁木齐 中亚 知乎 编辑:程序博客网 时间:2024/05/17 02:51

这个拦截器比较复杂,是通过判断传入的参数有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 >= 16return 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.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsdhttp://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





原创粉丝点击