如果父级不存在,弹簧数据保存只会创建级联子级 [英] spring data save does only create cascaded children if parent did not exist
问题描述
我有以下实体:Question
有 OneToOne Config
.而Config
有很多Option
.全部配置为CASCADE.ALL
(s. appendix)
I have the following Entities: Question
has OneToOne Config
. And Config
has many Option
s. All are configured to CASCADE.ALL
(s. appendix)
基于 RequestDTO
(requestConfig
) 我创建新的 Option
实体与 id=null
新问题或现有问题.
Based on a RequestDTO
(requestConfig
) I create new Option
entities with id=null
for either a NEW question or an EXISTING question.
在这两种情况下我都想访问生成的新 Options
的 id..但是,它确实适用于新问题,但不适用于现有问题:
In both cases I want to access the generated ids of the new Options
.. However, it does work for new questions, but not for existing ones:
新问题(好的)
// RequestDTO requestConfig is a controller parameter
Question question = new Question(...);
Config config = requestDTO.createConfig(Optional.empty());
question.setConfig(config);
LinkedHashMap<String, Option> idMapping = requestConfig.getNewOptions();
idMapping.forEach((foo, option) -> System.out.println(option.getId())); // all null
question = questionRepo.save(question);
idMapping.forEach((foo, option) -> System.out.println(option.getId())); // 675, 676, ... etc
现有问题(已损坏,见最后一行,id 为空)
Existing Question (Broken, see last line, ids are null)
// RequestDTO requestConfig is a controller parameter
Question question = questionRepo.find(...);
Config config = requestDTO.getConfig(Optional.of(question.getConfig()));
question.setConfig(config);
LinkedHashMap<String, Option> idMapping = requestConfig.getNewOptions();
idMapping.forEach((foo, option) -> System.out.println(option.getId())); // all null
question = questionRepo.save(question);
idMapping.forEach((foo, option) -> System.out.println(option.getId())); // all null
为什么会这样?我希望 LinkedHashMap
idMapping
包含新创建的 Option
及其创建的 id,因为它们是从问题保存操作级联的.我检查了数据库,它们被插入了!
Why is this happening? I would expect the LinkedHashMap
idMapping
to contain the newly created Option
s with their created ids since they were cascaded from the question save operation. I checked the DB and they are inserted!
附录
作为参考,这是我的 RequestDTO
和实体:
For reference, here is my RequestDTO
and the entities:
public class RequestDTO {
private LinkedHashMap<String, OptionDTO> optionDTOs;
@JsonIgnore
private LinkedHashMap<String, Option> newOptions = new LinkedHashMap<>();
public Config getConfig(Optional<Config> oldConfig) {
Config config = new Config();
if (oldConfig.isPresent()) {
config = oldConfig.get();
}
// update the options based on our OptionDTOs
config.getOptions().clear();
optionDTOs.stream()
.map(o -> {
try { // to find the existing option
Option theOption = config.getOptions().stream()
// try to find in given config
.filter(existing -> o.getId().equals(existing.getId()))
.findAny()
// fallback to db
.orElse(optionRepo.findOne(Long.parseLong(o.getId())));
if (null != theOption) {
return theOption;
}
} catch (Exception e) {
}
// handle as new one by creating a new one with id=null
Option newOption = new Option(null, config);
newOptions.add(newOption);
return newOption;
})
.forEach(o -> config.getOptions().add(o));
return config;
}
// getters
}
实体:问题
@Entity
public class Question {
@OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "config_id", nullable = false)
private Config config;
// ...
}
实体:配置
@Entity
public class Config {
@OneToOne(mappedBy = "config")
@JoinColumn(name = "question_id", nullable = true)
private Question question;
@OneToMany(mappedBy = "config", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Option> options = new ArrayList<>();
// ...
}
实体:选项
@Entity
public class Option {
@ManyToOne
@JoinColumn(name = "config_id", nullable = false)
private Config config;
public Option(Long id, Config config) {
super();
this.id = id;
this.config = config;
}
// ...
}
推荐答案
当你在一个新的 Question
上调用 questionRepo.save()
时,Spring Data 会识别你'重新尝试保存新实体,并在内部调用 EntityManager.persist()
.
When you call questionRepo.save()
on a new Question
, Spring Data recognizes that you're trying to save a new entity, and invokes EntityManager.persist()
internally.
EntityManager.persist(entity)
使作为参数传递的实体持久.
但是,当您在现有 Question
上调用 questionRepo.save()
时,Spring Data 会在内部调用 EntityManager.merge()
.
However, when you call questionRepo.save()
on an existing Question
, Spring Data invokes EntityManager.merge()
internally.
EntityManager.merge(entity)
返回实体的永久副本.
问题是你调用 requestConfig.getNewOptions()
之前调用 questionRepo.save()
.在您描述的第一种情况下并不重要,因为分配给 question
的原始实例(即使用 Question question = new Question(...); 创建的实例)code>),以及使用
.forEach(o -> config.getOptions().add(o))
行添加到 Option
的子实例,成为持久化,并获得一个自动生成的 id.
The problem is that you call requestConfig.getNewOptions()
before calling questionRepo.save()
. It doesn't matter in the first case you've described, since the original instance assigned to question
(i.e. the one created using Question question = new Question(...);
), as well as the child instances added to Option
using the line .forEach(o -> config.getOptions().add(o))
, become persistent, and obtain an autogenerated id.
但是,在第二种情况下确实很重要,因为新的子实例使用 .forEach(o -> config.getOptions().add(o))
,不会持久化.相反,只有 questionRepo.save()
返回的 副本 引用的子实体实例(反过来返回 EntityManager.merge()
的结果代码>)是持久的.
However, it does matter in the second case, since the new child instances added to Option
using the line .forEach(o -> config.getOptions().add(o))
, do not become persistent. Instead, only the child entity instaces referenced by the copy returned by questionRepo.save()
(which in turn returns the result of EntityManager.merge()
) are persistent.
您应该在调用 questionRepo.save()
之后简单地构造 idMapping
映射 (使用 question.getConfig().getNewOptions()
).那应该处理这两种情况.
You should simply construct the idMapping
map after calling questionRepo.save()
(using question.getConfig().getNewOptions()
). That should handle both cases.
这篇关于如果父级不存在,弹簧数据保存只会创建级联子级的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!