Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
180 views
in Technique[技术] by (71.8m points)

java - Add questions to quiz on a ManyToMany relationship

I have a Many-To-Many relationship between a Quiz table and a Question table. All I want to ask is how I get the logic down to be able to add a question to a quiz by the Quiz Id, so I can link multiple questions to one particular Quiz.

  • Quiz entity side
@ManyToMany(fetch = FetchType.EAGER, cascade = {
    CascadeType.MERGE
})
@JoinTable(name = "quiz_content", 
           joinColumns = @JoinColumn(name = "quiz_id"), 
           inverseJoinColumns = @JoinColumn(name = "question_id")
)
private List<Question> questions = new ArrayList<>();
  • Question entity side
@ManyToMany(mappedBy = "questions")
private List<Quiz> quizList = new ArrayList<>();

This is my Controller:

@PostMapping(value = "/add")
public String addQuestionToQuiz(Quiz quiz, BindingResult bindingResult, Model model) {
        
       return "quiz/index";
    }

How can I implement the logic in the comments, because when I do, it throws an error like:

invalid property of bean class cannot get element with index 0 from set of size 0, accessed using property path.

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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";
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...