spring - 如何将前端给出的临时 ID 映射到生成的后端 ID?

标签 spring spring-boot jpa spring-data spring-data-jpa

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

  • 创建一个新问题并添加一些选项都发生在浏览器/前端 (FE) 中。
  • FE 为问题和所有选项创建并使用临时 ID(“_1”、“_2”、...),直到用户单击保存按钮。
  • 保存新创建的问题时,FE 将包含临时 ID 的 JSON 发送到后端
  • 因此,FE 预计 201 CREATED包含 map 临时 id -> 后端 id 以更新其 id。
  • 用户决定添加另一个 Option(在 FE 端再次使用临时 id)
  • 用户点击保存,FE 发送更新后的问题,其中包含后端 ID(对于问题和现有选项)和临时 ID(对于新创建的选项)
  • 为了更新新创建的选项的 id,FE 期望响应包含此 id 的映射。

  • 我们应该如何在后端实现最后一部分的对应物(5-7 添加一个选项)?

    我尝试了这个,但是在坚持之后我无法获得 child 的 ID。

    实体
    @Entity
    public class Question {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
    
        @OneToMany(mappedBy = "config", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
        private List<Option> options = new ArrayList<>();
        // ...
    }
    
    
    @Entity
    public class Option {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
    
        @ManyToOne
        @JoinColumn(name = "question_id", nullable = false)
        private Question question;
    
        public Option(Long id, Config config) {
            this.id = id;
            this.question = question;
        }
        // ...
    }
    

    Controller
    @RestController
    @RequestMapping("/questions")
    public class AdminQuestionsController {
    
        @Autowired
        private QuestionRepository questionRepo;
    
        @Autowired
        private OptionRepository optionRepo;
    
        @PutMapping("/{id}")
        @ResponseStatus(HttpStatus.OK)
        public QuestionDTO updateQuestion(@PathVariable("id") String id, @RequestBody QuestionDTO requestDTO) {
            Question question = questionRepo.findOneById(Long.parseLong(id));
    
            // will hold a mapping of the temporary id to the newly created Options.
            Map<String, Option> newOptions = new HashMap<>();
    
            // update the options        
            question.getOptions().clear();
    
            requestDTO.getOptions().stream()
                .map(o -> {
                    try { // to find the existing option
                        Option theOption = question.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.put(o.getId(), newOption);
                    return newOption;
                })
                .forEach(o -> question.getOptions().add(o));
    
            question = questionRepo.save(question);
    
            // create the id mapping
            Map<String, String> idMap = new HashMap<>();
            for (Entry<String, Option> e : newOptions.entrySet()) {
                idMap.put(e.getKey(), e.getValue().getId());
                // PROBLEM: e.getValue().getId() is null 
            }
    
            return QuestionDTO result = QuestionDTO.from(question, idMap);
        }
    }
    

    在 Controller 中我标记了问题:e.getValue().getId() is null

    这样的 Controller 应该如何创建 idMap?

    最佳答案

    最好单独保存每个选项,然后将生成的 Id 保存在 map 上。

    我做了下面的测试,效果很好。

    @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);
    }
    

    控制台输出:
    {-1=2, -2=3, -3=4, -4=5}

    更新:
    或者,如果前端只是控制选项的顺序,并根据未保存选项的顺序获取新的 id,那么这将是一个更好的代码风格。

    选项:
    @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());//
    }
    

    控制台输出: [选项[id=2, order=1], 选项[id=3, order=2], 选项[id=4, order=3], 选项[id=5, order=4]]

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

    关于spring - 如何将前端给出的临时 ID 映射到生成的后端 ID?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50638928/

    相关文章:

    java - 如何检查 th :utext in thymeleaf page 的值

    java - 使用 Vaadin 14 的 RTL 支持

    java - JDBC Template异常表或 View 不存在但实际存在

    docker - docker 中的 Eureka 客户端未与 Eureka 服务器连接

    java - 使用 Hibernate、JPA 建立一对多关系

    java - JPA 在此持久单元中定义的名为 ... 的多个转换器

    java - 将系统属性传递给 spring boot

    当 https 端口被 http 请求命中时,Spring boot 禁用响应

    spring - 如何在 Spring 端点的 Swagger UI 中显示所需的用户角色(访问控制信息)?

    java - 使用 JPA/EclipseLink/EJB 从 Java Web 应用程序访问多个数据库