Entities created correctly from Json with @JoinColumn on the opposite side

huangapple go评论78阅读模式
英文:

Entities created correctly from Json with @JoinColumn on the opposite side

问题

我以为我理解了JPA的*@JoinColumns注解和mappedBy参数,但后来我需要根据这个Question的Json创建新的实体Question。它有一组答案选项,也需要映射为新的实体。我决定Question实体将成为拥有方,因此我省略了mappedBy参数。当我在AnswerChoice端使用@JoinColumns*注解时,所有实体都从Json对象创建了出来,但是AnswerChoiceQuestion实体的外键关系没有被设置。

将*@JoinColumns*注解放在Question实体中解决了这个问题,但我的问题是:这样做是正确的方式吗?我会遇到任何副作用吗?我是否应该在AnswerChoice集合上运行for循环并设置外键呢?

问题的Json:

{
    "text": "你知道JPA吗?",
    "answerChoices": [{
        "text": "是",
    }, {
        "text": "否",
    }]
}

带有JpaRepository的控制器:

@PostMapping("/questions/create")
@ResponseBody
public String create(@RequestBody Question json) {
    questionRepo.save(json);
}

Question实体:

@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。

英文:

I thought I understood the JPA's @JoinColumns annotation and mappedBy parameter, but then I needed to create new entities from this Json of a Question. It has a set of answer choices which need to be mapped to new entities as well. I decided that the Question entity is gonna be the owning side, therefore I omitted the mappedBy parameter. When I used the @JoinColumns annotation on the AnswerChoice side, all the entities were created from the Json objects, but the AnswerChoices' FKs to the Question entity were not set.

Putting the @JoinColumns in the Question entity solved the problem, but my question is: is this the correct way? Will I be facing any side effects? Should I instead have run a for-loop on the set of AnswerChoices and set the FK?

Question Json

{
    &quot;text&quot;: &quot;Do you know JPA?&quot;,
    &quot;answerChoices&quot;: [{
	    &quot;text&quot;: &quot;yes&quot;,
    }, {
	    &quot;text&quot;: &quot;no&quot;,
    }, ]
}

Controller with a JpaRepository:

@PostMapping(&quot;/questions/create&quot;)
@ResponseBody
public String create(@RequestBody Question json) {
    questionRepo.save(json);
}

Question entity:

@Entity
public class Question {
    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY)
    @JoinColumn(name=&quot;question_id&quot;)
    private Set&lt;AnswerChoice&gt; answerChoices;
}

AnswerChoice entity:

@Entity
public class AnswerChoice {
    @ManyToOne(fetch = FetchType.LAZY)
    @JsonIgnore
    private Question question;
}

For the sake of brevity I omitted the auto-generated Id's.

答案1

得分: 3

不对,映射不正确。实际上它创建了两个分开的关联,这些关联碰巧共享连接列。

要么从 Answer 中移除 question,使关联成为单向的(自己考虑是否真的需要关联的这一侧),要么返回到原始解决方案并使用 @JsonBackReference/@JsonManagedReference(这样字段在反序列化过程中会自动填充)。

英文:

Nope, the mapping is not correct. It actually creates two separate associations that happen to share the join column.

Either remove question from Answer, making the association unidirectional (ask yourself if you really need that side of the association) or go back to the original solution and use @JsonBackReference/@JsonManagedReference (so that the field gets automatically populated during deserialization).

答案2

得分: 3

如@crizzis所说,你的映射是不正确的,通常情况下,在一对多关系中,子项位于关系的拥有端(当一个域与多个项关联时),但在你的情况下,Question 是关系的拥有端,因为你使用了 @JoinColumn。因此,你可以完全从 AnswerChoice 中删除 Question 的引用。当你创建带有答案选项的问题时,Hibernate 将执行以下操作:

  • 创建问题
  • 创建答案
  • 更新答案的外键以关联到问题

如果从问题实体中删除此行 @JoinColumn(name = &quot;question_id&quot;, foreignKey = @ForeignKey(name = &quot;fk_question_id&quot;)),Hibernate 将创建额外的表来管理这个关系,称为 question_answer_choices,因此为了消除额外的表,我们手动指定 AnswerChoice 中将引用的外键列。

实体 Question.java

@Entity
@Table(name = &quot;question&quot;)
public class Question {
    @Id
    @GeneratedValue
    @Type(type = &quot;uuid-char&quot;)
    private UUID id;

    private String description;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = &quot;question_id&quot;, foreignKey = @ForeignKey(name = &quot;fk_question_id&quot;))
    private Set&lt;AnswerChoice&gt; answerChoices = new HashSet&lt;&gt;();

    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&lt;AnswerChoice&gt; getAnswerChoices() {
        return answerChoices;
    }
}

实体 AnswerChoice.java

@Entity
@Table(name = &quot;answer_choice&quot;)
public class AnswerChoice {
    @Id
    @GeneratedValue
    @Type(type = &quot;uuid-char&quot;)
    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(&quot;How is the weather today?&quot;);

    question.addAnswerChoice(new AnswerChoice(&quot;Sunny&quot;));
    question.addAnswerChoice(new AnswerChoice(&quot;Cloudy&quot;));
    question.addAnswerChoice(new AnswerChoice(&quot;Rainy&quot;));
    question.addAnswerChoice(new AnswerChoice(&quot;Windy&quot;));
    question.addAnswerChoice(new AnswerChoice(&quot;Snowy&quot;));

    // 子实体一起持久化
    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&lt;AnswerChoice&gt; answerChoices = searchedQuestion.getAnswerChoices();
    for (AnswerChoice answerChoice : answerChoices) {
        Assertions.assertNotNull(answerChoice.getId());
    }
}

生成的表语句如下:

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
英文:

As told by @crizzis, your mapping is not correct, usually, the child is at the owning side of the relationship(when 1-to-many is big with a single domain), but in your case, Question is the owning side of the relationship since you have @JoinColumn. So you can completely get rid of Question reference from AnswerChoice. When you create Question with Answer choices hibernate will

  • create questions
  • create answers
  • updates answers foreign key to questions

If you remove this line @JoinColumn(name = &quot;question_id&quot;, foreignKey = @ForeignKey(name = &quot;fk_question_id&quot;)) from Question entity, hibernate will create extra table to manage this relationship called question_answer_choices hence to get rid off extra table we manually specify which column will be reference in AnswerChoice to map to foreign key.

Entity Question.java

@Entity
@Table(name = &quot;question&quot;)
public class Question {
    @Id
    @GeneratedValue
    @Type(type = &quot;uuid-char&quot;)
    private UUID id;

    private String description;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = &quot;question_id&quot;, foreignKey = @ForeignKey(name = &quot;fk_question_id&quot;))
    private Set&lt;AnswerChoice&gt; answerChoices = new HashSet&lt;&gt;();

    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&lt;AnswerChoice&gt; getAnswerChoices() {
        return answerChoices;
    }
}

Entity AnswerChoice.java

@Entity
@Table(name = &quot;answer_choice&quot;)
public class AnswerChoice {
    @Id
    @GeneratedValue
    @Type(type = &quot;uuid-char&quot;)
    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 code below

    @Test
    public void testQuestionAndAnswersTest() {
        Question question = new Question();
        question.setDescription(&quot;How is the weather today?&quot;);

        question.addAnswerChoice(new AnswerChoice(&quot;Sunny&quot;));
        question.addAnswerChoice(new AnswerChoice(&quot;Cloudy&quot;));
        question.addAnswerChoice(new AnswerChoice(&quot;Rainy&quot;));
        question.addAnswerChoice(new AnswerChoice(&quot;Windy&quot;));
        question.addAnswerChoice(new AnswerChoice(&quot;Snowy&quot;));

        //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&lt;AnswerChoice&gt; answerChoices = searchedQuestion.getAnswerChoices();
        for (AnswerChoice answerChoice : answerChoices) {
            Assertions.assertNotNull(answerChoice.getId());
        }
    }

Table statements generated are as below:

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

huangapple
  • 本文由 发表于 2020年10月2日 16:31:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/64168459.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定