java - 在 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 值并分别保存答案。
有没有办法一次进行这些交易?
解决方案
根据我的经验,您只是以错误的方式建模您的实体。正如另一条评论中所说,了解您的用例将是什么很重要。例如,当您对问题/答案进行建模时,在很多情况下您将使用您的问题,并且只使用问题,并使用与答案的关系来显示它们。但是从答案中访问问题不太可能发生......
那么,你是怎么做到的呢?您听说过聚合根和值对象的概念吗?这很容易。在这里你可以如何设计你的关系问题/答案:
- 创建一个答案类,并使用
@Embeddable
注释。因此,hibernate 不会尝试创建一个表来存储您的答案,但您可以使用它来帮助 hibernate 映射最佳方式。
@Data
@Embeddable
public class Answer {
private String text;
}
就是这样,这就是您代表您的Value Object Answer
所需要的一切。
- 创建您的实体
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 ofAnswer
,因此,创建一个 Table 来存储您的问题的无限数量的答案。@AttributeOverride(name = "text", column = @Column(name = "answer_text"))
在这里对Hibernate说:请把text
我的Answer
类的属性值存入,并将这个值存入name为的列中answer_text
。
接下来做什么 ?创建一个Controller
并Repository
尝试发布一个问题。
@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** ...
推荐阅读
- python - 为什么 mypy 会根据使用 `.` 与特定文件的路径运行产生不同的错误?
- android - 如何在 Kotlin 语言中找到系统存储中的数据地址?
- javascript - Vue.js Jest 模拟了未调用的导入函数
- javascript - puppeteer 上的 scrollIntoView() 循环元素
- c++ - OpenCL SHA-256 GPU 不能计算超过 7670 个哈希
- go - 如何更新地图内的切片
- ssh - ssh:连接到主机 xxx.xxx.xx.xxx 端口 22:操作超时 - 有时 ssh 登录有效,但更多时候我收到来自 WAN 的错误消息
- android - 如何使用Retrofit前台服务异步工作Rxjava3
- arrays - Bash - 获取数组的最后一个索引
- couchdb - 为什么删除的 CouchDB 数据库会重新出现?