非空属性为持久值引用空值或瞬态值

Posted

技术标签:

【中文标题】非空属性为持久值引用空值或瞬态值【英文标题】:Not-null property references a null or transient value for persisted value 【发布时间】:2015-01-22 15:13:26 【问题描述】:

我正在尝试使用 JPA1 和 Hibernate 实现来持久化两个不同的实体。 代码如下:

父实体类

@Entity
@Table(name = "parent")
public class Parent implements Serializable 

    ...
    private Child child;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "child_id", nullable = "false")
    public Child getChild() 
        return child;
    

    public void setChild(Child child) 
        this.child = child;

子实体类

@Entity
@Table(name = "child")
public class Child implements Serializable 

    private Integer id;

    @Id
    @Column(name = "child_id")
    public Integer getId() 
        return id;
    

    public void setId(Integer id) 
        this.id = id;
    

测试用例

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:META-INF/application.xml")
@Transactional
public class ParentTest extends TestCase 

    @PersistenceContext
    private EntityManager entityManager;

    @Test
    public void testSave() 
        Child child = new Child();
        child.setId(1);

        Parent parent = new Parent();
        parent.setChild(child);

        entityManager.persist(parent.getChild());
        entityManager.persist(parent); // throws the exception
    

application.xml 上的实体管理器和事务

<tx:annotation-driven transaction-manager="transactionManager" /> 

<jee:jndi-lookup id="dataSource" jndi-name="java:/jdbc/myds" expected-type="javax.sql.DataSource" /> 

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="packagesToScan" value="com.mypackage" />
    <property name="dataSource" ref="dataSource" /> 
    <property name="jpaVendorAdapter"› 
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
    </property>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.dialect>org.hibernate.dialect.Oracle10gDialect</prop>
        </props>
    </property>
</bean> 

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" /> 
</bean>

当试图插入父对象时,hibernate 会抛出一个 PropertyValueException,说 child 是 null 或暂时的,即使 child 是在此操作之前创建并持久化的。奇怪的是,这仅在单元测试中失败,而在实际应用程序中,使用预先插入的孩子,这可以按预期工作。

PS: 我很清楚我可以用级联持续映射孩子,但这不是这里的想法。我只是想检查这两个是否独立工作。

【问题讨论】:

EntityManager 是什么类型?我找不到任何对 insert() 方法的引用。 我认为可能与Junit方法中的事务处理有关。只有当孩子已经在数据库中(提交后)时,才能插入父母。这可以解释为什么它适用于您的“真实”应用程序而不是在这里。你能分享你的 application.xml 文件吗? @PredragMaric 我的错。我正在使用持久化方法。固定在帖子上。 @santedicola 从 application.xml 添加了事务和实体管理器配置 你能告诉我们堆栈跟踪吗? 【参考方案1】:

这里的问题是您正在使用设置的值来持久化父表。 当它要持久化时,它需要已经持久化的子表 ID,因为它是一个外键,因此它是一个非空属性引用一个空值。

    @Test
    public void testSave() 
        Child child = new Child();
        child.setId(1);
        entityManager.persist(child); 

        Parent parent = new Parent();
        parent.setChild(child);

        entityManager.persist(parent); 
    

尝试这个先保存孩子,然后再保存父母。否则更改映射

【讨论】:

在提供的 sn-p 中,我也将 child 保留在 parent 之前,唯一的区别是您在保留 child 之后设置它。这与我所做的没有什么不同,因为我保留了对 child 的引用并且关系映射不是双向的。【参考方案2】:

您在 Parent 中使用 @ManyToOne 的第一件事告诉我,您对一个 Child 对象有多个父级,我想情况并非如此。

根据您的类结构,我可以理解您在父实体和子实体之间具有 OneToOne 映射。

关于异常,您是否有理由不使用 Parent 和 Child 之间的级联来自动处理映射以进行保存、更新和删除?如果您还没有考虑过,可以通过设置以下配置来尝试一下:Example Parent/Child - Hibernate

cascade = CascadeType.ALL

虽然我建议将 Child 和 Parent 之间的映射从 ManyToOne 更改为 OneToOne。

请告诉我。

【讨论】:

【参考方案3】:

除了让父级对子级有 FK 问题外,持久顺序是导致您问题的原因。

您的问题是related to flushing。仅仅因为您指示 Hibernate 持久化一个对象,并不意味着它会自动满足您的请求。持久请求进入操作队列,仅在刷新时实现。所以你的第二个坚持只是找到没有实际“坚持”孩子的父实体。

您可以按如下方式简单地修复您的代码:

    Child child = new Child();
    child.setId(1);

    entityManager.persist(parent.getChild());
    entityManager.flush();

    Parent parent = new Parent();
    parent.setChild(child);
    entityManager.persist(parent);
    entityManager.flush;

【讨论】:

在这种情况下,child 有一个手动设置的 id,因此不需要刷新到数据库即可使用并在上下文中持久化。尽管如此,我尝试在每次坚持后刷新实体管理器,但仍然没有运气。【参考方案4】:

试试这个:

@ManyToOne(fetch = FetchType.LAZY,optional=false)
@JoinColun(name = "child_id", nullable = "false")
public Child getChild() 
    return child;

【讨论】:

【参考方案5】:

某些字段设置为not-null="true",您必须在保存到数据库之前为其提供值。 对于父表和子表中的所有字段,将所有非空字段设置为适当的值

【讨论】:

...这正是我正在做的事情。【参考方案6】:

尝试为public Child getChild() 提供@ManyToOne(fetch = FetchType.LAZY,cascade=PERSIST, mappedBy="parent") 并仅保留父对象。两者都将被保留。

【讨论】:

【参考方案7】:

我遇到了类似的问题,在测试中出现“非空属性引用空值或瞬态值”的错误,但在正常运行应用程序时却没有。

原来我只需要在我的hibernate.properties 文件中添加一行:

hibernate.check_nullability=false

由于某种原因,此选项在运行应用程序时默认设置为 false,但在测试时设置为 true。将其添加到 hibernate.properties 不会影响正在运行的应用程序,但会修复测试。

【讨论】:

以上是关于非空属性为持久值引用空值或瞬态值的主要内容,如果未能解决你的问题,请参考以下文章

NHibernate - 非空属性引用空值或瞬态值

Grails - 非空属性引用空值或瞬态值

Spring 和 Hibernate 错误——非空属性引用空值或瞬态值:com.tharaka.model.Employee.designation

非空属性引用瞬态值 - 瞬态实例必须在当前操作之前保存

插入时 NHibernate 组件非空属性

一对多关系,如何匹配非空值