jpa – 如果父项不存在,则spring数据保存仅创建级联子项

我有以下实体:问题有OneToOne配置.而Config有很多选项.全部配置为CASCADE.ALL(附录)

基于RequestDTO(requestConfig),我为新问题或现有问题创建id = null的新Option实体.

在这两种情况下,我都想访问新选项的生成ID.但是,它确实适用于新问题,但不适用于现有问题:

新问题(好)

// 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

现有问题(破碎,见最后一行,ids为空)

// 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包含新创建的选项及其创建的ID,因为它们是从问题保存操作级联的.我检查了数据库并插入了它们!

附录

作为参考,这是我的RequestDTO和实体:

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;
    }
    // ...
}

最佳答案 当您在新问题上调用questionRepo.save()时,Spring Data会识别您正在尝试保存新实体,并在内部调用EntityManager.persist().

EntityManager.persist(entity)使实体作为参数持久化传递.

但是,当您在现有问题上调用questionRepo.save()时,Spring Data会在内部调用EntityManager.merge().

EntityManager.merge(entity)返回实体的持久副本.

问题是你在调用questionRepo.save()之前调用requestConfig.getNewOptions().在您描述的第一种情况下无关紧要,因为分配给问题的原始实例(即使用Question question = new Question(…)创建的实例;),以及使用添加到Option的子实例.forEach(o – > config.getOptions().add(o))行,变为持久化,并获得自动生成的id.

但是,它在第​​二种情况下很重要,因为使用行.forEach(o – > config.getOptions().add(o))添加到Option的新子实例不会变为持久化.相反,只有由questionRepo.save()返回的副本引用的子实体instaces(它反过来返回EntityManager.merge()的结果)是持久的.

您应该在调用questionRepo.save()之后构造idMapping映射(使用question.getConfig().getNewOptions()).这应该处理两种情况.

点赞