Skip to content

Commit 3a1f102

Browse files
committed
fix #951
when replace() needs to load an association, do it reactively
1 parent 522b897 commit 3a1f102

File tree

3 files changed

+307
-134
lines changed

3 files changed

+307
-134
lines changed
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
/* Hibernate, Relational Persistence for Idiomatic Java
2+
*
3+
* SPDX-License-Identifier: Apache-2.0
4+
* Copyright: Red Hat Inc. and Hibernate Authors
5+
*/
6+
package org.hibernate.reactive.engine.impl;
7+
8+
import org.hibernate.AssertionFailure;
9+
import org.hibernate.HibernateException;
10+
import org.hibernate.engine.spi.EntityKey;
11+
import org.hibernate.engine.spi.EntityUniqueKey;
12+
import org.hibernate.engine.spi.PersistenceContext;
13+
import org.hibernate.engine.spi.SessionFactoryImplementor;
14+
import org.hibernate.engine.spi.SessionImplementor;
15+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
16+
import org.hibernate.persister.entity.EntityPersister;
17+
import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister;
18+
import org.hibernate.reactive.session.ReactiveQueryExecutor;
19+
import org.hibernate.type.EntityType;
20+
import org.hibernate.type.OneToOneType;
21+
import org.hibernate.type.Type;
22+
import org.hibernate.type.TypeHelper;
23+
24+
import java.io.Serializable;
25+
import java.util.Map;
26+
import java.util.concurrent.CompletionStage;
27+
28+
import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY;
29+
import static org.hibernate.property.access.internal.PropertyAccessStrategyBackRefImpl.UNKNOWN;
30+
import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture;
31+
import static org.hibernate.reactive.util.impl.CompletionStages.loop;
32+
import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture;
33+
34+
public class EntityTypes {
35+
36+
/**
37+
* Replacement for {@link EntityType#resolve(Object, SharedSessionContractImplementor, Object, Boolean)}
38+
*/
39+
public static CompletionStage<Object> resolve(EntityType entityType, Object idOrUniqueKey, Object owner,
40+
SharedSessionContractImplementor session) {
41+
if ( idOrUniqueKey != null && !isNull( entityType, owner, session ) ) {
42+
if ( entityType.isReferenceToPrimaryKey() ) {
43+
return ((ReactiveQueryExecutor) session).reactiveInternalLoad(
44+
entityType.getAssociatedEntityName(),
45+
(Serializable) idOrUniqueKey,
46+
true,
47+
entityType.isNullable()
48+
);
49+
}
50+
else {
51+
return loadByUniqueKey( entityType, idOrUniqueKey, session );
52+
}
53+
}
54+
else {
55+
return null;
56+
}
57+
}
58+
59+
static boolean isNull(EntityType entityType, Object owner,
60+
SharedSessionContractImplementor session) {
61+
if ( entityType instanceof OneToOneType) {
62+
OneToOneType type = (OneToOneType) entityType;
63+
String propertyName = type.getPropertyName();
64+
if ( propertyName != null ) {
65+
EntityPersister ownerPersister =
66+
session.getFactory().getMetamodel()
67+
.entityPersister( entityType.getAssociatedEntityName() );
68+
Serializable id = session.getContextEntityIdentifier(owner);
69+
EntityKey entityKey = session.generateEntityKey(id, ownerPersister);
70+
return session.getPersistenceContextInternal().isPropertyNull( entityKey, propertyName);
71+
}
72+
else {
73+
return false;
74+
}
75+
}
76+
else {
77+
return false;
78+
}
79+
}
80+
81+
/**
82+
* Load an instance by a unique key that is not the primary key.
83+
*
84+
* @param entityType The {@link EntityType} of the association
85+
* @param key The unique key property value.
86+
* @param session The originating session.
87+
*
88+
* @return The loaded entity
89+
*
90+
* @throws HibernateException generally indicates problems performing the load.
91+
*/
92+
static CompletionStage<Object> loadByUniqueKey(
93+
EntityType entityType,
94+
Object key,
95+
SharedSessionContractImplementor session) throws HibernateException {
96+
SessionFactoryImplementor factory = session.getFactory();
97+
String entityName = entityType.getAssociatedEntityName();
98+
String uniqueKeyPropertyName = entityType.getRHSUniqueKeyPropertyName();
99+
100+
ReactiveEntityPersister persister =
101+
(ReactiveEntityPersister) factory.getMetamodel().entityPersister( entityName );
102+
103+
//TODO: implement 2nd level caching?! natural id caching ?! proxies?!
104+
105+
EntityUniqueKey euk = new EntityUniqueKey(
106+
entityName,
107+
uniqueKeyPropertyName,
108+
key,
109+
entityType.getIdentifierOrUniqueKeyType( factory ),
110+
persister.getEntityMode(),
111+
factory
112+
);
113+
114+
PersistenceContext persistenceContext = session.getPersistenceContextInternal();
115+
Object result = persistenceContext.getEntity( euk );
116+
if ( result != null ) {
117+
return completedFuture( persistenceContext.proxyFor( result ) );
118+
}
119+
else {
120+
return persister.reactiveLoadByUniqueKey( uniqueKeyPropertyName, key, session )
121+
.thenApply( loaded -> {
122+
// If the entity was not in the Persistence Context, but was found now,
123+
// add it to the Persistence Context
124+
if ( loaded != null ) {
125+
persistenceContext.addEntity(euk, loaded);
126+
}
127+
return loaded;
128+
} );
129+
130+
}
131+
}
132+
133+
/**
134+
* @see TypeHelper#replace(Object[], Object[], Type[], SharedSessionContractImplementor, Object, Map)
135+
*/
136+
public static CompletionStage<Object[]> replace(
137+
final Object[] original,
138+
final Object[] target,
139+
final Type[] types,
140+
final SessionImplementor session,
141+
final Object owner,
142+
final Map copyCache) {
143+
Object[] copied = new Object[original.length];
144+
for ( int i=0; i<types.length; i++ ) {
145+
if ( original[i] == UNFETCHED_PROPERTY || original[i] == UNKNOWN ) {
146+
copied[i] = target[i];
147+
}
148+
else {
149+
if ( !(types[i] instanceof EntityType) ) {
150+
copied[i] = types[i].replace(
151+
original[i],
152+
target[i] == UNFETCHED_PROPERTY ? null : target[i],
153+
session,
154+
owner,
155+
copyCache
156+
);
157+
}
158+
}
159+
}
160+
return loop(0, types.length,
161+
i -> original[i] != UNFETCHED_PROPERTY && original[i] != UNKNOWN
162+
&& types[i] instanceof EntityType,
163+
i -> replace(
164+
(EntityType) types[i],
165+
original[i],
166+
target[i] == UNFETCHED_PROPERTY ? null : target[i],
167+
session,
168+
owner,
169+
copyCache
170+
).thenAccept( copy -> copied[i] = copy )
171+
).thenApply(v -> copied );
172+
}
173+
174+
/**
175+
* @see EntityType#replace(Object, Object, SharedSessionContractImplementor, Object, Map)
176+
*/
177+
private static CompletionStage<Object> replace(
178+
EntityType entityType,
179+
Object original,
180+
Object target,
181+
SessionImplementor session,
182+
Object owner,
183+
Map copyCache) {
184+
if ( original == null ) {
185+
return nullFuture();
186+
}
187+
Object cached = copyCache.get( original );
188+
if ( cached != null ) {
189+
return completedFuture(cached);
190+
}
191+
else {
192+
if ( original == target ) {
193+
return completedFuture(target);
194+
}
195+
if ( session.getContextEntityIdentifier( original ) == null ) {
196+
return ForeignKeys.isTransient( entityType.getAssociatedEntityName(), original, false, session )
197+
.thenCompose( isTransient -> {
198+
if ( isTransient ) {
199+
// original is transient; it is possible that original is a "managed" entity that has
200+
// not been made persistent yet, so check if copyCache contains original as a "managed" value
201+
// that corresponds with some "merge" value.
202+
if ( copyCache.containsValue( original ) ) {
203+
return completedFuture(original);
204+
}
205+
else {
206+
// the transient entity is not "managed"; add the merge/managed pair to copyCache
207+
final Object copy = session.getEntityPersister( entityType.getAssociatedEntityName(), original )
208+
.instantiate( null, session );
209+
copyCache.put( original, copy );
210+
return completedFuture(copy);
211+
}
212+
}
213+
else {
214+
return resolveIdOrUniqueKey( entityType, original, session, owner, copyCache );
215+
}
216+
} );
217+
}
218+
else {
219+
return resolveIdOrUniqueKey( entityType, original, session, owner, copyCache );
220+
}
221+
}
222+
}
223+
224+
private static CompletionStage<Object> resolveIdOrUniqueKey(
225+
EntityType entityType,
226+
Object original,
227+
SessionImplementor session,
228+
Object owner,
229+
Map copyCache) {
230+
return getIdentifier( entityType, original, session )
231+
.thenCompose( id -> {
232+
if ( id == null ) {
233+
throw new AssertionFailure(
234+
"non-transient entity has a null id: "
235+
+ original.getClass().getName()
236+
);
237+
}
238+
Object idOrUniqueKey =
239+
entityType.getIdentifierOrUniqueKeyType( session.getFactory() )
240+
.replace( id, null, session, owner, copyCache);
241+
return resolve( entityType, idOrUniqueKey, owner, session );
242+
} );
243+
}
244+
245+
/**
246+
* see EntityType#getIdentifier(Object, SharedSessionContractImplementor)
247+
*/
248+
private static CompletionStage<Serializable> getIdentifier(EntityType entityType, Object value, SessionImplementor session) {
249+
if ( entityType.isReferenceToPrimaryKey()
250+
/*|| entityType.uniqueKeyPropertyName == null*/ //TODO: expose this in core
251+
) {
252+
return ForeignKeys.getEntityIdentifierIfNotUnsaved(
253+
entityType.getAssociatedEntityName(),
254+
value,
255+
session
256+
); //tolerates nulls
257+
}
258+
else if ( value == null ) {
259+
return nullFuture();
260+
}
261+
else {
262+
throw new UnsupportedOperationException("unique key properties not yet supported in merge()");
263+
//TODO: expose stuff in core
264+
// EntityPersister entityPersister = entityType.getAssociatedEntityPersister( session.getFactory() );
265+
// Object propertyValue = entityPersister.getPropertyValue( value, entityType.uniqueKeyPropertyName );
266+
// // We now have the value of the property-ref we reference. However,
267+
// // we need to dig a little deeper, as that property might also be
268+
// // an entity type, in which case we need to resolve its identifier
269+
// Type type = entityPersister.getPropertyType( entityType.uniqueKeyPropertyName );
270+
// if ( type.isEntityType() ) {
271+
// propertyValue = ( (EntityType) type ).getIdentifier( propertyValue, session );
272+
// }
273+
//
274+
// return propertyValue;
275+
}
276+
}
277+
278+
}

0 commit comments

Comments
 (0)