【问题标题】:JPA one to many without join tableJPA 一对多没有连接表
【发布时间】:2018-10-31 08:21:41
【问题描述】:

我正在尝试为测验数据库创建 JPA 实体类,其中我有两个实体 Questions 和 Options。

方法一

像这样从问题到选项创建和 OneToMany 关系

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

从这样的选项实体创建的 ManyToOne 关系

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

它工作得很好,除了它创建了一个额外的表 questions_options 并且问题-选项关系在该表中进行管理。此外,对于所有记录,Option 都有 questionId 列和它的 null。

我想避免那个额外的表,并想在选项表中填充 questionid。所以经过一番谷歌搜索后,我知道我需要使用 mappedBy 属性。

方法2

问题实体

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

选项实体

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

现在没有创建 Join 表,而是将 question_questionId 列添加到 Options 表中,但它再次以 null 的形式出现。因此,我的端点没有返回带有问题的选项。

我希望我的问题很清楚。如何在选项表中填充 questionId。

编辑

完整的问题实体

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

}

选项实体 @实体 @Table(name="选项") 公共类选项实现​​可序列化{ 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
public interface QuestionRepository extends CrudRepository<Question,Long>{

}

服务类

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

控制器

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

【问题讨论】:

  • 映射在第二种方法中似乎是正确的,因此您应该在创建问题实体、选项实体以及在它们之间设置关系的位置发布一些代码。
  • 任何 JPA 文档都会告诉你,mappedBy 是将关系标记为 BIDIRECTIONAL 并关联双方。那些相同的文档会说@JoinColumn 将其定义为不使用连接表。这有多难?
  • 要填充 questionId,只需设置 Option 实体的 Question 实体。 JPA 会从中读取 id 并将其保存在数据库中。因此,您必须确保首先保留了 Question 实体,以便它具有 id。
  • 这个问题被问过无数次了。第一个映射是错误的:它定义了两个独立的关联,而不是双向关联。第二个映射是正确的。如果 questionId 为空,则意味着您忘记将 option.question 设置为非空值。将选项添加到集合中是不够的:您还需要设置选项的问题。阅读指南:docs.jboss.org/hibernate/orm/current/userguide/html_single/…
  • @Nikolay Shevchenko 我在这里添加了完整代码

标签: hibernate jpa orm spring-data-jpa hibernate-mapping


【解决方案1】:

我认为你的addQuestion 方法应该是这样的:

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

【讨论】:

  • Shevchenko 感谢您查看此内容。我尝试了此代码,但它在选项表中创建了重复条目。当我们执行第一个保存语句时,它会在其中 questionId 为空的选项表中创建一条记录,当我们执行最后一次保存时,即 optionRepository.save 然后它会在填充问题 ID 的位置插入新记录。我怎样才能更新它而不是创建新的?我应该手动还是 jpa 中有现成的解决方案?再次感谢您
  • Shevchenko :当我以单向方式连接表格时,它起作用了。 @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) 。 @JoinColumn(name = "questionId") private Set
猜你喜欢
  • 2021-08-23
  • 2020-05-25
  • 1970-01-01
  • 1970-01-01
  • 2017-07-13
  • 1970-01-01
  • 2021-09-07
  • 1970-01-01
  • 2011-12-25
相关资源
最近更新 更多