JPA/Hibernate中能否复用同一列实现多外键关联?
问题描述
现有数据库包含Project、TableA、TableB三张表:
- TableA的复合主键由非唯一varchar类型的
id和varchar类型的project_id组成; - TableB的主键为
id列,通过project_id外键关联Project;
现需新增TableC,要求满足以下规则:
- 必须关联Project;
- 可选择关联TableA;
- 只能关联TableA或TableB中的一个,不可同时关联;
尝试通过JPA实体配置让TableC同时拥有Project和TableA引用并复用project_id列时,遇到「重复列映射」的构建错误;若将Project关联设为insertable = false, updatable = false,则无法单独维护TableC与Project的关联。
现有实体代码
@Entity public class Project { @Id private String id; } public class TableAId implements Serializable { private String id; private Project project; } @Entity @IdClass(TableAId.class) public class TableA { @Id private String id; @Id @ManyToOne @JoinColumn(name = "project_id", nullable = false) private Project project; } @Entity public class TableB { @Id @GeneratedValue private long id; @ManyToOne @JoinColumn(name = "project_id", nullable = false) private Project project; } @Entity public class TableC { @Id @GeneratedValue private long id; @ManyToOne @JoinColumns({ @JoinColumn(name = "tablea_id", referencedColumnName = "id"), @JoinColumn(name = "project_id", referencedColumnName = "project_id") }) private TableA tableA; @ManyToOne @JoinColumn(name = "tableb_id") private TableB tableB; @ManyToOne @JoinColumn(name = "project_id") private Project project; }
解决方案
要实现需求,核心是解决project_id列的重复映射冲突,同时保证业务规则的约束,具体配置如下:
1. 调整TableC的关联映射
- 将TableA关联中的
project_id设为insertable = false, updatable = false,避免与Project关联的同名列产生冲突; - 让Project关联作为
project_id列的主维护入口,设置optional = false确保TableC必须关联Project; - 添加业务校验,确保不同时关联TableA和TableB。
2. 修改后的TableC实体代码
import javax.persistence.*; import javax.validation.constraints.AssertTrue; import java.util.Objects; @Entity public class TableC { @Id @GeneratedValue private long id; @ManyToOne @JoinColumns({ @JoinColumn(name = "tablea_id", referencedColumnName = "id"), // 标记该列不可插入/更新,交由Project关联维护 @JoinColumn(name = "project_id", referencedColumnName = "project_id", insertable = false, updatable = false) }) private TableA tableA; @ManyToOne @JoinColumn(name = "tableb_id") private TableB tableB; // 主映射,负责维护project_id列,强制关联Project @ManyToOne(optional = false) @JoinColumn(name = "project_id", nullable = false) private Project project; // 业务校验:确保只能关联TableA或TableB中的一个(或都不关联,根据需求调整) @AssertTrue(message = "只能关联TableA或TableB中的一个,不可同时关联") public boolean isAssociationRuleValid() { // 若需求是"必须关联TableA或TableB中的一个",则改为: // return (tableA != null && tableB == null) || (tableA == null && tableB != null); return (tableA == null && tableB != null) || (tableA != null && tableB == null) || (tableA == null && tableB == null); } // Getter、Setter方法省略 }
3. 补充TableAId的正确实现
TableA的复合主键类TableAId需保证字段与实体类主键对应,并实现equals和hashCode方法,否则JPA无法正确识别复合主键:
import java.io.Serializable; import java.util.Objects; public class TableAId implements Serializable { // 与TableA的id字段对应 private String id; // 与TableA中project关联的列名对应(Project的id类型为String) private String project; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TableAId tableAId = (TableAId) o; return Objects.equals(id, tableAId.id) && Objects.equals(project, tableAId.project); } @Override public int hashCode() { return Objects.hash(id, project); } // Getter、Setter方法省略 }
4. 关键说明
- 当关联TableA时,JPA会通过
tablea_id和已由Project关联维护的project_id找到对应的TableA记录(因为TableA的复合主键是id+project_id),映射逻辑有效; - 若需要数据库层面强制约束「只能关联TableA或TableB」,可在TableC表上添加数据库检查约束:
ALTER TABLE TableC ADD CONSTRAINT chk_tablec_association CHECK ((tablea_id IS NOT NULL AND tableb_id IS NULL) OR (tablea_id IS NULL AND tableb_id IS NOT NULL));
内容的提问来源于stack exchange,提问作者Madmartigan1421
相关产品推荐
相关产品推荐

