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

Stu*_*uck 5 spring jpa spring-data spring-data-jpa spring-boot

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

  1. 创建一个新问题并添加一些选项都发生在浏览器/前端(FE)中.
  2. 在用户单击保存按钮之前,FE会为问题和所有选项创建并使用临时ID("_ 1","_ 2",...).
  3. 保存新创建的问题时,FE会将包含临时ID的JSON发送到后端
  4. 因此,FE期望201 CREATED包含一个map 临时id - > backend id来更新其id.
  5. 用户决定添加另一个选项(在FE侧再次使用临时ID)
  6. 用户单击"保存",FE会发送更新的问题,其中包含后端ID(对于问题和现有选项)和临时ID(对于新创建的选项)
  7. 要更新新创建的选项的ID,FE期望响应包含此id的映射.

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

我试试这个,但我坚持不能得到孩子的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;
    }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

调节器

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在控制器中我标记了问题:e.getValue().getId()为空

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

Nor*_*ann 1

最好单独保存每个选项,然后将生成的 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);
}
Run Code Online (Sandbox Code Playgroud)

控制台输出: {-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;
}
Run Code Online (Sandbox Code Playgroud)

更新示例:

@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());//
}
Run Code Online (Sandbox Code Playgroud)

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

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