【问题标题】:Exception while persisting a large object graph using JPA/Hibernate使用 JPA/Hibernate 持久化大型对象图时出现异常
【发布时间】:2013-01-19 16:23:32
【问题描述】:

我们正在创建一个由 JPA 支持的新 Web 应用程序来替换旧的 Web 应用程序。作为迁移的一部分,我们正在将旧应用程序的数据库转换为新的、更复杂的 JPA 管理的数据库。

所以我编写了一个“脚本”,将旧数据库转换为一组 JPA 实体,然后保存它们。它的工作原理是这样的:

  1. 根据领域模型的依赖关系创建转换顺序
  2. 对于每个实体
    1. 对旧版数据库执行数据库查询
    2. 将获得的每个表行的新对象存储在内存中的列表中
  3. 以与转换相同的顺序迭代生成的列表,并保留每个实体。

现在,前两个步骤运行良好。坚持,但是我得到一个例外。当一个实体与另一个实体有关系时会发生异常。例如,如果我们的一个实体是Book,另一个是Chapter,定义了与Book@ManyToOne(optional=false) 关系。在持久化章节时,它会抛出异常java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation: models.Chapter.book -> models.Book

当然,这表明这本书的状态有问题:它似乎要么没有设置,要么还没有被持久化。但是,我可以验证BookChapter 的转换中设置正确,并且我还可以验证Book 类型的所有实体都被EntityManager 持久化之前 Chapter 类型的实体被持久化。显然,我的 JPA 提供程序没有按预期运行,并且由于某种原因没有真正持久化我的 Book 对象。

什么解决方案可以让我保存已转换到数据库的整个对象图?我使用 Hibernate 作为我的 JPA 提供程序,我还使用 Spring 3.1 来注入依赖项和EntityManagers。

编辑 1:一些附加信息:我再次验证了 entityManager.persist() 在每个书籍对象上被调用之前 entityManager.persist() 在章节上被调用。但是,book 对象的 id 仍然为 null,这意味着它没有正确持久化。尽管没有使用事务,但数据库仍然是空的。

编辑 2:因为我认为从上面的文字中看不清楚:书和章节的故事只是一个例子。它发生在引用另一个实体的 any 实体上。这看起来好像我没有正确使用 JPA/Hibernate,而不是没有正确设置我的实体的值。

编辑 3:核心问题似乎是 尽管 正确地坚持 Book,拥有所有正确的注释,book.getId() 仍然为空。基本上,Hibernate 不会在持久化实体后为其设置 id,这会导致我以后需要使用这些实体时出现问题。

【问题讨论】:

  • 如何保存实体?该流程涉及多少实体经理?
  • 我通过使用 Spring 的 @PersistenceContext 获得的 EntityManager 调用 entityManager.persist(entity) 来保存我的实体。这是一个相当复杂的领域模型,包含 60 多种不同类型的实体,它们之间存在各种关系。

标签: java hibernate jpa spring-orm


【解决方案1】:

我曾经与 hibernate 自己的此类错误作斗争。原来是对象图中的一个圆圈和导致问题的级联设置的组合。

已经有一段时间了,所以捕鸟可能不是 100% 准确,但也许它足以跟踪您的问题:

  1. Hibernate 想要插入 章节。意识到它需要先插入
  2. 想要插入。意识到它需要先插入另一个实体(例如 publisher
  3. 插入发布者并执行发布者定义的级联(例如作者
  4. 作者有例如参考他的lastestBook。因为 hibernate 在内部已经将该书标记为已处理(在第 2 步中),所以您不会收到异常说明 author.book 引用了一个瞬态实例。

要确定这是否是您的问题,您可以启用完整的休眠调试并遵循休眠通过您的对象图所采用的路径。

【讨论】:

  • 嗯,就像我的示例一样,这也发生在 没有双向关系或任何层次结构中更高级别的对象级联的方式。不过,打开完全调试是一个有用的提示,我会试一试。
  • 我也有同样的问题。我该如何解决?
【解决方案2】:

感谢我与 user1888440 的讨论,我找到了答案。

这个答案的解决方案是 Spring @Transactional 注释在我的应用程序中不起作用。这意味着 Hibernate 所做的一切都不会发生在事务的上下文中。这意味着 Hibernate 在持久化后不会设置 id,这意味着所有转换都会中断。

@Transactional 不起作用的原因可能是因为我没有提到的一个事实:这个脚本是 Play 2.0(实际上是 2.1)应用程序的一部分,因此是使用 SBT 构建的。 SBT 不使用普通的 Java 设置来构建应用程序,而是使用 Scala 编译器来编译 Java。我的猜测是 Scala 编译不能很好地与 Spring 需要使 @Transactional 工作的 AspectJ 一起工作。

相反,我在programmatically defined Spring transaction(第 11.6 节)中执行了与此转换相关的所有数据库工作。现在一切正常。

【讨论】:

    【解决方案3】:

    检查 hbm 文件中主键/对象 ID 的未保存值。如果您通过 hibernate 框架自动创建 ID,并且您将 ID 设置在某个地方,它会抛出此错误。默认未保存值是 0 ,因此,如果您将 ID 设置为 0,您会看到此错误。

    【讨论】:

      【解决方案4】:

      听起来您忘记在持久化之前为每个章节分配一本书。即使您已将 Book 持久化,也需要将其分配给 Chapter 实例的 #book 属性,然后才能持久化 Chapter。这是因为您已将关系指定为非可选的。 #book 永远不能为空。

      【讨论】:

      • 我已经提到我验证了该章有正确分配的书。 “但是,我可以在 Chapter 的转换中验证 Book 是否正确设置,并且我还可以验证 Book 类型的所有实体在 Chapter 类型的实体被持久化之前都被 EntityManager 持久化。”
      • 您能否发布 Book 和 Chapter 实体以及错误的堆栈跟踪? Book 是否有任何与 Chapter 或只是 Chapter to Book 的关系注释?
      • 您是说当您使用调试器单步执行代码时,#book 不为空?如果是这样,导致休眠代码抛出此异常的条件是什么(使用调试器单步执行)?
      • 导致 Hibernate 抛出异常的条件不是#book 为空,而是#book.id 为空,尽管我坚持了那本书之前。
      • 只有章节参考书。请注意,我实际上没有文字 Chapter 和 Book 实体,我只是将它们用作示例。但是,我所有的实体都只是在字段上使用@Entity 和其他注释进行注释的常规 Java bean。
      猜你喜欢
      • 1970-01-01
      • 2012-10-24
      • 2017-04-09
      • 2017-08-18
      • 1970-01-01
      • 2013-11-12
      • 1970-01-01
      • 2012-01-17
      • 2014-11-13
      相关资源
      最近更新 更多