我知道关于延迟负载的SO有很多不同的问题,但我的一个有点不同。
假设我有一个实体A,其中我有实体B的集合。同样,在实体B中,我有A的集合。在这两种情况下,使用lazy =“true”选项。
实体A 的实例aA有 - &gt; Set<B>
===(此集包含实体B的实例bB)
实体B 的实例bB有 - &gt; Set<A>
===(此集合包含实体A的实例aA)
现在,如果我加载实体A的集合(即Set<B>
)。它现在已初始化,即完成A的aA实例,包括集合。我现在期望的是实体B的实例bB也被完全初始化但是没有,当我引用具有实体A的实例aA的实体B的集合时,它没有和我得到延迟初始化异常。
如果从数据库加载两次,那么hibernate会加载同一个实例的两个seprate副本吗?如果是这样,有没有办法同步会话中所有副本的更改?
希望我足够清楚并且没有弄乱杂乱的信息:)
答案 0 :(得分:2)
如果从数据库中加载两次,那么hibernate会加载同一个实例的两个seprate副本吗?
不,它不会在同一个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”)
答案 1 :(得分:1)
流程是这样的:
aA
和aB
。他们的藏品是懒惰和代理。aA
的收藏集。其中,它包含实例aB
,它与之前已加载的实例(同一Java对象)相同。 aB
实例中的集合仍未初始化;你没有访问它,所以Hibernate没有浪费时间来初始化它。目前,在此会话中未知aA
是aB
集合的成员。aB
的收藏集。其中,它包含实例aA
,它与之前已加载的实例(同一Java对象)相同。如您所见,当前会话(持久性上下文)中没有重复项。可能有不同的代理/集合,但它们都委托/包含相同的实体实例。
答案 2 :(得分:0)
Hibernate以及JPA保证会话中不会有一个对象的两个副本(实体管理器)。
但是,通过清除会话或直接从会话(session.evict()
)中分离实体并再次加载它,您始终可以在内存中获得一个对象的两个或更多副本。
所以你可以认为只有一个实体副本是持久的。
只有此副本与数据库同步。
您可以通过调用session.merge()
再次重新附加分离的实体,但请注意,此方法始终返回实体的副本,可能与原始实体不同。
如果存在实体的持久副本,则只需获取它。
BWT,要确定实体是否持久,可以调用session.contains()
。
Hibernate中有几个错误有时会导致实体的两个或多个持久性副本出现在会话中,与继承有关。 首先,加载实体的正常副本。 其次,您加载另一个与第一个实体具有惰性@ManyToOne关系的实体。 第三,您遵循参考,并获得第一个实体的另一个副本! 所以要么不在Hibernate中使用继承,要么不使用Hibernate。 考虑使用EclipseLink,因为它没有这样的错误。
答案 3 :(得分:0)
如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集合之前从Hibernate会话中撤出A的实例(Hibernate.evict(aInstance);),然后将其合并回并尝试访问该集合,则Hibernate缓存已清除,因此对于每个B,都有一个新的数据库调用来查找对应的A。 在这种情况下,将创建同一实体的新实例,从而导致我在尝试保存整体对象时遇到异常。
我为这个问题提供了多种解决方案: