始终使用JPA @Id的原始对象包装器而不是原始类型?

aka*_*lou 11 java jpa parent-child spring-data-jpa

我发现使用原始类型作为JPA的对象@Id与Spring Data JPA一起使用的问题.我在父母方面与Cascade.ALL有父/子关系,而且孩子有PK,同时也是父母的FK.

class Parent {
    @Id
    private long id;

    @OneToOne(mappedBy = "parent", cascade = ALL)
    private Child child;
}

class Child {
    @Id
    @OneToOne
    private Parent parent;
}
Run Code Online (Sandbox Code Playgroud)

所以,当我跑:

...
Parent parent = new Parent();
Child child  = new Child(parent);
parent.setChild(child);  
em.persist(parent)
...
Run Code Online (Sandbox Code Playgroud)

一切正常.但我使用Spring Data JPA来持久保存实体,所以我改为运行:

parentRepository.save(parent); // instead of em.persist(parent);
Run Code Online (Sandbox Code Playgroud)

而且这个失败了以下例外:

Caused by: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: Parent
Run Code Online (Sandbox Code Playgroud)

问题是Spring Data JPA save()方法检查实体是否是新的,如果是new,则使用em.persist(),否则使用em.merge().

这里有趣的部分是Spring如何检查实体是否是新的:

getId(entity) == null;
Run Code Online (Sandbox Code Playgroud)

当然,这是错误的,因为我使用long作为@Id的类型,而long的默认值是0.当我将long改为Long时,一切都适用于Spring Data JPA.

因此,建议的做法总是使用对象包装器来处理基本类型(如Long而不是long)而不是基本类型.将此描述为推荐做法的任何第三方资源都将非常好.

小智 15

我会说是的,因为你看到的情况,建议使用对象类型而不是基元.没有办法区分实体是新的还是预先存在的原始标识符.我已经使用了多年的休眠,我总是使用对象作为标识符.