Skip to content

Commit b31dc71

Browse files
committed
cleanups in EmbeddableBinder
1 parent 48d74cf commit b31dc71

File tree

5 files changed

+144
-106
lines changed

5 files changed

+144
-106
lines changed

hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java

Lines changed: 133 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,13 @@
5959
import java.util.Locale;
6060
import java.util.Map;
6161

62+
import static org.hibernate.boot.model.internal.AggregateComponentBinder.processAggregate;
6263
import static org.hibernate.boot.model.internal.AnnotatedDiscriminatorColumn.DEFAULT_DISCRIMINATOR_COLUMN_NAME;
6364
import static org.hibernate.boot.model.internal.AnnotatedDiscriminatorColumn.buildDiscriminatorColumn;
6465
import static org.hibernate.boot.model.internal.BinderHelper.getPath;
6566
import static org.hibernate.boot.model.internal.BinderHelper.getRelativePath;
6667
import static org.hibernate.boot.model.internal.BinderHelper.hasToOneAnnotation;
68+
import static org.hibernate.boot.model.internal.ComponentPropertyHolder.applyExplicitTableName;
6769
import static org.hibernate.boot.model.internal.DialectOverridesAnnotationHelper.getOverridableAnnotation;
6870
import static org.hibernate.boot.model.internal.GeneratorBinder.createIdGeneratorsFromGeneratorAnnotations;
6971
import static org.hibernate.boot.model.internal.PropertyBinder.addElementsOfClass;
@@ -438,9 +440,7 @@ static Component fillEmbeddable(
438440
);
439441

440442
final String subpath = getPath( propertyHolder, inferredData );
441-
if ( BOOT_LOGGER.isTraceEnabled() ) {
442-
BOOT_LOGGER.bindingEmbeddable( subpath );
443-
}
443+
BOOT_LOGGER.bindingEmbeddable( subpath );
444444
final var subholder = buildPropertyHolder(
445445
embeddable,
446446
subpath,
@@ -465,7 +465,7 @@ static Component fillEmbeddable(
465465
embeddable.setTypeName( compositeUserTypeClass.getName() );
466466
returnedClassOrElement = context.getBootstrapContext().getModelsContext().getClassDetailsRegistry().resolveClassDetails( compositeUserType.embeddable().getName() );
467467
}
468-
AggregateComponentBinder.processAggregate(
468+
processAggregate(
469469
embeddable,
470470
propertyHolder,
471471
inferredData,
@@ -529,7 +529,7 @@ static Component fillEmbeddable(
529529
&& !hasAnnotationsOnIdClass( annotatedTypeDetails ) ) {
530530
processIdClassElements( propertyHolder, baseInferredData, classElements, baseClassElements );
531531
}
532-
for ( PropertyData propertyAnnotatedElement : classElements ) {
532+
for ( var propertyAnnotatedElement : classElements ) {
533533
processElementAnnotations(
534534
subholder,
535535
entityBinder.getPersistentClass() instanceof SingleTableSubclass
@@ -544,27 +544,15 @@ static Component fillEmbeddable(
544544
context,
545545
inheritanceStatePerClass
546546
);
547-
548-
final var memberDetails = propertyAnnotatedElement.getAttributeMember();
549-
if ( isIdClass || subholder.isOrWithinEmbeddedId() ) {
550-
final var property = findProperty( embeddable, memberDetails.getName() );
551-
if ( property != null ) {
552-
// Identifier properties are always simple values
553-
final var value = (SimpleValue) property.getValue();
554-
createIdGeneratorsFromGeneratorAnnotations(
555-
subholder,
556-
propertyAnnotatedElement,
557-
value,
558-
context
559-
);
560-
}
561-
}
562-
else if ( memberDetails.hasDirectAnnotationUsage( GeneratedValue.class ) ) {
563-
throw new AnnotationException(
564-
"Property '" + memberDetails.getName() + "' of '"
565-
+ getPath( propertyHolder, inferredData )
566-
+ "' is annotated '@GeneratedValue' but is not part of an identifier" );
567-
}
547+
processGeneratorAnnotations(
548+
propertyHolder,
549+
inferredData,
550+
isIdClass,
551+
propertyAnnotatedElement,
552+
subholder,
553+
embeddable,
554+
context
555+
);
568556
}
569557

570558
if ( compositeUserType != null ) {
@@ -574,6 +562,34 @@ else if ( memberDetails.hasDirectAnnotationUsage( GeneratedValue.class ) ) {
574562
return embeddable;
575563
}
576564

565+
private static void processGeneratorAnnotations(
566+
PropertyHolder propertyHolder, PropertyData inferredData,
567+
boolean isIdClass,
568+
PropertyData propertyAnnotatedElement, PropertyHolder subholder,
569+
Component embeddable,
570+
MetadataBuildingContext context) {
571+
final var memberDetails = propertyAnnotatedElement.getAttributeMember();
572+
if ( isIdClass || subholder.isOrWithinEmbeddedId() ) {
573+
final var property = findProperty( embeddable, memberDetails.getName() );
574+
if ( property != null ) {
575+
// Identifier properties are always simple values
576+
final var value = (SimpleValue) property.getValue();
577+
createIdGeneratorsFromGeneratorAnnotations(
578+
subholder,
579+
propertyAnnotatedElement,
580+
value,
581+
context
582+
);
583+
}
584+
}
585+
else if ( memberDetails.hasDirectAnnotationUsage( GeneratedValue.class ) ) {
586+
throw new AnnotationException(
587+
"Property '" + memberDetails.getName() + "' of '"
588+
+ getPath( propertyHolder, inferredData )
589+
+ "' is annotated '@GeneratedValue' but is not part of an identifier" );
590+
}
591+
}
592+
577593
private static Property findProperty(Component embeddable, String name) {
578594
for ( var property : embeddable.getProperties() ) {
579595
if ( property.getName().equals( name ) ) {
@@ -773,45 +789,48 @@ private static String collectDiscriminatorValue(
773789
ClassDetails annotatedClass,
774790
BasicType<?> discriminatorType,
775791
Map<Object, String> discriminatorValues) {
792+
return discriminatorValues.put(
793+
discriminatorType.getJavaTypeDescriptor()
794+
.fromString( discriminatorValue( annotatedClass, discriminatorType ) ),
795+
annotatedClass.getName().intern()
796+
);
797+
}
798+
799+
private static String discriminatorValue(ClassDetails annotatedClass, BasicType<?> discriminatorType) {
776800
final String explicitValue =
777801
annotatedClass.hasDirectAnnotationUsage( DiscriminatorValue.class )
778802
? annotatedClass.getDirectAnnotationUsage( DiscriminatorValue.class ).value()
779803
: null;
780-
final String discriminatorValue;
781804
if ( isBlank( explicitValue ) ) {
782805
final String name = unqualify( annotatedClass.getName() );
783-
if ( "character".equals( discriminatorType.getName() ) ) {
784-
throw new AnnotationException( String.format(
785-
"Embeddable '%s' has a discriminator of character type and must specify its '@DiscriminatorValue'",
786-
name
787-
) );
788-
}
789-
else if ( "integer".equals( discriminatorType.getName() ) ) {
790-
discriminatorValue = String.valueOf( name.hashCode() );
791-
}
792-
else {
793-
discriminatorValue = name;
794-
}
806+
return switch ( discriminatorType.getName() ) {
807+
case "character" ->
808+
throw new AnnotationException( "Embeddable '" + name
809+
+ "' has a discriminator of character type and must specify its '@DiscriminatorValue'" );
810+
case "integer" -> String.valueOf( name.hashCode() );
811+
default -> name;
812+
};
795813
}
796814
else {
797-
discriminatorValue = explicitValue;
815+
return explicitValue;
798816
}
799-
return discriminatorValues.put(
800-
discriminatorType.getJavaTypeDescriptor().fromString( discriminatorValue ),
801-
annotatedClass.getName().intern()
802-
);
803817
}
804818

805-
806819
private static boolean isValidSuperclass(ClassDetails superClass, boolean isIdClass) {
807820
if ( superClass == null ) {
808821
return false;
809822
}
810-
811-
return superClass.hasDirectAnnotationUsage( MappedSuperclass.class )
812-
|| isIdClass
813-
&& !superClass.getName().equals( Object.class.getName() )
814-
&& !superClass.getName().equals( "java.lang.Record" );
823+
else if ( superClass.hasDirectAnnotationUsage( MappedSuperclass.class ) ) {
824+
return true;
825+
}
826+
else if ( isIdClass ) {
827+
final String superClassName = superClass.getName();
828+
return !superClassName.equals( Object.class.getName() )
829+
&& !superClassName.equals( "java.lang.Record" );
830+
}
831+
else {
832+
return false;
833+
}
815834
}
816835

817836
private static List<PropertyData> collectBaseClassElements(
@@ -823,13 +842,13 @@ private static List<PropertyData> collectBaseClassElements(
823842
final List<PropertyData> baseClassElements = new ArrayList<>();
824843
// iterate from base returned class up hierarchy to handle cases where the @Id attributes
825844
// might be spread across the subclasses and super classes.
845+
final String objectClassName = Object.class.getName();
826846
TypeDetails baseReturnedClassOrElement = baseInferredData.getClassOrElementType();
827-
while ( !Object.class.getName().equals( baseReturnedClassOrElement.getName() ) ) {
828-
final var container =
829-
new PropertyContainer( baseReturnedClassOrElement.determineRawClass(),
830-
entityAtStake, propertyAccessor );
847+
while ( !objectClassName.equals( baseReturnedClassOrElement.getName() ) ) {
848+
final var rawClass = baseReturnedClassOrElement.determineRawClass();
849+
final var container = new PropertyContainer( rawClass, entityAtStake, propertyAccessor );
831850
addElementsOfClass( baseClassElements, container, context, 0 );
832-
baseReturnedClassOrElement = baseReturnedClassOrElement.determineRawClass().getGenericSuperType();
851+
baseReturnedClassOrElement = rawClass.getGenericSuperType();
833852
}
834853
return baseClassElements;
835854
}
@@ -848,11 +867,12 @@ private static void processCompositeUserType(Component embeddable, CompositeUser
848867
sortedPropertyTypes
849868
);
850869
for ( var property : embeddable.getProperties() ) {
851-
sortedPropertyNames.add( property.getName() );
870+
final String propertyName = property.getName();
871+
sortedPropertyNames.add( propertyName );
852872
sortedPropertyTypes.add(
853873
PropertyAccessStrategyGetterImpl.INSTANCE.buildPropertyAccess(
854874
compositeUserType.embeddable(),
855-
property.getName(),
875+
propertyName,
856876
false
857877
).getGetter().getReturnType()
858878
);
@@ -893,41 +913,55 @@ private static void processIdClassElements(
893913
List<PropertyData> classElements,
894914
List<PropertyData> baseClassElements) {
895915
final Map<String, PropertyData> baseClassElementsByName = new HashMap<>();
896-
for ( PropertyData element : baseClassElements ) {
916+
for ( var element : baseClassElements ) {
897917
baseClassElementsByName.put( element.getPropertyName(), element );
898918
}
899-
900919
for ( int i = 0; i < classElements.size(); i++ ) {
901-
final PropertyData idClassPropertyData = classElements.get( i );
902-
final String propertyName = idClassPropertyData.getPropertyName();
903-
final PropertyData entityPropertyData = baseClassElementsByName.get( propertyName );
904-
if ( propertyHolder.isInIdClass() ) {
905-
if ( entityPropertyData == null ) {
906-
throw new AnnotationException(
907-
"Property '" + getPath(propertyHolder, idClassPropertyData )
908-
+ "' belongs to an '@IdClass' but has no matching property in entity class '"
909-
+ baseInferredData.getPropertyType().getName()
910-
+ "' (every property of the '@IdClass' must have a corresponding persistent property in the '@Entity' class)"
911-
);
912-
}
913-
if ( hasToOneAnnotation( entityPropertyData.getAttributeMember() )
914-
&& !entityPropertyData.getClassOrElementType().equals( idClassPropertyData.getClassOrElementType() ) ) {
915-
//don't replace here as we need to use the actual original return type
916-
//the annotation overriding will be dealt with by a mechanism similar to @MapsId
917-
continue;
918-
}
919-
if ( !hasCompatibleType( idClassPropertyData.getTypeName(), entityPropertyData.getTypeName() ) ) {
920-
throw new AnnotationException(
921-
"Property '" + propertyName + "' in @IdClass '" + idClassPropertyData.getDeclaringClass().getName()
922-
+ "' doesn't match type in entity class '" + baseInferredData.getPropertyType().getName()
923-
+ "' (expected '" + entityPropertyData.getTypeName() + "' but was '" + idClassPropertyData.getTypeName() + "')"
924-
);
925-
}
920+
final var idClassPropertyData = classElements.get( i );
921+
final var entityPropertyData = baseClassElementsByName.get( idClassPropertyData.getPropertyName() );
922+
if ( !propertyHolder.isInIdClass()
923+
|| checkIdProperty( propertyHolder, baseInferredData, entityPropertyData, idClassPropertyData ) ) {
924+
classElements.set( i, entityPropertyData ); //this works since they are in the same order
926925
}
927-
classElements.set( i, entityPropertyData ); //this works since they are in the same order
928926
}
929927
}
930928

929+
private static boolean checkIdProperty(
930+
PropertyHolder propertyHolder, PropertyData baseInferredData,
931+
PropertyData entityPropertyData, PropertyData idClassPropertyData) {
932+
if ( entityPropertyData == null ) {
933+
throw new AnnotationException(
934+
"Property '" + getPath( propertyHolder, idClassPropertyData )
935+
+ "' belongs to an '@IdClass' but has no matching property in entity class '"
936+
+ baseInferredData.getPropertyType().getName()
937+
+ "' (every property of the '@IdClass' must have a corresponding persistent property in the '@Entity' class)"
938+
);
939+
}
940+
else if ( deferIdProperty( entityPropertyData, idClassPropertyData ) ) {
941+
return false;
942+
}
943+
else {
944+
final String idPropertyTypeName = idClassPropertyData.getTypeName();
945+
final String entityPropertyTypeName = entityPropertyData.getTypeName();
946+
if ( !hasCompatibleType( idPropertyTypeName, entityPropertyTypeName ) ) {
947+
throw new AnnotationException(
948+
"Property '" + idClassPropertyData.getPropertyName()
949+
+ "' in @IdClass '" + idClassPropertyData.getDeclaringClass().getName()
950+
+ "' doesn't match type in entity class '" + baseInferredData.getPropertyType().getName()
951+
+ "' (expected '" + entityPropertyTypeName + "' but was '" + idPropertyTypeName + "')"
952+
);
953+
}
954+
return true;
955+
}
956+
}
957+
958+
//don't replace here as we need to use the actual original return type
959+
//the annotation overriding will be dealt with by a mechanism similar to @MapsId
960+
private static boolean deferIdProperty(PropertyData entityPropertyData, PropertyData idClassPropertyData) {
961+
return hasToOneAnnotation( entityPropertyData.getAttributeMember() )
962+
&& !entityPropertyData.getClassOrElementType().equals( idClassPropertyData.getClassOrElementType() );
963+
}
964+
931965
private static boolean hasCompatibleType(String typeNameInIdClass, String typeNameInEntityClass) {
932966
return typeNameInIdClass.equals( typeNameInEntityClass )
933967
|| canonicalize( typeNameInIdClass ).equals( typeNameInEntityClass )
@@ -957,10 +991,10 @@ static Component createEmbeddable(
957991
MetadataBuildingContext context) {
958992
final var embeddable = new Component( context, propertyHolder.getPersistentClass() );
959993
embeddable.setEmbedded( isNonAggregated );
960-
ComponentPropertyHolder.applyExplicitTableName( embeddable, inferredData, propertyHolder, context );
994+
applyExplicitTableName( embeddable, inferredData, propertyHolder, context );
961995

962996
if ( isIdentifierMapper
963-
|| isNonAggregated && inferredData.getPropertyName() == null ) {
997+
|| isNonAggregated && inferredData.getPropertyName() == null ) {
964998
embeddable.setComponentClassName( embeddable.getOwner().getClassName() );
965999
}
9661000
else {
@@ -987,17 +1021,14 @@ private static void checkEmbeddableRecursiveHierarchy(
9871021
PropertyHolder propertyHolder) {
9881022
final var embeddableClass = type.determineRawClass();
9891023
while ( propertyHolder.isComponent() ) {
990-
final ComponentPropertyHolder componentHolder = (ComponentPropertyHolder) propertyHolder;
1024+
final var componentHolder = (ComponentPropertyHolder) propertyHolder;
9911025
// we need to check that the embeddable is not used in a recursive hierarchy
9921026
var classDetails = embeddableClass;
9931027
while ( classDetails != null ) {
9941028
if ( propertyHolder.getClassName().equals( classDetails.getClassName() ) ) {
995-
throw new MappingException( String.format(
996-
Locale.ROOT,
997-
"Recursive embeddable mapping detected for property '%s' for type [%s]",
998-
getPath( propertyHolder, propertyData ),
999-
propertyHolder.getClassName()
1000-
) );
1029+
throw new MappingException( "Recursive embeddable mapping detected for property '"
1030+
+ getPath( propertyHolder, propertyData )
1031+
+ "' of class '" + propertyHolder.getClassName() + "'" );
10011032
}
10021033
classDetails = classDetails.getSuperClass();
10031034
}
@@ -1137,7 +1168,7 @@ public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws
11371168
boolean isExplicitReference = true;
11381169
final List<AnnotatedJoinColumn> columns = joinColumns.getJoinColumns();
11391170
final Map<String, AnnotatedJoinColumn> columnByReferencedName = mapOfSize( columns.size() );
1140-
for ( AnnotatedJoinColumn joinColumn : columns ) {
1171+
for ( var joinColumn : columns ) {
11411172
if ( !joinColumn.isReferenceImplicit() ) {
11421173
//JPA 2 requires referencedColumnNames to be case-insensitive
11431174
columnByReferencedName.put( joinColumn.getReferencedColumn().toLowerCase( Locale.ROOT), joinColumn );
@@ -1151,8 +1182,8 @@ public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws
11511182
}
11521183
}
11531184

1154-
final MutableInteger index = new MutableInteger();
1155-
for ( Property referencedProperty : referencedComponent.getProperties() ) {
1185+
final var index = new MutableInteger();
1186+
for ( var referencedProperty : referencedComponent.getProperties() ) {
11561187
final Property property;
11571188
if ( referencedProperty.isComposite() ) {
11581189
property = createComponentProperty(
@@ -1260,7 +1291,7 @@ private Property createSimpleProperty(
12601291
value.copyTypeFrom( referencedValue );
12611292

12621293
//TODO: this bit is nasty, move up to AnnotatedJoinColumns
1263-
final AnnotatedJoinColumn firstColumn = joinColumns.getJoinColumns().get(0);
1294+
final var firstColumn = joinColumns.getJoinColumns().get(0);
12641295
if ( firstColumn.isNameDeferred() ) {
12651296
firstColumn.copyReferencedStructureAndCreateDefaultJoinColumns(
12661297
referencedPersistentClass,
@@ -1269,6 +1300,9 @@ private Property createSimpleProperty(
12691300
);
12701301
}
12711302
else {
1303+
final var physicalNamingStrategy =
1304+
buildingContext.getBuildingOptions().getPhysicalNamingStrategy();
1305+
final var database = buildingContext.getMetadataCollector().getDatabase();
12721306
for ( var selectable : referencedValue.getSelectables() ) {
12731307
if ( selectable instanceof org.hibernate.mapping.Column column ) {
12741308
final AnnotatedJoinColumn joinColumn;
@@ -1296,9 +1330,8 @@ private Property createSimpleProperty(
12961330
? "tata_" + column.getName()
12971331
: joinColumn.getName();
12981332

1299-
final var database = buildingContext.getMetadataCollector().getDatabase();
13001333
final String physicalName =
1301-
buildingContext.getBuildingOptions().getPhysicalNamingStrategy()
1334+
physicalNamingStrategy
13021335
.toPhysicalColumnName( database.toIdentifier( columnName ),
13031336
database.getJdbcEnvironment() )
13041337
.render( database.getDialect() );

0 commit comments

Comments
 (0)