将问题添加到多对多关系的测验中



我在Quiz表和Question表之间有一个Many-To-Many relationship。我想问的只是如何通过测验 ID 将问题添加到测验中,以便能够将多个问题链接到一个特定的测验。

  • 测验实体端
@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<>();
  • 问题实体端
@ManyToMany(mappedBy = "questions")
private List<Quiz> quizList = new ArrayList<>();

这是我的控制器:

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

return "quiz/index";
}

如何在注释中实现逻辑,因为当我这样做时,它会抛出如下错误:

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

你应该使用Set而不是List。在这种情况下,您不应该使用级联。我在下面添加了一个带有测试类的实现,您可以在其中了解如何Question实例添加到Quiz实例。

问题类

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<>();
}

问题虚拟机类

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

问题存储库

package no.mycompany.myapp.misc;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QuestionRepo extends JpaRepository<Question, Long> { }

问答课

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

测验虚拟机类

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

测验回购

package no.mycompany.myapp.misc;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QuizRepo extends JpaRepository<Quiz, Long> { }

测验回购测试

更新:请注意,在回答这个问题的过程中,OP有点变化,这个过程持续了几天。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");
}
}

测验服务类(请注意,问题是关于将问题实例添加到测验实例,因此不会添加删除问题实例的逻辑)。

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

控制器方法

@PostMapping(value = "/add")
public String addQuestionToQuiz(QuizVM quiz, BindingResult bindingResult, Model model) {
quizService.saveQuiz(quiz);
return "redirect:/quiz/index";
}

这是一种双向关系,因此需要使用自定义方法处理添加或删除操作,以避免任何不可预测的错误。您应该将自定义addQuestion()方法添加到作为Quiz实体的欠侧。

@Entity
public class Quiz {
//...
public void addQuestion(Question question) {
questions.add(question);
question.getQuizList().add(this);
}
//...
}

然后,您可以在测验中添加问题:

Question question = new Question();
questionRepository.save(question);
Quiz quiz = quizRepository.findById(quizId).get();
quiz.addQuestion(question);
quizRepository.save(quiz);

PS:我建议您使用Set而不是List@ManyToMany关系以获得更好的表现。

在贡献者的帮助下,我已经能够解决问题。 我将在这里发布我的代码,也许这对尝试同样事情的人是有益的。

  • 测验服务:
@Override
public void save(QuizVM quiz) {
Quiz quizDb = !isNull(quiz.getId()) && quiz.getId() > 0 ? quizRepository.getOne(quiz.getId()) : new Quiz();
Set<Question> questionsToSave = quiz.getQuestions()
.stream()
.filter(f -> !isNull(f.getId()))
.filter(g -> questionRepository.existsById(g.getId()))
.map(Question::new)
.collect(Collectors.toSet());
quizDb.setQuestions(questionsToSave);
quizRepository.save(quizDb);
}

我还想指出,为了使其正常工作,我必须在Question类中创建一个构造函数,该构造函数将QuestionVM作为参数,如下所示:

public Question(QuestionVM questionVM){
this.id = questionVM.getId();
}

最后,Controller代码:

@PostMapping(value = "/add")
public String addQuestionToQuiz(QuizVM quiz, BindingResult bindingResult, Model model) {
quizService.save(quiz);
List<Quiz> quizList = quizService.findAll();
return "redirect:/quiz/index";
}

最新更新