使用@ManyToOne与复合主键的JPA实体保存时出现多余参数问题
Hibernate 5.6复合主键关联@ManyToOne保存时参数越界问题
问题场景
使用Hibernate 5.6的旧版Spring Boot项目中,定义了如下实体类:
@Entity class A { Long getId(); // 其他字段及实现 } @Entity class B { Long getId(); // 其他字段及实现 } @Entity @IdClass(ABPK.class) @Data class AB { @Id @Column(name="a_id", insertable=false, updatable=false) private Long aId; @Id @Column(name="b_id", insertable=false, updatable=false) private Long bId; @ManyToOne @JoinColumn(name="a_id") private A a; @ManyToOne @JoinColumn(name="b_id") private B b; public AB() { } public AB(A a, B b) { this.a = a; this.aId = a.getId(); this.b = b; this.bId = b.getId(); } } @Data class ABPK implements Serializable { Long aId; Long bId; }
执行保存操作时:
var a = new A("a"); var b = new B("b"); var ab = new AB(a,b); abDao.saveAndFlush(ab);
抛出错误:
Caused by: java.sql.SQLException: Parameter index out of range (3 > number of parameters, which is 2).
日志中生成的INSERT语句看似正确:
Hibernate: insert into ab (a_id, b_id) values (?, ?)
调试发现映射时多传递了参数,实际传递了AB类型、A类型、B类型对应的值,而预期仅需两个字段参数。
解决方案
问题根源在于**@Id字段和@ManyToOne的关联字段重复映射了数据库中的同一列**,Hibernate在处理时重复绑定了参数。正确的做法是使用@MapsId注解将@ManyToOne关联与@Id字段绑定,让Hibernate知道两者对应同一列,避免重复处理:
修改AB实体类如下:
@Entity @IdClass(ABPK.class) @Data class AB { @Id private Long aId; @Id private Long bId; @ManyToOne @MapsId("aId") // 将该关联与aId主键字段绑定 @JoinColumn(name="a_id") private A a; @ManyToOne @MapsId("bId") // 将该关联与bId主键字段绑定 @JoinColumn(name="b_id") private B b; public AB() { } public AB(A a, B b) { this.a = a; this.b = b; // 无需手动设置aId和bId,Hibernate会通过关联对象自动赋值 } }
说明
- 去掉@Id字段上的
insertable=false, updatable=false,因为@MapsId已经明确了主键字段由关联对象维护 @MapsId("aId")表示将@ManyToOne关联的A对象的主键值,映射到当前实体的aId主键字段,对应数据库的a_id列- 构造方法中无需手动设置
aId和bId,Hibernate会自动从关联的A、B对象中获取主键值赋值给主键字段
这样修改后,Hibernate在生成INSERT语句时只会绑定两个参数,不会出现参数索引越界的问题。
内容的提问来源于stack exchange,提问作者Archimedes Trajano
相关产品推荐
相关产品推荐

