从 Json 正确创建的实体,@JoinColumn 在对面
Entities created correctly from Json with @JoinColumn on the opposite side
我以为我理解了 JPA 的 @JoinColumns 注释和 mappedBy 参数,但后来我需要从这个 [= 问题 的 37=]。它有一组答案选择,也需要映射到新实体。我决定问题实体将成为拥有方,因此我省略了 mappedBy 参数。当我在 AnswerChoice 端使用 @JoinColumns 注释时,所有实体都是从 Json 对象创建的,但未设置 AnswerChoices 到问题实体的 FK。
将@JoinColumns 放在问题实体中解决了问题,但我的问题是:这是正确的方法吗?我会面临任何副作用吗?我是否应该 运行 在 AnswerChoices 集合上使用 for 循环并设置 FK?
问题Json
{
"text": "Do you know JPA?",
"answerChoices": [{
"text": "yes",
}, {
"text": "no",
}, ]
}
带有 JpaRepository 的控制器:
@PostMapping("/questions/create")
@ResponseBody
public String create(@RequestBody Question json) {
questionRepo.save(json);
}
问题实体:
@Entity
public class Question {
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
@JoinColumn(name="question_id")
private Set<AnswerChoice> answerChoices;
}
AnswerChoice 实体:
@Entity
public class AnswerChoice {
@ManyToOne(fetch = FetchType.LAZY)
@JsonIgnore
private Question question;
}
为了简洁起见,我省略了自动生成的 ID。
不对,映射不正确。它实际上创建了两个恰好共享连接列的独立关联。
要么从 Answer
中删除 question
,使关联成为单向的(问问自己是否真的需要关联的那一边),要么回到原来的解决方案并使用 @JsonBackReference/@JsonManagedReference
(以便在反序列化期间自动填充该字段)。
正如@crizzis 所说,您的映射不正确,通常,child 位于关系的拥有方(当 1-to-many 大且具有单个域时),但在你的情况下,Question
是关系的拥有方,因为你有 @JoinColumn
。所以你可以完全摆脱 AnswerChoice
中的 Question
引用。当您创建带有答案选项的问题时,hibernate 将
- 创建问题
- 创建答案
- 更新问题的外键答案
如果您从问题实体中删除此行 @JoinColumn(name = "question_id", foreignKey = @ForeignKey(name = "fk_question_id"))
,hibernate 将创建额外的 table 来管理这种称为 question_answer_choices
的关系,因此为了摆脱额外的 table 我们手动指定 AnswerChoice
中将引用哪个列以映射到外键。
实体Question.java
@Entity
@Table(name = "question")
public class Question {
@Id
@GeneratedValue
@Type(type = "uuid-char")
private UUID id;
private String description;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "question_id", foreignKey = @ForeignKey(name = "fk_question_id"))
private Set<AnswerChoice> answerChoices = new HashSet<>();
public UUID getId() {
return id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public void addAnswerChoice(AnswerChoice answerChoice) {
if (answerChoice != null) {
this.answerChoices.add(answerChoice);
}
}
public Set<AnswerChoice> getAnswerChoices() {
return answerChoices;
}
}
实体AnswerChoice.java
@Entity
@Table(name = "answer_choice")
public class AnswerChoice {
@Id
@GeneratedValue
@Type(type = "uuid-char")
private UUID id;
private String content;
public AnswerChoice() {
}
public AnswerChoice(String content) {
this.content = content;
}
public UUID getId() {
return id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
下面是测试代码
@Test
public void testQuestionAndAnswersTest() {
Question question = new Question();
question.setDescription("How is the weather today?");
question.addAnswerChoice(new AnswerChoice("Sunny"));
question.addAnswerChoice(new AnswerChoice("Cloudy"));
question.addAnswerChoice(new AnswerChoice("Rainy"));
question.addAnswerChoice(new AnswerChoice("Windy"));
question.addAnswerChoice(new AnswerChoice("Snowy"));
//child entities persisted together
entityManager.persist(question);
Question searchedQuestion = entityManager.find(Question.class, question.getId());
Assertions.assertNotNull(searchedQuestion);
Assertions.assertNotNull(searchedQuestion.getId());
Assertions.assertNotNull(searchedQuestion.getAnswerChoices());
Assertions.assertEquals(5, searchedQuestion.getAnswerChoices().size());
Set<AnswerChoice> answerChoices = searchedQuestion.getAnswerChoices();
for (AnswerChoice answerChoice : answerChoices) {
Assertions.assertNotNull(answerChoice.getId());
}
}
Table 生成的语句如下:
questions.sql
CREATE TABLE `question` (
`id` varchar(255) NOT NULL,
`description` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
answer_choice.sql
CREATE TABLE `answer_choice` (
`id` varchar(255) NOT NULL,
`content` varchar(255) DEFAULT NULL,
`question_id` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_question_id` (`question_id`),
CONSTRAINT `fk_question_id` FOREIGN KEY (`question_id`) REFERENCES `question` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
我以为我理解了 JPA 的 @JoinColumns 注释和 mappedBy 参数,但后来我需要从这个 [= 问题 的 37=]。它有一组答案选择,也需要映射到新实体。我决定问题实体将成为拥有方,因此我省略了 mappedBy 参数。当我在 AnswerChoice 端使用 @JoinColumns 注释时,所有实体都是从 Json 对象创建的,但未设置 AnswerChoices 到问题实体的 FK。
将@JoinColumns 放在问题实体中解决了问题,但我的问题是:这是正确的方法吗?我会面临任何副作用吗?我是否应该 运行 在 AnswerChoices 集合上使用 for 循环并设置 FK?
问题Json
{
"text": "Do you know JPA?",
"answerChoices": [{
"text": "yes",
}, {
"text": "no",
}, ]
}
带有 JpaRepository 的控制器:
@PostMapping("/questions/create")
@ResponseBody
public String create(@RequestBody Question json) {
questionRepo.save(json);
}
问题实体:
@Entity
public class Question {
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
@JoinColumn(name="question_id")
private Set<AnswerChoice> answerChoices;
}
AnswerChoice 实体:
@Entity
public class AnswerChoice {
@ManyToOne(fetch = FetchType.LAZY)
@JsonIgnore
private Question question;
}
为了简洁起见,我省略了自动生成的 ID。
不对,映射不正确。它实际上创建了两个恰好共享连接列的独立关联。
要么从 Answer
中删除 question
,使关联成为单向的(问问自己是否真的需要关联的那一边),要么回到原来的解决方案并使用 @JsonBackReference/@JsonManagedReference
(以便在反序列化期间自动填充该字段)。
正如@crizzis 所说,您的映射不正确,通常,child 位于关系的拥有方(当 1-to-many 大且具有单个域时),但在你的情况下,Question
是关系的拥有方,因为你有 @JoinColumn
。所以你可以完全摆脱 AnswerChoice
中的 Question
引用。当您创建带有答案选项的问题时,hibernate 将
- 创建问题
- 创建答案
- 更新问题的外键答案
如果您从问题实体中删除此行 @JoinColumn(name = "question_id", foreignKey = @ForeignKey(name = "fk_question_id"))
,hibernate 将创建额外的 table 来管理这种称为 question_answer_choices
的关系,因此为了摆脱额外的 table 我们手动指定 AnswerChoice
中将引用哪个列以映射到外键。
实体Question.java
@Entity
@Table(name = "question")
public class Question {
@Id
@GeneratedValue
@Type(type = "uuid-char")
private UUID id;
private String description;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "question_id", foreignKey = @ForeignKey(name = "fk_question_id"))
private Set<AnswerChoice> answerChoices = new HashSet<>();
public UUID getId() {
return id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public void addAnswerChoice(AnswerChoice answerChoice) {
if (answerChoice != null) {
this.answerChoices.add(answerChoice);
}
}
public Set<AnswerChoice> getAnswerChoices() {
return answerChoices;
}
}
实体AnswerChoice.java
@Entity
@Table(name = "answer_choice")
public class AnswerChoice {
@Id
@GeneratedValue
@Type(type = "uuid-char")
private UUID id;
private String content;
public AnswerChoice() {
}
public AnswerChoice(String content) {
this.content = content;
}
public UUID getId() {
return id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
下面是测试代码
@Test
public void testQuestionAndAnswersTest() {
Question question = new Question();
question.setDescription("How is the weather today?");
question.addAnswerChoice(new AnswerChoice("Sunny"));
question.addAnswerChoice(new AnswerChoice("Cloudy"));
question.addAnswerChoice(new AnswerChoice("Rainy"));
question.addAnswerChoice(new AnswerChoice("Windy"));
question.addAnswerChoice(new AnswerChoice("Snowy"));
//child entities persisted together
entityManager.persist(question);
Question searchedQuestion = entityManager.find(Question.class, question.getId());
Assertions.assertNotNull(searchedQuestion);
Assertions.assertNotNull(searchedQuestion.getId());
Assertions.assertNotNull(searchedQuestion.getAnswerChoices());
Assertions.assertEquals(5, searchedQuestion.getAnswerChoices().size());
Set<AnswerChoice> answerChoices = searchedQuestion.getAnswerChoices();
for (AnswerChoice answerChoice : answerChoices) {
Assertions.assertNotNull(answerChoice.getId());
}
}
Table 生成的语句如下:
questions.sql
CREATE TABLE `question` (
`id` varchar(255) NOT NULL,
`description` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
answer_choice.sql
CREATE TABLE `answer_choice` (
`id` varchar(255) NOT NULL,
`content` varchar(255) DEFAULT NULL,
`question_id` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_question_id` (`question_id`),
CONSTRAINT `fk_question_id` FOREIGN KEY (`question_id`) REFERENCES `question` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci