Skip to content

Commit 793a30b

Browse files
committed
[#1770] Fix Better error message when transparently loading lazy fields
1 parent f2e7ec0 commit 793a30b

14 files changed

+586
-3
lines changed

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ org-hibernate-orm-hibernate-core = { group = "org.hibernate.orm", name = "hibern
3838
org-hibernate-orm-hibernate-jcache = { group = "org.hibernate.orm", name = "hibernate-jcache", version.ref = "hibernateOrmVersion" }
3939
org-hibernate-orm-hibernate-jpamodelgen = { group = "org.hibernate.orm", name = "hibernate-jpamodelgen", version.ref = "hibernateOrmVersion" }
4040
org-hibernate-validator-hibernate-validator = { group = "org.hibernate.validator", name = "hibernate-validator", version = "8.0.3.Final" }
41+
org-hibernate-models = { group = "org.hibernate.models", name = "hibernate-models", version = "1.0.1" }
4142
org-jboss-logging-jboss-logging = { group = "org.jboss.logging", name = "jboss-logging", version.ref = "jbossLoggingVersion" }
4243
org-jboss-logging-jboss-logging-annotations = { group = "org.jboss.logging", name = "jboss-logging-annotations", version.ref = "jbossLoggingAnnotationVersion" }
4344
org-jboss-logging-jboss-logging-processor = { group = "org.jboss.logging", name = "jboss-logging-processor", version.ref = "jbossLoggingAnnotationVersion" }

hibernate-reactive-core/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ apply from: publishScript
1515
dependencies {
1616

1717
api(libs.org.hibernate.orm.hibernate.core)
18+
compileOnly(libs.org.hibernate.models)
1819

1920
api(libs.io.smallrye.reactive.mutiny)
2021

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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.boot.spi;
7+
8+
import org.hibernate.boot.CacheRegionDefinition;
9+
import org.hibernate.boot.archive.scan.spi.ScanEnvironment;
10+
import org.hibernate.boot.archive.scan.spi.ScanOptions;
11+
import org.hibernate.boot.archive.spi.ArchiveDescriptorFactory;
12+
import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
13+
import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject;
14+
import org.hibernate.boot.registry.StandardServiceRegistry;
15+
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
16+
import org.hibernate.boot.spi.BootstrapContext;
17+
import org.hibernate.boot.spi.ClassLoaderAccess;
18+
import org.hibernate.boot.spi.ClassmateContext;
19+
import org.hibernate.boot.spi.MetadataBuildingOptions;
20+
import org.hibernate.engine.config.spi.ConfigurationService;
21+
import org.hibernate.jpa.spi.MutableJpaCompliance;
22+
import org.hibernate.metamodel.spi.ManagedTypeRepresentationResolver;
23+
import org.hibernate.models.spi.ModelsContext;
24+
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
25+
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
26+
import org.hibernate.reactive.metamodel.spi.ReactiveManagedTypeRepresentationResolver;
27+
import org.hibernate.resource.beans.spi.BeanInstanceProducer;
28+
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
29+
import org.hibernate.type.BasicType;
30+
import org.hibernate.type.spi.TypeConfiguration;
31+
32+
import java.util.Collection;
33+
import java.util.List;
34+
import java.util.Map;
35+
36+
/**
37+
* Adapt {@link BootstrapContext#getRepresentationStrategySelector()} to return a {@link ReactiveManagedTypeRepresentationResolver}
38+
*/
39+
public class ReactiveBootstrapContextAdapter implements BootstrapContext {
40+
41+
private final BootstrapContext delegate;
42+
43+
public ReactiveBootstrapContextAdapter(BootstrapContext bootstrapContext) {
44+
this.delegate = bootstrapContext;
45+
}
46+
47+
@Override
48+
public StandardServiceRegistry getServiceRegistry() {
49+
return delegate.getServiceRegistry();
50+
}
51+
52+
@Override
53+
public MutableJpaCompliance getJpaCompliance() {
54+
return delegate.getJpaCompliance();
55+
}
56+
57+
@Override
58+
public TypeConfiguration getTypeConfiguration() {
59+
return delegate.getTypeConfiguration();
60+
}
61+
62+
@Override
63+
public ModelsContext getModelsContext() {
64+
return delegate.getModelsContext();
65+
}
66+
67+
@Override
68+
public SqmFunctionRegistry getFunctionRegistry() {
69+
return delegate.getFunctionRegistry();
70+
}
71+
72+
@Override
73+
public BeanInstanceProducer getCustomTypeProducer() {
74+
return delegate.getCustomTypeProducer();
75+
}
76+
77+
@Override
78+
public MetadataBuildingOptions getMetadataBuildingOptions() {
79+
return delegate.getMetadataBuildingOptions();
80+
}
81+
82+
@Override
83+
public ClassLoaderService getClassLoaderService() {
84+
return delegate.getClassLoaderService();
85+
}
86+
87+
@Override
88+
public ManagedBeanRegistry getManagedBeanRegistry() {
89+
return delegate.getManagedBeanRegistry();
90+
}
91+
92+
@Override
93+
public ConfigurationService getConfigurationService() {
94+
return delegate.getConfigurationService();
95+
}
96+
97+
@Override
98+
public boolean isJpaBootstrap() {
99+
return delegate.isJpaBootstrap();
100+
}
101+
102+
@Override
103+
public void markAsJpaBootstrap() {
104+
delegate.markAsJpaBootstrap();
105+
}
106+
107+
@Override
108+
public ClassLoader getJpaTempClassLoader() {
109+
return delegate.getJpaTempClassLoader();
110+
}
111+
112+
@Override
113+
public ClassLoaderAccess getClassLoaderAccess() {
114+
return delegate.getClassLoaderAccess();
115+
}
116+
117+
@Override
118+
public ClassmateContext getClassmateContext() {
119+
return delegate.getClassmateContext();
120+
}
121+
122+
@Override
123+
public ArchiveDescriptorFactory getArchiveDescriptorFactory() {
124+
return delegate.getArchiveDescriptorFactory();
125+
}
126+
127+
@Override
128+
public ScanOptions getScanOptions() {
129+
return delegate.getScanOptions();
130+
}
131+
132+
@Override
133+
public ScanEnvironment getScanEnvironment() {
134+
return delegate.getScanEnvironment();
135+
}
136+
137+
@Override
138+
public Object getScanner() {
139+
return delegate.getScanner();
140+
}
141+
142+
@Override
143+
public Object getJandexView() {
144+
return delegate.getJandexView();
145+
}
146+
147+
@Override
148+
public Map<String, SqmFunctionDescriptor> getSqlFunctions() {
149+
return delegate.getSqlFunctions();
150+
}
151+
152+
@Override
153+
public Collection<AuxiliaryDatabaseObject> getAuxiliaryDatabaseObjectList() {
154+
return List.of();
155+
}
156+
157+
@Override
158+
public Collection<ConverterDescriptor<?, ?>> getAttributeConverters() {
159+
return delegate.getAttributeConverters();
160+
}
161+
162+
@Override
163+
public Collection<CacheRegionDefinition> getCacheRegionDefinitions() {
164+
return delegate.getCacheRegionDefinitions();
165+
}
166+
167+
@Override
168+
public ManagedTypeRepresentationResolver getRepresentationStrategySelector() {
169+
return ReactiveManagedTypeRepresentationResolver.INSTANCE;
170+
}
171+
172+
@Override
173+
public void release() {
174+
delegate.release();
175+
}
176+
177+
@Override
178+
public void registerAdHocBasicType(BasicType<?> basicType) {
179+
delegate.registerAdHocBasicType( basicType );
180+
}
181+
182+
@Override
183+
public <T> BasicType<T> resolveAdHocBasicType(String key) {
184+
return delegate.resolveAdHocBasicType( key );
185+
}
186+
187+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.bythecode.enhance.spi.interceptor;
7+
8+
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
9+
import org.hibernate.engine.spi.EntityKey;
10+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
11+
import org.hibernate.reactive.logging.impl.Log;
12+
import org.hibernate.reactive.logging.impl.LoggerFactory;
13+
14+
import java.lang.invoke.MethodHandles;
15+
16+
/**
17+
* Reactive version of {@link EnhancementAsProxyLazinessInterceptor}.
18+
*
19+
* It throws a {@link org.hibernate.LazyInitializationException} when a lazy attribute
20+
* is not fetched using {@link org.hibernate.reactive.mutiny.Mutiny#fetch(Object)}
21+
* or {@link org.hibernate.reactive.stage.Stage#fetch(Object)} but transparently
22+
*/
23+
public class ReactiveEnhancementAsProxyLazinessInterceptor extends EnhancementAsProxyLazinessInterceptor {
24+
private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() );
25+
26+
public ReactiveEnhancementAsProxyLazinessInterceptor(
27+
EntityRelatedState meta,
28+
EntityKey entityKey,
29+
SharedSessionContractImplementor session) {
30+
super( meta, entityKey, session );
31+
}
32+
33+
@Override
34+
protected Object handleRead(Object target, String attributeName, Object value) {
35+
if ( isIdentifier( attributeName ) ) {
36+
return super.handleRead( target, attributeName, value );
37+
}
38+
else {
39+
throw LOG.lazyFieldInitializationException( attributeName, getEntityName() );
40+
}
41+
}
42+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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.bythecode.enhance.spi.internal;
7+
8+
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
9+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
10+
import org.hibernate.reactive.logging.impl.Log;
11+
import org.hibernate.reactive.logging.impl.LoggerFactory;
12+
13+
import java.lang.invoke.MethodHandles;
14+
15+
/**
16+
* Reactive version of {@link LazyAttributeLoadingInterceptor}.
17+
*
18+
* It throws a {@link org.hibernate.LazyInitializationException} when a lazy attribute
19+
* is not fetched using {@link org.hibernate.reactive.mutiny.Mutiny#fetch(Object)}
20+
* or {@link org.hibernate.reactive.stage.Stage#fetch(Object)} but transparently
21+
*/
22+
public class ReactiveLazyAttributeLoadingInterceptor extends LazyAttributeLoadingInterceptor {
23+
24+
private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() );
25+
26+
public ReactiveLazyAttributeLoadingInterceptor(
27+
EntityRelatedState entityMeta,
28+
Object identifier,
29+
SharedSessionContractImplementor session) {
30+
super( entityMeta, identifier, session );
31+
}
32+
33+
@Override
34+
protected Object handleRead(Object target, String attributeName, Object value) {
35+
if ( !isAttributeLoaded( attributeName ) ) {
36+
throw LOG.lazyFieldInitializationException( attributeName, getEntityName() );
37+
}
38+
return value;
39+
}
40+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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.bythecode.spi;
7+
8+
import org.hibernate.boot.Metadata;
9+
import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor;
10+
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
11+
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
12+
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata;
13+
import org.hibernate.bytecode.internal.BytecodeEnhancementMetadataPojoImpl;
14+
import org.hibernate.bytecode.spi.NotInstrumentedException;
15+
import org.hibernate.engine.spi.EntityKey;
16+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
17+
import org.hibernate.mapping.PersistentClass;
18+
import org.hibernate.reactive.bythecode.enhance.spi.interceptor.ReactiveEnhancementAsProxyLazinessInterceptor;
19+
import org.hibernate.reactive.bythecode.enhance.spi.internal.ReactiveLazyAttributeLoadingInterceptor;
20+
import org.hibernate.type.CompositeType;
21+
22+
import java.util.Set;
23+
24+
import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptableType;
25+
26+
/**
27+
* Extends {@link BytecodeEnhancementMetadataPojoImpl} to inject Reactive versions of {@link BytecodeLazyAttributeInterceptor}
28+
*/
29+
public class ReactiveBytecodeEnhancementMetadataPojoImplAdapter extends BytecodeEnhancementMetadataPojoImpl {
30+
31+
public static BytecodeEnhancementMetadataPojoImpl from(
32+
PersistentClass persistentClass,
33+
Set<String> identifierAttributeNames,
34+
CompositeType nonAggregatedCidMapper,
35+
boolean collectionsInDefaultFetchGroupEnabled,
36+
Metadata metadata) {
37+
final Class<?> mappedClass = persistentClass.getMappedClass();
38+
final boolean enhancedForLazyLoading = isPersistentAttributeInterceptableType( mappedClass );
39+
final LazyAttributesMetadata lazyAttributesMetadata = enhancedForLazyLoading
40+
? LazyAttributesMetadata.from( persistentClass, true, collectionsInDefaultFetchGroupEnabled, metadata )
41+
: LazyAttributesMetadata.nonEnhanced( persistentClass.getEntityName() );
42+
43+
return new ReactiveBytecodeEnhancementMetadataPojoImplAdapter(
44+
persistentClass.getEntityName(),
45+
mappedClass,
46+
identifierAttributeNames,
47+
nonAggregatedCidMapper,
48+
enhancedForLazyLoading,
49+
lazyAttributesMetadata
50+
);
51+
}
52+
53+
ReactiveBytecodeEnhancementMetadataPojoImplAdapter(String entityName, Class<?> mappedClass, Set<String> identifierAttributeNames, CompositeType nonAggregatedCidMapper, boolean enhancedForLazyLoading, LazyAttributesMetadata lazyAttributesMetadata) {
54+
super(
55+
entityName,
56+
mappedClass,
57+
identifierAttributeNames,
58+
nonAggregatedCidMapper,
59+
enhancedForLazyLoading,
60+
lazyAttributesMetadata
61+
);
62+
}
63+
64+
@Override
65+
public LazyAttributeLoadingInterceptor injectInterceptor(
66+
Object entity,
67+
Object identifier,
68+
SharedSessionContractImplementor session) throws NotInstrumentedException {
69+
if ( !isEnhancedForLazyLoading() ) {
70+
throw new NotInstrumentedException( "Entity class [" + getEntityClass()
71+
.getName() + "] is not enhanced for lazy loading" );
72+
}
73+
74+
if ( !getEntityClass().isInstance( entity ) ) {
75+
throw new IllegalArgumentException(
76+
String.format(
77+
"Passed entity instance [%s] is not of expected type [%s]",
78+
entity,
79+
getEntityName()
80+
)
81+
);
82+
}
83+
final LazyAttributeLoadingInterceptor interceptor = new ReactiveLazyAttributeLoadingInterceptor(
84+
getLazyAttributeLoadingInterceptorState(),
85+
identifier,
86+
session
87+
);
88+
89+
injectInterceptor( entity, interceptor, session );
90+
91+
return interceptor;
92+
}
93+
94+
@Override
95+
public void injectEnhancedEntityAsProxyInterceptor(
96+
Object entity,
97+
EntityKey entityKey,
98+
SharedSessionContractImplementor session) {
99+
final EnhancementAsProxyLazinessInterceptor.EntityRelatedState meta =
100+
getEnhancementAsProxyLazinessInterceptorMetastate( session );
101+
injectInterceptor(
102+
entity,
103+
new ReactiveEnhancementAsProxyLazinessInterceptor( meta, entityKey, session ),
104+
session
105+
);
106+
}
107+
}

0 commit comments

Comments
 (0)