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

时间:2018-05-31 23:49:12

标签: jpa spring-data

我有以下实体:Question有OneToOne ConfigConfig有很多Option个。所有都配置为CASCADE.ALL(附录)

基于RequestDTOrequestConfig)我为新问题或现有问题创建了Optionid=null实体。

在这两种情况下我想访问新Options生成的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包含新创建的Option及其创建的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;
    }
    // ...
}

2 个答案:

答案 0 :(得分:1)

当您在新Question上调用EntityManager.persist()时,Spring Data会识别您正在尝试保存新实体,并在内部调用EntityManager.persist(entity)

questionRepo.save()使作为参数持久性传递的实体

但是,当您在现有Question上调用EntityManager.merge()时,Spring Data会在内部调用EntityManager.merge(entity)

requestConfig.getNewOptions()会返回实体的持久副本

问题是您在呼叫questionRepo.save()之前致电question 。在您描述的第一种情况下,无关紧要,因为分配给Question question = new Question(...);的原始实例(即使用Option创建的实例)以及添加到{{1}的子实例使用行.forEach(o -> config.getOptions().add(o))成为持久,并获取自动生成的ID。

但是,确实在第二种情况下很重要,因为新的子实例使用行Option添加到.forEach(o -> config.getOptions().add(o))不会变得持久。相反,只有questionRepo.save()返回的引用的引用的子实体实例(后者返回EntityManager.merge()的结果)才是持久的。

您应该在调用idMapping之后构建questionRepo.save()地图(使用question.getConfig().getNewOptions())。这应该处理两种情况。

答案 1 :(得分:0)

  

我检查了数据库并插入了它们!

这发生在entityManager只返回当前实体的新id而不是cascade保存的相关实体。保存问题后,请尝试通过问题ID重新找到。