MyBatis-Plus分页功能优化:IPage转换与PageDTO增强

MyBatis-Plus分页功能优化:IPage转换与PageDTO增强

【免费下载链接】mybatis-plus mybatis 增强工具包,简化 CRUD 操作。 文档 http://baomidou.com 低代码组件库 http://aizuda.com 【免费下载链接】mybatis-plus 项目地址: https://gitcode.com/baomidou/mybatis-plus

前言:分页功能的痛点与挑战

在日常开发中,分页查询是最常见的业务场景之一。然而,传统的分页实现往往面临诸多挑战:

  • 序列化问题:分页对象在JSON序列化时产生冗余字段
  • 跨服务传输:微服务架构下分页数据在不同服务间传递困难
  • 类型转换:不同层之间的分页对象转换繁琐
  • 配置复杂性:分页参数配置分散,难以统一管理

MyBatis-Plus作为MyBatis的增强工具,提供了强大的分页功能解决方案。本文将深入探讨IPage接口的设计理念、Page类的实现细节,以及如何通过PageDTO进行功能增强和优化。

IPage接口:分页功能的核心抽象

接口设计概览

IPage接口定义了分页功能的核心契约,提供了丰富的方法来支持各种分页场景:

public interface IPage<T> extends Serializable {
    // 排序信息
    List<OrderItem> orders();
    
    // SQL优化配置
    boolean optimizeCountSql();
    boolean optimizeJoinOfCountSql();
    
    // 分页计算
    long offset();
    long getPages();
    
    // 数据操作
    List<T> getRecords();
    IPage<T> setRecords(List<T> records);
    
    // 分页参数
    long getTotal();
    IPage<T> setTotal(long total);
    long getSize();
    IPage<T> setSize(long size);
    long getCurrent();
    IPage<T> setCurrent(long current);
    
    // 类型转换
    <R> IPage<R> convert(Function<? super T, ? extends R> mapper);
}

核心功能详解

1. 分页计算能力
// 计算偏移量
default long offset() {
    long current = getCurrent();
    if (current <= 1L) {
        return 0L;
    }
    return Math.max((current - 1) * getSize(), 0L);
}

// 计算总页数
default long getPages() {
    if (getSize() == 0) {
        return 0L;
    }
    long pages = getTotal() / getSize();
    if (getTotal() % getSize() != 0) {
        pages++;
    }
    return pages;
}
2. 类型安全转换
@SuppressWarnings("unchecked")
default <R> IPage<R> convert(Function<? super T, ? extends R> mapper) {
    List<R> collect = this.getRecords().stream().map(mapper).collect(toList());
    return ((IPage<R>) this).setRecords(collect);
}

Page类:IPage接口的标准实现

类结构设计

Page类提供了IPage接口的完整实现,支持丰富的配置选项:

public class Page<T> implements IPage<T> {
    // 核心数据字段
    private List<T> records = Collections.emptyList();
    private long total = 0;
    private long size = 10;
    private long current = 1;
    
    // 配置选项
    private List<OrderItem> orders = new ArrayList<>();
    private boolean optimizeCountSql = true;
    private boolean searchCount = true;
    private boolean optimizeJoinOfCountSql = true;
    private Long maxLimit;
    private String countId;
}

使用示例

基础分页查询
// 创建分页对象
Page<User> page = new Page<>(1, 10);
page.addOrder(OrderItem.asc("create_time"));

// 执行分页查询
IPage<User> result = userMapper.selectPage(page, 
    Wrappers.<User>lambdaQuery()
        .eq(User::getStatus, 1)
);

// 处理结果
List<User> users = result.getRecords();
long total = result.getTotal();
long pages = result.getPages();
高级配置选项
// 自定义分页配置
Page<User> page = new Page<>(1, 20, 0, false);
page.setOptimizeCountSql(false);      // 禁用COUNT SQL优化
page.setSearchCount(false);           // 不进行COUNT查询
page.setMaxLimit(100L);               // 设置最大分页限制
page.setCountId("customCountQuery");  // 指定COUNT查询ID

PageDTO:解决跨服务传输难题

设计背景与问题

在微服务架构中,分页对象在不同服务间传输时面临以下问题:

  1. 序列化冗余:Page对象包含大量内部状态字段
  2. 版本兼容:服务间版本不一致导致反序列化失败
  3. 配置泄露:内部配置信息不应暴露给外部服务

PageDTO实现方案

PageDTO继承自Page类,主要提供Getter方法用于序列化:

public class PageDTO<T> extends Page<T> {
    // 提供Getter方法用于序列化
    public String getCountId() {
        return super.countId();
    }
    
    public Long getMaxLimit() {
        return super.maxLimit();
    }
    
    public List<OrderItem> getOrders() {
        return super.orders();
    }
    
    public boolean isOptimizeCountSql() {
        return super.optimizeCountSql();
    }
    
    public boolean isSearchCount() {
        return super.searchCount();
    }
    
    public boolean isOptimizeJoinOfCountSql() {
        return super.optimizeJoinOfCountSql();
    }
}

使用场景对比

传统方式的问题
// 服务A:返回Page对象
public IPage<User> getUsers(int page, int size) {
    Page<User> pageParam = new Page<>(page, size);
    return userMapper.selectPage(pageParam, Wrappers.emptyWrapper());
}

// 服务B:接收Page对象(可能序列化失败)
public void processUsers(IPage<User> users) {
    // 反序列化可能失败,因为Page包含内部状态
}
PageDTO解决方案
// 服务A:返回PageDTO对象
public PageDTO<User> getUsers(int page, int size) {
    Page<User> pageParam = new Page<>(page, size);
    IPage<User> result = userMapper.selectPage(pageParam, Wrappers.emptyWrapper());
    
    // 转换为PageDTO进行传输
    PageDTO<User> dto = new PageDTO<>();
    dto.setRecords(result.getRecords());
    dto.setTotal(result.getTotal());
    dto.setCurrent(result.getCurrent());
    dto.setSize(result.getSize());
    return dto;
}

// 服务B:安全接收PageDTO
public void processUsers(PageDTO<User> users) {
    // 安全反序列化,只包含必要字段
    List<User> userList = users.getRecords();
    long total = users.getTotal();
}

高级优化技巧

1. 分页参数统一管理

/**
 * 分页参数构建器
 */
public class PageBuilder {
    
    public static <T> Page<T> buildPage(int pageNum, int pageSize) {
        return new Page<>(pageNum, pageSize);
    }
    
    public static <T> Page<T> buildPage(int pageNum, int pageSize, 
                                      String orderBy, boolean ascending) {
        Page<T> page = new Page<>(pageNum, pageSize);
        if (StringUtils.isNotBlank(orderBy)) {
            page.addOrder(ascending ? 
                OrderItem.asc(orderBy) : OrderItem.desc(orderBy));
        }
        return page;
    }
    
    public static <T> PageDTO<T> toDTO(IPage<T> page) {
        PageDTO<T> dto = new PageDTO<>();
        dto.setRecords(page.getRecords());
        dto.setTotal(page.getTotal());
        dto.setCurrent(page.getCurrent());
        dto.setSize(page.getSize());
        dto.setOrders(new ArrayList<>(page.orders()));
        return dto;
    }
}

2. 类型安全转换工具

/**
 * 分页数据转换工具
 */
public class PageConverter {
    
    public static <T, R> IPage<R> convert(IPage<T> source, 
                                         Function<T, R> converter) {
        return source.convert(converter);
    }
    
    public static <T, R> PageDTO<R> convertToDTO(IPage<T> source, 
                                                Function<T, R> converter) {
        IPage<R> converted = source.convert(converter);
        PageDTO<R> dto = new PageDTO<>();
        dto.setRecords(converted.getRecords());
        dto.setTotal(converted.getTotal());
        dto.setCurrent(converted.getCurrent());
        dto.setSize(converted.getSize());
        return dto;
    }
}

3. 响应对象封装

/**
 * 统一分页响应对象
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageResponse<T> {
    private long current;
    private long size;
    private long total;
    private long pages;
    private List<T> records;
    private List<OrderItem> orders;
    
    public static <T> PageResponse<T> of(IPage<T> page) {
        return new PageResponse<>(
            page.getCurrent(),
            page.getSize(),
            page.getTotal(),
            page.getPages(),
            page.getRecords(),
            new ArrayList<>(page.orders())
        );
    }
    
    public static <T> PageResponse<T> of(PageDTO<T> pageDTO) {
        return new PageResponse<>(
            pageDTO.getCurrent(),
            pageDTO.getSize(),
            pageDTO.getTotal(),
            pageDTO.getPages(),
            pageDTO.getRecords(),
            new ArrayList<>(pageDTO.getOrders())
        );
    }
}

实战应用案例

案例1:电商商品分页查询

@Service
@RequiredArgsConstructor
public class ProductService {
    
    private final ProductMapper productMapper;
    
    /**
     * 商品分页查询
     */
    public PageResponse<ProductVO> getProducts(ProductQuery query) {
        // 构建分页参数
        Page<Product> page = PageBuilder.buildPage(
            query.getPageNum(), 
            query.getPageSize(),
            query.getOrderBy(),
            query.isAscending()
        );
        
        // 执行查询
        IPage<Product> result = productMapper.selectPage(page,
            Wrappers.<Product>lambdaQuery()
                .eq(Product::getStatus, 1)
                .like(StringUtils.isNotBlank(query.getKeyword()),
                    Product::getName, query.getKeyword())
                .eq(query.getCategoryId() != null,
                    Product::getCategoryId, query.getCategoryId())
        );
        
        // 转换为VO并返回
        return PageResponse.of(
            PageConverter.convertToDTO(result, this::toVO)
        );
    }
    
    private ProductVO toVO(Product product) {
        return ProductVO.builder()
            .id(product.getId())
            .name(product.getName())
            .price(product.getPrice())
            .image(product.getImage())
            .build();
    }
}

案例2:微服务间的分页数据传输

// 商品服务
@RestController
@RequestMapping("/products")
@RequiredArgsConstructor
public class ProductController {
    
    private final ProductService productService;
    
    @GetMapping
    public ResponseEntity<PageResponse<ProductVO>> getProducts(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {
        
        PageResponse<ProductVO> response = productService.getProducts(page, size);
        return ResponseEntity.ok(response);
    }
}

// 订单服务(调用商品服务)
@Service
@RequiredArgsConstructor
public class OrderService {
    
    private final RestTemplate restTemplate;
    
    public PageResponse<ProductVO> getAvailableProducts(int page, int size) {
        String url = "http://product-service/products?page={page}&size={size}";
        
        // 调用商品服务获取分页数据
        ResponseEntity<PageResponse<ProductVO>> response = restTemplate.exchange(
            url, HttpMethod.GET, null,
            new ParameterizedTypeReference<PageResponse<ProductVO>>() {},
            page, size
        );
        
        return response.getBody();
    }
}

性能优化建议

1. COUNT查询优化

// 在明确知道不需要总数时禁用COUNT查询
Page<User> page = new Page<>(1, 10, false);

// 或者根据业务场景动态决定
public IPage<User> getUsers(boolean needTotal) {
    Page<User> page = new Page<>(1, 10, needTotal);
    return userMapper.selectPage(page, Wrappers.emptyWrapper());
}

2. 分页大小限制

// 防止过大的分页请求
public Page<User> createSafePage(int pageNum, int pageSize) {
    int safeSize = Math.min(pageSize, 100); // 最大100条
    return new Page<>(Math.max(pageNum, 1), safeSize);
}

3. 缓存策略

// 使用Redis缓存分页结果
@Cacheable(value = "users", key = "#pageNum + '-' + #pageSize")
public PageResponse<UserVO> getUsers(int pageNum, int pageSize) {
    Page<User> page = new Page<>(pageNum, pageSize);
    IPage<User> result = userMapper.selectPage(page, Wrappers.emptyWrapper());
    return PageResponse.of(result.convert(this::toVO));
}

总结

MyBatis-Plus的分页功能通过IPage接口、Page类和PageDTO的协同工作,提供了强大而灵活的分页解决方案。关键优化点包括:

  1. 接口设计:IPage提供了统一的分页契约,支持各种分页场景
  2. 实现完善:Page类提供了丰富的配置选项和工具方法
  3. 传输优化:PageDTO解决了跨服务传输的序列化问题
  4. 类型安全:内置的convert方法支持安全的类型转换

通过合理的架构设计和最佳实践,可以构建出高性能、易维护的分页系统,满足各种复杂的业务需求。

在实际项目中,建议:

  • 使用PageDTO进行跨服务数据传输
  • 封装统一的分页工具类
  • 实现类型安全的转换机制
  • 根据业务场景优化COUNT查询
  • 设置合理的分页大小限制

这些优化措施将显著提升系统的稳定性、性能和可维护性。

【免费下载链接】mybatis-plus mybatis 增强工具包,简化 CRUD 操作。 文档 http://baomidou.com 低代码组件库 http://aizuda.com 【免费下载链接】mybatis-plus 项目地址: https://gitcode.com/baomidou/mybatis-plus

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值