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

如何用JPA映射含双复合外键关联同一列的MOVEMENTS表

问题分析与解决方案

你的代码无法生成LOCATIONS对应外键的核心原因是:MOVEMENTS表的外键关联的是LOCATIONS的复合主键(ID, PROBLEM_NAME),但原写法只关联了LOCATIONS的ID字段,未关联PROBLEM_NAME字段,导致JPA无法识别完整的外键约束。

以下是完整的正确实体映射写法:


1. 定义Location的复合主键类

LOCATIONS表采用复合主键,需先创建嵌入式主键类:

@Embeddable
public class LocationId implements Serializable {
    private String id;
    private String problemName;

    // JPA要求必须提供无参构造函数
    public LocationId() {}

    public LocationId(String id, String problemName) {
        this.id = id;
        this.problemName = problemName;
    }

    // 复合主键必须重写equals和hashCode
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        LocationId that = (LocationId) o;
        return Objects.equals(id, that.id) && Objects.equals(problemName, that.problemName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, problemName);
    }

    // getter和setter
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getProblemName() { return problemName; }
    public void setProblemName(String problemName) { this.problemName = problemName; }
}

2. Location实体类

@Entity
@Table(name = "LOCATIONS")
public class Location {
    @EmbeddedId
    private LocationId id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "PROBLEM_NAME", referencedColumnName = "NAME", insertable = false, updatable = false)
    private Problem problem;

    public Location() {}

    // getter和setter
    public LocationId getId() { return id; }
    public void setId(LocationId id) { this.id = id; }
    public Problem getProblem() { return problem; }
    public void setProblem(Problem problem) { this.problem = problem; }
}

3. Problem实体类

@Entity
@Table(name = "PROBLEMS")
public class Problem {
    @Id
    @Column(name = "NAME")
    private String name;

    @Column(name = "AVG_COST")
    private Double avgCost;

    public Problem() {}

    // getter和setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public Double getAvgCost() { return avgCost; }
    public void setAvgCost(Double avgCost) { this.avgCost = avgCost; }
}

4. Movement实体类及复合主键

Movement复合主键类

@Embeddable
public class MovementId implements Serializable {
    @Column(name = "START_LOC_ID")
    private String startLocId;

    @Column(name = "END_LOC_ID")
    private String endLocId;

    @Column(name = "PROBLEM_NAME")
    private String problemName;

    public MovementId() {}

    public MovementId(String startLocId, String endLocId, String problemName) {
        this.startLocId = startLocId;
        this.endLocId = endLocId;
        this.problemName = problemName;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MovementId that = (MovementId) o;
        return Objects.equals(startLocId, that.startLocId) &&
               Objects.equals(endLocId, that.endLocId) &&
               Objects.equals(problemName, that.problemName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(startLocId, endLocId, problemName);
    }

    // getter和setter
    public String getStartLocId() { return startLocId; }
    public void setStartLocId(String startLocId) { this.startLocId = startLocId; }
    public String getEndLocId() { return endLocId; }
    public void setEndLocId(String endLocId) { this.endLocId = endLocId; }
    public String getProblemName() { return problemName; }
    public void setProblemName(String problemName) { this.problemName = problemName; }
}

Movement实体核心代码

@Entity
@Table(name = "MOVEMENTS")
public class Movement {
    @EmbeddedId
    private MovementId id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "PROBLEM_NAME", referencedColumnName = "NAME", insertable = false, updatable = false)
    private Problem problem;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumns({
        @JoinColumn(name = "START_LOC_ID", referencedColumnName = "ID", insertable = false, updatable = false),
        @JoinColumn(name = "PROBLEM_NAME", referencedColumnName = "PROBLEM_NAME", insertable = false, updatable = false)
    })
    private Location startLocation;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumns({
        @JoinColumn(name = "END_LOC_ID", referencedColumnName = "ID", insertable = false, updatable = false),
        @JoinColumn(name = "PROBLEM_NAME", referencedColumnName = "PROBLEM_NAME", insertable = false, updatable = false)
    })
    private Location endLocation;

    @Column(name = "DURATION")
    private int duration;

    public Movement() {}

    public Movement(MovementId id) {
        this.id = id;
    }

    // getter和setter
    public MovementId getId() { return id; }
    public void setId(MovementId id) { this.id = id; }
    public Problem getProblem() { return problem; }
    public void setProblem(Problem problem) { this.problem = problem; }
    public Location getStartLocation() { return startLocation; }
    public void setStartLocation(Location startLocation) { this.startLocation = startLocation; }
    public Location getEndLocation() { return endLocation; }
    public void setEndLocation(Location endLocation) { this.endLocation = endLocation; }
    public int getDuration() { return duration; }
    public void setDuration(int duration) { this.duration = duration; }
}

关键注意点

  • 所有复合主键类必须实现Serializable接口,并重写equals和hashCode方法,这是JPA的强制要求。
  • 关联Location时使用@JoinColumns映射完整的复合外键(ID + PROBLEM_NAME),同时设置insertable = false, updatable = false,避免JPA重复操作PROBLEM_NAME字段(实际通过主键类维护该字段)。

内容的提问来源于stack exchange,提问作者Iulian Muntean

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 15:38:18