5959import java .util .Locale ;
6060import java .util .Map ;
6161
62+ import static org .hibernate .boot .model .internal .AggregateComponentBinder .processAggregate ;
6263import static org .hibernate .boot .model .internal .AnnotatedDiscriminatorColumn .DEFAULT_DISCRIMINATOR_COLUMN_NAME ;
6364import static org .hibernate .boot .model .internal .AnnotatedDiscriminatorColumn .buildDiscriminatorColumn ;
6465import static org .hibernate .boot .model .internal .BinderHelper .getPath ;
6566import static org .hibernate .boot .model .internal .BinderHelper .getRelativePath ;
6667import static org .hibernate .boot .model .internal .BinderHelper .hasToOneAnnotation ;
68+ import static org .hibernate .boot .model .internal .ComponentPropertyHolder .applyExplicitTableName ;
6769import static org .hibernate .boot .model .internal .DialectOverridesAnnotationHelper .getOverridableAnnotation ;
6870import static org .hibernate .boot .model .internal .GeneratorBinder .createIdGeneratorsFromGeneratorAnnotations ;
6971import 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