0
votes

I am trying to create JPA entity classes for quiz database where I have two entities Questions and Options.

Approach 1

Create and OneToMany relation from Question to Option like this

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Option> options = new HashSet<>();

And ManyToOne relation created from options entity like this

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "questionId")
private Question question;

It worked fine except the fact that it created an extra table questions_options and question-option relations are managed in that table. Also, Option has questionId column and its null for all the records.

I want to avoid that extra table and want to populated questionid in the options table.So after a little googling, I came to know that I need to use mappedBy attribute.

Approach 2

Question Entity

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy="question")
private Set<Option> options = new HashSet<>();

Option Entity

@ManyToOne(fetch = FetchType.LAZY)
private Question question;

Now Join table is not created, instead question_questionId column is added to Options table but again it coming as null . Because of this my endpoint is not returning options with the question.

I hope my question is clear. How to populate questionId in Options table.

EDIT

Full Question entity

@Entity
@Table(name="questions")
public class Question implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int questionId;

    private String author;

    private boolean expired;

    private String questionText;

    //bi-directional many-to-one association to Option
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy="question")
    private Set<Option> options = new HashSet<>();

    public Question() {
    }

    public int getQuestionId() {
        return this.questionId;
    }

    public void setQuestionId(int questionId) {
        this.questionId = questionId;
    }

    public String getAuthor() {
        return this.author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public boolean isExpired() {
        return expired;
    }

    public void setExpired(boolean expired) {
        this.expired = expired;
    }

    public String getQuestionText() {
        return this.questionText;
    }

    public void setQuestionText(String questionText) {
        this.questionText = questionText;
    }

    public Set<Option> getOptions() {
        return this.options;
    }

    public void setOptions(Set<Option> options) {
        this.options = options;
    }

    public Option addOption(Option option) {
        getOptions().add(option);
        option.setQuestion(this);

        return option;
    }

    public Option removeOption(Option option) {
        getOptions().remove(option);
        option.setQuestion(null);

        return option;
    }

}

Option Entity @Entity @Table(name="options") public class Option implements Serializable { private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int optionId;

private boolean correctAnswer;

private String optionText;

//bi-directional many-to-one association to Question
@ManyToOne(fetch = FetchType.LAZY)
private Question question;

public Option() {
}

public int getOptionId() {
    return this.optionId;
}

public void setOptionId(int optionId) {
    this.optionId = optionId;
}

public boolean isCorrectAnswer() {
    return correctAnswer;
}

public void setCorrectAnswer(boolean correctAnswer) {
    this.correctAnswer = correctAnswer;
}

public String getOptionText() {
    return this.optionText;
}

public void setOptionText(String optionText) {
    this.optionText = optionText;
}

public Question getQuestion() {
    return this.question;
}

public void setQuestion(Question question) {
    this.question = question;
}

}

Repository

@Repository
public interface QuestionRepository extends CrudRepository<Question,Long>{

}

Service Class

@Autowired
    private QuestionRepository questionRepository;
    public Question getQuestion(Long id) {
        Question  question= questionRepository.findOne(id);
        Set<Option> options = question.getOptions();
        options.forEach(s -> s.setCorrectAnswer(false));
        return question;
    }

    public Question addQuestion(Question question) {
        return questionRepository.save(question);
    }

Controller

@GetMapping
    @RequestMapping(method = RequestMethod.GET, value="/questions/{id}")
    public ResponseEntity<Question> getQuestion(@PathVariable long id) {
        return new ResponseEntity<Question>(questionService.getQuestion(id),HttpStatus.OK);
    }

    @PostMapping
    @RequestMapping(method = RequestMethod.POST, value= "/questions")
    @Transactional
    public ResponseEntity<Question> addQuestion(@RequestBody Question question) {
        logger.info("Request recieved from client : " + question.toString());
        return new ResponseEntity<Question>(questionService.addQuestion(question),HttpStatus.OK);
    }

enter image description here

enter image description here

1
Mapping seems to be correct in 2nd approach, so you should post some pieces of your code where you create Question entity, Option entities and where you set up relation between them.Nikolai Shevchenko
As any JPA docs would tell you, mappedBy is to mark the relation as BIDIRECTIONAL and associate both sides. Those same docs would say that the @JoinColumn defines it as NOT using a join table. How hard can that be ?user3973283
To populate questionId simply set the Question entity of the Option entity. JPA will read the id out of it and save it in the database. Therefore you have to be sure you have persisted the Question entity first so it has an id.K.Nicholas
This has been asked countless times. The first mapping is wrong: it defines two separate associations, instead of a bidirectional one. The second mapping is correct. If the questionId is null, it means that ou forgot to ever set option.question to a non-null value. Adding the option to the set is not sufficient: you need to set the question of the option, too. Read the guide: docs.jboss.org/hibernate/orm/current/userguide/html_single/…JB Nizet
@Nikolay Shevchenko I have added full code hereDeepu Nair

1 Answers

1
votes

I think your addQuestion method should look like this:

public Question addQuestion(Question question) {
   Question newQuestion = questionRepository.save(question);
   question.getQuestions().forEach(option -> {
      Option newOption = new Option();
      newOption.setQuestion(newQuestion); // need to reference managed (attached to JPA session) entity
      newOption.setOptionText(option.getOptionText());
      newOption.setCorrectAnswer(Optional.ofNullable(option.isCorrectAnswer()).orElse(false)));
      newQuestion.getOptions().add(optionRepository.save(newOption));
   });

   // it's done implicitly here because your controller's (or service's) method 
   // is marked with @Transactional, otherwise it must be done explicitly
   // newQuestion = questionRepository.save(newQuestion);

   return newQuestion;
}