Hibernate 不允许保存引用未保存的瞬态实例的对象的原因是什么?

what is the reason that Hibernate does not allow to save the object which references an unsaved transient instance?

我是 Hibernate 的新手,我正在尝试了解 JPA 和 Hibernate。

我想知道Hibernate不允许保存引用未保存的瞬态实例的对象的原因是什么?我想知道为什么这是一个问题?

我问了一些人,他们中的一些人这样回答我:

How could we possibly map the customer to the address, if there is no adress record in the DB yet?

you are assigning particular Address to Customer. But Address does not have any ID

但老实说我听不懂。

(我知道会抛出异常,解决方法是级联,但我想在数据库里面找到问题的原因)

现在,假设我们拥有所有这些代码:

(我的示例使用双向一对一关系)

public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;
    private String lastName;


    @OneToOne(mappedBy = "customer")
    private Address address;
}


@Entity
public class Address { 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String street;
    private String zipCode;

    @OneToOne
    private Customer customer;
}

   public static void main(String[] args) {

        EntityManager entityManager = emf.createEntityManager();
        entityManager.getTransaction().begin(); // Begin Transaction

        Customer c1 = new Customer("Mi", "S");
        Address addrss1 = new Address("5412 S 5th", "212524");

        c1.setAddress(addrss1);
        addrss1.setCustomer(c1);


        entityManager.persist(c1);

        entityManager.getTransaction().commit(); // Commit 
        entityManager.close();
    }

让我们假设没有抛出异常,并且 java 和休眠允许我们 运行 我们的代码,这是我们的 客户 table.

   id    firstName    lastName    
---------------------------------
    1        Mi           S

这是我们的地址table:

   id    street    zipCode    customer_id
---------------------------------------------
    -      -         -            -

现在,问题是什么?这些双向一对一关系中的一切似乎都是正确的。 那有什么问题呢?


PS: 如果可能的话,请解释并给我看代码。 我可以用代码更好地理解。谢谢。

例如,我想看看如果允许我们保存引用未保存的瞬态实例的对象,我们的代码和 table 中会遇到什么问题(对于例如,当我们想要检索客户等时我们有什么问题吗)

客户是新客户,从持久调用中可以清楚地看出您想要插入它,但不清楚您希望对任何客户的参考发生什么。为清楚起见,您定义希望 JPA 提供程序 (Hibernate) 在 any/all 情况下的映射中执行的操作 - 这就是级联操作所指的内容。在这种情况下,JPA 将查看 customer.address OneToOne 映射,但未发现任何定义;地址未在此 EntityManager 上下文中管理,因此它不知道如何处理此关系,因此它通过抛出错误来表示您犯了一个错误。

如果它让它通过,则您的 Customer 实例引用了不存在的内容,并且其状态与数据库中的内容不匹配。您传递给 persist 的内容应该是您在读取时返回的内容,因此它应该反映数据库中的状态。

问题不直接与您的持久调用有关,因为规范确实允许提供商忽略对 detached/new 没有级联设置的实例的引用 - 发生的事情只是未定义。在这种情况下,您出错的地方是 flush/commit,这是持久性单元同步到数据库的时候(JPA 3.0 的第 3.2.4 节),这需要提供者通过托管实体,然后确定任何更改。添加一个新的地址预持久化将导致相同的问题,就好像你这样做一样 post 持久化,并且要求提供者在发现新的或删除的实体并回滚事务时抛出 IllegalStateException。

为什么这是一个问题:JPA 在实体标识方面非常重要,因为这可以在多个级别的缓存中缓存这些实体,并且该实体可能会按原样进入这些缓存。它必须知道如何处理对不存在的实体的引用,并且规范决定需要一个例外。即使对于您的应用程序,这也是并且应该是一个问题,因为 EntityManager 上下文是一个工作单元,并且该工作单元内的状态基于错误的东西。说完这件事后,您的客户并没有真正的地址,但您的应用程序业务逻辑认为它分配了一个地址,但之后就不会再存在了。

您已经知道解决方案:

  • 通过在同一个 EntityManager 上下文中直接调用 persist 来更正客户以获得有效的托管地址。
  • 将映射上的级联选项设置为级联持续为您解决
  • 不要在同一操作中为新客户设置地址。

因为您的地址实体将客户的主键作为外键,(因为 mappedby 在客户实体中),地址引用的客户没有 ID,这告诉休眠该实体从未持久化数据库(字面意思是瞬态),hibernate 需要一个 persisted/managed 实体来确保它存在于数据库中,以便地址对象可以与现有客户相关联。