JPA中Address共享parent_id关联Person与Company的问题解决问询
解决方案:JPA 共享外键列关联多个实体
方案一:继承映射(优雅且符合JPA规范)
核心思路是将Person和Company抽象为同一个父实体,让Address关联这个父实体,从而实现通过单一parent_id列关联两种实体,同时避免冗余字段。
1. 创建抽象父实体Parent
@Entity @Inheritance(strategy = InheritanceType.JOINED) // 采用Joined继承,避免单表冗余 @DiscriminatorColumn(name = "entity_type", discriminatorType = DiscriminatorType.STRING) public abstract class Parent { @Id private String id; @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "parent") private Address address; // Getter & Setter }
2. 修改Person实体继承Parent
通过@AttributeOverride保留原表的主键字段名(person_id):
@Entity @DiscriminatorValue("PERSON") @AttributeOverride(name = "id", column = @Column(name = "person_id")) public class Person extends Parent { // 这里添加Person专属字段,无需重复定义personId }
3. 修改Company实体继承Parent
同样保留原主键字段名(company_id):
@Entity @DiscriminatorValue("COMPANY") @AttributeOverride(name = "id", column = @Column(name = "company_id")) public class Company extends Parent { // 这里添加Company专属字段,无需重复定义companyId }
4. 调整Address实体关联Parent
@Entity public class Address { @Id private Long id; @OneToOne @JoinColumn(name = "parent_id") // 唯一外键列,关联Parent的ID private Parent parent; @OneToMany(mappedBy = "address") private List<Area> areas = new ArrayList<>(); // Getter & Setter }
5. 保持Area实体不变
@Entity public class Area { @Id private Long id; @ManyToOne @JoinColumn(name = "address_id") private Address address; // Getter & Setter }
方案优势
- 完全符合JPA规范,Hibernate可以自动维护
parent_id字段,无需手动干预 - 查询
Person/Company时,直接通过父实体的address关联即可获取地址,支持懒加载或预加载 - 无冗余字段,仅用
parent_id实现多实体关联
方案二:手动维护外键(无需继承)
如果不想引入继承关系,可以通过手动维护parent_id字段,同时将Address中关联Person/Company的字段设为只读,避免Hibernate的冲突检测。
1. 修改Address实体
添加parentId字段用于手动维护,同时将person/company设为只读并添加过滤条件:
@Entity public class Address { @Id private Long id; private String parentId; // 手动维护的外键字段 @OneToOne @JoinColumn(name = "parent_id", insertable = false, updatable = false) @Where(clause = "exists (select 1 from person p where p.person_id = parent_id)") private Person person; @OneToOne @JoinColumn(name = "parent_id", insertable = false, updatable = false) @Where(clause = "exists (select 1 from company c where c.company_id = parent_id)") private Company company; @OneToMany(mappedBy = "address") private List<Area> areas = new ArrayList<>(); // Getter & Setter }
2. 修改Person实体
通过@PrePersist和@PreUpdate钩子,在保存/更新时自动设置Address的parentId:
@Entity public class Person { @Id private String personId; @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) private Address address; @PrePersist @PreUpdate private void syncAddressParentId() { if (address != null) { address.setParentId(this.personId); } } // Getter & Setter }
3. 修改Company实体
同样添加钩子自动维护Address的parentId:
@Entity public class Company { @Id private String companyId; @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) private Address address; @PrePersist @PreUpdate private void syncAddressParentId() { if (address != null) { address.setParentId(this.companyId); } } // Getter & Setter }
查询时关联获取Address
无论采用哪种方案,都可以通过以下方式实现查询Person/Company时一并加载Address:
方式1:使用@EntityGraph(推荐)
在Repository方法上添加注解,指定预加载关联字段:
public interface PersonRepository extends JpaRepository<Person, String> { @EntityGraph(attributePaths = "address") Optional<Person> findByPersonId(String personId); } public interface CompanyRepository extends JpaRepository<Company, String> { @EntityGraph(attributePaths = "address") Optional<Company> findByCompanyId(String companyId); }
方式2:使用JPQL的JOIN FETCH
public interface PersonRepository extends JpaRepository<Person, String> { @Query("SELECT p FROM Person p JOIN FETCH p.address WHERE p.personId = :id") Optional<Person> findByIdWithAddress(@Param("id") String id); }
内容的提问来源于stack exchange,提问作者Nagendra
相关产品推荐
相关产品推荐

