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

如何在Repository中正确映射原生查询到实体模型

解决方案:正确映射一对多的查询结果

你的核心问题是SQL返回的是一个实验对应多个化合物的一对多结果,但之前的模型设计没有正确处理这种关系,导致要么重复数据要么映射失败。以下是两种可行的解决方式:

方式一:用DTO手动组装结果(快速适配现有原生查询)

因为你用的是原生SQL查询,直接用DTO(数据传输对象,不需要@Entity注解)接收聚合结果是最直接的方案,能避开JPA实体映射的限制。

1. 定义DTO类

// 实验详情DTO,聚合多个化合物
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExperimentDetailsDTO {
    private String externalId;
    private String isid;
    private List<Compound> compounds;
}

// 化合物DTO
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Compound {
    private String name;
    private String controlledStatus;
    private String jurisdictionName;
    private String resultComments;
    private String codeName;
    private String corporateId;
}

2. 修改Repository返回原始结果

将Repository方法改为返回Tuple类型,获取每一行的原始查询数据:

public interface ExperimentDetailsRepository extends JpaRepository<ControlledEvent, String> {

    @Query(value = "Select ce.external_id, ce.isid, ji.name, ji.controlled_status, " +
        "ji.jurisdiction_name, ji.result_comments," +
        "ji.code_name, s.corporate_id " +
        "from cs_jurisdiction_information ji " +
        "Join substance s on ji.substance_id = s.id " +
        "Join controlled_event ce on ce.id = s.controlled_event_id " +
        "and ce.calling_system_id = 402 " +
        "and ce.external_id = :externalId", nativeQuery = true)
    List<Tuple> findRawDataByExternalId(@Param("externalId") String externalId);
}

3. 在Service层组装DTO

提取实验的公共信息,再把每一行的化合物数据组装成集合:

@Service
public class ExperimentDetailsService {

    @Autowired
    private ExperimentDetailsRepository repository;

    public ExperimentDetailsDTO getExperimentDetails(String externalId) {
        List<Tuple> rawData = repository.findRawDataByExternalId(externalId);
        if (rawData.isEmpty()) {
            return null;
        }

        // 提取实验公共字段(所有行的external_id和isid一致)
        String expExternalId = (String) rawData.get(0).get("external_id");
        String isid = (String) rawData.get(0).get("isid");

        // 组装化合物列表
        List<Compound> compounds = new ArrayList<>();
        for (Tuple row : rawData) {
            Compound compound = new Compound();
            compound.setName((String) row.get("name"));
            compound.setControlledStatus((String) row.get("controlled_status"));
            compound.setJurisdictionName((String) row.get("jurisdiction_name"));
            compound.setResultComments((String) row.get("result_comments"));
            compound.setCodeName((String) row.get("code_name"));
            compound.setCorporateId((String) row.get("corporate_id"));
            compounds.add(compound);
        }

        return new ExperimentDetailsDTO(expExternalId, isid, compounds);
    }
}

方式二:建立JPA实体关联(符合ORM规范)

如果数据库表本身是一对多关系(一个controlled_event对应多个substance,每个substance对应一个jurisdiction_information),可以通过实体关联实现自动映射,无需原生SQL。

1. 定义关联实体

// ControlledEvent实体(对应controlled_event表)
@Entity
@Table(name = "controlled_event")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ControlledEvent {
    @Id
    private Long id;
    private String externalId;
    private String isid;
    private Integer callingSystemId;

    // 一对多关联Substance
    @OneToMany(mappedBy = "controlledEvent", fetch = FetchType.LAZY)
    private List<Substance> substances;
}

// Substance实体(对应substance表)
@Entity
@Table(name = "substance")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Substance {
    @Id
    private Long id;
    private String corporateId;

    // 多对一关联ControlledEvent
    @ManyToOne
    @JoinColumn(name = "controlled_event_id")
    private ControlledEvent controlledEvent;

    // 一对一关联JurisdictionInformation
    @OneToOne(mappedBy = "substance", fetch = FetchType.EAGER)
    private JurisdictionInformation jurisdictionInfo;
}

// JurisdictionInformation实体(对应cs_jurisdiction_information表)
@Entity
@Table(name = "cs_jurisdiction_information")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JurisdictionInformation {
    @Id
    private Long id;
    private String name;
    private String controlledStatus;
    private String jurisdictionName;
    private String resultComments;
    private String codeName;

    // 一对一关联Substance
    @OneToOne
    @JoinColumn(name = "substance_id")
    private Substance substance;
}

2. 简化Repository查询

public interface ControlledEventRepository extends JpaRepository<ControlledEvent, Long> {
    Optional<ControlledEvent> findByExternalIdAndCallingSystemId(String externalId, Integer callingSystemId);
}

3. Service层组装DTO

从关联实体中提取数据,转换为目标DTO格式:

@Service
public class ExperimentDetailsService {

    @Autowired
    private ControlledEventRepository eventRepository;

    public ExperimentDetailsDTO getExperimentDetails(String externalId) {
        Optional<ControlledEvent> eventOpt = eventRepository.findByExternalIdAndCallingSystemId(externalId, 402);
        if (eventOpt.isEmpty()) {
            return null;
        }

        ControlledEvent event = eventOpt.get();
        List<Compound> compounds = event.getSubstances().stream()
                .map(substance -> {
                    JurisdictionInformation ji = substance.getJurisdictionInfo();
                    Compound compound = new Compound();
                    compound.setName(ji.getName());
                    compound.setControlledStatus(ji.getControlledStatus());
                    compound.setJurisdictionName(ji.getJurisdictionName());
                    compound.setResultComments(ji.getResultComments());
                    compound.setCodeName(ji.getCodeName());
                    compound.setCorporateId(substance.getCorporateId());
                    return compound;
                })
                .collect(Collectors.toList());

        return new ExperimentDetailsDTO(event.getExternalId(), event.getIsid(), compounds);
    }
}

为什么之前的方案失败?

  • 扁平实体模型:JPA会把查询的每一行映射为一个实体对象,而externalId是@Id,JPA会认为所有行都是同一个实体,后续行的字段会覆盖前面的,导致重复第一个化合物的数据。
  • @Embedded单Compound:@Embedded只能映射单个嵌入对象,无法处理多行的化合物数据,所以只能得到第一个化合物。
  • @Embedded List:JPA的@Embedded不支持集合类型,无法将多行结果映射为一个实体中的集合字段,因此compounds字段为空。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 14:40:24