Hibernate ManyToOne映射下双表带条件关联查询方法
Hibernate双向关联多表条件查询实现
你当前的实体映射有几处配置错误,会直接导致关联查询失败,先修正映射,再通过JPA标准的关联路径写条件即可,Hibernate会自动根据外键映射生成对应JOIN逻辑,不需要手写ON关联条件。
先修正实体映射问题
现有映射的核心问题:
Subs实体重复映射了外键列:单独定义的Long cinId普通字段和@ManyToOne关联都指向cinId列,启动时会报重复列映射错误;且@ManyToOne修饰的关联属性未命名Cin实体@OneToMany的mappedBy配置错误,该属性要指向对方实体中关联自身的属性名,不是数据库字段名Cin实体中is_fix属性不符合Java驼峰命名规范,后续查询容易写错属性名
修正后的实体代码:
Subs 实体
@Entity @Table(name = "subs") public class Subs { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // 按实际主键策略调整 private Long id; // 移除重复的cinId普通字段,只保留关联属性 @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "cinId", insertable = false, updatable = false) private Cin cin; // 此处属性名要和Cin侧mappedBy配置一致 @NotNull @NotBlank @Column(name = "status") private String status; @NotNull @NotBlank @Column(name = "mode") private String mode; // 省略getter、setter、构造方法 }
Cin 实体
@Entity @Table(name = "cin") public class Cin { @Id @Column(name = "cinId") private Long id; // mappedBy指向Subs中关联Cin的属性名"cin" @OneToMany(fetch = FetchType.LAZY, mappedBy = "cin") private List<Subs> subs; @Column(name = "is_eligible") private Boolean isEligible; // 调整为驼峰命名,对应数据库列is_fix @Column(name = "is_fix") private Boolean isFix; // 省略getter、setter、构造方法 }
编写查询逻辑
映射修正后,两种常用写法都可以实现你需要的查询效果,最终生成的SQL和你给出的原生SQL逻辑完全一致。
写法1:JPQL/HQL(简单场景推荐)
直接在查询中JOIN关联属性,通过导航路径指定两边表的过滤条件即可:
// DAO层注入EntityManager即可使用 public List<Long> queryMatchedCinIds(String inputMode, String inputStatus) { String hql = """ select distinct c.id from Cin c join c.subs s where s.mode = :mode and s.status = :status and c.isEligible = true and c.isFix = false """; return entityManager.createQuery(hql, Long.class) .setParameter("mode", inputMode) .setParameter("status", inputStatus) .getResultList(); }
必须加
distinct关键字:因为Cin和Subs是一对多关系,JOIN后如果一个Cin关联多个符合条件的Subs,会返回重复的Cin ID,加distinct可以去重,和原生SQL预期结果一致。如果需要查询完整Cin实体,把select子句改成select distinct c即可。
写法2:Criteria API(动态条件场景推荐)
如果后续需要动态拼接过滤条件,用类型安全的Criteria API更方便:
public List<Cin> queryMatchedCins(String inputMode, String inputStatus) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Cin> cq = cb.createQuery(Cin.class); Root<Cin> cinRoot = cq.from(Cin.class); // 默认是INNER JOIN,和需求一致 Join<Cin, Subs> subsJoin = cinRoot.join("subs"); List<Predicate> predicates = new ArrayList<>(); predicates.add(cb.equal(subsJoin.get("mode"), inputMode)); predicates.add(cb.equal(subsJoin.get("status"), inputStatus)); predicates.add(cb.isTrue(cinRoot.get("isEligible"))); predicates.add(cb.isFalse(cinRoot.get("isFix"))); cq.select(cinRoot) .distinct(true) .where(predicates.toArray(new Predicate[0])); return entityManager.createQuery(cq).getResultList(); }
如果项目引入了JPA元模型生成依赖,把字符串属性名替换成元模型类的属性引用即可,避免硬编码字符串写错字段名。
说明
不推荐保留原映射中重复定义cinId普通字段的写法,这种写法会导致关联对象和外键字段状态不同步,后续更新数据很容易出现不一致问题,完全不符合JPA关联映射的设计规范。
内容的提问来源于stack exchange,提问作者Naveen Yadav
相关产品推荐
相关产品推荐

