Spring 如何将前端提供的临时ID映射到生成的后端ID?

Spring 如何将前端提供的临时ID映射到生成的后端ID?,spring,spring-boot,jpa,spring-data,spring-data-jpa,Spring,Spring Boot,Jpa,Spring Data,Spring Data Jpa,用例:用户可以使用JavaScript编写的单页web应用程序来CRUD多项选择题 创建新问题和添加一些选项都发生在浏览器/前端(FE)中 FE为问题和所有选项创建并使用临时ID(“_1”、“_2”、…),直到用户单击保存按钮 保存新创建的问题时,FE将包含临时ID的JSON发送到后端 因此,FE希望创建一个包含映射临时id->后端id的201来更新其id 用户决定添加另一个选项(FE端再次使用临时id) 用户单击save,FE发送更新的问题,其中包含后端id(针对问题和现有选项)和临时id(针

用例:用户可以使用JavaScript编写的单页web应用程序来CRUD多项选择题

  • 创建新问题和添加一些选项都发生在浏览器/前端(FE)中
  • FE为问题和所有选项创建并使用临时ID(“_1”、“_2”、…),直到用户单击保存按钮
  • 保存新创建的问题时,FE将包含临时ID的JSON发送到后端
  • 因此,FE希望创建一个包含映射临时id->后端id的
    201来更新其id
  • 用户决定添加另一个选项(FE端再次使用临时id)
  • 用户单击save,FE发送更新的问题,其中包含后端id(针对问题和现有选项)和临时id(针对新创建的选项)
  • 要更新新创建的选项的id,FE希望响应包含该id的映射
  • 我们应该如何在后端实现最后一部分(5-7添加选项)的对应项?

    我尝试了这个,但是在持久化之后我无法获得子ID

    实体

    @实体
    公开课问题{
    @身份证
    @GeneratedValue(策略=GenerationType.AUTO)
    私人长id;
    @OneToMany(mappedBy=“config”,fetch=FetchType.EAGER,cascade=CascadeType.ALL,orphan=true)
    私有列表选项=新建ArrayList();
    // ...
    }
    @实体
    公共类选项{
    @身份证
    @GeneratedValue(策略=GenerationType.AUTO)
    私人长id;
    @许多酮
    @JoinColumn(name=“question\u id”,null=false)
    私人问题;
    公共选项(长id,配置){
    this.id=id;
    这个问题=问题;
    }
    // ...
    }
    
    控制器

    @RestController
    @请求映射(“/问题”)
    公共类AdminQuestionsController{
    @自动连线
    私人问题库问题库;
    @自动连线
    私人期权库期权回购;
    @PutMapping(“/{id}”)
    @ResponseStatus(HttpStatus.OK)
    公共问题到updateQuestion(@PathVariable(“id”)字符串id,@RequestBody问题到requestDTO){
    Question-Question=questionRepo.findOneById(Long.parseLong(id));
    //将保留临时id到新创建的选项的映射。
    Map newOptions=newhashmap();
    //更新选项
    question.getOptions().clear();
    requestDTO.getOptions().stream()
    .map(o->{
    尝试{//查找现有选项
    Option theOption=question.getOptions().stream()
    //尝试在给定配置中查找
    .filter(现有->o.getId().equals(现有.getId()))
    .findAny()
    //回退到数据库
    .orElse(optionRepo.findOne(Long.parseLong(o.getId())));
    如果(null!=选项){
    返回选项;
    }
    }捕获(例外e){
    }
    //通过创建id=null的新句柄,将其作为新句柄处理
    选项newOption=新选项(空,配置);
    newOptions.put(o.getId(),newOption);
    返回新选项;
    })
    .forEach(o->question.getOptions().add(o));
    问题=问题报告保存(问题);
    //创建id映射
    Map idMap=newhashmap();
    对于(条目e:newOptions.entrySet()){
    put(e.getKey(),e.getValue().getId());
    //问题:e.getValue().getId()为空
    }
    返回QuestionDToResult=QuestionDToFrom(问题,idMap);
    }
    }
    
    在控制器中,我标记了问题:e.getValue().getId()为空


    这样的控制器应该如何创建idMap?

    您可以在
    问题
    选项
    类中创建附加字段,并标记为
    @Transient
    ,以确保它不会持久化

    class Question {
       ....
       private String id; // actual data field
    
       @Transient
       private String tempId;
    
       // getter & setter
    }
    
    最初,当UI发送数据时,设置
    tmpId
    并持久化对象。成功操作时,
    id
    将具有实际id值。现在,让我们创建映射(tmpId->implementd)

    关于控制器代码,在添加新选项之前,您正在清除所有现有选项。如果您得到的问题对象的id(实际)字段已经填充,您可以直接将其持久化。这不会影响任何事情。另外,如果它在这方面有一些变化,它将被持久化

    class Question {
       ....
       private String id; // actual data field
    
       @Transient
       private String tempId;
    
       // getter & setter
    }
    
    关于您的控制器代码,您正在清除

    question.getOptions().clear();
    
    在此之后,您可以简单地添加新选项

    question.setOptions(requestDTO.getOptions());
    
    question = questionRepo.save(question);
    

    我希望现在能有所帮助。

    最好是单独保存每个选项,然后在地图上保存生成的Id

    我做了下面的测试,效果非常好

    @Autowired
    void printServiceInstance(QuestionRepository questions, OptionRepository options) {
        Question question = new Question();
    
        questions.save(question);
    
        question.add(new Option(-1L, question));
        question.add(new Option(-2L, question));
        question.add(new Option(-3L, question));
        question.add(new Option(-4L, question));
    
        Map<Long, Long> idMap = new HashMap<>();
    
        question.getOptions().stream()
                .filter(option -> option.getId() < 0)
                .forEach(option -> idMap.put(option.getId(), options.save(option).getId()));
    
        System.out.println(idMap);
    }
    
    更新示例:

    @Autowired
    void printServiceInstance(QuestionRepository questions, OptionRepository options) {
        Question question = new Question();
    
        Question merged = questions.save(question);
    
        merged.add(new Option(-1L, 1, merged));
        merged.add(new Option(-2L, 2, merged));
        merged.add(new Option(-3L, 3, merged));
        merged.add(new Option(-4L, 4, merged));
    
        questions.save(merged);
    
        System.out.println(questions.findById(merged.getId()).get().getOptions());//
    }
    
    控制台输出:[选项[id=2,订单=1],选项[id=3,订单=2],选项[id=4,订单=3],选项[id=5,订单=4]]


    注意:不需要映射来控制新ID,前端应该通过按选项的顺序获取它来知道。

    因此,您需要区分FE生成的ID和待生成的ID? 你可以

  • 在生成的FE上使用负ID,在BE上使用正ID
  • 为生成的FE选择特殊前缀/后缀(“FE_1”、“FE_2”、…)
  • 保留已映射ID的会话列表(服务器端)
  • 保留FE生成的ID列表,并将其与POST上的数据一起发送(客户端)

  • 在任何情况下,当混合两个ID生成器时,请注意冲突。

    您能否描述一下选项构造函数,您在其中提供了param Config,但没有使用它,而是使用了问号,另一点是,当您尝试处理新选项,然后再次使用带有空ID的Config时,您能否定义
    question.setOptions(requestDTO.getOptions());
    
    question = questionRepo.save(question);
    
    @Autowired
    void printServiceInstance(QuestionRepository questions, OptionRepository options) {
        Question question = new Question();
    
        questions.save(question);
    
        question.add(new Option(-1L, question));
        question.add(new Option(-2L, question));
        question.add(new Option(-3L, question));
        question.add(new Option(-4L, question));
    
        Map<Long, Long> idMap = new HashMap<>();
    
        question.getOptions().stream()
                .filter(option -> option.getId() < 0)
                .forEach(option -> idMap.put(option.getId(), options.save(option).getId()));
    
        System.out.println(idMap);
    }
    
    @Column(name = "order_num")
    private Integer order;
    
    public Option(Long id, Integer order, Question question) {
        this.id = id;
        this.question = question;
        this.order = order;
    }
    
    @Autowired
    void printServiceInstance(QuestionRepository questions, OptionRepository options) {
        Question question = new Question();
    
        Question merged = questions.save(question);
    
        merged.add(new Option(-1L, 1, merged));
        merged.add(new Option(-2L, 2, merged));
        merged.add(new Option(-3L, 3, merged));
        merged.add(new Option(-4L, 4, merged));
    
        questions.save(merged);
    
        System.out.println(questions.findById(merged.getId()).get().getOptions());//
    }