JPA多对一映射:如何跨关联实体传递字段关联Revision?
解决方案
首先你需要先正确映射Revision实体的复合唯一键,因为JPA要求关联的目标实体必须有主键(或复合主键):
1. 映射Revision的复合主键
方式一:使用@IdClass
import java.io.Serializable; import java.util.Objects; // 复合主键类,必须实现Serializable,并重写equals和hashCode class RevisionId implements Serializable { private Integer id; private String code; public RevisionId() {} public RevisionId(Integer id, String code) { this.id = id; this.code = code; } // getter和setter略 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; RevisionId that = (RevisionId) o; return Objects.equals(id, that.id) && Objects.equals(code, that.code); } @Override public int hashCode() { return Objects.hash(id, code); } } @Entity @IdClass(RevisionId.class) class Revision { @Id private Integer id; @Id private String code; // 其他字段和getter/setter略 }
方式二:使用@EmbeddedId
import java.io.Serializable; import java.util.Objects; @Embeddable class RevisionId implements Serializable { private Integer id; private String code; public RevisionId() {} public RevisionId(Integer id, String code) { this.id = id; this.code = code; } // getter和setter略 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; RevisionId that = (RevisionId) o; return Objects.equals(id, that.id) && Objects.equals(code, that.code); } @Override public int hashCode() { return Objects.hash(id, code); } } @Entity class Revision { @EmbeddedId private RevisionId id; // 其他字段和getter/setter略 }
2. 关联Product与Revision
由于Product表中没有存储manufacturer的code,需要通过关联的Manufacturer实体获取该值作为关联条件之一。这里可以使用Hibernate的扩展注解@JoinColumnsOrFormulas和@JoinFormula实现跨表关联:
@Entity class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @ManyToOne @JoinColumn(name = "manufacturer_id") private Manufacturer manufacturer; private Integer revisionId; @ManyToOne @JoinColumnsOrFormulas({ // 关联revision的id字段,对应product的revision_id @JoinColumnOrFormula(column = @JoinColumn(name = "revision_id", referencedColumnName = "id", insertable = false, updatable = false)), // 通过子查询获取当前product关联的manufacturer的code,关联revision的code字段 @JoinColumnOrFormula(formula = @JoinFormula(value = "(SELECT m.code FROM manufacturer m WHERE m.id = manufacturer_id)", referencedColumnName = "code")) }) private Revision revision; // 其他字段和getter/setter略 }
说明
@JoinColumnsOrFormulas是Hibernate的扩展特性,允许混合使用列映射和SQL公式作为关联条件,非标准JPA规范。insertable = false, updatable = false表示该关联字段不参与Product的插入/更新操作,因为revision_id是Product自身维护的字段。- 如果要严格遵循标准JPA,无法直接通过实体关联实现这种跨表条件关联,此时可以手动处理:在获取Product后,通过
manufacturer.getCode()和revisionId手动查询Revision实例,或者在Product中添加方法封装该查询逻辑。
3. 可选方案:在Product中冗余存储manufacturer_code
如果可以修改数据库结构,在Product表中添加manufacturer_code字段同步存储关联Manufacturer的code值,就能用标准JPA的@JoinColumns实现关联:
修改后的Product表结构
ALTER TABLE product ADD COLUMN manufacturer_code VARCHAR(50); -- 可通过数据库触发器或业务逻辑保证manufacturer_code与manufacturer.code同步
实体映射
@Entity class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @ManyToOne @JoinColumn(name = "manufacturer_id") private Manufacturer manufacturer; @Column(name = "manufacturer_code") private String manufacturerCode; private Integer revisionId; @ManyToOne @JoinColumns({ @JoinColumn(name = "revision_id", referencedColumnName = "id"), @JoinColumn(name = "manufacturer_code", referencedColumnName = "code") }) private Revision revision; // 通过生命周期回调同步manufacturerCode @PostLoad private void syncManufacturerCode() { if (manufacturer != null) { this.manufacturerCode = manufacturer.getCode(); } } // 其他字段和getter/setter略 }
这种方案完全符合标准JPA规范,但需要维护冗余数据的一致性,可通过数据库触发器或实体生命周期回调实现。
内容的提问来源于stack exchange,提问作者Andrei Rykhalski
相关产品推荐
相关产品推荐

