【问题标题】:Does hibernate load two seprate copies of same instance if they are loaded twice from database?如果从数据库加载两次,休眠是否会加载同一实例的两个单独副本?
【发布时间】:2015-06-25 09:07:15
【问题描述】:

我知道关于延迟加载的 SO 有很多不同的问题,但我的问题有点不同。

假设我有一个实体 A,其中我有实体 B 的集合。类似地,在实体 B 中,我有 A 的集合。在这两种情况下,都使用了 lazy="true" 选项。

实体 A 的实例 aA 有 -->Set<B>===(此集合包含实体 B 的实例 bB)

实体 B 的实例 bB 有 -->Set<A>===(此集合包含实体 A 的实例 aA)

现在,如果我加载实体 A 的集合(即 Set<B> )。它现在已初始化,即完成 A 的 aA 实例,包括集合。我现在期望的是实体 B 的实例 bB 也已完全初始化,但不,它没有,当我引用具有实体 A 的实例 aA 的实体 B 的集合时,我得到延迟初始化异常。

如果从数据库加载两次,hibernate 是否会加载同一实例的两个单独副本?如果是这样,有没有办法同步会话内所有副本的更改?

希望我足够清楚,没有用混乱的信息把事情搞砸:)

【问题讨论】:

  • lazy=true,加载A会延迟加载B,但是在'B'中,只会加载标识符(ie b.id),但是我们需要使用@单独初始化lzy集合987654328@
  • 这很可能是对性能的影响,几乎没有附加值。
  • 你能解释一下你为什么会这样吗?如果我理解,您希望如果一件事被延迟初始化,那么整个会话中的其他所有内容都应该立即初始化?
  • 不,我不希望如果一件事被延迟初始化,那么整个会话中的其他所有内容都应该立即初始化。我期望的是在整个会话中会有任何特定实例的单个副本。所以我确实希望如果 A 的实例 aA 完全初始化,那么 aA 应该在其副本存在的任何地方自动初始化,在这种情况下,在实体 B 的集合中。
  • 是的,您的那部分期望是正确的。请看我的回答。

标签: java hibernate


【解决方案1】:

如果从数据库中加载两次,hibernate 是否会加载同一实例的两个单独副本?

不,它不会在同一个 Session 中加载同一个对象两次(为了我们的理智)。

我创建了一个简单的Spring Boot project 来查看这个。

EntityA#setOfB(B1,B2)
EntityB#setOfA(A1)

在通过ID将EntityA 1和EntityB加载到Session之后,我们强制A1.setOfB初始化。 在下面的日志中我们可以看到,尽管它必须查询获取两行(B1 和 B2)的集合,但它只会隐藏一个对象(B2),因为 B1 在会话缓存中找到。见测试 1 (mvn spring-boot:run -Drun.arguments="1")

DEBUG 6452 --- [           main] o.g.hiplay.app.HibernateService          : ########## Retrieving A1's set of B ##########
DEBUG 6452 --- [           main] org.hibernate.SQL                        : select setofb0_.id_a as id_a1_0_0_, setofb0_.id_b as id_b2_2_0_, entityb1_.id as id1_1_1_, entityb1_.description as descript2_1_1_ from relations setofb0_ inner join entityb entityb1_ on setofb0_.id_b=entityb1_.id where setofb0_.id_a=?
TRACE 6452 --- [           main] o.h.l.p.e.i.AbstractLoadPlanBasedLoader  : Bound [2] parameters total
DEBUG 6452 --- [           main] o.h.l.p.e.p.i.ResultSetProcessorImpl     : Preparing collection intializer : [oss.gabrielgiussi.hiplay.entities.EntityA.setOfB#1]
TRACE 6452 --- [           main] o.h.e.loading.internal.LoadContexts      : Constructing collection load context for result set [rs3: org.h2.result.LocalResult@5c99abd7 columns: 4 rows: 2 pos: -1]
TRACE 6452 --- [           main] o.h.e.l.internal.CollectionLoadContext   : Starting attempt to find loading collection [[oss.gabrielgiussi.hiplay.entities.EntityA.setOfB#1]]
TRACE 6452 --- [           main] o.h.e.l.internal.CollectionLoadContext   : Collection not yet initialized; initializing
TRACE 6452 --- [           main] o.h.l.p.e.p.i.ResultSetProcessorImpl     : Processing result set
DEBUG 6452 --- [           main] o.h.l.p.e.p.i.ResultSetProcessorImpl     : Starting ResultSet row #0
DEBUG 6452 --- [           main] e.p.i.CollectionReferenceInitializerImpl : Found row of collection: [oss.gabrielgiussi.hiplay.entities.EntityA.setOfB#1]
TRACE 6452 --- [           main] o.h.e.l.internal.CollectionLoadContext   : Starting attempt to find loading collection [[oss.gabrielgiussi.hiplay.entities.EntityA.setOfB#1]]
TRACE 6452 --- [           main] o.h.e.loading.internal.LoadContexts      : Attempting to locate loading collection entry [CollectionKey[oss.gabrielgiussi.hiplay.entities.EntityA.setOfB#1]] in any result-set context
TRACE 6452 --- [           main] o.h.e.loading.internal.LoadContexts      : Collection [CollectionKey[oss.gabrielgiussi.hiplay.entities.EntityA.setOfB#1]] located in load context
TRACE 6452 --- [           main] o.h.e.l.internal.CollectionLoadContext   : Found loading collection bound to current result set processing; reading row
TRACE 6452 --- [           main] o.h.e.internal.DefaultLoadEventListener  : Loading entity: [oss.gabrielgiussi.hiplay.entities.EntityB#1]
TRACE 6452 --- [           main] o.h.e.internal.DefaultLoadEventListener  : Attempting to resolve: [oss.gabrielgiussi.hiplay.entities.EntityB#1]
TRACE 6452 --- [           main] o.h.e.internal.DefaultLoadEventListener  : Resolved object in session cache: [oss.gabrielgiussi.hiplay.entities.EntityB#1]
DEBUG 6452 --- [           main] o.h.l.p.e.p.i.ResultSetProcessorImpl     : Starting ResultSet row #1
TRACE 6452 --- [           main] l.p.e.p.i.EntityReferenceInitializerImpl : hydrating entity state
TRACE 6452 --- [           main] l.p.e.p.i.EntityReferenceInitializerImpl : Initializing object from ResultSet: [oss.gabrielgiussi.hiplay.entities.EntityB#2]
TRACE 6452 --- [           main] o.h.p.entity.AbstractEntityPersister     : Hydrating entity: [oss.gabrielgiussi.hiplay.entities.EntityB#2]
DEBUG 6452 --- [           main] e.p.i.CollectionReferenceInitializerImpl : Found row of collection: [oss.gabrielgiussi.hiplay.entities.EntityA.setOfB#1]
TRACE 6452 --- [           main] o.h.e.l.internal.CollectionLoadContext   : Starting attempt to find loading collection [[oss.gabrielgiussi.hiplay.entities.EntityA.setOfB#1]]
TRACE 6452 --- [           main] o.h.e.loading.internal.LoadContexts      : Attempting to locate loading collection entry [CollectionKey[oss.gabrielgiussi.hiplay.entities.EntityA.setOfB#1]] in any result-set context
TRACE 6452 --- [           main] o.h.e.loading.internal.LoadContexts      : Collection [CollectionKey[oss.gabrielgiussi.hiplay.entities.EntityA.setOfB#1]] located in load context
TRACE 6452 --- [           main] o.h.e.l.internal.CollectionLoadContext   : Found loading collection bound to current result set processing; reading row
TRACE 6452 --- [           main] o.h.e.internal.DefaultLoadEventListener  : Loading entity: [oss.gabrielgiussi.hiplay.entities.EntityB#2]
TRACE 6452 --- [           main] o.h.e.internal.DefaultLoadEventListener  : Attempting to resolve: [oss.gabrielgiussi.hiplay.entities.EntityB#2]
TRACE 6452 --- [           main] o.h.e.internal.DefaultLoadEventListener  : Resolved object in session cache: [oss.gabrielgiussi.hiplay.entities.EntityB#2]
TRACE 6452 --- [           main] o.h.l.p.e.p.i.ResultSetProcessorImpl     : Done processing result set (2 rows)
TRACE 6452 --- [           main] o.h.l.p.e.p.internal.AbstractRowReader   : Total objects hydrated: 1
DEBUG 6452 --- [           main] o.h.engine.internal.TwoPhaseLoad         : Resolving associations for [oss.gabrielgiussi.hiplay.entities.EntityB#2]
TRACE 6452 --- [           main] o.h.e.loading.internal.LoadContexts      : Attempting to locate loading collection entry [CollectionKey[oss.gabrielgiussi.hiplay.entities.EntityB.setOfA#2]] in any result-set context
TRACE 6452 --- [           main] o.h.e.loading.internal.LoadContexts      : Collection [CollectionKey[oss.gabrielgiussi.hiplay.entities.EntityB.setOfA#2]] not located in load context
TRACE 6452 --- [           main] org.hibernate.type.CollectionType        : Created collection wrapper: [oss.gabrielgiussi.hiplay.entities.EntityB.setOfA#2]
DEBUG 6452 --- [           main] o.h.engine.internal.TwoPhaseLoad         : Done materializing entity [oss.gabrielgiussi.hiplay.entities.EntityB#2]
TRACE 6452 --- [           main] o.h.e.loading.internal.LoadContexts      : Attempting to locate loading collection entry [CollectionKey[oss.gabrielgiussi.hiplay.entities.EntityA.setOfB#1]] in any result-set context
TRACE 6452 --- [           main] o.h.e.loading.internal.LoadContexts      : Collection [CollectionKey[oss.gabrielgiussi.hiplay.entities.EntityA.setOfB#1]] located in load context
TRACE 6452 --- [           main] o.h.e.l.internal.CollectionLoadContext   : Removing collection load entry [org.hibernate.engine.loading.internal.LoadingCollectionEntry<rs=rs3: org.h2.result.LocalResult@5c99abd7 columns: 4 rows: 2 pos: 2, coll=[oss.gabrielgiussi.hiplay.entities.EntityA.setOfB#1]>@131c8e88]
DEBUG 6452 --- [           main] o.h.e.l.internal.CollectionLoadContext   : 1 collections were found in result set for role: oss.gabrielgiussi.hiplay.entities.EntityA.setOfB
TRACE 6452 --- [           main] o.h.e.l.internal.CollectionLoadContext   : Ending loading collection [org.hibernate.engine.loading.internal.LoadingCollectionEntry<rs=rs3: org.h2.result.LocalResult@5c99abd7 columns: 4 rows: 2 pos: 2, coll=[oss.gabrielgiussi.hiplay.entities.EntityA.setOfB#1]>@131c8e88]
DEBUG 6452 --- [           main] o.h.e.l.internal.CollectionLoadContext   : Collection fully initialized: [oss.gabrielgiussi.hiplay.entities.EntityA.setOfB#1]
DEBUG 6452 --- [           main] o.h.e.l.internal.CollectionLoadContext   : 1 collections initialized for role: oss.gabrielgiussi.hiplay.entities.EntityA.setOfB
TRACE 6452 --- [           main] o.h.e.i.StatefulPersistenceContext       : Initializing non-lazy collections
DEBUG 6452 --- [           main] o.g.hiplay.app.HibernateService          : ########## The object hasn't been loaded twice ##########

Hibernate 检查 Object to hydrate 是否已经在会话缓存中 asking to the StatefulPersistenceContext

我现在期望的是实体 B 的实例 bB 也已完全初始化,但不,它没有,当我引用具有实体 A 的实例 aA 的实体 B 的集合时,我得到延迟初始化异常。

真正未初始化的是bB的一组A。在做了类似的事情之后:

EntityA aA = session.get("A",a) aA.setOfB.size() // 强制初始化惰性集合。此时 bB 处于水合状态并被放入内存,但他对 A 的集合未初始化。

如果您尝试访问集合的元素,Hibernate 需要初始化集合(到目前为止,它所拥有的只是一个代理,可以在需要时从数据库中加载集合,例如请求一个元素或大小集合)。见测试 3 (mvn spring-boot:run -Drun.arguments="2")

// outside of the transaction
EntityB bB = aA.setOfB().get(0)
bB.setOfA().size() // LazyInitializationExample

如果您在事务内部,则集合将被初始化,并且与 aA 对应的行将再次从基中检索,但不会被水合。见测试 3 (mvn spring-boot:run -Drun.arguments="3")

【讨论】:

    【解决方案2】:

    流程是这样的:

    1. 您加载了aAaB。他们的收藏是惰性的和代理的。
    2. 您初始化aA 的集合。其中,它包含实例aB,它与您之前已加载的实例相同(相同的Java 对象)。 aB 实例中的集合仍未初始化;你没有访问它,所以 Hibernate 不会浪费时间来初始化它。目前,此会话中未知aAaB 集合的成员。
    3. 您访问(初始化)aB 的收藏。其中,它包含实例aA,它与您之前已加载的实例相同(相同的Java 对象)。

    如您所见,当前会话(持久性上下文)中没有重复项。可能有不同的代理/集合,但它们都委托/包含相同的实体实例。

    【讨论】:

      【解决方案3】:

      Hibernate 和 JPA 保证会话(实体管理器)中不会有一个对象的两个副本。 但是,您始终可以通过清除会话或直接从会话中分离实体 (session.evict()) 并再次加载它,在内存中获得一个对象的两个或多个副本。 所以你可以认为只有一个实体副本是持久的。 只有这个副本与数据库同步。 您可以通过调用session.merge() 再次重新附加分离的实体,但请注意,此方法始终返回实体的副本,可能与原始实体不同。 如果存在实体的持久副本,您只需获取它。 BWT,判断实体是否持久化,可以调用session.contains()

      Hibernate 中存在一些与继承相关的错误,有时会导致实体的两个或多个持久副本出现在会话中。 首先,您加载实体的正常副本。 其次,您加载另一个与第一个实体具有惰性 @ManyToOne 关系的实体。 第三,您遵循参考,并获得第一个实体的另一个副本! 所以要么不要在 Hibernate 中使用继承,要么不要使用 Hibernate。 考虑使用 EclipseLink,因为它没有这样的错误。

      【讨论】:

      • 考虑改写always return a new copy,因为它不这样做always,并且未指定实例是否为new
      • @SteveC 是的,你是对的。我的意思是合并返回的实例可能与原始实例不同,并且您应该始终使用新实例而不是旧实例,因为旧实例可能已分离。当然,Hibernate 可以返回相同的实例、创建一个新实例或返回现有的持久实体。
      【解决方案4】:

      正如 Alexey Andreev 所描述的,如果您使用 session.evict(),您可以拥有同一实体的两个副本。 我想提供反馈,因为它实际上是作为我的应用程序中的一个错误发生在我身上的:“InvalidDataAccessApiUsageException:正在合并同一实体的多个表示”。

      问题是,我有一个这样的模型(并非所有代码都显示为便于阅读):

      public class A{
      
          @OneToMany(mappedBy = "a")
          private List<B> b;
      }
      
      public class B {
      
          @ManyToOne(cascade = CascadeType.ALL)
          @JoinColumn(name = "a_fk")
          private A a;
      }
      

      一个A和多个B之间存在双向关系。

      如您所见,A 包含 B 的集合(具有默认的 fetchType LAZY)。所以当我加载一个实体A时,它不会为B的集合请求数据,除非我尝试获取集合的内容。一旦我得到 B 的集合,对于每个 B,相应的 A 都会被积极加载(默认 fetchType EAGER)。

      所以在正常情况下,如果我加载 A 实体的实例,然后访问其 B 集合的内容,则 B 的每个实例都将具有相同的 A 实例,这是原始实例,因为在 Hibernate缓存,实体 A 存在。

      但是,如果我在访问 B 集合之前将我的 A 实例从 Hibernate 会话 (Hibernate.evict(aInstance);) 中逐出,然后将其合并回来并尝试访问该集合,则 Hibernate 缓存已被清除,所以对于每个 B,都有一个新的数据库调用来查找对应的 A。 在这种情况下,会创建同一实体的新实例,从而导致我在尝试保存整个对象时遇到异常。

      我为这个问题提供了多种解决方案:

      1. 在我驱逐之前初始化集合 (Hibernate.initialize(a.getB();)
      2. 积极加载集合 (fetcType EAGER)(可能导致性能问题的不良做法)

      【讨论】:

        猜你喜欢
        • 2014-01-03
        • 2017-08-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多