首页 > 解决方案 > 在 Spring Boot 中使用生成的 ID 持久化 OneToMany 实体

问题描述

我有一个实体Question,我想将它保存到数据库中。每个问题都由一些答案通过questionId引用字段引用。

两个实体都有一个 ID 字段,该字段在持久化时自动生成。这是实体的简化代码:

问题.java

@Data
@Entity
public class Question {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;

    @Size(max=1000, message="Text too long")
    @NotNull(message="Field text cannot be null")
    private String text;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name="id", referencedColumnName = "questionId")
    private List<Answer> answers;
}

答案.java

@Data
@Entity
public class Answer {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;

    @Size(max=255, message="Text too long")
    @NotNull(message="Field text cannot be null")
    private String text;

    @NotNull(message="Field questionId cannot be null")
    private Integer questionId;
}

因此,为了保留一个包含一些答案的 Question 对象,我创建了那些没有 ID 字段的对象,这些对象将自动生成。Question 对象从QuestionController中的 JSON 序列化:

@RestController
@RequestMapping(value="/questions")
public class QuestionController {
    @Autowired
    private QuestionRepository questionRepository;

    @PostMapping
    public void createQuestion(@RequestBody Question question){
        questionRepository.save(question);
    }
}

问题是我还必须将questionId字段留空,因为在将其写入数据库之前我不知道它。这会导致事务抛出请求该字段值的错误。

到目前为止,我想出的唯一解决方案是从问题中删除答案,一旦坚持,填写 questionId 值并分别保存答案。

有没有办法一次进行这些交易?

标签: javaspring-bootjpajackson

解决方案


根据我的经验,您只是以错误的方式建模您的实体。正如另一条评论中所说,了解您的用例将是什么很重要。例如,当您对问题/答案进行建模时,在很多情况下您将使用您的问题,并且只使用问题,并使用与答案的关系来显示它们。但是从答案中访问问题不太可能发生......

那么,你是怎么做到的呢?您听说过聚合根和值对象的概念吗?这很容易。在这里你可以如何设计你的关系问题/答案:

  1. 创建一个答案类,并使用@Embeddable注释。因此,hibernate 不会尝试创建一个表来存储您的答案,但您可以使用它来帮助 hibernate 映射最佳方式。
@Data
@Embeddable
public class Answer {
    private String text;
}

就是这样,这就是您代表您的Value Object Answer所需要的一切。

  1. 创建您的实体Question,并列出Answers(注意我删除了您的约束注释,但请随意使用它们):
@Data
@Entity
public class Question {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Integer id;

    private String text;

    @ElementCollection(fetch = FetchType.EAGER)
    @AttributeOverride(name = "text", column = @Column(name = "answer_text"))
    private List<Answer> answers;
}
  • @ElementCollection(fetch = FetchType.EAGER)Hibernate 必须知道它需要存储 List of Answer,因此,创建一个 Table 来存储您的问题的无限数量的答案。
  • @AttributeOverride(name = "text", column = @Column(name = "answer_text"))在这里对Hibernate说:请把text我的Answer类的属性值存入,并将这个值存入name为的列中answer_text

接下来做什么 ?创建一个ControllerRepository尝试发布一个问题。

@RestController
public class QuestionController {

    @Autowired
    private QuestionRepository repository;

    @PostMapping("/questions")
    public void postQuestion(@RequestBody Question question) {
        repository.save(question);
    }

    @GetMapping("/questions")
    public Iterable<Question> questions() {
        return repository.findAll();
    }
}
public interface QuestionRepository extends CrudRepository<Question, Integer> {}

最后,发布您的问题:

{
    "text": "quetion text", 
    "answers": [{
        "text": "answer1"
    }, {
        "text": "answer2"
    }]
}

这将使用它的答案列表创建您的问题。

即使我将在下面列出优点/缺点,我仍然认为这是您可以拥有的最佳解决方案/妥协:

优点

  • 易于配置
  • 您不必管理和之间的Question链接Answer
  • 当您获取问题时,它也会获取关联的Answer.

缺点

  • 您不能修改特定问题的特定答案(但您会修改吗?)
  • 您没有一个表格来代表您在数据库中拥有的所有答案(但再一次,您会不会)

给定基本用例,试试这个。

正如您所意识到的,这里是 hibernate 使用此解决方案创建的表:

Hibernate:创建表问题(默认生成的id整数为identity(从1开始),文本varchar(255),主键(id))
Hibernate:创建表question_answers(question_id整数不为空,answer_text varchar(255))

只有两张桌子,一个问题和一个** question_answers** ...


推荐阅读