Java 为什么hibernate要创建空外键?

Java 为什么hibernate要创建空外键?,java,hibernate,spring-boot,jpa,Java,Hibernate,Spring Boot,Jpa,我试图创建一个具有两个实体的spring启动应用程序:Question和QuestionChoices。我使用的是双向的一对一关系。当我尝试创建一个问题实体和一个QuestionChoice列表时,QuestionChoice中的外键显示为null 以下是我的问题选择实体: @实体 @资料 @诺尔格构装师 @AllArgsConstructor 公共类问题选择{ @身份证 @GeneratedValue(策略=GenerationType.IDENTITY) 私有int-id; 私有字符串选择;

我试图创建一个具有两个实体的spring启动应用程序:Question和QuestionChoices。我使用的是双向的一对一关系。当我尝试创建一个问题实体和一个QuestionChoice列表时,QuestionChoice中的外键显示为null

以下是我的问题选择实体:

@实体
@资料
@诺尔格构装师
@AllArgsConstructor
公共类问题选择{
@身份证
@GeneratedValue(策略=GenerationType.IDENTITY)
私有int-id;
私有字符串选择;
@许多酮
@JoinColumn(name=“question\u id”)
私人问题;
公共问题选择(字符串选择,问题){
这个。选择=选择;
这个问题=问题;
}
公共问题选择(字符串选择){
这个。选择=选择;
}
}
以下是我的问题:

@实体
@资料
@诺尔格构装师
@AllArgsConstructor
公开课问题{
@身份证
@GeneratedValue(策略=GenerationType.IDENTITY)
私人问题;
私有字符串名称;
私有字符串文本;
@OneToMany(mappedBy=“question”,cascade=CascadeType.ALL)
私人列表选择;
公共问题(字符串问题名称、字符串问题文本、列表问题选项){
this.questionName=questionName;
this.questionText=questionText;
this.questionChoices=questionChoices;
this.questionChoices.forEach(x->x.setQuestion(this));
}
}
我有一个问题库和问题选择库:

@存储库
公共接口问题库扩展了JpaRepository{
}
@存储库
公共界面问题选择库扩展了JPA库{
}
这是我的控制器:

@PostMapping("/question")
public QuestionDTO createQuestion(@RequestBody Question question) {
    Question savedQuestion = questionRepository.save(question);

    List<QuestionChoiceDTO> questionChoices = new ArrayList<>();
    savedQuestion.getQuestionChoices().forEach(choice -> {
        questionChoices.add(new QuestionChoiceDTO(choice.getId(), choice.getChoice()));
    });

    QuestionDTO response = new QuestionDTO(savedQuestion.getQuestion_id(), savedQuestion.getQuestionName(), savedQuestion.getQuestionText(), questionChoices);

    return response;
}

@GetMapping("/question")
public List<QuestionDTO> getQuestions() {
    List<Question> questions = questionRepository.findAll();
    List<QuestionDTO> response = new ArrayList<>();

    questions.forEach(question -> {
        List<QuestionChoicesDTO> questionChoices = new ArrayList<>();
        question.getQuestionChoices().forEach(choice -> questionChoices.add(new QuestionChoiceDTO(choice.getId(), choice.getChoice()));

        responses.add(new QuestionDTO(savedQuestion.getQuestion_id(), savedQuestion.getQuestionName(), savedQuestion.getQuestionText(), questionChoices));
    });
}
@RestController
公共类控制器{
问题库问题库;
问题选择假设性问题选择假设性问题;
公共控制员(问题库问题库),
问题选择假设性问题选择假设性问题){
this.questionRepository=questionRepository;
this.questionChoicePository=questionChoicePository;
}
@邮戳(“/问题”)
公共问题createQuestion(@RequestBody问题){
返回questionRepository.save(问题);
}
@GetMapping(“/问题”)
公共列表问题(){
返回questionRepository.findAll();
}
}
以下是我的发帖请求:

POST http://localhost:8080/question
Content-Type: application/json

{
  "questionName": "gender",
  "questionText": "What is your gender?",
  "questionChoices": ["male", "female"]
}
GET http://localhost:8080/question

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 16 Oct 2019 11:10:51 GMT

[
  {
    "id": 1,
    "questionName": "gender",
    "questionText": "What is your gender?",
    "questionChoices": []
  }
]
以下是邮报的回复:

{
  "id": 1,
  "questionName": "gender",
  "questionText": "What is your gender?",
  "questionChoices": [
    {
      "id": 1,
      "choice": "male",
      "question": null
    },
    {
      "id": 2,
      "choice": "female",
      "question": null
    }
  ]
}
下面是GET请求的响应:

POST http://localhost:8080/question
Content-Type: application/json

{
  "questionName": "gender",
  "questionText": "What is your gender?",
  "questionChoices": ["male", "female"]
}
GET http://localhost:8080/question

HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 16 Oct 2019 11:10:51 GMT

[
  {
    "id": 1,
    "questionName": "gender",
    "questionText": "What is your gender?",
    "questionChoices": []
  }
]
因此,不仅QuestionChoices的外键为null,而且QuestionEntity中的问题选项列表也为null

知道我做错了什么吗


更新


我在这里找到了解决这个问题的好方法:。问题在于杰克逊,而不是冬眠。只需在实体内的参考对象中添加一个附加注释,一切都很好

您正在为JSON正文中的
questionChoices
发送字符串数组。JSON映射器需要从这个字符串数组填充一个
列表
。因此,它需要将每个
字符串
转换为
问题选择
对象。大概是通过调用
QuestionChoice
构造函数来实现的,该构造函数将
字符串作为参数

因此,您正在保存一个
问题
,该问题具有
问题选项
,所有选项都具有空
问题
属性。所以你告诉JPA所有的QuestionChoices都没有任何问题(因为它是空的)。所以JPA保存了您告诉它保存的内容:没有任何父问题的问题选择


您需要正确初始化
QuestionChoice

question
属性,请求后您不使用构造函数公共问题(…)。您应该使用一种方法将选项与问题链接起来,反序列化程序将始终使用默认构造函数来构造对象。自定义构造函数对反序列化没有影响

您可以做的是:

1-保证服务/控制器层中的关联

@PostMapping("/question")
public Question createQuestion(@RequestBody Question question) {
    question.getQuestionChoices().forEach(choice -> choice.setQuestion(question));
    return questionRepository.save(question);
}
或2-保证setter方法中的关联:

public class Question {

    // omitted for brevity

    @OneToMany(mappedBy = "question", cascade = CascadeType.ALL)
    private List<QuestionChoice> questionChoices;

    public void setQuestionChoices(List<QuestionChoice> questionChoices) {
        if (questionChoices != null) {
            questionChoices.forEach(choice -> choice.setQuestion(this));
        }
        this.questionChoices = questionChoices;
    }
}
这会将您的问题选择和外键保存到数据库中,但会在发送响应时将
questionChoices.question
序列化为null,以防止无限递归

2-DTO的使用

您可以创建一个DTO,将它们序列化为响应对象,以准确返回您想要的内容

QuestionDTO.java

public class QuestionDTO {

    private int question_id;
    private String questionName;
    private String questionText;

    // notice that here you're using composition of DTOs (QuestionChoiceDTO instead of QuestionChoice)
    private List<QuestionChoiceDTO> questionChoices;

    // constructors..

    // getters and setters..
}
public class QuestionChoiceDTO {

    private int id;
    private String choice;

    // notice that you don't need to create the Question object here

    // constructors..

    // getters and setters..

}
然后在控制器中:

@PostMapping("/question")
public QuestionDTO createQuestion(@RequestBody Question question) {
    Question savedQuestion = questionRepository.save(question);

    List<QuestionChoiceDTO> questionChoices = new ArrayList<>();
    savedQuestion.getQuestionChoices().forEach(choice -> {
        questionChoices.add(new QuestionChoiceDTO(choice.getId(), choice.getChoice()));
    });

    QuestionDTO response = new QuestionDTO(savedQuestion.getQuestion_id(), savedQuestion.getQuestionName(), savedQuestion.getQuestionText(), questionChoices);

    return response;
}

@GetMapping("/question")
public List<QuestionDTO> getQuestions() {
    List<Question> questions = questionRepository.findAll();
    List<QuestionDTO> response = new ArrayList<>();

    questions.forEach(question -> {
        List<QuestionChoicesDTO> questionChoices = new ArrayList<>();
        question.getQuestionChoices().forEach(choice -> questionChoices.add(new QuestionChoiceDTO(choice.getId(), choice.getChoice()));

        responses.add(new QuestionDTO(savedQuestion.getQuestion_id(), savedQuestion.getQuestionName(), savedQuestion.getQuestionText(), questionChoices));
    });
}
@PostMapping(“/question”)
公共问题到createQuestion(@RequestBody-Question-Question){
Question savedQuestion=questionRepository.save(问题);
List questionChoices=新建ArrayList();
savedQuestion.getQuestionChoices().forEach(选项->{
添加(新的QuestionChoiceDTO(choice.getId(),choice.getChoice());
});
QuestionDTO response=新的QuestionDTO(savedQuestion.getQuestion_id(),savedQuestion.getQuestionName(),SavedQuestionText(),questionChoices);
返回响应;
}
@GetMapping(“/问题”)
公共列表问题(){
列出问题=questionRepository.findAll();
列表响应=新建ArrayList();
问题。forEach(问题->{
List questionChoices=新建ArrayList();
question.getQuestionChoices().forEach(choice->questionChoices.add(newQuestionChoicedTo(choice.getId(),choice.getChoice());
添加(新问题到(savedQuestion.getQuestion_id(),savedQuestion.getQuestionName(),savedQuestion.getQuestionText(),questionChoices));
});
}
我总是倾向于后者,因为对于大型项目,IMHO,DTO的使用可以是一个强大的工具,用于组织代码和简洁地使用请求/响应对象,而无需使用您的域