Spring Boot中能否将单个外键列关联至多个实体主键?
问题描述
我想让一个外键列关联到3个不同实体的主键,允许该列取这三个实体中任意一个的主键值,请问这种设计是否可行?以下是我尝试将三个关联映射到id_operation列的代码:
public class workflow { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" }) @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "id_operation", insertable=false, updatable=false) private demande_de_conge demande_de_conge; @JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" }) @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "id_operation", insertable=false, updatable=false) private annulation annulation; @JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" }) @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "id_operation", insertable=false, updatable=false) private regularisation regularisation; }
public class demande_de_conge { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id_demande; @JsonIgnore @OneToMany(mappedBy="demande_de_conge", cascade = CascadeType.ALL, orphanRemoval = true) private List<workflow>workflow; }
public class regularisation { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id_regularisation; @JsonIgnore @OneToMany(mappedBy="regularisation", cascade = CascadeType.ALL, orphanRemoval = true) private List<workflow>workflow; }
public class annulation { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id_annulation; @JsonIgnore @OneToMany(mappedBy="annulation", cascade = CascadeType.ALL, orphanRemoval = true) private List<workflow>workflow; }
分析与解决方案
这种需求是可行的,但你当前的实现存在明显问题:
- 三个
@ManyToOne关联映射到同一列且设置insertable=false, updatable=false,导致Hibernate无法通过workflow实体维护该字段的值,数据插入/更新会失效。 - 数据库层面无约束保证
id_operation的值属于三个实体中的某一个,易出现数据不一致。 - 对象模型中
workflow同时持有三个实体引用,逻辑上会造成混乱(实际应仅关联其中一个)。
推荐两种标准实现方案:
方案1:JPA单表继承(优先推荐)
创建一个基类作为三个实体的父类,让workflow关联该基类,通过鉴别器字段区分具体实体类型。
步骤1:定义基类
@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "operation_type", discriminatorType = DiscriminatorType.STRING) public abstract class Operation { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; // 可添加三个实体的通用字段 }
步骤2:修改三个实体继承基类
@Entity @DiscriminatorValue("CONGE") public class demande_de_conge extends Operation { // 该实体特有字段 @JsonIgnore @OneToMany(mappedBy = "operation", cascade = CascadeType.ALL, orphanRemoval = true) private List<workflow> workflows; } @Entity @DiscriminatorValue("ANNULATION") public class annulation extends Operation { // 该实体特有字段 @JsonIgnore @OneToMany(mappedBy = "operation", cascade = CascadeType.ALL, orphanRemoval = true) private List<workflow> workflows; } @Entity @DiscriminatorValue("REGULARISATION") public class regularisation extends Operation { // 该实体特有字段 @JsonIgnore @OneToMany(mappedBy = "operation", cascade = CascadeType.ALL, orphanRemoval = true) private List<workflow> workflows; }
步骤3:修改workflow类
@Entity public class workflow { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" }) @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "id_operation") private Operation operation; }
这种方式的优势:
- 数据库层面通过外键约束保证
id_operation的值一定是合法的实体主键。 - 对象模型清晰,
workflow仅需持有一个Operation引用,可通过类型转换获取具体实体。
方案2:添加类型字段+应用层约束
如果不想使用继承,可在workflow表中新增operation_type字段标记关联的实体类型,通过应用层逻辑维护数据一致性。
修改workflow类
@Entity public class workflow { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(name = "operation_type", nullable = false) private String operationType; // 可选值:"CONGE", "ANNULATION", "REGULARISATION" @Column(name = "id_operation", nullable = false) private Long idOperation; @JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" }) @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "id_operation", insertable=false, updatable=false) private demande_de_conge demande_de_conge; @JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" }) @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "id_operation", insertable=false, updatable=false) private annulation annulation; @JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" }) @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "id_operation", insertable=false, updatable=false) private regularisation regularisation; // 应用层方法:根据类型返回对应的关联实体 public Object getOperation() { return switch (operationType) { case "CONGE" -> demande_de_conge; case "ANNULATION" -> annulation; case "REGULARISATION" -> regularisation; default -> null; }; } // 设置关联实体时同步更新类型和ID public void setDemandeDeConge(demande_de_conge demande) { this.demande_de_conge = demande; this.operationType = "CONGE"; this.idOperation = demande.getId_demande(); } // 同理实现另外两个实体的set方法 }
这种方式需要手动维护数据一致性,数据库无法通过外键直接约束,出错风险较高,仅适合无法使用继承的场景。
内容的提问来源于stack exchange,提问作者F4rou9 21
相关产品推荐
相关产品推荐

