级联保存具有外键的实体对象作为复合主键的一部分

Posted

技术标签:

【中文标题】级联保存具有外键的实体对象作为复合主键的一部分【英文标题】:Cascading save Entity objects with Foreign Key as a part of composite Primary Key 【发布时间】:2013-02-21 17:34:04 【问题描述】:

我想将我的 QuestionCompletion 类的对象与所有子元素一起保留。其中一个孩子有一个复合主键。作为这个主键的一部分,我还有另一个实体的外键。结果我收到了这个错误:

Exception caught during request processing: javax.ejb.EJBTransactionRolledbackException:
could not set a field value by reflection setter of com.example.model.domain.QuestionCompletionAnswerPK.questionCompletionId
javax.ejb.EJBTransactionRolledbackException: could not set a field value by reflection
setter of com.example.model.domain.QuestionCompletionAnswerPK.questionCompletionId

而最后一个“由”当然是NullPointerException

Caused by: java.lang.NullPointerException

这是我的代码的一部分。最后一行导致错误。

QuestionCompletion questionCompletion = new QuestionCompletion();
List<QuestionCompletionAnswer> answers = new ArrayList<QuestionCompletionAnswer>();
for (;;)  // loop isn't important; it's loop for answers
    ExtendedQuestion extendedQuestion = new ExtendedQuestion();
    extendedQuestion.setId(extendedQuestionId); //extendedQuestionId is known to me in that place
    for (;;)  // loop isn't important; it's loop for question answers
        //questionCompletion and extendedQuestion are popualted here
        QuestionCompletionAnswer questionCompletionAnswer = new QuestionCompletionAnswer();
        questionCompletionAnswer.setQuestionCompletion(questionCompletion); 
        questionCompletionAnswer.setExtendedQuestion(extendedQuestion); 
        answers.add(questionCompletionAnswer);
    

questionCompletion.setAnswers(answers);
questionCompletionService.saveOrMerge(questionCompletion);

这是我希望保留其所有子元素的基本实体类。我意识到List&lt;QuestionCompletionAnswer&gt; 会导致问题。我已经使用cascade = CascadeType.ALL 来允许持久化子元素。

@javax.persistence.Entity
@org.hibernate.annotations.Entity(dynamicUpdate = true)
@Table(name = "question_completion")
public class QuestionCompletion implements Serializable 

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "question_completion_gen")
    @SequenceGenerator(name = "question_completion_gen", sequenceName = "question_completion_id_seq")
    @Column(name = "question_completion_id")
    private Long id;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "extended_question_id")
    protected List<QuestionCompletionAnswer> answers;

这是我的班级 - QuestionCompletionAnswer 班级的主键。

@Embeddable
public class QuestionCompletionAnswerPK implements Serializable 

    @Column(name = "question_completion_id")
    protected Long questionCompletionId;

    @Column(name = "extended_question_id")
    protected Long extendedQuestionId;

这是使用我的EmbeddedId 的课程。属性questionCompletionIdquestionCompletionId 是其他一些实体的外键,所以我还用@MapsId 注释将这些实体的整个对象放在了下面。

@javax.persistence.Entity
@org.hibernate.annotations.Entity(dynamicUpdate = true)
@Table(name = "extended_question_answer")
public class QuestionCompletionAnswer implements Serializable 

    @EmbeddedId
    private QuestionCompletionAnswerPK id;

    @ManyToOne
    @MapsId(value = "questionCompletionId")
    @JoinColumn(name = "question_completion_id", nullable = false, insertable = false, updatable = false)
    protected QuestionCompletion questionCompletion;

    @ManyToOne
    @MapsId(value = "extendedQuestionId")
    @JoinColumn(name = "extended_question_id", nullable = false, insertable = false, updatable = false)
    protected ExtendedQuestion extendedQuestion;

您能告诉我我的注释是否正确吗?也许我混合了几种方法。或者在这种情况下,我不能将我的基本对象及其所有子元素持久化。

编辑

现在我的映射看起来像:

@javax.persistence.Entity
@org.hibernate.annotations.Entity(dynamicUpdate = true)
@Table(name = "question_completion")
public class QuestionCompletion implements Serializable 

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "question_completion_gen")
    @SequenceGenerator(name = "question_completion_gen", sequenceName = "question_completion_id_seq")
    @Column(name = "question_completion_id")
    private Long id;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "questionCompletion")
    protected List<QuestionCompletionAnswer> answers;

QuestionCompletionAnswerPK 类的代码相同。

@javax.persistence.Entity
@org.hibernate.annotations.Entity(dynamicUpdate = true)
@Table(name = "extended_question_answer")
public class QuestionCompletionAnswer implements Serializable 

    @EmbeddedId
    private QuestionCompletionAnswerPK id;

    @ManyToOne(cascade = CascadeType.PERSIST)
    @MapsId(value = "questionCompletionId")
    @JoinColumn(name = "question_completion_id", nullable = false)
    protected QuestionCompletion questionCompletion;

    @ManyToOne
    @MapsId(value = "extendedQuestionId")
    @JoinColumn(name = "extended_question_id", nullable = false)
    protected ExtendedQuestion extendedQuestion;

使用该映射,我仍然遇到相同的异常。

编辑 #2 但是,当我以这种方式更改 QuestionCompletionAnswer 类时:

@javax.persistence.Entity
@org.hibernate.annotations.Entity(dynamicUpdate = true)
@Table(name = "extended_question_answer")
public class QuestionCompletionAnswer implements Serializable 

    @EmbeddedId
    private QuestionCompletionAnswerPK id;

    @ManyToOne(cascade = CascadeType.PERSIST)
    @JoinColumn(name = "question_completion_id", nullable = false, insertable = false, updatable = false)
    protected QuestionCompletion questionCompletion;

    @ManyToOne
    @JoinColumn(name = "extended_question_id", nullable = false, insertable = false, updatable = false)
    protected ExtendedQuestion extendedQuestion;

我得到了那个例外:

Caused by: org.hibernate.id.IdentifierGenerationException: null id generated 
for:class com.example.model.domain.QuestionCompletionAnswer

【问题讨论】:

不要将@joincolumn 添加到onetomany 映射中。而是通过扩展问题映射将其标记为映射。同时删除 manytoone joincolumn def 上的 updatable=false insertable=false,因为 mapid 注释所暗示的。 @Chris 感谢您的评论。我已经改变了你描述的那几行,但我仍然遇到同样的错误。 您应该更改上面的代码以显示您现在拥有的内容,因为上面的模型是错误的。还要显示 NPE 堆栈以查看它的来源,并确保您正在持久化/合并引用的问题,以便分配它们的 ID。您可能需要在 questionCompletion 映射上设置 cascade persist 我的错误恰好出现在保存 questionCompletion - Serializable id = getSession().save(questionCompletion); 的那一刻,所以事实上我没有得到这个实体的 id。 @Chris 我已经更新了我的问题。我仍然遇到同样的异常。 【参考方案1】:

编辑 1 和 2 仍然不正确。您需要在关系上指定 mapid,或者您必须自己在嵌入的 id 中设置字段值。并且当您使用 mapid 时,您不应该将连接列标记为 insertable=false,否则 jpa 无法为您插入值。我看到的最后一个问题是新问题没有持久化,因此它没有分配答案可以引用的 id - 您需要在 for 循环中显式保留新问题或将答案中的关系标记为级联持续存在。

【讨论】:

我已经实现了您所描述的更改。但是保存QuestionCompletionAnswer 仍然存在问题——同样的例外。对我来说cascade = CascadeType.ALL 在这种情况下不起作用。我将明确保存它。但是,如果没有 QuestionCompletionAnswer 对象列表,QuestionCompletion 将毫无问题地保存。 错误表明 NPE 正在尝试在 QuestionCompletionAnswerPK 中设置字段。您的 QuestionCompletionAnswer 甚至有分配给 id 的 QuestionCompletionAnswerPK 吗?如果在创建新的 QuestionCompletionAnswer 时分配新的 QuestionCompletionAnswerPK 没有帮助,则可能发布完整的 NPE 堆栈跟踪。【参考方案2】:

为什么要混合 jpa 和 hibernate 注释?仅 jpa 注释就足够了。 尝试使用以下映射(我删除了 QuestionCompletionAnswerPK 类):

QuestionCompletion.java

@Entity
@Table(name = "question_completion")
public class QuestionCompletion 

    @Id
    @SequenceGenerator(name = "question_completion_gen", sequenceName = "question_completion_id_seq")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "question_completion_gen")
    @Column(name = "question_completion_id")
    private Long id;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "questionCompletion")
    protected List<QuestionCompletionAnswer> answers;

QuestionCompletionAnswer.java

@Entity
@Table(name = "extended_question_answer")
public class QuestionCompletionAnswer implements Serializable 

    @Id
    @ManyToOne
    @JoinColumn(name = "question_completion_fk")
    private QuestionCompletion questionCompletion;

    @Id
    @ManyToOne
    @JoinColumn(name = "extended_question_fk")
    private ExtendedQuestion extendedQuestion;

通过这个映射,我运行了以下测试用例并且它工作了:

QuestionCompletion questionCompletion = new QuestionCompletion();
ExtendedQuestion extendedQuestion = new ExtendedQuestion();
QuestionCompletionAnswer answer = new QuestionCompletionAnswer();
answer.setQuestionCompletion(questionCompletion);
answer.setExtendedQuestion(extendedQuestion);
questionCompletion.setAnswers(Collections.singletonList(answer));
...
session.save(extendedQuestion);
session.save(questionCompletion);

【讨论】:

以上是关于级联保存具有外键的实体对象作为复合主键的一部分的主要内容,如果未能解决你的问题,请参考以下文章

Spring用外键保存复合主键

mysql外键仅引用复合主键的一部分

如何在实体框架中为复合主键的特定列创建外键

如何为具有复合主键的表构建外键?

主键和外键的复合键不能建立关系

Hibernate 对具有复合键的子实体执行错误的插入顺序