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
相关产品推荐
相关产品推荐

