mybaits跨表查询返回分页
1. 概述本文档详细介绍基于MyBatis-Plus框架实现的跨表查询分页功能。以供应商物料查询为例展示如何通过多表关联查询并返回标准分页对象的技术实现方案。2. 技术栈ORM框架: MyBatis-Plus数据库: MySQL使用LIMIT进行分页分页组件: MyBatis-Plus Page对象架构模式: Repository Mapper XML三层架构3. 核心实现原理3.1 分页策略采用手动分页方式分为两步执行查询总数: 执行COUNT查询获取符合条件的总记录数查询数据: 根据偏移量(offset)和页大小(size)查询具体数据3.2 为什么选择手动分页跨表查询涉及LEFT JOINMyBatis-Plus自动分页可能存在性能问题可以精确控制SQL语句优化查询性能支持复杂的动态条件拼接4. 代码实现详解4.1 参数对象设计SupplierIdParam.java - 查询参数封装Getter Setter ToString public class SupplierIdParam extends PageQueryParam { private Long tenantId; // 租户ID private Long supplierId; // 供应商ID private String materialCategoryExternalCode; // 物料分类外部编码 private String materialCategoryName; // 物料分类名称 private String materialName; // 物料名称 private String materialExternalCode; // 物料编码 private String materialStatus; // 物料状态 private String pricingType; // 定价类型 // ... 其他字段 }关键点:继承PageQueryParam包含currentPage和pageSize分页参数支持多维度动态查询条件使用包装类型便于判断参数是否为空4.2 领域对象设计SupplierMaterialDO.java - 供应商物料实体Getter Setter TableName(value t_supplier_material, autoResultMap true) public class SupplierMaterialDO implements BaseEntity { private Long id; private Long tenantId; private Long supplierDataId; private Long materialCategoryId; private String materialCategoryExternalCode; private String materialCategoryName; private Long materialId; private String externalCode; private String name; // ... 其他字段 }关键点:使用TableName指定表名autoResultMap true支持复杂结果映射实现BaseEntity接口包含通用字段4.3 Repository层实现SupplierMaterialRepository.javaRepository public class SupplierMaterialRepository extends HussarServiceImplSupplierMaterialMapper, SupplierMaterialDO { Resource private SupplierMaterialMapper supplierMaterialMapper; public PageSupplierMaterialDO queryMaterialsBySupplierId(SupplierIdParam param) { Long tenantId param.getTenantId(); // 第一步查询总数 Long total supplierMaterialMapper.queryMaterialsBySupplierIdCount(param, tenantId); // 第二步创建分页对象 PageSupplierMaterialDO page new Page(param.getCurrentPage(), param.getPageSize(), total); // 第三步查询分页数据 ListSupplierMaterialDO supplierMaterialDOList supplierMaterialMapper.queryMaterialsBySupplierId(param, tenantId, page.offset(), page.getSize()); // 第四步设置结果集 if (CollectionUtils.isNotEmpty(supplierMaterialDOList)) { page.setRecords(supplierMaterialDOList); } return page; } }实现要点:分离计数查询: 单独执行COUNT查询避免查询不必要的数据列计算偏移量: 使用page.offset()方法自动计算OFFSET值空值保护: 检查查询结果是否为空避免设置null集合租户隔离: 所有查询都携带tenantId实现多租户数据隔离4.4 Mapper接口定义SupplierMaterialMapper.javaMapper public interface SupplierMaterialMapper extends BaseMapperSupplierMaterialDO, HussarMapperSupplierMaterialDO { /** * 查询总数 */ Long queryMaterialsBySupplierIdCount(Param(param) SupplierIdParam param, Param(tenantId) Long tenantId); /** * 查询分页数据 */ ListSupplierMaterialDO queryMaterialsBySupplierId(Param(param) SupplierIdParam param, Param(tenantId) Long tenantId, Param(offset) Long offset, Param(size) Long size); }设计说明:使用Param注解明确参数名称便于XML中引用将offset和size作为独立参数传递提高SQL可读性返回类型分别为Long和ListSupplierMaterialDO4.5 MyBatis XML实现SupplierMaterialMapper.xml4.5.1 总数查询SQLselect idqueryMaterialsBySupplierIdCount resultTypejava.lang.Long SELECT count(*) AS total FROM t_supplier_material tsm LEFT JOIN t_material tm ON tsm.material_id tm.id WHERE tsm.delete_flag 0 AND tsm.tenant_id #{tenantId} if testparam.supplierId ! null and param.supplierId ! AND supplier_id #{param.supplierId} /if if testparam.materialCategoryExternalCode ! null and param.materialCategoryExternalCode ! AND tsm.material_category_external_code LIKE CONCAT(%, #{param.materialCategoryExternalCode}, %) /if if testparam.materialCategoryName ! null and param.materialCategoryName ! AND tsm.material_category_name LIKE CONCAT(%, #{param.materialCategoryName}, %) /if if testparam.materialName ! null and param.materialName ! AND tsm.name LIKE CONCAT(%, #{param.materialName}, %) /if if testparam.materialExternalCode ! null and param.materialExternalCode ! AND tsm.external_code LIKE CONCAT(%, #{param.materialExternalCode}, %) /if if testparam.materialStatus ! null and param.materialStatus ! AND tm.status #{param.materialStatus} /if if testparam.pricingType ! null and param.pricingType ! AND tm.pricing_type #{param.pricingType} /if /select4.5.2 分页数据查询SQLselect idqueryMaterialsBySupplierId resultTypecom.mdgyl.hussar.basic.supplier.masterdata.domain.SupplierMaterialDO SELECT tsm.* FROM t_supplier_material tsm LEFT JOIN t_material tm ON tsm.material_id tm.id WHERE tsm.delete_flag 0 AND tsm.tenant_id #{tenantId} !-- 动态条件与COUNT查询保持一致 -- if testparam.supplierId ! null and param.supplierId ! AND supplier_id #{param.supplierId} /if if testparam.materialCategoryExternalCode ! null and param.materialCategoryExternalCode ! AND tsm.material_category_external_code LIKE CONCAT(%, #{param.materialCategoryExternalCode}, %) /if !-- ... 其他动态条件 ... -- ORDER BY tsm.id DESC LIMIT #{offset}, #{size} /selectSQL编写要点:表别名规范: 使用简短且有意义的别名如tsm、tm动态条件: 使用if标签实现条件可选确保COUNT和DATA查询条件一致模糊查询: 使用CONCAT(%, value, %)实现LIKE模糊匹配软删除过滤: 始终添加delete_flag 0条件排序规则: 使用ORDER BY id DESC保证数据稳定性LIMIT分页: 使用LIMIT #{offset}, #{size}实现物理分页