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

JPA @ManyToMany多对多关联如何获取中间表bonus字段并绑定到实体

完全可以通过@ManyToOne+@OneToMany的关联配置实现需求,这也是JPA规范中处理带额外字段的多对多关联的标准方案。你现有实现的核心问题是存在N+1查询,遍历每个关联车辆时都单独查询一次中间表,数据量较大时性能会大幅下降。

注意前置说明

bonus是Midia和Veiculo关联关系的属性,不是Veiculo本身的固有属性:同一个Veiculo和不同Midia绑定的时候,bonus值可以完全不同,所以不建议将bonus直接作为固定字段写入Veiculos实体,避免出现逻辑冲突。


具体改造步骤

1. 定义中间表的复合主键类

中间表用两个外键作为联合主键,需要先定义主键类:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MidiaVeiculoId implements Serializable {
    // 字段名、类型要和中间表实体的对应字段完全一致
    private Long midiaCodigo;
    private Long veiculoCodigo;
}

2. 改造中间表为独立实体

把原来仅作为关联中间表的MidiaVeiculos改造为可映射的实体,配置关联关系:

@Entity
@Data
@IdClass(MidiaVeiculoId.class)
public class MidiaVeiculos {
    @Id
    private Long midiaCodigo;
    @Id
    private Long veiculoCodigo;
    
    // 中间表的额外字段
    private BigDecimal bonus;
    
    // 关联Midias表
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "midiaCodigo", insertable = false, updatable = false)
    private Midias midia;
    
    // 关联Veiculos表
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "veiculoCodigo", insertable = false, updatable = false)
    private Veiculos veiculo;
}

3. 替换原有@ManyToMany关联

将Midias、Veiculos实体中原有的@ManyToMany注解,替换为关联中间表的@OneToMany:

  • Midias实体修改:
// 原有的@ManyToMany关联veiculos的注解删除,替换为下面的配置
@OneToMany(mappedBy = "midia", cascade = CascadeType.ALL, orphanRemoval = true)
private List<MidiaVeiculos> midiaVeiculos;
  • Veiculos实体修改:
// 原有的@ManyToMany关联midias的注解删除,替换为下面的配置
@OneToMany(mappedBy = "veiculo", cascade = CascadeType.ALL, orphanRemoval = true)
private List<MidiaVeiculos> midiaVeiculos;

优化业务查询代码,消除N+1

首先在Midias的Repository中新增关联查询方法,用fetch join一次性把Midia、中间表、关联的Veiculo全部查出来,避免分次查询:

// JPQL写法,根据你的实际查询条件调整WHERE部分
@Query("SELECT m FROM Midias m LEFT JOIN FETCH m.midiaVeiculos mv LEFT JOIN FETCH mv.veiculo v WHERE 你的过滤条件")
List<Midias> findAllWithVeiculosAndBonus(你的查询参数);

改造后的业务代码不需要再单独查询中间表,直接从关联的中间表实例中取bonus即可:

private List<MidiaResponseBonus> listarMidiasPorFiltros(BuscarMidiaRequest request) {
    List<MidiaModel> midias = this.mRepository.findAllWithVeiculosAndBonus(request.listarMidiasPorFiltros());

    return midias.stream()
        .map(m -> {
            List<VeiculoResponseComBonus> veiculos = m.getMidiaVeiculos().stream()
                .map(mv -> new VeiculoResponseComBonus(
                    mv.getVeiculo().getCodigo(),
                    mv.getVeiculo().getNumeroOrdem(),
                    mv.getVeiculo().getAnoFabricacao(),
                    mv.getVeiculo().getEmpresa(),
                    mv.getVeiculo().getRota(),
                    mv.getVeiculo().getOperacional(),
                    mv.getBonus() // 直接从中间表获取,无额外查询
                )).collect(Collectors.toList());
            
            return new MidiaResponseBonus(
                m.getCodigo(),
                m.getCliente(),
                m.getCampanha(),
                m.getProduto(),
                m.getPedidoInsercao(),
                m.getPedidoProducao(),
                m.getInicioPeriodo(),
                m.getTerminoPeriodo(),
                m.getInicioFotos(),
                m.getTerminoFotos(),
                veiculos
            );
        }).collect(Collectors.toList());
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 11:15:03