You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Data JPA同请求查询3个子类返回按日期排序的Page分页对象

实现方案

你当前使用@MappedSuperclass的继承模式不会为父类生成独立数据表,三个子类对应三张完全独立的表,JPA默认无法直接跨表做统一排序分页,可根据项目实际情况选择以下两种实现路径:

方案一:调整JPA继承策略(推荐,适合新项目/可调整实体结构的场景)

JPA原生支持多态查询,只需要把父类的@MappedSuperclass替换为实体继承注解,即可直接实现跨表的统一分页查询,无需手写复杂SQL。

  1. 修改父类实体注解,选择TABLE_PER_CLASS继承策略(该策略下每个子类表独立存储父类+自身字段,和你现有表结构完全兼容,不需要调整表字段):
//lombok annotations
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Parent {
    @Id
    // 注意:不要用IDENTITY自增主键,会导致三张表主键重复,建议改用雪花ID等全局唯一主键生成策略
    @GeneratedValue(generator = "snowflake")
    @GenericGenerator(name = "snowflake", strategy = "com.example.SnowflakeIdGenerator")
    private Long id;
    private String city; // 原字段名City首字母大写不符合小驼峰规范,调整后和返回结构的city字段匹配
    private Date date;
}
  1. 给三个子类加上@Entity注解,原有字段逻辑无需修改:
//lombok annotations
@Entity
public class Child1 extends Parent {
    private Obj1 obj1;
}

//lombok annotations
@Entity
public class Child2 extends Parent {
    private Obj1 obj2;    
}

//lombok annotations
@Entity
public class Child3 extends Parent {
    private Obj1 obj3;
}
  1. 新增父类通用Repository,直接调用内置的分页查询方法即可:
public interface ParentRepository extends JpaRepository<Parent, Long> {}
  1. 业务层直接调用查询,传入排序分页参数即可得到符合要求的Page<Parent>结果,JPA会自动生成UNION ALL语句合并三张表的数据、按date排序后分页,返回的实体序列化为JSON时会自动带上子类独有的obj1/obj2/obj3字段,完全匹配你给出的返回结构:
@Service
public class ParentService {
    @Autowired
    private ParentRepository parentRepository;

    public Page<Parent> queryPage(int pageNum, int pageSize) {
        Pageable pageable = PageRequest.of(pageNum, pageSize, Sort.by(Direction.ASC, "date"));
        return parentRepository.findAll(pageable);
    }
}

方案二:手动实现跨表分页(适合老项目/无法调整现有实体映射的场景)

如果不能修改现有@MappedSuperclass的实体结构,可以通过原生SQL+批量查询的方式手动实现分页,避免全表拉取到内存排序的性能问题:

  1. 业务层实现逻辑:先统计三张表总数据量,再通过原生SQLUNION ALL排序分页取出当前页的ID和对应实体类型,最后分类型批量查询实体按顺序组装即可,示例代码如下:
@Service
public class ParentQueryService {
    @Autowired
    private Child1Repository child1Repository;
    @Autowired
    private Child2Repository child2Repository;
    @Autowired
    private Child3Repository child3Repository;
    @PersistenceContext
    private EntityManager entityManager;

    public Page<Parent> getMergedPage(int pageNum, int pageSize) {
        Sort sort = Sort.by(Sort.Direction.ASC, "date");
        Pageable pageable = PageRequest.of(pageNum, pageSize, sort);
        
        // 统计总记录数
        long total = child1Repository.count() + child2Repository.count() + child3Repository.count();
        if (total == 0) {
            return Page.empty(pageable);
        }

        // 原生SQL查询当前页的ID和实体类型,表名字段名替换成你实际数据库的名称
        String unionSql = """
            (SELECT id, date, 'child1' AS type FROM child1)
            UNION ALL
            (SELECT id, date, 'child2' AS type FROM child2)
            UNION ALL
            (SELECT id, date, 'child3' AS type FROM child3)
            ORDER BY date ASC
            LIMIT ? OFFSET ?
        """;
        int offset = (int) pageable.getOffset();
        List<?> idRows = entityManager.createNativeQuery(unionSql)
                .setParameter(1, pageSize)
                .setParameter(2, offset)
                .getResultList();

        // 按类型分组收集ID
        List<Long> c1Ids = new ArrayList<>(), c2Ids = new ArrayList<>(), c3Ids = new ArrayList<>();
        for (Object row : idRows) {
            Object[] cols = (Object[]) row;
            Long id = ((Number) cols[0]).longValue();
            String type = (String) cols[2];
            switch (type) {
                case "child1" -> c1Ids.add(id);
                case "child2" -> c2Ids.add(id);
                case "child3" -> c3Ids.add(id);
            }
        }

        // 批量查询对应实体
        Map<String, Map<Long, Parent>> entityMap = Map.of(
                "child1", new HashMap<>(),
                "child2", new HashMap<>(),
                "child3", new HashMap<>()
        );
        if (!c1Ids.isEmpty()) {
            child1Repository.findAllById(c1Ids).forEach(e -> entityMap.get("child1").put(e.getId(), e));
        }
        if (!c2Ids.isEmpty()) {
            child2Repository.findAllById(c2Ids).forEach(e -> entityMap.get("child2").put(e.getId(), e));
        }
        if (!c3Ids.isEmpty()) {
            child3Repository.findAllById(c3Ids).forEach(e -> entityMap.get("child3").put(e.getId(), e));
        }

        // 按排序顺序组装结果
        List<Parent> content = new ArrayList<>();
        for (Object row : idRows) {
            Object[] cols = (Object[]) row;
            Long id = ((Number) cols[0]).longValue();
            String type = (String) cols[2];
            content.add(entityMap.get(type).get(id));
        }

        return new PageImpl<>(content, pageable, total);
    }
}

注意事项

  • 方案一必须保证三个子表主键全局唯一,禁止使用数据库自增ID,避免不同表出现相同ID导致数据映射错误。
  • 方案二的原生SQL需要和你实际数据库的表名、字段名保持一致,如果需要多字段排序,直接修改ORDER BY后的条件即可。
  • 你原有父类中City字段名首字母大写,不符合Java编码规范,会导致JSON序列化后字段名首字母大写,和你给出的返回示例不符,建议调整为小驼峰city。

内容的提问来源于stack exchange,提问作者xp test

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 07:51:18