从 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