You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 16:42:51