自定义注解,更优雅的使用MP分页功能

分页功能使用

MP的分页功能是通过MyBatis的插件实现的,使用起来也非常简单。下面先介绍下使用方式。

step1:配置分页插件

@Configuration
@EnableTransactionManagement
@MapperScan("com.csx.demo.spring.boot.dao")
public class MyBatisPlusConfig {

    private static final Logger log = LoggerFactory.getLogger(MyBatisPlusConfig.class);

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 如果是单数据源的话,最好制定数据库类型,不然的话MP需要根据数据库连接来推断数据库类型
        // 性能上略有损失
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

需要注意的是:MP提供了很多开箱即用的插件,这些插件的使用顺序有讲究。官方文档建议的配置顺序是:

目前已有的功能:

  • 自动分页: PaginationInnerInterceptor
  • 多租户: TenantLineInnerInterceptor
  • 动态表名: DynamicTableNameInnerInterceptor
  • 乐观锁: OptimisticLockerInnerInterceptor
  • sql性能规范: IllegalSQLInnerInterceptor
  • 防止全表更新与删除: BlockAttackInnerInterceptor

注意:
使用多个功能需要注意顺序关系,建议使用如下顺序
多租户,动态表名
分页,乐观锁
sql性能规范,防止全表更新与删除
总结: 对sql进行单次改造的优先放入,不对sql进行改造的最后放入

step2:写分页代码

IPage<User> page = new Page<>(1,10);
((Page<User>) page).addOrder(OrderItem.desc("user_id"));
IPage<User> userIPage = userDAO.selectPage(page, null);

自定义注解,更优雅的使用MP分页功能

我们发现:虽然MP的分页插件使用起来非常简单,但是还是需要每次从参数中拿分页参数、排序参数等,代码看起来还是略微显得冗余。

这边定义了一个自定义注解,可以简化上面的这些操作。下面是实现的代码。

step1:定义自己的PageInfo

说明下:这边的PageInfo是可以不定义的,你可以直接使用MP的IPage实现。但是个人具有洁癖,有些信息不想返回前端,所以定义了一个精简的PageInfo.

/**
 * 自定义的PageInfo
 * 内容比MP中的IPage精简
 * @param <T>
 */
public class PageInfo<T> {

    private Integer pageNo;

    private Integer pageSize;

    private String sortColumn;
    
    private Long total;

    private List<T> rows;
    
    public PageInfo(Integer pageNo, Integer pageSize, String sortColumn) {
        this.pageNo = pageNo;
        this.pageSize = pageSize;
        this.sortColumn = sortColumn;
    }
    // 省略get和set方法
}

step2:定义分页注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Pagination {

    // 可以自定义分页字段名称,当前页字段名称默认是pageNO
    String pageNoField() default "pageNo";
    // 可以自定义分页字段名称,每页数量名称默认是pageNO
    String pageSizeField() default "pageSize";
    // 可以自定义分页字段名称,排序字段名称默认是pageNO
    String sortField() default "sort";
    // 也可以通过注解指定排序字段
    String sortItem() default "";
}

step3:注解处理类

@Aspect
@Component
public class PaginationHandler {

    private static final Logger log = LoggerFactory.getLogger(PaginationHandler.class);

    private static final int DEFAULT_PAGE_NO = 1;
    private static final int DEFAULT_PAGE_SIZE = 10;

    @Around("@annotation(pagination)&&args(pageParam)")
    public Object handlePagination(ProceedingJoinPoint point,
                                   Pagination pagination,
                                   Object pageParam) throws Throwable {
        int pageNo = DEFAULT_PAGE_NO;
        int pageSize = DEFAULT_PAGE_SIZE;
        String sortCols;

        String pageNoField = pagination.pageNoField();
        String pageSizeField = pagination.pageSizeField();
        String sortField = pagination.sortField();
        sortCols = pagination.sortItem();

        if (pageParam == null) {
            PageInfo pageInfo = new PageInfo(pageNo, pageSize, sortCols);
            PageUtil.setPageInfo(pageInfo);
        } else {
            if (pageParam instanceof Map) {
                Map<String, Object> param = (Map<String, Object>) pageParam;
                JSONObject json = new JSONObject(param);
                if (json.getInteger(pageNoField) != null) {
                    pageNo = json.getIntValue(pageNoField);
                }
                if (json.getInteger(pageSizeField) != null) {
                    pageSize = json.getIntValue(pageSizeField);
                }
                if (json.getInteger(sortField) != null) {
                    sortCols = json.getString(sortField);
                }
                PageInfo pageInfo = new PageInfo(pageNo, pageSize, sortCols);
                PageUtil.setPageInfo(pageInfo);
            } else {
                // 暂时只支持Map类型的参数
                // 如果需要支持其他类型的参数,可以在这边添加
                PageInfo pageInfo = new PageInfo(pageNo, pageSize, sortCols);
                PageUtil.setPageInfo(pageInfo);
            }
        }
        try {
            Object result = point.proceed();
            if (result instanceof Response) {
                Object data = ((Response) result).getData();
                if(data instanceof IPage){
                    long total = ((IPage) data).getTotal();
                    List records = ((IPage) data).getRecords();
                    PageInfo pageInfo = PageUtil.getPageInfo();
                    pageInfo.setTotal(total);
                    pageInfo.setRows(records);
                    ((Response) result).setData(pageInfo);
                }
                return result;
            } else {
                // Todo 暂时没想到好的处理方法
                return result;
            }
        } finally {
            PageUtil.removePageInfo();
        }
    }
}

step4:分页工具

public class PageUtil {

    private static final String ASC = "asc";

    private static final String DESC = "desc";

    private static final Logger log = LoggerFactory.getLogger(PageUtil.class);

    private static final ThreadLocal<PageInfo> pageInfoHolder = new ThreadLocal<>();

    public static void setPageInfo(PageInfo pageInfo) {
        pageInfoHolder.set(pageInfo);
    }

    public static void removePageInfo() {
        pageInfoHolder.remove();
    }

    public static PageInfo getPageInfo() {
        return pageInfoHolder.get();
    }

    public static <T> IPage<T> page() {
        PageInfo pageInfo = getPageInfo();
        Integer pageNo = pageInfo.getPageNo();
        Integer pageSize = pageInfo.getPageSize();
        //col1:aes,col2:des形式
        String sortCols = pageInfo.getSortColumn();
        IPage<T> iPage = new Page<>(pageNo, pageSize);
        if (!StringUtils.isEmpty(sortCols)) {
            String[] split = sortCols.split(",");
            for (String s : split) {
                try {
                    int index = s.lastIndexOf(':');
                    String col = s.substring(0, index);
                    String sortType = s.substring(index, s.length());
                    if(ASC.equalsIgnoreCase(sortCols)){
                        ((Page<T>) iPage).addOrder(OrderItem.asc(col));
                    } else if (DESC.equalsIgnoreCase(sortType)){
                        ((Page<T>) iPage).addOrder(OrderItem.desc(col));
                    } else {
                        log.warn("sort col {} is invalid, ignore it...",s);
                        continue;
                    }
                } catch (Exception e) {
                    log.warn("sort col {} is invalid, ignore it...",s);
                }
            }
        }
        return iPage;
    }
}

step5:使用

 @PostMapping("/page")
    @Pagination
    public Object info(@RequestBody Map param)  {
        IPage<User> page = userService.page(PageUtil.page(), null);
        Response response = new Response();
        response.success().success().setData(page);
        return response;
    }

上面的代码比较简单,具体就不分析了。