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

Spring Boot中仅含外键的多对多关联表实体类映射问题

解决JPA多对多关联表(无独立主键)的实体映射问题

你遇到的No identifier specified for entity: com.poc.questionnarie.QuestionResponse错误,是因为直接在@ManyToOne字段上标注@Id的写法不符合JPA复合主键的规范——Hibernate无法正确识别这种复合主键的结构。下面给你两种标准的实现方案,同时修正你代码里的其他小问题:


方案一:使用@EmbeddedId(推荐)

这种方式通过一个可嵌入的类封装复合主键,结构更清晰,维护性更强。

1. 创建复合主键类

这个类必须实现Serializable,并重写equals和hashCode方法(JPA需要用它判断主键唯一性):

import java.io.Serializable;
import java.util.Objects;

public class QuestionResponseId implements Serializable {
    // 字段名要和QuestionResponse中关联实体的属性名对应
    private Long question;
    private Long response;

    public QuestionResponseId() {}

    public QuestionResponseId(Long questionId, Long responseId) {
        this.question = questionId;
        this.response = responseId;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        QuestionResponseId that = (QuestionResponseId) o;
        return Objects.equals(question, that.question) && Objects.equals(response, that.response);
    }

    @Override
    public int hashCode() {
        return Objects.hash(question, response);
    }

    // getter和setter
    public Long getQuestion() { return question; }
    public void setQuestion(Long question) { this.question = question; }
    public Long getResponse() { return response; }
    public void setResponse(Long response) { this.response = response; }
}

2. 修改QuestionResponse实体

用@EmbeddedId引用主键类,再通过@MapsId映射关联实体:

@Entity
@Table(name = "question_response")
public class QuestionResponse {
    @EmbeddedId
    private QuestionResponseId id;

    @ManyToOne
    @MapsId("question") // 对应QuestionResponseId中的question字段
    @JoinColumn(name = "question_id")
    private Question question;

    @ManyToOne
    @MapsId("response") // 对应QuestionResponseId中的response字段
    @JoinColumn(name = "response_id")
    private Response response;

    public QuestionResponse() {}

    public QuestionResponse(Question question, Response response) {
        this.id = new QuestionResponseId(question.getId(), response.getId());
        this.question = question;
        this.response = response;
    }

    // getter和setter
    public QuestionResponseId getId() { return id; }
    public void setId(QuestionResponseId id) { this.id = id; }
    public Question getQuestion() { return question; }
    public void setQuestion(Question question) {
        this.question = question;
        if (id != null) id.setQuestion(question.getId());
    }
    public Response getResponse() { return response; }
    public void setResponse(Response response) {
        this.response = response;
        if (id != null) id.setResponse(response.getId());
    }
}

方案二:使用@IdClass

这种方式不需要嵌入类,直接通过主键类映射复合主键。

1. 创建主键类

同样需要实现Serializable并重写equals和hashCode:

import java.io.Serializable;
import java.util.Objects;

public class QuestionResponseId implements Serializable {
    private Long question;
    private Long response;

    public QuestionResponseId() {}

    public QuestionResponseId(Long questionId, Long responseId) {
        this.question = questionId;
        this.response = responseId;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        QuestionResponseId that = (QuestionResponseId) o;
        return Objects.equals(question, that.question) && Objects.equals(response, that.response);
    }

    @Override
    public int hashCode() {
        return Objects.hash(question, response);
    }

    // getter和setter
    public Long getQuestion() { return question; }
    public void setQuestion(Long question) { this.question = question; }
    public Long getResponse() { return response; }
    public void setResponse(Long response) { this.response = response; }
}

2. 修改QuestionResponse实体

用@IdClass指定主键类,同时给关联字段标注@Id:

@Entity
@Table(name = "question_response")
@IdClass(QuestionResponseId.class)
public class QuestionResponse {
    @Id
    @ManyToOne
    @JoinColumn(name = "question_id")
    private Question question;

    @Id
    @ManyToOne
    @JoinColumn(name = "response_id")
    private Response response;

    public QuestionResponse() {}

    public QuestionResponse(Question question, Response response) {
        this.question = question;
        this.response = response;
    }

    // getter和setter
    public Question getQuestion() { return question; }
    public void setQuestion(Question question) { this.question = question; }
    public Response getResponse() { return response; }
    public void setResponse(Response response) { this.response = response; }
}

修正你代码中的其他小问题

  1. Response实体的序列生成错误:
    你当前的SequenceGenerator用了questions_id_seq,应该改成responses_id_seq,否则会错误使用questions的序列生成response的id:
    @SequenceGenerator(name = "rid_seq", sequenceName="responses_id_seq")
    
  2. 字段与数据库列名不匹配:
    • Question实体的@Column(name = "questionText")要改成@Column(name = "question")(对应数据库表的question字段)
    • Response实体的@Column(name = "responseText")要改成@Column(name = "response")(对应数据库表的response字段)
  3. 语义化字段名优化:
    Response实体中的private List<QuestionResponse> question;建议改成private List<QuestionResponse> questionResponses;,并修改getter为getQuestionResponses(),语义更清晰。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:18:57