Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 45 additions & 10 deletions hibernate-core/src/main/java/org/hibernate/type/ComponentType.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

import org.hibernate.FetchMode;
import org.hibernate.HibernateException;
Expand Down Expand Up @@ -54,8 +55,8 @@
* @author Gavin King
*/
public class ComponentType extends AbstractType implements CompositeTypeImplementor, ProcedureParameterExtractionAware {
private final Class<?> componentClass;
private final boolean mutable;
private Supplier<Class<?>> componentClass;
private Supplier<Boolean> mutable;

private final String[] propertyNames;
private final Type[] propertyTypes;
Expand All @@ -79,14 +80,48 @@ public ComponentType(Component component, int[] originalPropertyOrder, MetadataB
}

public ComponentType(Component component, int[] originalPropertyOrder) {
this( component, originalPropertyOrder,
component.getComponentClassName() != null
&& !isRecord( component.getComponentClass() ) );
this( component, originalPropertyOrder, (Boolean) null);
}

public ComponentType(Component component, int[] originalPropertyOrder, boolean mutable) {
this.componentClass = component.isDynamic() ? Map.class : component.getComponentClass();
this.mutable = mutable;
this(component, originalPropertyOrder, (Boolean) mutable);
}

private ComponentType(final Component component, int[] originalPropertyOrder, Boolean mutable) {
// As mutable and componentClass are based on the actual component
// type, both variables must be lazily initialized in some cases.
// E.g. on code generation by hibernate-tools, the component type
// might not yet exist (compound keys), resulting in a
// ClassNotFoundException.
Comment on lines +91 to +95
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I fully understand this use-case: why would the component class, i.e. the class of the generated composite key, not exist while the componentClassName does not return null? Maybe the problem is we shouldn't return a class name at all, or we should resolve the class eagerly when generating the ComponentType.

Could you please include a test demonstrating the issue within your PR? This can help us understand the problem better and ensure no regressions will be introduced in the future.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The classes are also used by the hibernate-tool project for the code generation. So the to-be-generated XXXId class is present in the DB metadata (because to a compound key) and therefore in the class model but does not yet physically exist.

There is also an active hibernate-tools issue for this: HBX-2840

But I will try to create a standalone reproducer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also an active hibernate-tools issue for this: HBX-2840

Great, hopefully Koen will be able to take a look soon and if we can identify this as a tools issue I'm sure it will be fixed. If you want, you can also try taking a look and proposing a fix, if possible.

Let us know about the test. If you don't mean to continue contributing through this PR, please close it so we can progress with cleanup in our backlog.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I doubt it is a pure TOOLS issue, as the second-pass processing is setup and run by ORM, based on the metadata feed via TOOLS. See this line in mapping.BasicValue.
Either TOOLS is not allowed to use the classes from CORE anymore - which would be a huge pain in the a.. (i think) or this use case needs to be handled by ORM.

This commit is the root cause of this issue: 3e8056d

Reproducer is attaches as a separat comment.

//
// The lazy initialization is performance optimized as far as possible.

if(mutable != null) {
this.mutable = () -> mutable;
}
else {
this.mutable = () -> {
final Class<?> clazz = component.getComponentClass();
final boolean mutableVal = ( clazz != null && !isRecord(clazz) );

this.mutable = () -> mutableVal;

return mutableVal;
};
}

if(component.isDynamic()) {
this.componentClass = () -> Map.class;
}
else {
this.componentClass = () -> {
final Class<?> clazz = component.getComponentClass();
this.componentClass = () -> clazz;

return clazz;
};
}

this.isAggregate = component.getAggregateColumn() != null;
this.isKey = component.isKey();
this.propertySpan = component.getPropertySpan();
Expand Down Expand Up @@ -165,7 +200,7 @@ public final boolean isComponentType() {
}

public Class<?> getReturnedClass() {
return componentClass;
return componentClass.get();
}

@Override
Expand Down Expand Up @@ -585,7 +620,7 @@ public CascadeStyle getCascadeStyle(int i) {

@Override
public boolean isMutable() {
return mutable;
return mutable.get();
}

@Override
Expand Down Expand Up @@ -822,7 +857,7 @@ protected final EmbeddableInstantiator instantiator(Object compositeInstance) {
if ( embeddableTypeDescriptor().isPolymorphic() ) {
final String compositeClassName = compositeInstance != null ?
compositeInstance.getClass().getName() :
componentClass.getName();
componentClass.get().getName();
return representationStrategy.getInstantiatorForClass( compositeClassName );
}
else {
Expand Down