为了账号安全,请及时绑定邮箱和手机立即绑定

为什么 hibernate 创建空外键?

为什么 hibernate 创建空外键?

杨魅力 2023-11-01 21:47:47
我正在尝试创建一个包含两个实体的 Spring Boot 应用程序:Question 和 QuestionChoices。我正在使用双向一对多关系。当我尝试创建一个 Question 实体以及 QuestionChoices 列表时,QuestionChoice 中的外键为空。这是我的 QuestionChoice 实体:@Entity@Data@NoArgsConstructor@AllArgsConstructorpublic class QuestionChoice {    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    private int id;    private String choice;    @ManyToOne    @JoinColumn(name = "question_id")    private Question question;    public QuestionChoice(String choice, Question question) {        this.choice = choice;        this.question = question;    }    public QuestionChoice(String choice) {        this.choice = choice;    }}这是我的问题实体:@Entity@Data@NoArgsConstructor@AllArgsConstructorpublic class Question {    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    private int question_id;    private String questionName;    private String questionText;    @OneToMany(mappedBy = "question", cascade = CascadeType.ALL)    private List<QuestionChoice> questionChoices;    public Question(String questionName, String questionText, List<QuestionChoice> questionChoices) {        this.questionName = questionName;        this.questionText = questionText;        this.questionChoices = questionChoices;        this.questionChoices.forEach(x -> x.setQuestion(this));    }}我有一个 QuestionRepository 和 QuestionChoiceRepository:@Repositorypublic interface QuestionRepository extends JpaRepository<Question, Integer> {}@Repositorypublic interface QuestionChoiceRepository extends JpaRepository<QuestionChoice, Integer> {}这是我的控制器:@RestControllerpublic class Controller {    QuestionRepository questionRepository;    QuestionChoiceRepository questionChoiceRepository;    public Controller(QuestionRepository questionRepository,                      QuestionChoiceRepository questionChoiceRepository) {        this.questionRepository = questionRepository;        this.questionChoiceRepository = questionChoiceRepository;    }
查看完整描述

3 回答

?
MMMHUHU

TA贡献1834条经验 获得超8个赞

questionChoices您正在JSON 正文中发送一个字符串数组。List<Question>您的 JSON 映射器需要从该字符串数组中填充 a 。所以它需要将每一个都转化String为一个QuestionChoice对象。据推测,它是通过调用QuestionChoice以 aString作为参数的构造函数来实现的。

因此,您正在保存一个Question其中QuestionChoices所有属性都为空的question属性。因此,您告诉 JPA 所有 QuestionChoices 都没有任何问题(因为它为空)。因此,JPA 会保存您告诉它保存的内容:QuestionChoices,而没有任何父问题。

您需要正确question初始化QuestionChoice.


查看完整回答
反对 回复 2023-11-01
?
跃然一笑

TA贡献1826条经验 获得超6个赞

反序列化器将始终使用默认构造函数来构造对象。您的自定义构造函数对反序列化没有影响。


你能做的是:


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;

    }

}

更新


为了防止无限递归,只需从“questionChoice”中删除“question”属性以进行演示。


我可以想到两个选择:


1 - 将question内部设置为 nullquestionChoice


@PostMapping("/question")

public Question createQuestion(@RequestBody Question question) {

    Question savedQuestion = questionRepository.save(question);

    savedQuestion.getQuestionChoices().forEach(choice -> choice.setQuestion(null));


    return savedQuestion;

}


@GetMapping("/question")

public List<Question> getQuestions() {

    List<Question> questions questionRepository.findAll();

    questions.forEach(question -> {

        question.getQuestionChoices.forEach(choice -> choice.setQuestion(null));

    });


    return questions;

}

这会将您的问题选择和外键保存到数据库中,但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..

}

QuestionChoiceDTO.java


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

    });

}

我总是更喜欢后者,因为对于大型项目,恕我直言,使用 DTO 可以成为组织代码和简洁使用请求/响应对象而不使用域对象的强大工具。


查看完整回答
反对 回复 2023-11-01
?
BIG阳

TA贡献1859条经验 获得超6个赞

您在请求后不使用构造函数 public Question(...) 。你应该制定一种方法将选择与问题联系起来



查看完整回答
反对 回复 2023-11-01
  • 3 回答
  • 0 关注
  • 117 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信