• SpringBoot整合Mybatis分页


    SpringBoot整合Mybatis分页

    ?> 学习目的: 分页的好处就是减少数据的处理量

    mybatis框架分页实现,常用得几种方式,最简单的就是

    1. sql分页,利用原生的sql关键字limit来实现;(不推荐)

    2. 还有一种就是利用interceptor来拼接sql,实现和limit一样的功能;(不推荐)

    3. 再一个就是利用PageHelper来实现。这里讲解这三种常见的实现方式 。

    4. 拦截器分页。( 数据量大时,实现拦截器就很有必要了)

    !> 注意:分页的实现,是基于 SpringBoot整合MyBatis 之上。


    1、pagehelper 分页

    添加相关依赖

    首先,我们需要在 pom.xml 文件中添加分页插件依赖包。

    pom.xml

    <!-- pagehelper -->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.2.5</version>
    </dependency>

    添加相关配置

    然后在 application-dev.yml 配置文件中添加分页插件有关的配置。

    application-dev.yml

    # pagehelper   
    pagehelper:
        helperDialect: mysql
        reasonable: true
        supportMethodsArguments: true
        params: count=countSql

    添加分页配置

    分页查询请求封装类。

    PageRequest.java

    import lombok.Data;
    ​
    @Data
    public class PageRequest {
        /**当前页码*/
        private int pageNum;
        /**每页数量*/
        private int pageSize;
    }

    分页查询结果封装类。

    PageResult.java

    import lombok.Data;
    import java.util.List;
    ​
    @Data
    public class PageResult {
        /** 当前页码 */
        private int pageNum;
        /** 每页数量 */
        private int pageSize;
        /** 记录总数 */
        private long totalSize;
        /** 页码总数 */
        private int totalPages;
        /** 数据模型*/
        private List<?> content;
    }

    分页查询相关工具类。

    PageUtils.java

    import com.github.pagehelper.PageInfo;
    ​
    public class PageUtils {
        /**
         * 将分页信息封装到统一的接口
         * @param pageInfo
         * @return
         */
        public static PageResult getPageResult(PageInfo<?> pageInfo) {
            PageResult pageResult = new PageResult();
            pageResult.setPageNum(pageInfo.getPageNum());
            pageResult.setPageSize(pageInfo.getPageSize());
            pageResult.setTotalSize(pageInfo.getTotal());
            pageResult.setTotalPages(pageInfo.getPages());
            pageResult.setContent(pageInfo.getList());
            return pageResult;
        }
    }

    添加代码

    UserMapper.xml

    <!--  查询分页  -->
        <select id="getAllUserByPage" resultMap="BaseResultMap">
            <include refid="Base_Column_List" />
            from db_user
        </select>

    dao(UserMapper.java)

        /** 分页 */
        List<User> getAllUserByPage();

    service(IUserService.java && UserServiceImpl.java)

    // IUserService
        /**
         * 分页查询接口
         * 这里统一封装了分页请求和结果,避免直接引入具体框架的分页对象, 如MyBatis或JPA的分页对象
         * 从而避免因为替换ORM框架而导致服务层、控制层的分页接口也需要变动的情况,替换ORM框架也不会
         * 影响服务层以上的分页接口,起到了解耦的作用
         * @param pageRequest 自定义,统一分页查询请求
         * @return PageResult 自定义,统一分页查询结果
         */
        PageResult getAllUserByPage(PageRequest pageRequest);
    //  UserServiceImpl
        /** 分页查询 */
        @Override
        public PageResult getAllUserByPage(PageRequest pageRequest) {
            return PageUtils.getPageResult(getPageInfo(pageRequest));
        }
    ​
        /**
         * 调用分页插件完成分页
         * @param pageRequest
         * @return
         */
        private PageInfo<User> getPageInfo(PageRequest pageRequest) {
            int pageNum = pageRequest.getPageNum();
            int pageSize = pageRequest.getPageSize();
            PageHelper.startPage(pageNum, pageSize);
            List<User> sysMenus = userMapper.getAllUserByPage();
            return new PageInfo<User>(sysMenus);
        }

    controller(UserController.java)

        // pagehelper 分页 post 请求
        @PostMapping("/findPage")
        public Result findPage(@RequestBody PageRequest pageQuery) {
            return Result.success(userService.getAllUserByPage(pageQuery));
        }

    测试

    测试工具:postman(自己用自己喜欢的工具)

    测试路径: http://localhost:8081/findPage

    结果图:


    2、拦截器分页

    实现原理主要是在数据库执行session查询的过程中,修改sql语句,先查询记录总条数,然后再分页查询数据记录,再把数据整合成分页数据形式就可以了。

    添加相关配置

    application-dev.yml

    mybatis:
      mapper-locations: classpath:mapper/*Mapper.xml
      type-aliases-package: com.mmdz.entity
      # sql 打印
    #  configuration:
    #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      config-location: classpath:mybatis/mybatis-config.xml

    注意:要注释 configuration ,否则会报错 IllegalStateException: Property 'configuration' and 'configLocation' can not specified with together

    // 配置重复导致冲突

    Caused by: java.lang.IllegalStateException: Property 'configuration' and 'configLocation' can not specified with together
    at org.springframework.util.Assert.state(Assert.java:76) ~[spring-core-5.3.1.jar:5.3.1]
    at org.mybatis.spring.SqlSessionFactoryBean.afterPropertiesSet(SqlSessionFactoryBean.java:488) ~[mybatis-spring-2.0.6.jar:2.0.6]
    at org.mybatis.spring.SqlSessionFactoryBean.getObject(SqlSessionFactoryBean.java:633) ~[mybatis-spring-2.0.6.jar:2.0.6]

    mybatis-config.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="cacheEnabled" value="false" /><!-- 暂时禁用缓存 -->
            <setting name="logImpl" value="STDOUT_LOGGING"/><!-- 打印sql-->
        </settings>
        <plugins>
            <plugin interceptor="com.mmdz.common.interceptor.PaginationInterceptor"></plugin>
        </plugins>
    </configuration>

    添加拦截器代码和配置

    PaginationInterceptor.java

    注意:这个类拦截StatementHandler类的prepare方法,解析获取请求方法参数。如果是单个参数,且参数类型为SimplePage,则采用分页模式。或者有多个参数,并且其中一个参数被标注为page(即接口中@Param("page")),也启用分页模式。其中getCountSql(String sql)方法和getPageSql(String sql, SimplePage page)方法需要根据不同数据库进行修改,我用Mysql。

    import com.mmdz.common.interceptor.page.SimplePage;
    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.DefaultReflectorFactory;
    import org.apache.ibatis.reflection.MetaObject;
    import org.apache.ibatis.reflection.ReflectorFactory;
    import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
    import org.apache.ibatis.reflection.factory.ObjectFactory;
    import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
    import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
    import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.util.StringUtils;
    ​
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.Properties;
    ​
    /**
     * @Author: MMDZ
     * @Date: 2021/5/31
     * @Desc: 分页拦截器mybatis
     */
    @Intercepts(
            { @Signature(type = StatementHandler.class,
                    method = "prepare", args = { Connection.class , Integer.class}) })
    public class PaginationInterceptor implements Interceptor {
    ​
        private static final Logger logger = LoggerFactory.getLogger(PaginationInterceptor.class);
    ​
        private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
        private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
        private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();
        private static String dialect = "mysql";
    ​
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            // 获得拦截的对象
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            // 待执行的sql的包装对象
            BoundSql boundSql = statementHandler.getBoundSql();
            // 判断是否是查询语句
            if (isSelect(boundSql.getSql())) {
                // 获得参数集合
                Object params = boundSql.getParameterObject();
    ​
                if (params instanceof Map) { // 请求为多个参数,参数采用Map封装
                    return complexParamsHandler(invocation, boundSql, (Map<?, ?>) params);
                } else if (params instanceof SimplePage) { // 单个参数且为Page,则表示该操作需要进行分页处理
                    return simpleParamHandler(invocation, boundSql, (SimplePage) params);
                }
            }
            return invocation.proceed();
        }
    ​
        private Object complexParamsHandler(Invocation invocation, BoundSql boundSql, Map<?, ?> params) throws Throwable {
            //判断参数中是否指定分页
            if (containsPage(params)) {
                return pageHandlerExecutor(invocation, boundSql, (SimplePage) params.get("page"));
            } else {
                return invocation.proceed();
            }
        }
    ​
        private boolean containsPage(Map<?, ?> params) {
            if(params==null){
                return false;
            }else if(!params.containsKey("page")){
                return false;
            }
            Object page = params.get("page");
            if(page==null){
                return false;
            }else if(page instanceof SimplePage){
                return true;
            }
            return false;
        }
    ​
        private boolean isSelect(String sql) {
            if (!StringUtils.isEmpty(sql) && sql.toUpperCase().trim().startsWith("SELECT")) {
                return true;
            }
            return false;
        }
    ​
        private Object simpleParamHandler(Invocation invocation, BoundSql boundSql, SimplePage page) throws Throwable {
            return pageHandlerExecutor(invocation, boundSql, page);
        }
    ​
        private Object pageHandlerExecutor(Invocation invocation, BoundSql boundSql, SimplePage page) throws Throwable {
            // 获得数据库连接
            Connection connection = (Connection) invocation.getArgs()[0];
            // 使用Mybatis提供的MetaObject,该对象主要用于获取包装对象的属性值
            MetaObject statementHandler = MetaObject.forObject(invocation.getTarget(), DEFAULT_OBJECT_FACTORY,
                    DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);
    ​
            // 获取该sql执行的结果集总数
            int maxSize = getTotalSize(connection, (MappedStatement) statementHandler.getValue("delegate.mappedStatement"),
                    boundSql);
    ​
            // 生成分页sql
            page.setTotalRecord(maxSize);
            String wrapperSql = getPageSql(boundSql.getSql(), page);
    ​
            MetaObject boundSqlMeta = MetaObject.forObject(boundSql, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY,
                    DEFAULT_REFLECTOR_FACTORY);
            // 修改boundSql的sql
            boundSqlMeta.setValue("sql", wrapperSql);
            return invocation.proceed();
        }
    ​
        private int getTotalSize(Connection connection, MappedStatement mappedStatement, BoundSql boundSql) {
            String countSql = getCountSql(boundSql.getSql());
            PreparedStatement countStmt;
            ResultSet rs;
            List<AutoCloseable> closeableList = new ArrayList<AutoCloseable>();
    ​
            try {
                countStmt = connection.prepareStatement(countSql);
                BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,
                        boundSql.getParameterMappings(), boundSql.getParameterObject());
                setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());
                rs = countStmt.executeQuery();
    ​
                if (rs.next()) {
                    return rs.getInt(1);
                }
                closeableList.add(countStmt);
                closeableList.add(rs);
            } catch (SQLException e) {
                logger.error("append an exception[{}] when execute sql[{}] with {}", e, countSql,
                        boundSql.getParameterObject());
            } finally {
                for (AutoCloseable closeable : closeableList) {
                    try {
                        if (closeable != null)
                            closeable.close();
                    } catch (Exception e) {
                        logger.error("append an exception[{}] when close resource[{}] ", e, closeable);
                    }
                }
            }
            return 0;
        }
    ​
        private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
                                   Object parameterObject) throws SQLException {
            ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
            parameterHandler.setParameters(ps);
        }
    ​
        @Override
        public Object plugin(Object target) {
            // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的次数
            if (target instanceof StatementHandler) {
                return Plugin.wrap(target, this);
            } else {
                return target;
            }
        }
    ​
        @Override
        public void setProperties(Properties properties) {
    ​
        }
    ​
        public String getCountSql(String sql) {
            if("mysql".equals(dialect)){
                return "select count(0) from (" + sql + ") as total";
            }
            return sql;
        }
    ​
        public String getPageSql(String sql, SimplePage page) {
            if(page.getPage()<=0){
                page.setPage(1);
            }
            if(page.getRows()<=0){
                page.setRows(20);
            }
            int startRow = (page.getPage()-1)*page.getRows();
    ​
            if(startRow>=page.getTotalRecord()){
                page.setPage(1);
                startRow=0;
            }
            if("mysql".equals(dialect)){
                return sql+" limit "+startRow+", "+page.getRows();
            }
            return sql;
        }
    }

    SimplePage.java

    注意:这个类是分页对象所需的最简单的类,只要包含页码、每页条数、总条数和数据记录,就可以推算出所有需要的分页参数。所以用这个类来给拦截器做参数判断,若需要更多的页码信息可以重写一个分页类继承这个SimplePage即可。

    import lombok.Getter;
    import lombok.Setter;
    import java.io.Serializable;
    import java.util.List;
    
    @Getter
    @Setter
    public class SimplePage implements Serializable {
    
        protected static final long serialVersionUID = 5136213157391895517L;
    
        protected int page = 1;// 页码,默认是第一页
        protected int rows = 10;// 每页显示的记录数,默认是10
        protected int totalRecord;// 总记录数
        protected List data;// 当前页记录
    
        public SimplePage setData(List data) {
            this.data = data;
            return this;
        }
    }

    Page.java

    注意:这个Page类主要是丰富SimplePage类,最重要的就是 setData(List data)方法,由这个方法来丰富一些变量数据。

    import lombok.Getter;
    import lombok.Setter;
    
    import java.util.List;
    
    /**
     * @Author: MMDZ
     * @Date: 2021/5/31
     * @Desc: 这个Page类主要是丰富SimplePage类,最重要的就是 setData(List data)方法,
     *                  由这个方法来丰富一些变量数据。
     */
    @Getter
    @Setter
    public class Page extends SimplePage {
        private static final long serialVersionUID = -6190845403265328029L;
    
        private boolean isFirstPage = true;//是否是第一页
        private boolean isLastPage = false;//是否是最后一页
        private int pageCount = 0;//当前页总记录数
        private int totalPage = 0;//总页数
        private int prePage = 1;//上一页页码
        private int nextPage = 1;//下一页页码
    
        public Page() {
            super();
        }
    
        public Page(int page, int rows) {
            super();
            setPage(page);
            setRows(rows);
        }
    
        @Override
        public Page setData(List data){
            super.setData(data);
            if(data!=null && data.size()>0){
                pageCount = data.size();
                if(this.page==1){
                    isFirstPage=true;
                }else{
                    isFirstPage=false;
                }
                //***
                totalPage = (int)Math.ceil(totalRecord/(double)rows);
                //***
                if(page==totalPage){
                    isLastPage = true;
                }else{
                    isLastPage = false;
                }
                //***
                if(isFirstPage){
                    prePage = 1;
                }else{
                    prePage = page-1;
                }
                //***
                if(isLastPage){
                    nextPage = 1;
                }else{
                    nextPage = page+1;
                }
            }else{
                isLastPage = true;
            }
            return this;
        }
    }

    添加代码

    UserMapper.xml

         <select id="findPage" resultMap="BaseResultMap">
            <include refid="Base_Column_List" />
            from db_user
        </select>

    UserMapper.java

    注意:如果这个查询方法只有分页参数page,没有别的参数,可以不写@Param("page"),若有多个参数必须用@Param标明参数名,这是拦截器判断分页的依据。

       /** @Param("page")是应分页插件要求编写的 */
        List<User> findPage(@Param("page") Page page);

    UserServiceImpl.java

        /**
         * 拦截器分页
         * @param page
         * @return
         */
        @Override
        public Page findPage(Page page) {
            List<User> list = userMapper.findPage(page);
            page.setData(list);
            return page;
        }

    IUserService.java

        /**
         * 拦截器分页
         * @param page
         * @return
         */
        Page findPage(Page page);

    UserController.java

        // 拦截器 分页 post 请求
        @PostMapping("/findPage2")
        public Result findPage(@RequestBody Page page){
            return Result.success(userService.findPage(page));
        }

    测试

    测试工具:postman(自己用自己喜欢的工具)

    测试路径: http://localhost:8081/findPage2

    结果图:

     

  • 相关阅读:
    JDBC学习笔记
    hdfs文件格式
    全国疫情防控监控平台开发
    MySQL学习笔记
    拖拽表单生成
    Cython加密(含Windows和Linux)
    pcl 文字点云
    新装Ubuntu系统--常用软件安装配置
    GIT
    Data Analysis With Python
  • 原文地址:https://www.cnblogs.com/mmdz/p/14831363.html
Copyright © 2020-2023  润新知