JPA多集合关联同一中间表时数据分两行存储问题求助
问题场景
数据库中security_margin与security_margin_service_model为一对多关系。在JPA实体SecurityMargin中,尝试通过@JoinTable关联security_margin、model、service三个实体,代码如下:
@Column @OneToMany @JoinTable(name = "security_margin_service_model", joinColumns = {@JoinColumn(name = "margin_id")}, inverseJoinColumns = {@JoinColumn(name = "service_id")}) List<Service> services = new ArrayList<>(); @Column @OneToMany @JoinTable(name = "security_margin_service_model", joinColumns = {@JoinColumn(name = "margin_id")}, inverseJoinColumns = {@JoinColumn(name = "model_id")}) List<Model> models = new ArrayList<>();
持久化SecurityMargin实体时,期望数据在security_margin_service_model中存储为一行(包含margin_id、service_id、model_id),但实际被分成两行存储,需要解决Hibernate执行两次插入的问题。
解决方案
你当前的写法把security_margin_service_model当成了两个独立的关联表(分别关联margin-service和margin-model),所以Hibernate会生成两条插入记录。要实现一行存储三个字段的关联,需要调整实体映射方式:
显式创建中间实体
SecurityMarginServiceModel
将包含三个外键的关联表直接映射为JPA实体,替代隐式的@JoinTable关联:@Entity @Table(name = "security_margin_service_model") public class SecurityMarginServiceModel { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "margin_id") private SecurityMargin securityMargin; @ManyToOne @JoinColumn(name = "service_id") private Service service; @ManyToOne @JoinColumn(name = "model_id") private Model model; // 构造方法、getter、setter省略 }修改
SecurityMargin实体的关联方式
把原来的两个独立集合,改成关联中间实体的集合:@Entity @Table(name = "security_margin") public class SecurityMargin { // 其他字段及主键映射省略 @OneToMany(mappedBy = "securityMargin", cascade = CascadeType.ALL, orphanRemoval = true) private List<SecurityMarginServiceModel> serviceModels = new ArrayList<>(); // 添加便捷方法,快速关联service和model public void bindServiceAndModel(Service service, Model model) { SecurityMarginServiceModel smsm = new SecurityMarginServiceModel(); smsm.setSecurityMargin(this); smsm.setService(service); smsm.setModel(model); serviceModels.add(smsm); } // getter、setter省略 }执行持久化操作
通过调用bindServiceAndModel方法关联数据,Hibernate会插入一条包含三个外键的记录:SecurityMargin margin = new SecurityMargin(); Service targetService = serviceRepository.findById(1L).orElseThrow(); Model targetModel = modelRepository.findById(1L).orElseThrow(); margin.bindServiceAndModel(targetService, targetModel); securityMarginRepository.save(margin);
原理说明
@JoinTable本质是用来处理双向多对多的隐式关联表,每个@OneToMany+@JoinTable都会将中间表视为自身的关联容器,因此会生成两条独立记录。而将中间表显式映射为实体后,可精确控制每条记录包含的三个外键,实现一行存储的需求。
内容的提问来源于stack exchange,提问作者Antonio

