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; } }
修正你代码中的其他小问题
- Response实体的序列生成错误:
你当前的SequenceGenerator用了questions_id_seq,应该改成responses_id_seq,否则会错误使用questions的序列生成response的id:@SequenceGenerator(name = "rid_seq", sequenceName="responses_id_seq") - 字段与数据库列名不匹配:
- Question实体的
@Column(name = "questionText")要改成@Column(name = "question")(对应数据库表的question字段) - Response实体的
@Column(name = "responseText")要改成@Column(name = "response")(对应数据库表的response字段)
- Question实体的
- 语义化字段名优化:
Response实体中的private List<QuestionResponse> question;建议改成private List<QuestionResponse> questionResponses;,并修改getter为getQuestionResponses(),语义更清晰。
内容的提问来源于stack exchange,提问作者samdset
相关产品推荐
相关产品推荐

