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 实体来确保它存在于数据库中,以便地址对象可以与现有客户相关联。
我是 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 实体来确保它存在于数据库中,以便地址对象可以与现有客户相关联。