Use PageHelper


这一篇讲一下 Mybatis Common Mapper 中的 PageHelper 用法。PageHelper 确实使分页简单了很多,它自动实现了分页查询逻辑及返回结果的封装,具体的使用方法可以参考 官方文档。下面主要介绍一下自己的实践,另外会附带介绍一下打印 sql、打印 sql 运行时间等一些小技巧。

使用方法

在 spring boot 中使用 mysql 和 common mapper,首先在 pom 中添加以下依赖,其中 pagehelper-spring-boot-starter 即是 pagehelper 插件。

<!-- mysql 连接器 -->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
</dependency>
<!--common mapper (包含了 mybatis-spring-boot-starter 依赖) -->
<dependency>
  <groupId>tk.mybatis</groupId>
  <artifactId>mapper-spring-boot-starter</artifactId>
  <version>1.1.4</version>
</dependency>
<!--pagehelper 通用 mapper 分页插件 -->
<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper-spring-boot-starter</artifactId>
  <version>1.1.3</version>
</dependency>

配置文件添加以下配置

-----------mybatis common-mapper configurations----------
pagehelper.helperDialect=mysql

代码中实现分页

/**
     * 分页查找
     * @param wrap 
     *     通过 wrap 属性设置查找筛选条件
     * @param params
     *     设置分页属性:pageNum(当前页数,默认 1),pageSize(页面行数,默认 10)
     * @return
     *     见 PageWrap 说明
     */
public PageWrap<SkillWrap> getAll(SkillWrap wrap, Map<String, Object> params) {
  // 此处进行分页参数设置
  PageUtil.handlePage(params);
  Example example = new Example(Skill.class);
  example.setOrderByClause("state, create_time desc");
  Example.Criteria criteria = example.createCriteria();
  criteria.andEqualTo("yn", "N");
  if (wrap.getState() != null) {criteria.andEqualTo("state", wrap.getState());
  }
  List<Skill> skills = mapper.selectByExample(example);
  // 将 sql 查询结果转换为分页对象
  PageInfo<Skill> page = new PageInfo<Skill>(skills);
  // 将分页对象实体从 model 转换为 view
  PageWrap<SkillWrap> pageWrap = PageUtil.transfer(page, SkillWrap.class);
  return pageWrap;
}

// 其中 PageUtil 类的实现如下
public class PageUtil {
    /**
     * 将 PageInfo 转换为自定义的 PageWrap
     * @param page
     * @param clazz
     * @return
     */
    public static <T> PageWrap<T> transfer(PageInfo<? extends BaseEntity> page, Class<T> clazz) {PageWrap<T> pageWrap = null;
        if (page != null) {
            pageWrap = new PageWrap<T>();
            List<? extends BaseEntity> lists = page.getList();
            List<T> wraps = lists.stream().map(model -> {
                T t = null;
                try {
                    t = clazz.newInstance();
                    BeanUtils.copyProperties(model, t);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return t;
            }).collect(Collectors.toList());
            pageWrap.setList(wraps);
            pageWrap.setPageNum(page.getPageNum());
            pageWrap.setPageSize(page.getPageSize());
            pageWrap.setTotal(Long.valueOf(page.getTotal()).intValue());
            pageWrap.setHasNextPage(page.isHasNextPage());
            pageWrap.setHasPreviousPage(page.isHasPreviousPage());
        }
        return pageWrap;
    }
    /**
     * 分页逻辑抽取
     * @param params
     */
    public static void handlePage(Map<String, Object> params) {
        int pageNum = 1;
        int pageSize = 10;
        int numSet = MapUtils.getInteger(params,"pageNum", 1);
        int sizeSet = MapUtils.getInteger(params,"pageSize", 10);
        if (params != null && numSet> 0 && sizeSet > 0) {
            pageNum = numSet;
            pageSize = sizeSet;
        }
        PageHelper.startPage(pageNum, pageSize); // 这里使用了 threadlocal 变量
    }
}

刚开始觉得很神奇,使用一个静态方法设置了一下 pageNum 和 pageSize 怎么就能实现分页呢,跟到源码里面看了一下,原来分页的参数是一个静态的 ThreadLocal 变量

protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
/**
     * 设置 Page 参数
     *
     * @param page
     */
protected static void setLocalPage(Page page) {
    LOCAL_PAGE.set(page);
}

public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count) {
    Page<E> page = new Page<E>(pageNum, pageSize, count);
  setLocalPage(page);
  return page;
}

打印 sql

由于使用 Common Mapper 不用手写 sql,有时候需要知道真正运行 sql 长什么样,便于优化和排查问题,因此最好能将 当前执行的 sql 在 console 中打印出来,网上说的一些设置 mysql 的日志级别等我这里都没有生效,由于框架的 sql 日志以 debug 级别输出,最后无奈将 console 全局日志级别设置为 debug,然后排除了一些不想看到的类日志。

<Loggers>
  <!-- 可以使用 OFF 关闭一些日志 -->
  <logger name="org.springframework"level="OFF"/>
  <!-- 可以对特定类单独设置日志级别 -->
  <logger name="org.hibernate"level="INFO"/>
  <logger name="org.mybatis"level="INFO"/>
  <logger name="io.netty"level="INFO"/>
  <logger name="org.apache.http"level="INFO"/>

  <Root level="DEBUG"includeLocation="true">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="INFO-LOG"/>
    <appender-ref ref="ERROR-LOG"/>
  </Root>
</Loggers>

打印 sql 执行时间

有时候会比较关心程序运行的效率,尤其是数据库操作的效率,以便于后期优化,这时打印 sql 的执行时间即可监控影响性能的一些数据库操作进行针对性的优化。打印 sql 运行时间是一个典型的 AOP 插入日志的问题,很容易想到动态代理什么的,这里通过实现 ibatis 的拦截器 org.apache.ibatis.plugin.Interceptor 来实现。

另外注意这里并不针对 Common Mapper 或者 PageHelper,使用了 Mybatis 都可以通过这种方式实现。

@Override
public Object intercept(Invocation invocation) throws Throwable {Object target = invocation.getTarget();
  long startTime = System.currentTimeMillis();
  StatementHandler statementHandler = (StatementHandler) target;
  try {return invocation.proceed();
  } finally {long endTime = System.currentTimeMillis();
    long sqlCost = endTime - startTime;
    BoundSql boundSql = statementHandler.getBoundSql();
    String sql = boundSql.getSql();
    Object parameterObject = boundSql.getParameterObject();
    List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings();
    // 格式化 Sql 语句,去除换行符,替换参数
    sql = formatSql(sql, parameterObject, parameterMappingList);
    System.out.println("SQL:[" + sql + "] cost [" + sqlCost + "ms]");
  }
}

需要在配置类中将改拦截器加入到 SqlSessionFactory 的插件列表中才能生效

@Configuration
public class MyBatisConf implements TransactionManagementConfigurer {
    @Autowired
    private DataSource dataSource;
    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name ="sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactoryBean() throws IOException {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        // 配置打印 sql 执行时间的拦截器插件
        bean.setPlugins(new Interceptor[] {new SqlCostInterceptor() });
        try {return bean.getObject();
        } catch (Exception e) {e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}

参考

Copyright © jverson.com 2018 all right reserved,powered by GitbookFile Modify: 2019-09-09 22:56:44

results matching ""

    No results matching ""