zoukankan      html  css  js  c++  java
  • 轻量级封装DbUtils&Mybatis之三MyBatis分页

    MyBatis假分页#

    参考DefaultResultSetHandler的skipRows方法。

    温馨提示:部分代码请参考轻量级封装DbUtils&Mybatis之一概要

    解决方案#

    1)之前公司同事,亦师亦上司勇哥已经处理过分页的逻辑:自定义一个包装类包装SqlSession,完全开放SqlSession的各类访问方法,直接可通过传入RowBounds(包装offset&limit)参数完成分页逻辑。
    2)参考mybatis-pagination项目。

    备注:因为个人希望不要和MyBatis原有的使用方法差异太大,尽量减少自定义的处理,所以才总结自己的思路和想法,目标实际上是希望保留MyBatis自定义mapper接口即可实现Jdbc访问的特性。

    温馨提示:请下载上面提到的项目,并对MyBatis分页处理有一定了解。

    分页处理解决方案#

    mybatis-pagination分页处理分析##

    1)通过外置增加的排序和分页选项,在mapper文件中配置排序选项,通过参数控制排序的条件,而在interceptor拦截时处理分页
    2)自定义Interceptor和Executor,修改了目标执行逻辑处理各类条件逻辑

    优点:功能够强大
    缺点:自定义的内容偏多,实现过于复杂,不知是否会受到MyBatis升级的影响

    现有解决方案##

    feature
    1)目前暂不支持排序,后续考虑,但绝对不会考虑在mapper配置文件中定义排序条件
    2)自定义Interceptor,不考虑将结果集列表包装成Page对象,保证分页逻辑和不分页的逻辑成为可选项,调用方法可共用
    3)所有和查询结果集无关但有用的返回结果都包装成一个对象,存放到ThreadLocal
    4)定义Mapper接口的分页方法最后一个参数类型务必是Criteria(参考下文代码实现),否则无法提供分页功能

    拦截器处理流程
    1)获取MetaObject,得到MappedStatement和ParameterHandler
    2)判定stamentId是否匹配配置的表达式,mapper接口方法最后一个参数是否为Criteria类型,不满足判定则执行原有SQL逻辑,满足则执行实际分页处理逻辑
    3)实际分页处理时,关闭原有的分页设置,将分页参数绑定到SQL上,并执行获取总记录数的方法

    代码呈上#

    测试样例

    package org.wit.ff.jdbc;
    
    import org.junit.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
    import org.wit.ff.jdbc.dao.HomeTownDao;
    import org.wit.ff.jdbc.query.Criteria;
    import org.wit.ff.jdbc.result.CriteriaResultHolder;
    
    /**
     * Created by F.Fang on 2015/11/19.
     */
    @ContextConfiguration(locations = {"classpath:applicationContext-paging.xml"})
    public class HomeTownDaoPagingTest extends AbstractJUnit4SpringContextTests {
    
        @Autowired
        private HomeTownDao homeTownDao;
    
        @Test
        public void testFind() {
            // Criteria对象包装分页条件.
            System.out.println(homeTownDao.find(1, new Criteria().page(1, 1)));
            //System.out.println(homeTownDao.find(1, null));
            // 从线程上下文中获取总页数,总记录数等信息.
            try {
                System.out.println(CriteriaResultHolder.get());
            }finally {
                CriteriaResultHolder.remove();
            }
        }
    
    }
    

    HomeTownDao

    package org.wit.ff.jdbc.dao;
    
    import org.wit.ff.jdbc.model.HomeTown;
    import org.wit.ff.jdbc.query.Criteria;
    
    import java.util.List;
    
    /**
     * Created by F.Fang on 2015/11/17.
     * Version :2015/11/17
     */
    public interface HomeTownDao {
        List<HomeTown> find(int id,Criteria criteria);
    }
    

    Criteria

    package org.wit.ff.jdbc.query;
    
    
    /**
     * Created by F.Fang on 2015/11/19.
     * 后续可扩展排序参数.
     */
    public class Criteria {
    
        private int pageNumber;
    
        private int pageSize;
    
        public Criteria page(int pageNumber, int pageSize){
            this.pageNumber = pageNumber;
            this.pageSize = pageSize;
            return this;
        }
    
    
        public int getPageSize() {
            return pageSize;
        }
    
        public void setPageSize(int pageSize) {
            this.pageSize = pageSize;
        }
    
        public int getPageNumber() {
            return pageNumber;
        }
    
        public void setPageNumber(int pageNumber) {
            this.pageNumber = pageNumber;
        }
    }
    

    CriteriaResult

    package org.wit.ff.jdbc.result;
    
    import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
    import org.apache.commons.lang3.builder.ToStringStyle;
    
    /**
     * Created by Yong.Huang.
     * Updated by F.Fang on 2015/11/19.
     */
    public class CriteriaResult{
    
        private int pageNumber;
    
        private int pageSize;
    
        private long pageCount;
    
        private long totalCount;
    
        public CriteriaResult(int pageNumber, int pageSize, long totalCount) {
            this.pageNumber = pageNumber;
            this.pageSize = pageSize;
            this.totalCount = totalCount;
            if (pageSize != 0) {
                if (totalCount % pageSize == 0) {
                    pageCount = totalCount / pageSize;
                } else {
                    pageCount = totalCount / pageSize + 1;
                }
            }
        }
    
        public int getPageNumber() {
            return pageNumber;
        }
    
        public int getPageSize() {
            return pageSize;
        }
    
        public long getPageCount() {
            return pageCount;
        }
    
        public long getTotalCount() {
            return totalCount;
        }
    
        public boolean hasPrevPage() {
            return pageNumber > 1 && pageNumber <= pageCount;
        }
    
        public boolean hasNextPage() {
            return pageNumber < pageCount;
        }
    
        public boolean isFirstPage() {
            return pageNumber == 1;
        }
    
        public boolean isLastPage() {
            return pageNumber == pageCount;
        }
    
        @Override
        public String toString() {
            return ReflectionToStringBuilder.toString(this, ToStringStyle.SIMPLE_STYLE);
        }
    }
    
    

    CriteriaResultHolder

    package org.wit.ff.jdbc.result;
    
    /**
     * Created by F.Fang on 2015/11/19.
     */
    public class CriteriaResultHolder {
    
        private static final ThreadLocal<CriteriaResult> criteriaResult = new ThreadLocal<CriteriaResult>();
    
        private CriteriaResultHolder(){}
    
        public static CriteriaResult get() {
            return criteriaResult.get();
        }
    
        public static void set(CriteriaResult value){
            if(criteriaResult.get() == null && value!=null){
                criteriaResult.set(value);
            }
        }
    
        public static void remove(){
            criteriaResult.remove();
        }
    }
    
    

    Mapper定义

    <select id="find" resultType="HomeTown" >
            select * from hometown
        </select>
    

    mybatis.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>
    
        <settings>
            <setting name="mapUnderscoreToCamelCase" value="true"/>
        </settings> 
    
        <plugins>
            <plugin interceptor="org.wit.ff.jdbc.paging.MysqlPagingInterceptor">
                <property name="statementRegex" value=".*find.*"/>
            </plugin>
        </plugins>
    
    </configuration>
    

    Spring配置文件

    <?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:mybatis="http://mybatis.org/schema/mybatis-spring"
           xsi:schemaLocation="
         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
         http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">
    
    
        <!-- 数据源,请自行修改 -->
        <bean id="dataSource"
              class="org.apache.commons.dbcp.BasicDataSource"
              destroy-method="close">
            <property name="driverClassName" value="${db.driverClass}"/>
            <property name="url" value="${db.jdbcUrl}"/>
            <property name="username" value="${db.user}"/>
            <property name="password" value="${db.password}"/>
        </bean>
    
        <!-- 配置 SqlSessionFactory -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource"/>
            <property name="configLocation" value="classpath:mybatis.xml"/>
            <!-- 制定路径自动加载mapper配置文件 -->
            <property name="mapperLocations" value="classpath:mappers/*Dao.xml"/>
    
            <!-- 配置myibatis的settings http://mybatis.github.io/mybatis-3/zh/configuration.html#settings -->
            <property name="configurationProperties">
                <props>
                    <prop key="cacheEnabled">true</prop>
                </props>
            </property>
            <!-- 类型别名是为 Java 类型命名一个短的名字。 它只和 XML 配置有关, 只用来减少类完全 限定名的多余部分 -->
            <property name="typeAliasesPackage" value="org.wit.ff.jdbc.model"/>
    
        </bean>
    
        <mybatis:scan base-package="org.wit.ff.jdbc.dao"/>
    
    </beans>
    

    核心拦截器
    若对MyBatis拦截器相关的内容有疑问,请自行谷歌or百度,好的资源太多

    package org.wit.ff.jdbc.paging;
    
    import org.apache.ibatis.executor.parameter.ParameterHandler;
    import org.apache.ibatis.executor.statement.StatementHandler;
    import org.apache.ibatis.mapping.BoundSql;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.plugin.*;
    import org.apache.ibatis.reflection.MetaObject;
    import org.apache.ibatis.reflection.SystemMetaObject;
    import org.apache.ibatis.session.RowBounds;
    import org.wit.ff.jdbc.dialect.Dialect;
    import org.wit.ff.jdbc.query.Criteria;
    import org.wit.ff.jdbc.result.CriteriaResult;
    import org.wit.ff.jdbc.result.CriteriaResultHolder;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.HashMap;
    import java.util.Properties;
    
    /**
     * Created by Yong.Huang
     * Updated by F.Fang on 2015/11/19.
     * Mybatis属于假分页 , 参考代码: DefaultResultSetHandler执行方法链:
     * handleResultSets--> handleResultSet --> handleRowValues --> handleRowValuesForNestedResultMap
     * --> skipRows --> 执行 rs.absolute跳过记录数, 实际执行的语句仍然是查询了相同数量的记录.
     */
    public abstract class PagingInterceptor implements Interceptor {
    
        /**
         * regex匹配statementId.
         */
        protected String statementRegex;
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
    
            MetaObject metaObject = SystemMetaObject.forObject(invocation.getTarget());
            MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
    
            // 匹配拦截StatementId
            if (!mappedStatement.getId().matches(statementRegex)) {
                return invocation.proceed();
            }
    
            // 最后一个参数必须是Creteria.
            ParameterHandler parameterHandler = (ParameterHandler) metaObject.getValue("delegate.resultSetHandler.parameterHandler");
    
            if (parameterHandler != null) {
                Object object = parameterHandler.getParameterObject();
                Object lastParam = null;
                // 如果有多个参数,取最后一个参数.
                if (object instanceof HashMap) {
                    HashMap map = (HashMap) object;
                    // 这个逻辑始终不太放心, 日后若有更好的实现再改.
                    String key = "param"+String.valueOf(map.keySet().size()/2);
                    lastParam = map.get(key);
                } else {
                    lastParam = object;
                }
    
                // 参数一定要匹配Criteria类型
                if (lastParam == null || !(lastParam instanceof Criteria)) {
                    return invocation.proceed();
                }
    
                Criteria criteria = (Criteria) lastParam;
                StatementHandler stamentHandler = (StatementHandler) invocation.getTarget();
                BoundSql boundSql = stamentHandler.getBoundSql();
                // 原始mapper文件中配置的Sql.
                String originSql = boundSql.getSql();
    
                int offSet = (criteria.getPageNumber() - 1) * criteria.getPageSize();
                // 实际的分页sql.
                String pagingSql = getDialect().getLimitString(originSql, offSet, criteria.getPageSize());
    
                // 重新设置属性.
                metaObject.setValue("delegate.boundSql.sql", pagingSql);
                metaObject.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
                metaObject.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
    
                // 获取连接参数.
                Connection connection = (Connection) invocation.getArgs()[0];
                // 总页数.
                int totalCount = getTotalCount(connection, originSql, parameterHandler);
                // 填充各属性值.
                CriteriaResult result = new CriteriaResult(criteria.getPageNumber(), criteria.getPageSize(), totalCount);
                CriteriaResultHolder.set(result);
            }
            // 执行SQL.
            return invocation.proceed();
        }
    
        @Override
        public Object plugin(Object target) {
            if (target instanceof StatementHandler) {
                return Plugin.wrap(target, this);
            }
            return target;
        }
    
        @Override
        public void setProperties(Properties properties) {
            statementRegex = properties.getProperty("statementRegex");
        }
    
        public abstract Dialect getDialect();
    
        private int getTotalCount(Connection connection, String sql, ParameterHandler parameterHandler) throws SQLException {
            int result = 0;
            PreparedStatement ps = null;
            ResultSet rs = null;
            try {
                String countSql = getDialect().getCountString(sql);
                ps = connection.prepareStatement(countSql);
                parameterHandler.setParameters(ps);
                rs = ps.executeQuery();
                if (rs.next()) {
                    result = rs.getInt(1);
                }
            } finally {
                if (ps != null) {
                    ps.close();
                }
                if (rs != null) {
                    rs.close();
                }
            }
            return result;
        }
    }
    
    package org.wit.ff.jdbc.paging;
    
    import org.apache.ibatis.executor.statement.StatementHandler;
    import org.apache.ibatis.plugin.Intercepts;
    import org.apache.ibatis.plugin.Signature;
    import org.wit.ff.jdbc.dialect.Dialect;
    import org.wit.ff.jdbc.dialect.db.MySQLDialect;
    
    import java.sql.Connection;
    
    /**
     * Created by F.Fang on 2015/11/19.
     */
    @Intercepts(
            @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})
    )
    public class MysqlPagingInterceptor extends PagingInterceptor{
        private Dialect dialect = new MySQLDialect();
    
        @Override
        public Dialect getDialect() {
            return dialect;
        }
    }
    

    QA#

  • 相关阅读:
    python学习手册笔记——14.迭代器和解析
    安装完Pydev却无法创建Python工程
    Android自动化学习笔记之Robotium:学习官网实例
    Android自动化学习笔记:获取APK包名的几种方法
    Android自动化学习笔记:编写MonkeyRunner脚本的几种方式
    Android自动化学习笔记之MonkeyRunner:MonkeyRunner的录制和回放
    Android自学笔记:Git下载源代码
    Android自动化学习笔记之MonkeyRunner:官方介绍和简单实例
    Android自动化学习笔记之MonkeyRunner:用Eclipse执行MonkeyRunner脚本
    Android自动化学习笔记之MonkeyRunner:MonkeyRunner环境搭建
  • 原文地址:https://www.cnblogs.com/fangfan/p/4992321.html
Copyright © 2011-2022 走看看