Use Case: A user can CRUD multiple choice questions using a single page web application written in JavaScript.
- Creating a new question and adding some options all happens within the browser / Frontend (FE).
- The FE creates and uses temporary ids ("_1", "_2", ...) for both the question and all the options until the user clicks a save button.
- When saving the newly created question the FE sends a JSON containing the temporary ids to the backend
- As result the FE expects a
201 CREATED
containing a map temporary id -> backend id to update its ids. - The user decides to add another Option (which on the FE side uses a temporary id again)
- The user clicks save and the FE sends the updated question with a mixture of backend ids (for the question and the existing options) and a temporary id (for the newly created option)
- To update the id of the the newly created option, the FE expects the reponse to contain the mapping for this id.
How should we implement the counterpart for the last part (5-7 adding an option) on the backend side?
I try this, but I cannot get the child ids after persistence.
Entities
@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);
}
}
In the controller I marked the Problem: e.getValue().getId() is null
How should such a controller create the idMap?