You should use Set
instead of List
. And you should not use cascading in this case. I added an implementation below with a test class where you can see how to add Question
instances to Quiz
instance.
Question class
package no.mycompany.myapp.misc;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import java.util.HashSet;
import java.util.Set;
@Entity
@Getter
@Setter
public class Question {
@Id
@GeneratedValue
private long id;
@ManyToMany(mappedBy = "questions")
private Set<Quiz> quizList = new HashSet<>();
}
QuestionVM class
package no.mycompany.myapp.misc;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class QuestionVM {
private long id;
// TODO add remaining fields except for collections containing Quiz instances
public QuestionVM(Question question) {
this.id = question.getId();
// TODO init remaining fields
}
}
QuestionRepo
package no.mycompany.myapp.misc;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QuestionRepo extends JpaRepository<Question, Long> { }
Quiz class
package no.mycompany.myapp.misc;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Getter
@Setter
@Entity
public class Quiz {
@Id
@GeneratedValue
private long id;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "quiz_content",
joinColumns = @JoinColumn(name = "quiz_id"),
inverseJoinColumns = @JoinColumn(name = "question_id")
)
private Set<Question> questions = new HashSet<>();
public void addQuestion(Question question) {
question.getQuizList().add(this);
questions.add(question);
}
public void removeQuestion(Question question) {
question.getQuizList().remove(this);
questions.remove(question);
}
}
QuizVM class
package no.mycompany.myapp.misc;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Getter
@Setter
@NoArgsConstructor
public class QuizVM {
private long id;
private List<QuestionVM> questions = new ArrayList<>();
// TODO add remaining fields
public QuizVM(Quiz quiz) {
this.id = quiz.getId();
this.questions = quiz.getQuestions().stream().map(QuestionVM::new).collect(Collectors.toList());
// TODO init remaining fields
}
}
QuizRepo
package no.mycompany.myapp.misc;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QuizRepo extends JpaRepository<Quiz, Long> { }
QuizRepoTest
Update: Please note that OP was a little bit shifting during the process of answering this question, a process that lasted for days. Case went from using existing db instances to creating new db instances, then back to only using existing db instances. Hence tests below may not reflect the actual case.
package no.mycompany.myapp.misc;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.ActiveProfiles;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
@ActiveProfiles("test")
@DataJpaTest
public class QuizRepoTest {
@Autowired
TestEntityManager testEntityManager;
@Autowired
QuizRepo quizRepo;
@Autowired
QuestionRepo questionRepo;
@Test
public void test() {
var quizInDb = new Quiz();
var question1InDb = questionRepo.save(new Question());
var question2InDb = questionRepo.save(new Question());
quizInDb.addQuestion(question1InDb);
quizInDb.addQuestion(question2InDb);
quizInDb = quizRepo.save(quizInDb);
// verify that Quiz instance has 2 questions
var quiz = testEntityManager.find(Quiz.class, quizInDb.getId());
assertThat(quiz.getQuestions().size()).isEqualTo(2);
// verify that question #1 has 1 Quiz instance
var question1 = testEntityManager.find(Question.class, question1InDb.getId());
assertThat(question1.getQuizList().size()).isEqualTo(1);
// remove question #1 from Quiz instance
quizInDb = quizRepo.getOne(quiz.getId());
question1InDb = questionRepo.getOne(question1.getId());
quizInDb.removeQuestion(question1InDb);
quizRepo.save(quizInDb);
// verify that Quiz instance has 1 question
quiz = testEntityManager.find(Quiz.class, quiz.getId());
assertThat(quiz.getQuestions().size()).isEqualTo(1);
// verify that question #1 has 0 Quiz instances
question1 = testEntityManager.find(Question.class, question1InDb.getId());
assertThat(question1.getQuizList().size()).isEqualTo(0);
questionRepo.delete(question1InDb);
// verify that question #1 is deleted from db
assertThat(testEntityManager.find(Question.class, question1InDb.getId())).isNull();
quizInDb = quizRepo.getOne(quiz.getId());
question2InDb = quizInDb.getQuestions().stream().findFirst().orElse(null);
question2InDb.setComment("test");
quizRepo.save(quizInDb);
var question2 = testEntityManager.find(Question.class, question2InDb.getId());
assertThat(question2.getComment()).isEqualTo("test");
}
}
QuizService class (please note question was about adding Question instances to Quiz instance, hence logic for removing Question instances is not added).
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class QuizService {
private final QuizRepo quizRepo;
private final QuestionRepo questionRepo;
public void saveQuiz(QuizVM quiz) {
var quizInDb = quizRepo.getOne(quiz.getId());
quiz.getQuestions().stream()
.filter(questionVM ->
quizInDb.getQuestions().stream()
.noneMatch(questionInDb -> questionInDb.getId() == questionVM.getId()))
.forEach(questionVM -> quizInDb.addQuestion(questionRepo.getOne(questionVM.getId())));
quizRepo.save(quizInDb);
}
}
Controller method
@PostMapping(value = "/add")
public String addQuestionToQuiz(QuizVM quiz, BindingResult bindingResult, Model model) {
quizService.saveQuiz(quiz);
return "redirect:/quiz/index";
}