如何在JPA中映射复合外键与复合主键的关联关系
现有client和device两张表,为一对多关系(一个client对应多个device),简化表结构如下:
TABLE client ( id, import_date, PRIMARY_KEY (id, import_date) )
以及
TABLE device ( id, import_date, client_id, PRIMARY_KEY (id, import_date), FOREIGN_KEY (client_id -> client.id, import_date -> client.import_date) )
注意:
device.import_date列既是主键的一部分,也是外键的一部分。
我的尝试
我无法用JPA正确描述上述结构,以下是我的尝试(省略getter、setter及实现的接口等):
public class ImportId { private Integer id; private LocalDate importDate; } @Entity @IdClass(ImportId.class) public class Client { @Id private Integer id; @Id private LocalDate importDate; } @Entity @IdClass(ImportId.class) public class Device { @Id private Integer id; @Id private LocalDate importDate; @ManyToOne //@MapsId("importDate") <-- 无效 //@JoinColumn //@JoinColumn <-- 同样无效 private Client client; }
将@MapsId与@IdClass配合使用未达预期,会在device表中生成多余的client_import_date列,不符合需求。
我还尝试将@MapsId与@Embeddable配合使用,但抛出异常:
org.hibernate.PropertyNotFoundException: Could not locate field name [id] on class [java.time.LocalDate]
求解决办法,谢谢!
更新1(针对Phil Ku的方案)
初看该方案可行,Hibernate不会创建client_import_date列,外键设置也正确,但无法保存device实体。执行以下代码:
// var client = clientRepository.save(new Client(1234, LocalDate.now()); deviceRepository.save(new Device(567, LocalDate.now(), client));
Hibernate生成的SQL:
INSERT INTO device(client_id, import_date, id) VALUES(?,?,?);
随后尝试绑定的参数:
[1] as [INTEGER] - [1234] [2] as [DATE] - [2023-03-22] [3] as [INTEGER] - [567] [4] as [DATE] - [2023-03-22]
抛出异常Invalid value "4" for parameter "parameterIndex",显然会失败,因为SQL只有3个参数位。有人知道为什么Hibernate参数列表和插入语句不匹配吗?
更新1.1
上述Hibernate异常似乎仅在H2平台出现,可能是集成测试的问题。测试版本:Hibernate 6.1.7和H2 2.1.214,均随Spring Boot 3.0.4提供。
使用MariaDB驱动时,Phil Ku的方案运行正常。追踪Hibernate输出发现仍有四个候选参数,但选择前三个即可正常执行。
更新2
接下来遇到事务相关问题。执行以下代码:
var importDate = LocalDate.now(); var client = clientRepository.save(new Client(100, importDate)); var device1 = new Device(1, importDate, client); var device2 = new Device(2, importDate, client); deviceRepository.saveAll(List.of(device1, device2));
抛出异常:
SQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`device`, CONSTRAINT `...` FOREIGN KEY (`client_id`, `import_date`) REFERENCES `client` (`id`, `import_date`))
但如果改为逐个调用deviceRepository.save保存每个设备,一切正常。
我无法在JPA/Hibernate上下文之外复现此行为,直接在数据库执行对应的INSERT语句——无论单事务还是多事务——都不会触发约束违反。
对此有什么思路吗?
更新3
基于K.Nicholas和Phil Ku的建议,我重新设计了数据库结构:client表使用普通的单列数值主键,import_date列仅作为与id列组成的唯一约束的一部分,主键随后被device表引用。
这算是个变通方案,但运行完全正常。
感谢大家的帮助。
内容的提问来源于stack exchange,提问作者darefilz

