Spring JPA映射旧数据库复合主键问题:保存时主键冲突报错
问题解决:Hibernate复合主键映射错误导致保存失败
问题根源
你的ClientTelephone实体仅将ROW_NUMBER设为主键,但数据库实际是**ROW_NUMBER + CLIENT_CODE**作为复合主键。Hibernate保存时仅校验ROW_NUMBER,当不同客户端存在相同ROW_NUMBER时,就会触发重复行的断言异常。
解决方案
1. 定义复合主键类
创建一个实现Serializable的嵌入类,用来表示复合主键,并用@Embeddable注解标记:
import jakarta.persistence.Embeddable; import java.io.Serializable; import java.util.Objects; @Embeddable public class ClientTelephoneId implements Serializable { @Column(name = "ROW_NUMBER") private int rownum; @Column(name = "CLIENT_CODE") private String clientCode; // 必须提供无参构造函数 public ClientTelephoneId() {} public ClientTelephoneId(int rownum, String clientCode) { this.rownum = rownum; this.clientCode = clientCode; } // 重写equals和hashCode方法,确保复合键的唯一性判断正确 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ClientTelephoneId that = (ClientTelephoneId) o; return rownum == that.rownum && Objects.equals(clientCode, that.clientCode); } @Override public int hashCode() { return Objects.hash(rownum, clientCode); } // getter和setter方法 public int getRownum() { return rownum; } public void setRownum(int rownum) { this.rownum = rownum; } public String getClientCode() { return clientCode; } public void setClientCode(String clientCode) { this.clientCode = clientCode; } }
2. 修改ClientTelephone实体
用@EmbeddedId替换原有的单个@Id,并调整@ManyToOne关联的映射方式:
import jakarta.persistence.*; @Entity @Table(name="AD_CLIENT_TELEPHONE") public class ClientTelephone { @EmbeddedId private ClientTelephoneId id; @Column(name = "TEL_NO") private String telephone; @Column(name = "TEL_TYPE") private String telType; @ManyToOne(fetch = FetchType.LAZY) @MapsId("clientCode") // 关联主键类中的clientCode字段 @JoinColumn(name = "CLIENT_CODE") @JsonIgnore private Client client; // getter和setter方法 public ClientTelephoneId getId() { return id; } public void setId(ClientTelephoneId id) { this.id = id; } public String getTelephone() { return telephone; } public void setTelephone(String telephone) { this.telephone = telephone; } public String getTelType() { return telType; } public void setTelType(String telType) { this.telType = telType; } public Client getClient() { return client; } public void setClient(Client client) { this.client = client; // 同步主键中的clientCode if (client != null && id != null) { id.setClientCode(client.getClientCode()); } } }
3. 调整Client实体的@OneToMany关联
确保关联指向ClientTelephone中的client属性,同时可指定cascade策略方便操作:
import jakarta.persistence.*; import java.util.List; @Entity @Table(name = "AD_CLIENT") public class Client { @Id @Column(name = "CLIENT_CODE") private String clientCode; @Column(name = "CLIENT_NAME") private String fullName; @OneToMany(mappedBy = "client", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) @JsonIgnore List<ClientTelephone> clientTelephones; // getter和setter方法 public String getClientCode() { return clientCode; } public void setClientCode(String clientCode) { this.clientCode = clientCode; } public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } public List<ClientTelephone> getClientTelephones() { return clientTelephones; } public void setClientTelephones(List<ClientTelephone> clientTelephones) { this.clientTelephones = clientTelephones; } }
关键说明
@Embeddable类必须实现Serializable,并正确重写equals和hashCode,这是Hibernate判断复合主键唯一性的基础。@MapsId注解用来将关联实体的主键字段与当前实体的复合主键字段绑定,避免重复映射CLIENT_CODE列。- 创建
ClientTelephone实例时,需要初始化其复合主键,并同步关联的Client对象,确保主键中的clientCode与关联客户端的clientCode一致。
内容的提问来源于stack exchange,提问作者inesh Dhanushka
相关产品推荐
相关产品推荐

