diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/CollectionType.java b/hibernate-core/src/main/java/org/hibernate/annotations/CollectionType.java index 3a2706f369..dbabb3fd3f 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/CollectionType.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/CollectionType.java @@ -31,8 +31,7 @@ public @interface CollectionType { * Names the type. * * Could name the implementation class (an implementation of {@link org.hibernate.type.CollectionType} or - * {@link org.hibernate.usertype.UserCollectionType}). Could also name a custom type defined via a - * {@link TypeDef @TypeDef} + * {@link org.hibernate.usertype.UserCollectionType}). */ String type(); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index 8f468e61b7..a60cd1b29c 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -2028,7 +2028,6 @@ public final class AnnotationBinder { ); } CollectionBinder collectionBinder = CollectionBinder.getCollectionBinder( - propertyHolder.getEntityName(), property, !indexColumn.isImplicit(), // ugh @@ -2060,7 +2059,6 @@ public final class AnnotationBinder { boolean ignoreNotFound = notFound != null && notFound.action().equals( NotFoundAction.IGNORE ); collectionBinder.setIgnoreNotFound( ignoreNotFound ); collectionBinder.setCollectionType( inferredData.getProperty().getElementClass() ); - collectionBinder.setBuildingContext( context ); collectionBinder.setAccessType( inferredData.getDefaultAccess() ); Ejb3Column[] elementColumns; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ArrayBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ArrayBinder.java index 01ad54919b..d06d10ec1a 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ArrayBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ArrayBinder.java @@ -6,9 +6,11 @@ */ package org.hibernate.cfg.annotations; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.mapping.Array; import org.hibernate.mapping.Collection; import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.SemanticsResolver; /** * Bind an Array @@ -16,11 +18,11 @@ import org.hibernate.mapping.PersistentClass; * @author Anthony Patricio */ public class ArrayBinder extends ListBinder { - - public ArrayBinder() { + public ArrayBinder(SemanticsResolver semanticsResolver, MetadataBuildingContext buildingContext) { + super( semanticsResolver, buildingContext ); } - protected Collection createCollection(PersistentClass persistentClass) { - return new Array( getBuildingContext(), persistentClass ); + protected Collection createCollection(PersistentClass owner) { + return new Array( getSemanticsResolver(), owner, getBuildingContext() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/BagBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/BagBinder.java index 64f6806303..cc6e525d9d 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/BagBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/BagBinder.java @@ -5,8 +5,11 @@ * See the lgpl.txt file in the root directory or . */ package org.hibernate.cfg.annotations; + +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.mapping.Collection; import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.SemanticsResolver; /** * Bind a bag. @@ -14,11 +17,11 @@ import org.hibernate.mapping.PersistentClass; * @author Matthew Inger */ public class BagBinder extends CollectionBinder { - public BagBinder() { - super( false ); + public BagBinder(SemanticsResolver semanticsResolver, MetadataBuildingContext context) { + super( semanticsResolver, false, context ); } - protected Collection createCollection(PersistentClass persistentClass) { - return new org.hibernate.mapping.Bag( getBuildingContext(), persistentClass ); + protected Collection createCollection(PersistentClass owner) { + return new org.hibernate.mapping.Bag( getSemanticsResolver(), owner, getBuildingContext() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java index 002b508e2e..29d5336fc8 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java @@ -72,11 +72,13 @@ import org.hibernate.cfg.PropertyHolderBuilder; import org.hibernate.cfg.PropertyInferredData; import org.hibernate.cfg.PropertyPreloadedData; import org.hibernate.cfg.SecondPass; +import org.hibernate.collection.internal.CustomCollectionTypeSemantics; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.jpa.spi.MutableJpaCompliance; import org.hibernate.mapping.Any; import org.hibernate.mapping.Backref; import org.hibernate.mapping.Collection; @@ -89,8 +91,10 @@ import org.hibernate.mapping.ManyToOne; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.Selectable; +import org.hibernate.mapping.SemanticsResolver; import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.Table; +import org.hibernate.metamodel.CollectionClassification; import org.hibernate.metamodel.EmbeddableInstantiator; import org.jboss.logging.Logger; @@ -134,7 +138,9 @@ public abstract class CollectionBinder { java.util.Collection.class ); - private MetadataBuildingContext buildingContext; + private final MetadataBuildingContext buildingContext; + private final SemanticsResolver semanticsResolver; + private final boolean isSortedCollection; protected Collection collection; protected String propertyName; @@ -169,7 +175,6 @@ public abstract class CollectionBinder { private AccessType accessType; private boolean hibernateExtensionMapping; - private boolean isSortedCollection; private jakarta.persistence.OrderBy jpaOrderBy; private OrderBy sqlOrderBy; private SortNatural naturalSort; @@ -178,16 +183,18 @@ public abstract class CollectionBinder { private String explicitType; private final Properties explicitTypeParameters = new Properties(); - protected CollectionBinder(boolean isSortedCollection) { + protected CollectionBinder(SemanticsResolver semanticsResolver, boolean isSortedCollection, MetadataBuildingContext buildingContext) { + this.semanticsResolver = semanticsResolver; this.isSortedCollection = isSortedCollection; + this.buildingContext = buildingContext; } protected MetadataBuildingContext getBuildingContext() { return buildingContext; } - public void setBuildingContext(MetadataBuildingContext buildingContext) { - this.buildingContext = buildingContext; + protected SemanticsResolver getSemanticsResolver() { + return semanticsResolver; } public boolean isMap() { @@ -260,151 +267,185 @@ public abstract class CollectionBinder { * collection binder factory */ public static CollectionBinder getCollectionBinder( - String entityName, XProperty property, boolean isIndexed, boolean isHibernateExtensionMapping, MetadataBuildingContext buildingContext) { - final CollectionBinder result; + final CollectionType typeAnnotation = property.getAnnotation( CollectionType.class ); - if ( property.isArray() ) { - if ( property.getElementClass().isPrimitive() ) { - result = new PrimitiveArrayBinder(); - } - else { - result = new ArrayBinder(); + final CollectionBinder binder; + if ( typeAnnotation != null ) { + binder = createBinderFromCustomTypeAnnotation( property, isIndexed, typeAnnotation, buildingContext ); + } + else { + binder = createBinderFromProperty( property, isIndexed, buildingContext ); + } + + binder.setIsHibernateExtensionMapping( isHibernateExtensionMapping ); + + if ( typeAnnotation != null ) { + binder.explicitType = typeAnnotation.type(); + for ( Parameter param : typeAnnotation.parameters() ) { + binder.explicitTypeParameters.setProperty( param.name(), param.value() ); } } - else if ( property.isCollection() ) { - //TODO consider using an XClass - final Class returnedClass = property.getCollectionClass(); - final CollectionBinder basicBinder = getBinderFromBasicCollectionType( - returnedClass, - property, - entityName, - isIndexed - ); - if ( basicBinder != null ) { - result = basicBinder; + return binder; + } + + private static CollectionBinder createBinderFromProperty( + XProperty property, + boolean isIndexed, + MetadataBuildingContext buildingContext) { + final CollectionClassification classification = determineCollectionClassification( property, null, isIndexed, buildingContext ); + return createBinder( property, classification, null, buildingContext ); + } + + private static CollectionBinder createBinderFromCustomTypeAnnotation( + XProperty property, + boolean isIndexed, + CollectionType typeAnnotation, + MetadataBuildingContext buildingContext) { + final CollectionClassification classification = determineCollectionClassification( property, typeAnnotation, isIndexed, buildingContext ); + final SemanticsResolver semanticsResolver = (collectionType) -> new CustomCollectionTypeSemantics<>( collectionType, classification ); + return createBinder( property, classification, semanticsResolver, buildingContext ); + } + + private static CollectionBinder createBinder( + XProperty property, + CollectionClassification classification, + SemanticsResolver semanticsResolver, + MetadataBuildingContext buildingContext) { + + switch ( classification ) { + case ARRAY: { + if ( property.getElementClass().isPrimitive() ) { + return new PrimitiveArrayBinder( semanticsResolver, buildingContext ); + } + return new ArrayBinder( semanticsResolver, buildingContext ); } - else if ( property.isAnnotationPresent( CollectionType.class ) ) { - Class semanticsClass = property.getAnnotation( CollectionType.class ).semantics(); + case BAG: { + return new BagBinder( semanticsResolver, buildingContext ); + } + case IDBAG: { + return new IdBagBinder( semanticsResolver, buildingContext ); + } + case LIST: { + return new ListBinder( semanticsResolver, buildingContext ); + } + case MAP: + case ORDERED_MAP: { + return new MapBinder( semanticsResolver, false, buildingContext ); + } + case SORTED_MAP: { + return new MapBinder( semanticsResolver, true, buildingContext ); + } + case SET: + case ORDERED_SET: { + return new SetBinder( semanticsResolver, false, buildingContext ); + } + case SORTED_SET: { + return new SetBinder( semanticsResolver, true, buildingContext ); + } + } - if ( semanticsClass != void.class ) { - result = getBinderFromBasicCollectionType( semanticsClass, property, entityName, isIndexed ); - } - else { - final Class inferredClass = inferCollectionClassFromSubclass( returnedClass ); - result = inferredClass != null ? getBinderFromBasicCollectionType( - inferredClass, - property, - entityName, - isIndexed - ) : null; - } + final XClass declaringClass = property.getDeclaringClass(); + + throw new AnnotationException( + String.format( + Locale.ROOT, + "Unable to determine proper CollectionBinder (`%s) : %s.%s", + classification, + declaringClass.getName(), + property.getName() + ) + ); + } + + private static CollectionClassification determineCollectionClassification( + XProperty property, + CollectionType typeAnnotation, + boolean isIndexed, + MetadataBuildingContext buildingContext) { + if ( property.isArray() ) { + return CollectionClassification.ARRAY; + } + + return determineCollectionClassification( + determineSemanticJavaType( property, typeAnnotation, isIndexed, buildingContext ), + property + ); + } + + private static CollectionClassification determineCollectionClassification(Class semanticJavaType, XProperty property) { + if ( semanticJavaType.isArray() ) { + return CollectionClassification.ARRAY; + } + else if ( java.util.List.class.isAssignableFrom( semanticJavaType ) ) { + return CollectionClassification.LIST; + } + else if ( java.util.SortedSet.class.isAssignableFrom( semanticJavaType ) ) { + return CollectionClassification.SORTED_SET; + } + else if ( java.util.Set.class.isAssignableFrom( semanticJavaType ) ) { + return CollectionClassification.SET; + } + else if ( java.util.SortedMap.class.isAssignableFrom( semanticJavaType ) ) { + return CollectionClassification.SORTED_MAP; + } + else if ( java.util.Map.class.isAssignableFrom( semanticJavaType ) ) { + return CollectionClassification.MAP; + } + else if ( java.util.Collection.class.isAssignableFrom( semanticJavaType ) ) { + if ( property.isAnnotationPresent( CollectionId.class ) ) { + return CollectionClassification.IDBAG; } else { - result = null; - } - if ( result == null ) { - throw new AnnotationException( - returnedClass.getName() + " collection type not supported for property: " - + StringHelper.qualify( entityName, property.getName() ) - ); + return CollectionClassification.BAG; } } else { + return null; + } + } + + private static Class determineSemanticJavaType(XProperty property, CollectionType typeAnnotation, boolean isIndexed, MetadataBuildingContext buildingContext) { + final Class returnedJavaType = property.getCollectionClass(); + if ( typeAnnotation != null ) { + final Class requestedSemanticsJavaType = typeAnnotation.semantics(); + if ( requestedSemanticsJavaType != null && requestedSemanticsJavaType != void.class ) { + return inferCollectionClassFromSubclass( requestedSemanticsJavaType, isIndexed, buildingContext ); + } + } + + if ( returnedJavaType == null ) { throw new AnnotationException( - "Illegal attempt to map a non collection as a @OneToMany, @ManyToMany or @ElementCollection: " - + StringHelper.qualify( entityName, property.getName() ) + String.format( + Locale.ROOT, + "Illegal attempt to map a non collection as a @OneToMany, @ManyToMany or @CollectionOfElements: %s.%s", + property.getDeclaringClass().getName(), + property.getName() + ) ); } - result.setIsHibernateExtensionMapping( isHibernateExtensionMapping ); - final CollectionType typeAnnotation = property.getAnnotation( CollectionType.class ); - if ( typeAnnotation != null ) { - final String typeName = typeAnnotation.type(); - // see if it names a type-def - final TypeDefinition typeDef = buildingContext.getMetadataCollector().getTypeDefinition( typeName ); - if ( typeDef != null ) { - result.explicitType = typeDef.getTypeImplementorClass().getName(); - result.explicitTypeParameters.putAll( typeDef.getParameters() ); - } - else { - result.explicitType = typeName; - for ( Parameter param : typeAnnotation.parameters() ) { - result.explicitTypeParameters.setProperty( param.name(), param.value() ); - } - } - } - - return result; + return inferCollectionClassFromSubclass( returnedJavaType, isIndexed, buildingContext ); } - private static CollectionBinder getBinderFromBasicCollectionType(Class clazz, XProperty property, - String entityName, boolean isIndexed) { - if ( java.util.Set.class.equals( clazz) ) { - if ( property.isAnnotationPresent( CollectionId.class) ) { - throw new AnnotationException("Set does not support @CollectionId: " - + StringHelper.qualify( entityName, property.getName() ) ); - } - return new SetBinder( false ); + private static Class inferCollectionClassFromSubclass(Class clazz, boolean isIndexed, MetadataBuildingContext buildingContext) { + if ( java.util.List.class.isAssignableFrom( clazz ) && !isIndexed ) { + final MutableJpaCompliance jpaCompliance = buildingContext.getBootstrapContext().getJpaCompliance(); + return jpaCompliance.isJpaListComplianceEnabled() + ? java.util.List.class + : java.util.Collection.class; } - else if ( java.util.SortedSet.class.equals( clazz ) ) { - if ( property.isAnnotationPresent( CollectionId.class ) ) { - throw new AnnotationException( "SortedSet does not support @CollectionId: " - + StringHelper.qualify( entityName, property.getName() ) ); - } - return new SetBinder( true ); - } - else if ( Map.class.equals( clazz ) ) { - if ( property.isAnnotationPresent( CollectionId.class ) ) { - throw new AnnotationException( "Map does not support @CollectionId: " - + StringHelper.qualify( entityName, property.getName() ) ); - } - return new MapBinder( false ); - } - else if ( java.util.SortedMap.class.equals( clazz ) ) { - if ( property.isAnnotationPresent( CollectionId.class ) ) { - throw new AnnotationException( "SortedMap does not support @CollectionId: " - + StringHelper.qualify( entityName, property.getName() ) ); - } - return new MapBinder( true ); - } - else if ( java.util.Collection.class.equals( clazz ) ) { - if ( property.isAnnotationPresent( CollectionId.class ) ) { - return new IdBagBinder(); - } - else { - return new BagBinder(); - } - } - else if ( List.class.equals( clazz ) ) { - if ( isIndexed ) { - if ( property.isAnnotationPresent( CollectionId.class ) ) { - throw new AnnotationException( - "List does not support @CollectionId and @OrderColumn (or @IndexColumn) at the same time: " - + StringHelper.qualify( entityName, property.getName() ) ); - } - return new ListBinder(); - } - else if ( property.isAnnotationPresent( CollectionId.class ) ) { - return new IdBagBinder(); - } - else { - return new BagBinder(); - } - } - return null; - } - private static Class inferCollectionClassFromSubclass(Class clazz) { for ( Class priorityClass : INFERRED_CLASS_PRIORITY ) { if ( priorityClass.isAssignableFrom( clazz ) ) { return priorityClass; } } + return null; } @@ -488,7 +529,8 @@ public abstract class CollectionBinder { Persister persisterAnn = property.getAnnotation( Persister.class ); if ( persisterAnn != null ) { - collection.setCollectionPersisterClass( persisterAnn.impl() ); + //noinspection rawtypes + collection.setCollectionPersisterClass( (Class) persisterAnn.impl() ); } applySortingAndOrdering( collection ); @@ -614,37 +656,32 @@ public abstract class CollectionBinder { } private void applySortingAndOrdering(Collection collection) { - boolean hadOrderBy = false; - boolean hadExplicitSort = false; + final boolean hadExplicitSort; + final Class> comparatorClass; - Class> comparatorClass = null; - - if ( jpaOrderBy == null && sqlOrderBy == null ) { - if ( naturalSort != null ) { - if ( comparatorSort != null ) { - throw buildIllegalSortCombination(); - } - hadExplicitSort = true; - } - else if ( comparatorSort != null ) { - hadExplicitSort = true; - comparatorClass = comparatorSort.value(); + if ( naturalSort != null ) { + if ( comparatorSort != null ) { + throw buildIllegalSortCombination(); } + hadExplicitSort = true; + comparatorClass = null; + } + else if ( comparatorSort != null ) { + hadExplicitSort = true; + comparatorClass = comparatorSort.value(); } else { + hadExplicitSort = false; + comparatorClass = null; + } + + boolean hadOrderBy = false; + if ( jpaOrderBy != null || sqlOrderBy != null ) { if ( jpaOrderBy != null && sqlOrderBy != null ) { - throw new AnnotationException( - String.format( - "Illegal combination of @%s and @%s on %s", - jakarta.persistence.OrderBy.class.getName(), - OrderBy.class.getName(), - safeCollectionRole() - ) - ); + throw buildIllegalOrderCombination(); } hadOrderBy = true; - hadExplicitSort = false; // we can only apply the sql-based order by up front. The jpa order by has to wait for second pass if ( sqlOrderBy != null ) { @@ -652,7 +689,13 @@ public abstract class CollectionBinder { } } - collection.setSorted( isSortedCollection || hadExplicitSort ); + final boolean isSorted = isSortedCollection || hadExplicitSort; + + if ( isSorted && hadOrderBy ) { + throw buildIllegalOrderAndSortCombination(); + } + + collection.setSorted( isSorted ); if ( comparatorClass != null ) { try { @@ -670,10 +713,36 @@ public abstract class CollectionBinder { } } + private AnnotationException buildIllegalOrderCombination() { + return new AnnotationException( + String.format( + Locale.ROOT, + "Illegal combination of ordering and sorting annotations (`%s`) - only one of `@%s` and `@%s` may be used", + jakarta.persistence.OrderBy.class.getName(), + OrderBy.class.getName(), + safeCollectionRole() + ) + ); + } + + private AnnotationException buildIllegalOrderAndSortCombination() { + throw new AnnotationException( + String.format( + Locale.ROOT, + "Illegal combination of ordering and sorting annotations (`%s`) - only one of `@%s`, `@%s`, `@%s` and `@%s` can be used", + safeCollectionRole(), + jakarta.persistence.OrderBy.class.getName(), + OrderBy.class.getName(), + SortComparator.class.getName(), + SortNatural.class.getName() + ) + ); + } + private AnnotationException buildIllegalSortCombination() { return new AnnotationException( String.format( - "Illegal combination of annotations on %s. Only one of @%s and @%s can be used", + "Illegal combination of sorting annotations (`%s`) - only one of `@%s` and `@%s` can be used", safeCollectionRole(), SortNatural.class.getName(), SortComparator.class.getName() diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java index 6cc0a01566..ebc089825f 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java @@ -26,6 +26,7 @@ import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Collection; import org.hibernate.mapping.IdentifierCollection; import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.SemanticsResolver; import org.hibernate.mapping.Table; import jakarta.persistence.Column; @@ -34,11 +35,12 @@ import jakarta.persistence.Column; * @author Emmanuel Bernard */ public class IdBagBinder extends BagBinder { - public IdBagBinder() { + public IdBagBinder(SemanticsResolver semanticsResolver, MetadataBuildingContext buildingContext) { + super( semanticsResolver, buildingContext ); } - protected Collection createCollection(PersistentClass persistentClass) { - return new org.hibernate.mapping.IdentifierBag( getBuildingContext(), persistentClass ); + protected Collection createCollection(PersistentClass owner) { + return new org.hibernate.mapping.IdentifierBag( getSemanticsResolver(), owner, getBuildingContext() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java index 62b2471b27..8d43f07503 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java @@ -27,6 +27,7 @@ import org.hibernate.mapping.IndexBackref; import org.hibernate.mapping.List; import org.hibernate.mapping.OneToMany; import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.SemanticsResolver; import org.hibernate.mapping.SimpleValue; import org.jboss.logging.Logger; @@ -41,13 +42,13 @@ import org.jboss.logging.Logger; public class ListBinder extends CollectionBinder { private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, ListBinder.class.getName() ); - public ListBinder() { - super( false ); + public ListBinder(SemanticsResolver semanticsResolver, MetadataBuildingContext buildingContext) { + super( semanticsResolver, false, buildingContext ); } @Override - protected Collection createCollection(PersistentClass persistentClass) { - return new List( getBuildingContext(), persistentClass ); + protected Collection createCollection(PersistentClass owner) { + return new List( getSemanticsResolver(), owner, getBuildingContext() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java index 01bb82c661..b85ea58f68 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java @@ -9,15 +9,6 @@ package org.hibernate.cfg.annotations; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import java.util.Random; -import jakarta.persistence.AttributeOverride; -import jakarta.persistence.AttributeOverrides; -import jakarta.persistence.ConstraintMode; -import jakarta.persistence.InheritanceType; -import jakarta.persistence.MapKeyClass; -import jakarta.persistence.MapKeyColumn; -import jakarta.persistence.MapKeyJoinColumn; -import jakarta.persistence.MapKeyJoinColumns; import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; @@ -40,27 +31,32 @@ import org.hibernate.cfg.PropertyData; import org.hibernate.cfg.PropertyHolderBuilder; import org.hibernate.cfg.PropertyPreloadedData; import org.hibernate.cfg.SecondPass; -import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.Size; -import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Column; import org.hibernate.mapping.Component; import org.hibernate.mapping.DependantBasicValue; -import org.hibernate.mapping.DependantValue; import org.hibernate.mapping.Formula; import org.hibernate.mapping.ManyToOne; import org.hibernate.mapping.OneToMany; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.Selectable; +import org.hibernate.mapping.SemanticsResolver; import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.Table; -import org.hibernate.mapping.ToOne; import org.hibernate.mapping.Value; -import org.hibernate.sql.Template; + +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.AttributeOverrides; +import jakarta.persistence.ConstraintMode; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.MapKeyClass; +import jakarta.persistence.MapKeyColumn; +import jakarta.persistence.MapKeyJoinColumn; +import jakarta.persistence.MapKeyJoinColumns; /** * Implementation to bind a Map @@ -68,16 +64,16 @@ import org.hibernate.sql.Template; * @author Emmanuel Bernard */ public class MapBinder extends CollectionBinder { - public MapBinder(boolean sorted) { - super( sorted ); + public MapBinder(SemanticsResolver semanticsResolver, boolean sorted, MetadataBuildingContext buildingContext) { + super( semanticsResolver, sorted, buildingContext ); } public boolean isMap() { return true; } - protected Collection createCollection(PersistentClass persistentClass) { - return new org.hibernate.mapping.Map( getBuildingContext(), persistentClass ); + protected Collection createCollection(PersistentClass owner) { + return new org.hibernate.mapping.Map( getSemanticsResolver(), owner, getBuildingContext() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PrimitiveArrayBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PrimitiveArrayBinder.java index f13b16bba7..65dabed5a4 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PrimitiveArrayBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/PrimitiveArrayBinder.java @@ -6,16 +6,22 @@ */ package org.hibernate.cfg.annotations; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.mapping.Collection; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.PrimitiveArray; +import org.hibernate.mapping.SemanticsResolver; /** * @author Emmanuel Bernard */ public class PrimitiveArrayBinder extends ArrayBinder { + public PrimitiveArrayBinder(SemanticsResolver semanticsResolver, MetadataBuildingContext buildingContext) { + super( semanticsResolver, buildingContext ); + } + @Override - protected Collection createCollection(PersistentClass persistentClass) { - return new PrimitiveArray( getBuildingContext(), persistentClass ); + protected Collection createCollection(PersistentClass owner) { + return new PrimitiveArray( getSemanticsResolver(), owner, getBuildingContext() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SetBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SetBinder.java index 4735be1d89..a17b348595 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SetBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/SetBinder.java @@ -7,8 +7,10 @@ package org.hibernate.cfg.annotations; import org.hibernate.annotations.OrderBy; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.mapping.Collection; import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.SemanticsResolver; /** * Bind a set. @@ -16,13 +18,13 @@ import org.hibernate.mapping.PersistentClass; * @author Matthew Inger */ public class SetBinder extends CollectionBinder { - public SetBinder(boolean sorted) { - super( sorted ); + public SetBinder(SemanticsResolver semanticsResolver, boolean sorted, MetadataBuildingContext buildingContext) { + super( semanticsResolver, sorted, buildingContext ); } @Override protected Collection createCollection(PersistentClass persistentClass) { - return new org.hibernate.mapping.Set( getBuildingContext(), persistentClass ); + return new org.hibernate.mapping.Set( getSemanticsResolver(), persistentClass, getBuildingContext() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractBagSemantics.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractBagSemantics.java index e0313a74bd..2dc71e9027 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractBagSemantics.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractBagSemantics.java @@ -13,14 +13,12 @@ import java.util.function.Consumer; import org.hibernate.collection.spi.BagSemantics; import org.hibernate.collection.spi.CollectionInitializerProducer; -import org.hibernate.engine.FetchTiming; +import org.hibernate.collection.spi.InitializerProducerBuilder; import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.query.NavigablePath; import org.hibernate.sql.results.graph.Fetch; -import org.hibernate.sql.results.graph.collection.internal.BagInitializerProducer; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.FetchParent; @@ -60,35 +58,6 @@ public abstract class AbstractBagSemantics implements BagSemantics implements BagSemantics, K, V> implement } } - @Override - public CollectionInitializerProducer createInitializerProducer( - NavigablePath navigablePath, - PluralAttributeMapping attributeMapping, - FetchParent fetchParent, - boolean selected, - String resultVariable, - DomainResultCreationState creationState) { - return new MapInitializerProducer( - attributeMapping, - fetchParent.generateFetchableFetch( - attributeMapping.getIndexDescriptor(), - navigablePath.append( CollectionPart.Nature.INDEX.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ), - fetchParent.generateFetchableFetch( - attributeMapping.getElementDescriptor(), - navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ) - ); - } - @Override public CollectionInitializerProducer createInitializerProducer( NavigablePath navigablePath, @@ -110,31 +79,15 @@ public abstract class AbstractMapSemantics, K, V> implement String resultVariable, Fetch indexFetch, Fetch elementFetch, - DomainResultCreationState creationState){ - if ( indexFetch == null ) { - indexFetch = fetchParent.generateFetchableFetch( - attributeMapping.getIndexDescriptor(), - navigablePath.append( CollectionPart.Nature.INDEX.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ); - } - if ( elementFetch == null ) { - elementFetch = fetchParent.generateFetchableFetch( - attributeMapping.getElementDescriptor(), - navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ); - } - return new MapInitializerProducer( + DomainResultCreationState creationState) { + return InitializerProducerBuilder.createMapInitializerProducer( + navigablePath, attributeMapping, + fetchParent, + selected, indexFetch, - elementFetch + elementFetch, + creationState ); } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractSetSemantics.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractSetSemantics.java index b4f565dac9..2e60303343 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractSetSemantics.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractSetSemantics.java @@ -12,13 +12,11 @@ import java.util.function.Consumer; import org.hibernate.collection.spi.CollectionInitializerProducer; import org.hibernate.collection.spi.CollectionSemantics; -import org.hibernate.engine.FetchTiming; -import org.hibernate.metamodel.mapping.CollectionPart; +import org.hibernate.collection.spi.InitializerProducerBuilder; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.query.NavigablePath; -import org.hibernate.sql.results.graph.Fetch; -import org.hibernate.sql.results.graph.collection.internal.SetInitializerProducer; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; /** @@ -45,27 +43,6 @@ public abstract class AbstractSetSemantics,E> implements Colle } } - @Override - public CollectionInitializerProducer createInitializerProducer( - NavigablePath navigablePath, - PluralAttributeMapping attributeMapping, - FetchParent fetchParent, - boolean selected, - String resultVariable, - DomainResultCreationState creationState) { - return new SetInitializerProducer( - attributeMapping, - fetchParent.generateFetchableFetch( - attributeMapping.getElementDescriptor(), - navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ) - ); - } - @Override public CollectionInitializerProducer createInitializerProducer( NavigablePath navigablePath, @@ -75,17 +52,15 @@ public abstract class AbstractSetSemantics,E> implements Colle String resultVariable, Fetch indexFetch, Fetch elementFetch, - DomainResultCreationState creationState){ - if ( elementFetch == null ) { - return createInitializerProducer( - navigablePath, - attributeMapping, - fetchParent, - selected, - resultVariable, - creationState - ); - } - return new SetInitializerProducer( attributeMapping, elementFetch ); + DomainResultCreationState creationState) { + assert indexFetch == null; + return InitializerProducerBuilder.createSetInitializerProducer( + navigablePath, + attributeMapping, + fetchParent, + selected, + elementFetch, + creationState + ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/CustomCollectionTypeSemantics.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/CustomCollectionTypeSemantics.java index f9d241bec3..287c78553a 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/CustomCollectionTypeSemantics.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/CustomCollectionTypeSemantics.java @@ -11,20 +11,16 @@ import java.util.function.Consumer; import org.hibernate.collection.spi.CollectionInitializerProducer; import org.hibernate.collection.spi.CollectionSemantics; +import org.hibernate.collection.spi.InitializerProducerBuilder; import org.hibernate.collection.spi.PersistentCollection; -import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.CollectionClassification; -import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.query.NavigablePath; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; -import org.hibernate.sql.results.graph.collection.internal.BagInitializerProducer; -import org.hibernate.sql.results.graph.collection.internal.ListInitializerProducer; -import org.hibernate.sql.results.graph.collection.internal.MapInitializerProducer; import org.hibernate.type.CollectionType; /** @@ -33,16 +29,17 @@ import org.hibernate.type.CollectionType; * @author Christian Beikov */ public class CustomCollectionTypeSemantics implements CollectionSemantics { - private final CollectionType collectionType; + private final CollectionClassification classification; - public CustomCollectionTypeSemantics(CollectionType collectionType) { + public CustomCollectionTypeSemantics(CollectionType collectionType, CollectionClassification classification) { this.collectionType = collectionType; + this.classification = classification; } @Override public CollectionClassification getCollectionClassification() { - return CollectionClassification.BAG; + return classification; } @Override @@ -52,11 +49,13 @@ public class CustomCollectionTypeSemantics implements CollectionSemantics @Override public CE instantiateRaw(int anticipatedSize, CollectionPersister collectionDescriptor) { + //noinspection unchecked return (CE) collectionType.instantiate( anticipatedSize ); } @Override public Iterator getElementIterator(CE rawCollection) { + //noinspection unchecked return collectionType.getElementsIterator( rawCollection, null ); } @@ -65,47 +64,6 @@ public class CustomCollectionTypeSemantics implements CollectionSemantics getElementIterator( rawCollection ).forEachRemaining( action ); } - @Override - public CollectionInitializerProducer createInitializerProducer( - NavigablePath navigablePath, - PluralAttributeMapping attributeMapping, - FetchParent fetchParent, - boolean selected, - String resultVariable, - DomainResultCreationState creationState) { - final Fetch indexFetch; - if ( attributeMapping.getIndexDescriptor() == null ) { - indexFetch = null; - } - else { - indexFetch = fetchParent.generateFetchableFetch( - attributeMapping.getIndexDescriptor(), - navigablePath.append( CollectionPart.Nature.INDEX.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ); - } - final Fetch elementFetch = fetchParent.generateFetchableFetch( - attributeMapping.getElementDescriptor(), - navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ); - if ( indexFetch == null ) { - return new BagInitializerProducer( attributeMapping, null, elementFetch ); - } - else if ( indexFetch.getResultJavaTypeDescriptor().getJavaTypeClass() == Integer.class ) { - return new ListInitializerProducer( attributeMapping, indexFetch, elementFetch ); - } - else { - return new MapInitializerProducer( attributeMapping, indexFetch, elementFetch ); - } - } - @Override public CollectionInitializerProducer createInitializerProducer( NavigablePath navigablePath, @@ -116,35 +74,16 @@ public class CustomCollectionTypeSemantics implements CollectionSemantics Fetch indexFetch, Fetch elementFetch, DomainResultCreationState creationState) { - if ( indexFetch == null ) { - indexFetch = fetchParent.generateFetchableFetch( - attributeMapping.getIndexDescriptor(), - navigablePath.append( CollectionPart.Nature.INDEX.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ); - } - if ( elementFetch == null ) { - elementFetch = fetchParent.generateFetchableFetch( - attributeMapping.getElementDescriptor(), - navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ); - } - if ( indexFetch == null ) { - return new BagInitializerProducer( attributeMapping, null, elementFetch ); - } - else if ( indexFetch.getResultJavaTypeDescriptor().getJavaTypeClass() == Integer.class ) { - return new ListInitializerProducer( attributeMapping, indexFetch, elementFetch ); - } - else { - return new MapInitializerProducer( attributeMapping, indexFetch, elementFetch ); - } + return InitializerProducerBuilder.createCollectionTypeWrapperInitializerProducer( + navigablePath, + attributeMapping, + classification, + fetchParent, + selected, + indexFetch, + elementFetch, + creationState + ); } @Override @@ -152,6 +91,7 @@ public class CustomCollectionTypeSemantics implements CollectionSemantics Object key, CollectionPersister collectionDescriptor, SharedSessionContractImplementor session) { + //noinspection unchecked return collectionType.instantiate( session, collectionDescriptor, key ); } @@ -160,6 +100,7 @@ public class CustomCollectionTypeSemantics implements CollectionSemantics CE rawCollection, CollectionPersister collectionDescriptor, SharedSessionContractImplementor session) { + //noinspection unchecked return collectionType.wrap( session, rawCollection ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardArraySemantics.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardArraySemantics.java index e280d471cb..2a76b4f444 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardArraySemantics.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardArraySemantics.java @@ -12,16 +12,14 @@ import java.util.function.Consumer; import org.hibernate.collection.spi.CollectionInitializerProducer; import org.hibernate.collection.spi.CollectionSemantics; +import org.hibernate.collection.spi.InitializerProducerBuilder; import org.hibernate.collection.spi.PersistentCollection; -import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.CollectionClassification; -import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.query.NavigablePath; import org.hibernate.sql.results.graph.Fetch; -import org.hibernate.sql.results.graph.collection.internal.ArrayInitializerProducer; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.FetchParent; @@ -93,35 +91,6 @@ public class StandardArraySemantics implements CollectionSemantics { } } - @Override - public CollectionInitializerProducer createInitializerProducer( - NavigablePath navigablePath, - PluralAttributeMapping attributeMapping, - FetchParent fetchParent, - boolean selected, - String resultVariable, - DomainResultCreationState creationState) { - return new ArrayInitializerProducer( - attributeMapping, - fetchParent.generateFetchableFetch( - attributeMapping.getIndexDescriptor(), - navigablePath.append( CollectionPart.Nature.INDEX.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ), - fetchParent.generateFetchableFetch( - attributeMapping.getElementDescriptor(), - navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ) - ); - } - @Override public CollectionInitializerProducer createInitializerProducer( NavigablePath navigablePath, @@ -131,31 +100,15 @@ public class StandardArraySemantics implements CollectionSemantics { String resultVariable, Fetch indexFetch, Fetch elementFetch, - DomainResultCreationState creationState){ - if ( indexFetch == null ) { - indexFetch = fetchParent.generateFetchableFetch( - attributeMapping.getIndexDescriptor(), - navigablePath.append( CollectionPart.Nature.INDEX.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ); - } - if ( elementFetch == null ) { - elementFetch = fetchParent.generateFetchableFetch( - attributeMapping.getElementDescriptor(), - navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ); - } - return new ArrayInitializerProducer( + DomainResultCreationState creationState) { + return InitializerProducerBuilder.createArrayInitializerProducer( + navigablePath, attributeMapping, + fetchParent, + selected, indexFetch, - elementFetch + elementFetch, + creationState ); } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardListSemantics.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardListSemantics.java index 9804c68293..0a869e92a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardListSemantics.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardListSemantics.java @@ -12,18 +12,16 @@ import java.util.function.Consumer; import org.hibernate.collection.spi.CollectionInitializerProducer; import org.hibernate.collection.spi.CollectionSemantics; +import org.hibernate.collection.spi.InitializerProducerBuilder; import org.hibernate.collection.spi.PersistentCollection; -import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.CollectionClassification; -import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.query.NavigablePath; -import org.hibernate.sql.results.graph.Fetch; -import org.hibernate.sql.results.graph.collection.internal.ListInitializerProducer; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; /** @@ -45,6 +43,7 @@ public class StandardListSemantics implements CollectionSemantics, E> return CollectionClassification.LIST; } + @SuppressWarnings("rawtypes") @Override public Class getCollectionJavaType() { return List.class; @@ -67,72 +66,6 @@ public class StandardListSemantics implements CollectionSemantics, E> rawCollection.forEach( action ); } - @Override - public CollectionInitializerProducer createInitializerProducer( - NavigablePath navigablePath, - PluralAttributeMapping attributeMapping, - FetchParent fetchParent, - boolean selected, - String resultVariable, - DomainResultCreationState creationState) { - return new ListInitializerProducer( - attributeMapping, - fetchParent.generateFetchableFetch( - attributeMapping.getIndexDescriptor(), - navigablePath.append( CollectionPart.Nature.INDEX.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ), - fetchParent.generateFetchableFetch( - attributeMapping.getElementDescriptor(), - navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ) - ); - } - - @Override - public CollectionInitializerProducer createInitializerProducer( - NavigablePath navigablePath, - PluralAttributeMapping attributeMapping, - FetchParent fetchParent, - boolean selected, - String resultVariable, - Fetch indexFetch, - Fetch elementFetch, - DomainResultCreationState creationState) { - if ( indexFetch == null ) { - indexFetch = fetchParent.generateFetchableFetch( - attributeMapping.getIndexDescriptor(), - navigablePath.append( CollectionPart.Nature.INDEX.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ); - } - if ( elementFetch == null ) { - elementFetch = fetchParent.generateFetchableFetch( - attributeMapping.getElementDescriptor(), - navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), - FetchTiming.IMMEDIATE, - selected, - null, - creationState - ); - } - return new ListInitializerProducer( - attributeMapping, - indexFetch, - elementFetch - ); - } - @Override public PersistentCollection instantiateWrapper( Object key, @@ -148,4 +81,17 @@ public class StandardListSemantics implements CollectionSemantics, E> SharedSessionContractImplementor session) { return new PersistentList<>( session, rawCollection ); } + + @Override + public CollectionInitializerProducer createInitializerProducer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParent fetchParent, + boolean selected, + String resultVariable, + Fetch indexFetch, + Fetch elementFetch, + DomainResultCreationState creationState) { + return InitializerProducerBuilder.createListInitializerProducer( navigablePath, attributeMapping, fetchParent, selected, indexFetch, elementFetch, creationState ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/CollectionSemantics.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/CollectionSemantics.java index 55a6bef25a..82daa10d89 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/spi/CollectionSemantics.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/CollectionSemantics.java @@ -10,7 +10,6 @@ import java.util.Iterator; import java.util.function.Consumer; import org.hibernate.Incubating; -import org.hibernate.LockMode; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.CollectionClassification; import org.hibernate.metamodel.mapping.PluralAttributeMapping; @@ -36,38 +35,69 @@ public interface CollectionSemantics { */ CollectionClassification getCollectionClassification(); + /** + * The collection's Java type + */ Class getCollectionJavaType(); + /** + * Create a raw (unwrapped) version of the collection + */ CE instantiateRaw( int anticipatedSize, CollectionPersister collectionDescriptor); + /** + * Create a wrapper for the collection + */ PersistentCollection instantiateWrapper( Object key, CollectionPersister collectionDescriptor, SharedSessionContractImplementor session); + /** + * Wrap a raw collection in wrapper + */ PersistentCollection wrap( CE rawCollection, CollectionPersister collectionDescriptor, SharedSessionContractImplementor session); + /** + * Obtain an iterator over the collection elements + */ Iterator getElementIterator(CE rawCollection); + /** + * Visit the elements of the collection + */ void visitElements(CE rawCollection, Consumer action); /** - * todo (6.0) : clean this contract up! + * Create a producer for {@link org.hibernate.sql.results.graph.collection.CollectionInitializer} + * instances for the given collection semantics + * + * @see InitializerProducerBuilder */ - CollectionInitializerProducer createInitializerProducer( + default CollectionInitializerProducer createInitializerProducer( NavigablePath navigablePath, PluralAttributeMapping attributeMapping, FetchParent fetchParent, boolean selected, String resultVariable, - DomainResultCreationState creationState); + DomainResultCreationState creationState) { + return createInitializerProducer( + navigablePath, attributeMapping, fetchParent, selected, resultVariable, null, null, creationState + ); + } - CollectionInitializerProducer createInitializerProducer( + /** + * Create a producer for {@link org.hibernate.sql.results.graph.collection.CollectionInitializer} + * instances for the given collection semantics + * + * @see InitializerProducerBuilder + */ + default CollectionInitializerProducer createInitializerProducer( NavigablePath navigablePath, PluralAttributeMapping attributeMapping, FetchParent fetchParent, @@ -75,5 +105,16 @@ public interface CollectionSemantics { String resultVariable, Fetch indexFetch, Fetch elementFetch, - DomainResultCreationState creationState); + DomainResultCreationState creationState) { + return InitializerProducerBuilder.createInitializerProducer( + navigablePath, + attributeMapping, + getCollectionClassification(), + fetchParent, + selected, + indexFetch, + elementFetch, + creationState + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/InitializerProducerBuilder.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/InitializerProducerBuilder.java new file mode 100644 index 0000000000..ec5ce52977 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/InitializerProducerBuilder.java @@ -0,0 +1,296 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.collection.spi; + +import org.hibernate.engine.FetchTiming; +import org.hibernate.metamodel.CollectionClassification; +import org.hibernate.metamodel.mapping.CollectionPart; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.collection.internal.ArrayInitializerProducer; +import org.hibernate.sql.results.graph.collection.internal.BagInitializerProducer; +import org.hibernate.sql.results.graph.collection.internal.ListInitializerProducer; +import org.hibernate.sql.results.graph.collection.internal.MapInitializerProducer; +import org.hibernate.sql.results.graph.collection.internal.SetInitializerProducer; + +/** + * @author Steve Ebersole + */ +public class InitializerProducerBuilder { + public static CollectionInitializerProducer createInitializerProducer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + CollectionClassification classification, + FetchParent fetchParent, + boolean selected, + Fetch indexFetch, + Fetch elementFetch, + DomainResultCreationState creationState) { + switch ( classification ) { + case ARRAY: { + return createArrayInitializerProducer( navigablePath, attributeMapping, fetchParent, selected, indexFetch, elementFetch, creationState ); + } + case BAG: + case IDBAG: { + return createBagInitializerProducer( navigablePath, attributeMapping, fetchParent, selected, elementFetch, creationState ); + } + case LIST: { + return createListInitializerProducer( navigablePath, attributeMapping, fetchParent, selected, indexFetch, elementFetch, creationState ); + } + case MAP: + case ORDERED_MAP: + case SORTED_MAP: { + return createMapInitializerProducer( navigablePath, attributeMapping, fetchParent, selected, indexFetch, elementFetch, creationState ); + } + case SET: + case ORDERED_SET: + case SORTED_SET: { + return createSetInitializerProducer( navigablePath, attributeMapping, fetchParent, selected, elementFetch, creationState ); + } + default: { + throw new IllegalArgumentException( "Unknown CollectionClassification : " + classification ); + } + } + } + + public static CollectionInitializerProducer createArrayInitializerProducer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParent fetchParent, + boolean selected, + Fetch indexFetch, + Fetch elementFetch, + DomainResultCreationState creationState) { + if ( indexFetch == null ) { + indexFetch = fetchParent.generateFetchableFetch( + attributeMapping.getIndexDescriptor(), + navigablePath.append( CollectionPart.Nature.INDEX.getName() ), + FetchTiming.IMMEDIATE, + selected, + null, + creationState + ); + } + if ( elementFetch == null ) { + elementFetch = fetchParent.generateFetchableFetch( + attributeMapping.getElementDescriptor(), + navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), + FetchTiming.IMMEDIATE, + selected, + null, + creationState + ); + } + + return new ArrayInitializerProducer( attributeMapping, indexFetch, elementFetch ); + } + + public static CollectionInitializerProducer createBagInitializerProducer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParent fetchParent, + boolean selected, + Fetch elementFetch, + DomainResultCreationState creationState) { + + final Fetch idBagIdFetch; + if ( attributeMapping.getIdentifierDescriptor() != null ) { + idBagIdFetch = fetchParent.generateFetchableFetch( + attributeMapping.getIdentifierDescriptor(), + navigablePath.append( CollectionPart.Nature.ID.getName() ), + FetchTiming.IMMEDIATE, + selected, + null, + creationState + ); + } + else { + idBagIdFetch = null; + } + + if ( elementFetch == null ) { + elementFetch = fetchParent.generateFetchableFetch( + attributeMapping.getElementDescriptor(), + navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), + FetchTiming.IMMEDIATE, + selected, + null, + creationState + ); + } + + return new BagInitializerProducer( attributeMapping, idBagIdFetch, elementFetch ); + } + + public static CollectionInitializerProducer createListInitializerProducer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParent fetchParent, + boolean selected, + Fetch indexFetch, + Fetch elementFetch, + DomainResultCreationState creationState) { + if ( indexFetch == null ) { + indexFetch = fetchParent.generateFetchableFetch( + attributeMapping.getIndexDescriptor(), + navigablePath.append( CollectionPart.Nature.INDEX.getName() ), + FetchTiming.IMMEDIATE, + selected, + null, + creationState + ); + } + if ( elementFetch == null ) { + elementFetch = fetchParent.generateFetchableFetch( + attributeMapping.getElementDescriptor(), + navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), + FetchTiming.IMMEDIATE, + selected, + null, + creationState + ); + } + + return new ListInitializerProducer( attributeMapping, indexFetch, elementFetch ); + } + + public static CollectionInitializerProducer createMapInitializerProducer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParent fetchParent, + boolean selected, + Fetch indexFetch, + Fetch elementFetch, + DomainResultCreationState creationState) { + assert attributeMapping.getIndexDescriptor() != null; + + if ( indexFetch == null ) { + indexFetch = fetchParent.generateFetchableFetch( + attributeMapping.getIndexDescriptor(), + navigablePath.append( CollectionPart.Nature.INDEX.getName() ), + FetchTiming.IMMEDIATE, + selected, + null, + creationState + ); + } + + if ( elementFetch == null ) { + elementFetch = fetchParent.generateFetchableFetch( + attributeMapping.getElementDescriptor(), + navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), + FetchTiming.IMMEDIATE, + selected, + null, + creationState + ); + } + + return new MapInitializerProducer( attributeMapping, indexFetch, elementFetch ); + } + + public static CollectionInitializerProducer createSetInitializerProducer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParent fetchParent, + boolean selected, + Fetch elementFetch, + DomainResultCreationState creationState) { + if ( elementFetch == null ) { + elementFetch = fetchParent.generateFetchableFetch( + attributeMapping.getElementDescriptor(), + navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), + FetchTiming.IMMEDIATE, + selected, + null, + creationState + ); + } + return new SetInitializerProducer( attributeMapping, elementFetch ); + } + + public static CollectionInitializerProducer createCollectionTypeWrapperInitializerProducer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + CollectionClassification classification, + FetchParent fetchParent, + boolean selected, + Fetch indexFetch, + Fetch elementFetch, + DomainResultCreationState creationState) { + switch ( classification ) { + case ARRAY: { + return createArrayInitializerProducer( + navigablePath, + attributeMapping, + fetchParent, + selected, + indexFetch, + elementFetch, + creationState + ); + } + case BAG: + case IDBAG: { + assert indexFetch == null; + return createBagInitializerProducer( + navigablePath, + attributeMapping, + fetchParent, + selected, + elementFetch, + creationState + ); + } + case LIST: { + return createListInitializerProducer( + navigablePath, + attributeMapping, + fetchParent, + selected, + indexFetch, + elementFetch, + creationState + ); + } + case MAP: + case ORDERED_MAP: + case SORTED_MAP: { + return createMapInitializerProducer( + navigablePath, + attributeMapping, + fetchParent, + selected, + indexFetch, + elementFetch, + creationState + ); + } + case SET: + case ORDERED_SET: + case SORTED_SET: { + return createSetInitializerProducer( + navigablePath, + attributeMapping, + fetchParent, + selected, + elementFetch, + creationState + ); + } + default: { + throw new IllegalArgumentException( "Unknown CollectionClassification : " + classification ); + } + } + } + + private InitializerProducerBuilder() { + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Array.java b/hibernate-core/src/main/java/org/hibernate/mapping/Array.java index 635bc74529..315435c9ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Array.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Array.java @@ -6,6 +6,8 @@ */ package org.hibernate.mapping; +import java.util.function.Function; + import org.hibernate.MappingException; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; @@ -29,7 +31,11 @@ public class Array extends List { super( buildingContext, owner ); } - public Class getElementClass() throws MappingException { + public Array(SemanticsResolver semanticsResolver, PersistentClass owner, MetadataBuildingContext buildingContext) { + super( semanticsResolver, owner, buildingContext ); + } + + public Class getElementClass() throws MappingException { if ( elementClassName == null ) { final org.hibernate.type.Type elementType = getElement().getType(); if ( isPrimitiveArray() ) { @@ -51,7 +57,7 @@ public class Array extends List { } @Override - public CollectionSemantics getDefaultCollectionSemantics() { + public CollectionSemantics getDefaultCollectionSemantics() { return StandardArraySemantics.INSTANCE; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Bag.java b/hibernate-core/src/main/java/org/hibernate/mapping/Bag.java index 4f3591e02b..39b825936b 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Bag.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Bag.java @@ -6,6 +6,8 @@ */ package org.hibernate.mapping; +import java.util.function.Function; + import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.collection.internal.StandardBagSemantics; import org.hibernate.collection.spi.CollectionSemantics; @@ -18,10 +20,20 @@ import org.hibernate.type.CollectionType; * @author Gavin King */ public class Bag extends Collection { + /** + * hbm.xml binding + */ public Bag(MetadataBuildingContext buildingContext, PersistentClass owner) { super( buildingContext, owner ); } + /** + * Annotation binding + */ + public Bag(SemanticsResolver semanticsResolver, PersistentClass owner, MetadataBuildingContext buildingContext) { + super( semanticsResolver, owner, buildingContext ); + } + public CollectionType getDefaultCollectionType() { return new BagType( getMetadata().getTypeConfiguration(), getRole(), getReferencedPropertyName() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java b/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java index 2fcfdac45e..213c539c5e 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java @@ -14,6 +14,8 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Properties; +import java.util.SortedMap; +import java.util.SortedSet; import org.hibernate.FetchMode; import org.hibernate.MappingException; @@ -27,6 +29,8 @@ import org.hibernate.engine.spi.Mapping; import org.hibernate.internal.FilterConfiguration; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.metamodel.CollectionClassification; +import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.service.ServiceRegistry; import org.hibernate.type.CollectionType; import org.hibernate.type.Type; @@ -63,18 +67,21 @@ public abstract class Collection implements Fetchable, Value, Filterable { private String referencedPropertyName; private String mappedByProperty; private boolean sorted; - private Comparator comparator; + private Comparator comparator; private String comparatorClassName; private boolean orphanDelete; private int batchSize = -1; private FetchMode fetchMode; private boolean embedded = true; private boolean optimisticLocked = true; - private Class collectionPersisterClass; + private String typeName; private Properties typeParameters; - private final List filters = new ArrayList(); - private final List manyToManyFilters = new ArrayList(); + private SemanticsResolver customSemanticsResolver; + private Class collectionPersisterClass; + + private final List filters = new ArrayList<>(); + private final List manyToManyFilters = new ArrayList<>(); private final java.util.Set synchronizedTables = new HashSet<>(); private String customSQLInsert; @@ -92,11 +99,23 @@ public abstract class Collection implements Fetchable, Value, Filterable { private String loaderName; + /** + * hbm.xml binding + */ protected Collection(MetadataBuildingContext buildingContext, PersistentClass owner) { this.buildingContext = buildingContext; this.owner = owner; } + /** + * Annotation binding + */ + protected Collection(SemanticsResolver customSemanticsResolver, PersistentClass owner, MetadataBuildingContext buildingContext) { + this.customSemanticsResolver = customSemanticsResolver; + this.owner = owner; + this.buildingContext = buildingContext; + } + public MetadataBuildingContext getBuildingContext() { return buildingContext; } @@ -142,13 +161,13 @@ public abstract class Collection implements Fetchable, Value, Filterable { return sorted; } - public Comparator getComparator() { + public Comparator getComparator() { if ( comparator == null && comparatorClassName != null ) { try { final ClassLoaderService classLoaderService = getMetadata().getMetadataBuildingOptions() .getServiceRegistry() .getService( ClassLoaderService.class ); - setComparator( (Comparator) classLoaderService.classForName( comparatorClassName ).newInstance() ); + setComparator( (Comparator) classLoaderService.classForName( comparatorClassName ).getConstructor().newInstance() ); } catch (Exception e) { throw new MappingException( @@ -202,7 +221,7 @@ public abstract class Collection implements Fetchable, Value, Filterable { return orderBy; } - public void setComparator(Comparator comparator) { + public void setComparator(@SuppressWarnings("rawtypes") Comparator comparator) { this.comparator = comparator; } @@ -296,11 +315,11 @@ public abstract class Collection implements Fetchable, Value, Filterable { this.fetchMode = fetchMode; } - public void setCollectionPersisterClass(Class persister) { + public void setCollectionPersisterClass(Class persister) { this.collectionPersisterClass = persister; } - public Class getCollectionPersisterClass() { + public Class getCollectionPersisterClass() { return collectionPersisterClass; } @@ -328,7 +347,7 @@ public abstract class Collection implements Fetchable, Value, Filterable { checkColumnDuplication(); } - private void checkColumnDuplication(java.util.Set distinctColumns, Value value) + private void checkColumnDuplication(java.util.Set distinctColumns, Value value) throws MappingException { final boolean[] insertability = value.getColumnInsertability(); final boolean[] updatability = value.getColumnUpdateability(); @@ -354,7 +373,7 @@ public abstract class Collection implements Fetchable, Value, Filterable { } private void checkColumnDuplication() throws MappingException { - HashSet cols = new HashSet(); + HashSet cols = new HashSet<>(); checkColumnDuplication( cols, getKey() ); if ( isIndexed() ) { checkColumnDuplication( @@ -374,7 +393,7 @@ public abstract class Collection implements Fetchable, Value, Filterable { } public Iterator getColumnIterator() { - return Collections.emptyList().iterator(); + return Collections.emptyIterator(); } @Override @@ -390,23 +409,78 @@ public abstract class Collection implements Fetchable, Value, Filterable { return getCollectionType(); } + private CollectionSemantics cachedCollectionSemantics; + + @SuppressWarnings("rawtypes") public CollectionSemantics getCollectionSemantics() { + if ( cachedCollectionSemantics == null ) { + cachedCollectionSemantics = resolveCollectionSemantics(); + } + + return cachedCollectionSemantics; + } + + private CollectionSemantics resolveCollectionSemantics() { + final CollectionType collectionType; if ( typeName == null ) { - return getDefaultCollectionSemantics(); + collectionType = null; } else { - final CollectionType collectionType = MappingHelper.customCollection( + collectionType = MappingHelper.customCollection( typeName, typeParameters, role, referencedPropertyName, getMetadata() ); - return new CustomCollectionTypeSemantics( collectionType ); } + + if ( customSemanticsResolver != null ) { + return customSemanticsResolver.resolve( collectionType ); + } + + if ( collectionType == null ) { + return getDefaultCollectionSemantics(); + } + + final Class semanticJavaType = collectionType.getReturnedClass(); + final CollectionClassification classification; + + if ( semanticJavaType.isArray() ) { + classification = CollectionClassification.ARRAY; + } + else if ( List.class.isAssignableFrom( semanticJavaType ) ) { + classification = CollectionClassification.LIST; + } + else if ( SortedSet.class.isAssignableFrom( semanticJavaType ) ) { + classification = CollectionClassification.SORTED_SET; + } + else if ( Set.class.isAssignableFrom( semanticJavaType ) ) { + classification = CollectionClassification.SET; + } + else if ( SortedMap.class.isAssignableFrom( semanticJavaType ) ) { + classification = CollectionClassification.SORTED_MAP; + } + else if ( Map.class.isAssignableFrom( semanticJavaType ) ) { + classification = CollectionClassification.MAP; + } + else if ( Collection.class.isAssignableFrom( semanticJavaType ) ) { + if ( isIdentified() ) { + classification = CollectionClassification.IDBAG; + } + else { + classification = CollectionClassification.BAG; + } + } + else { + throw new IllegalArgumentException( "Unexpected collection-semantics Java type : " + semanticJavaType ); + } + + return new CustomCollectionTypeSemantics<>( collectionType, classification ); + } - public CollectionSemantics getDefaultCollectionSemantics() { + public CollectionSemantics getDefaultCollectionSemantics() { throw new MappingException( "Unexpected org.hibernate.mapping.Collection impl [" + this + "]; unknown CollectionSemantics" @@ -598,7 +672,7 @@ public abstract class Collection implements Fetchable, Value, Filterable { ); } - public List getFilters() { + public List getFilters() { return filters; } @@ -620,7 +694,7 @@ public abstract class Collection implements Fetchable, Value, Filterable { ); } - public List getManyToManyFilters() { + public List getManyToManyFilters() { return manyToManyFilters; } @@ -677,6 +751,7 @@ public abstract class Collection implements Fetchable, Value, Filterable { this.typeParameters = parameterMap; } + @SuppressWarnings("rawtypes") public void setTypeParameters(java.util.Map parameterMap) { if ( parameterMap instanceof Properties ) { this.typeParameters = (Properties) parameterMap; diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Filterable.java b/hibernate-core/src/main/java/org/hibernate/mapping/Filterable.java index 9d41d3b211..f5a76598ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Filterable.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Filterable.java @@ -15,7 +15,7 @@ import org.hibernate.internal.FilterConfiguration; * @author Steve Ebersole */ public interface Filterable { - public void addFilter(String name, String condition, boolean autoAliasInjection, java.util.Map aliasTableMap, java.util.Map aliasEntityMap); + void addFilter(String name, String condition, boolean autoAliasInjection, java.util.Map aliasTableMap, java.util.Map aliasEntityMap); - public java.util.List getFilters(); + java.util.List getFilters(); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierBag.java b/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierBag.java index f6bc4df62c..cf95854527 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierBag.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierBag.java @@ -17,10 +17,21 @@ import org.hibernate.type.IdentifierBagType; * just the identifier column */ public class IdentifierBag extends IdentifierCollection { + + /** + * hbm.xml binding + */ public IdentifierBag(MetadataBuildingContext buildingContext, PersistentClass owner) { super( buildingContext, owner ); } + /** + * annotation binding + */ + public IdentifierBag(SemanticsResolver semanticsResolver, PersistentClass owner, MetadataBuildingContext buildingContext) { + super( semanticsResolver, owner, buildingContext ); + } + public CollectionType getDefaultCollectionType() { return new IdentifierBagType( getMetadata().getTypeConfiguration(), getRole(), getReferencedPropertyName() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierCollection.java b/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierCollection.java index d7cda4e675..ea19bbc5bc 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/IdentifierCollection.java @@ -8,6 +8,7 @@ package org.hibernate.mapping; import org.hibernate.MappingException; import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.collection.spi.CollectionSemantics; import org.hibernate.engine.spi.Mapping; /** @@ -23,6 +24,10 @@ public abstract class IdentifierCollection extends Collection { super( buildingContext, owner ); } + public IdentifierCollection(SemanticsResolver semanticsResolver, PersistentClass owner, MetadataBuildingContext buildingContext) { + super( semanticsResolver, owner, buildingContext ); + } + public KeyValue getIdentifier() { return identifier; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/IndexedCollection.java b/hibernate-core/src/main/java/org/hibernate/mapping/IndexedCollection.java index 74f3b1dc91..f5c750d81f 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/IndexedCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/IndexedCollection.java @@ -7,10 +7,13 @@ package org.hibernate.mapping; import java.util.Iterator; +import java.util.function.Function; import org.hibernate.MappingException; import org.hibernate.boot.spi.MetadataBuildingContext; +import org.hibernate.collection.spi.CollectionSemantics; import org.hibernate.engine.spi.Mapping; +import org.hibernate.type.CollectionType; /** * Indexed collections include Lists, Maps, arrays and @@ -27,6 +30,10 @@ public abstract class IndexedCollection extends Collection { super( buildingContext, owner ); } + public IndexedCollection(SemanticsResolver semanticsResolver, PersistentClass owner, MetadataBuildingContext buildingContext) { + super( semanticsResolver, owner, buildingContext ); + } + public Value getIndex() { return index; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/List.java b/hibernate-core/src/main/java/org/hibernate/mapping/List.java index 4bc5eb3d7e..7b5bc4056e 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/List.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/List.java @@ -6,6 +6,8 @@ */ package org.hibernate.mapping; +import java.util.function.Function; + import org.hibernate.MappingException; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.collection.internal.StandardListSemantics; @@ -22,14 +24,24 @@ public class List extends IndexedCollection { private int baseIndex; - public boolean isList() { - return true; - } - + /** + * hbm.xml binding + */ public List(MetadataBuildingContext buildingContext, PersistentClass owner) { super( buildingContext, owner ); } + /** + * annotation binding + */ + public List(SemanticsResolver semanticsResolver, PersistentClass owner, MetadataBuildingContext buildingContext) { + super( semanticsResolver, owner, buildingContext ); + } + + public boolean isList() { + return true; + } + @Override public CollectionSemantics getDefaultCollectionSemantics() { return StandardListSemantics.INSTANCE; diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Map.java b/hibernate-core/src/main/java/org/hibernate/mapping/Map.java index 8716153260..eea3205244 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Map.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Map.java @@ -28,7 +28,11 @@ public class Map extends IndexedCollection { public Map(MetadataBuildingContext buildingContext, PersistentClass owner) { super( buildingContext, owner ); } - + + public Map(SemanticsResolver semanticsResolver, PersistentClass owner, MetadataBuildingContext buildingContext) { + super( semanticsResolver, owner, buildingContext ); + } + public boolean isMap() { return true; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/PrimitiveArray.java b/hibernate-core/src/main/java/org/hibernate/mapping/PrimitiveArray.java index fd8b3a80d0..5b94b100f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PrimitiveArray.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PrimitiveArray.java @@ -18,6 +18,10 @@ public class PrimitiveArray extends Array { super( buildingContext, owner ); } + public PrimitiveArray(SemanticsResolver semanticsResolver, PersistentClass owner, MetadataBuildingContext buildingContext) { + super( semanticsResolver, owner, buildingContext ); + } + public boolean isPrimitiveArray() { return true; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/SemanticsResolver.java b/hibernate-core/src/main/java/org/hibernate/mapping/SemanticsResolver.java new file mode 100644 index 0000000000..17075e4aaf --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SemanticsResolver.java @@ -0,0 +1,17 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.mapping; + +import org.hibernate.collection.spi.CollectionSemantics; + +/** + * @author Steve Ebersole + */ +@FunctionalInterface +public interface SemanticsResolver { + CollectionSemantics resolve(org.hibernate.type.CollectionType explicitType); +} diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Set.java b/hibernate-core/src/main/java/org/hibernate/mapping/Set.java index 8d5cd675d8..1f8e7b9b70 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Set.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Set.java @@ -26,10 +26,20 @@ import org.hibernate.type.SortedSetType; * @author Gavin King */ public class Set extends Collection { + /** + * Used by hbm.xml binding + */ public Set(MetadataBuildingContext buildingContext, PersistentClass owner) { super( buildingContext, owner ); } + /** + * Used by annotation binding + */ + public Set(SemanticsResolver semanticsResolver, PersistentClass persistentClass, MetadataBuildingContext buildingContext) { + super( semanticsResolver, persistentClass, buildingContext ); + } + public void validate(Mapping mapping) throws MappingException { super.validate( mapping ); //for backward compatibility, disable this: diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/annotations/CollectionBinderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/annotations/CollectionBinderTest.java index 73ba132d22..ef522a256c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/annotations/CollectionBinderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/annotations/CollectionBinderTest.java @@ -22,6 +22,7 @@ import org.hibernate.cfg.Ejb3JoinColumn; import org.hibernate.cfg.InheritanceState; import org.hibernate.cfg.PropertyHolder; import org.hibernate.cfg.annotations.CollectionBinder; +import org.hibernate.collection.internal.StandardBagSemantics; import org.hibernate.mapping.Collection; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Table; @@ -55,7 +56,7 @@ public class CollectionBinderTest extends BaseUnitTestCase { String expectMessage = "Association [abc] for entity [CollectionBinderTest] references unmapped class [List]"; try { - new CollectionBinder( false) { + new CollectionBinder( (t) -> StandardBagSemantics.INSTANCE, false, buildingContext ) { { final PropertyHolder propertyHolder = Mockito.mock(PropertyHolder.class); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/SortAndOrderTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/SortAndOrderTests.java new file mode 100644 index 0000000000..f5871b64eb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/SortAndOrderTests.java @@ -0,0 +1,88 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.mapping.collections; + +import java.util.Set; + +import org.hibernate.AnnotationException; +import org.hibernate.annotations.SortNatural; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; + +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.ServiceRegistryScope; + +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Basic; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderBy; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author Steve Ebersole + */ +@JiraKey( "https://hibernate.atlassian.net/browse/HHH-9688" ) +@ServiceRegistry +public class SortAndOrderTests { + + @Test + void test(ServiceRegistryScope scope) { + final StandardServiceRegistry registry = scope.getRegistry(); + final MetadataSources sources = new MetadataSources( registry ).addAnnotatedClass( AnEntity.class ); + + try { + sources.buildMetadata(); + fail( "Expecting to fail" ); + } + catch (AnnotationException expected) { + assertThat( expected ).hasMessageStartingWith( "Illegal combination of ordering and sorting annotations" ); + } + } + + @Entity( name = "AnEntity" ) + @Table( name = "t_entity" ) + public static class AnEntity { + @Id + private Integer id; + @Basic + private String name; + + @ElementCollection + @SortNatural + @OrderBy + private Set aliases; + + private AnEntity() { + // for use by Hibernate + } + + public AnEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/Email.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/Email.java similarity index 93% rename from hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/Email.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/Email.java index 379ce9141f..6e0d8f52ea 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/Email.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/Email.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.collection.custom.basic; +package org.hibernate.orm.test.mapping.type.collection.custom.basic; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/IMyList.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/IMyList.java similarity index 81% rename from hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/IMyList.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/IMyList.java index f1b319ce87..465f83e1e7 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/IMyList.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/IMyList.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.collection.custom.basic; +package org.hibernate.orm.test.mapping.type.collection.custom.basic; import java.util.List; diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/MyList.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/MyList.java similarity index 89% rename from hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/MyList.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/MyList.java index ec0874a304..73bb82e94f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/MyList.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/MyList.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.collection.custom.basic; +package org.hibernate.orm.test.mapping.type.collection.custom.basic; import java.util.ArrayList; diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/MyListType.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/MyListType.java similarity index 96% rename from hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/MyListType.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/MyListType.java index f36e7ca50c..10f53c8d44 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/MyListType.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/MyListType.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.collection.custom.basic; +package org.hibernate.orm.test.mapping.type.collection.custom.basic; import java.util.Iterator; import java.util.Map; diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/PersistentMyList.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/PersistentMyList.java similarity index 90% rename from hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/PersistentMyList.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/PersistentMyList.java index 971b09a08b..54cfc2ce57 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/PersistentMyList.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/PersistentMyList.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.collection.custom.basic; +package org.hibernate.orm.test.mapping.type.collection.custom.basic; import org.hibernate.collection.internal.PersistentList; import org.hibernate.engine.spi.SharedSessionContractImplementor; diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/User.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/User.java similarity index 68% rename from hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/User.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/User.java index 1166a3e8ed..36b43577b4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/User.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/User.java @@ -4,11 +4,10 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.collection.custom.basic; +package org.hibernate.orm.test.mapping.type.collection.custom.basic; -import java.util.HashMap; import java.util.List; -import java.util.Map; + import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -17,9 +16,9 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToMany; import jakarta.persistence.OrderColumn; import jakarta.persistence.Table; -import jakarta.persistence.Transient; import org.hibernate.annotations.CollectionType; +import org.hibernate.annotations.NaturalId; /** * @author Gavin King @@ -28,43 +27,41 @@ import org.hibernate.annotations.CollectionType; @Entity @Table(name = "UC_BSC_USER") public class User { + @Id + private Integer id; + @NaturalId private String userName; - private IMyList emailAddresses = new MyList(); - private Map sessionData = new HashMap(); - - User() { + @OneToMany( fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true ) + @CollectionType( type = "org.hibernate.orm.test.mapping.type.collection.custom.basic.MyListType" ) + @JoinColumn( name = "userName" ) + @OrderColumn( name = "displayOrder" ) + private IMyList emailAddresses = new MyList<>(); + private User() { + // for use by Hibernate } - public User(String name) { + + public User(Integer id, String name) { + this.id = id; userName = name; } - @Id + public Integer getId() { + return id; + } + public String getUserName() { return userName; } + public void setUserName(String userName) { this.userName = userName; } - @OneToMany( fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true ) - @CollectionType( type = "org.hibernate.test.collection.custom.basic.MyListType" ) - @JoinColumn( name = "userName" ) - @OrderColumn( name = "displayOrder" ) public List getEmailAddresses() { -// does not work :( -// public IMyList getEmailAddresses() { return emailAddresses; } public void setEmailAddresses(IMyList emailAddresses) { this.emailAddresses = emailAddresses; } - - @Transient - public Map getSessionData() { - return sessionData; - } - public void setSessionData(Map sessionData) { - this.sessionData = sessionData; - } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/UserCollectionTypeAnnotationsVariantTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/UserCollectionTypeAnnotationsVariantTest.java similarity index 87% rename from hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/UserCollectionTypeAnnotationsVariantTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/UserCollectionTypeAnnotationsVariantTest.java index aed8504af1..ce54dd05b1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/UserCollectionTypeAnnotationsVariantTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/UserCollectionTypeAnnotationsVariantTest.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.collection.custom.basic; +package org.hibernate.orm.test.mapping.type.collection.custom.basic; /** * @author Steve Ebersole diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/UserCollectionTypeHbmVariantTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/UserCollectionTypeHbmVariantTest.java similarity index 61% rename from hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/UserCollectionTypeHbmVariantTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/UserCollectionTypeHbmVariantTest.java index 1636ea55d2..a0d096e5bb 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/UserCollectionTypeHbmVariantTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/UserCollectionTypeHbmVariantTest.java @@ -4,14 +4,19 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.collection.custom.basic; +package org.hibernate.orm.test.mapping.type.collection.custom.basic; /** * @author Steve Ebersole */ public class UserCollectionTypeHbmVariantTest extends UserCollectionTypeTest { + @Override + protected String getBaseForMappings() { + return ""; + } + @Override public String[] getMappings() { - return new String[] { "collection/custom/basic/UserPermissions.hbm.xml" }; + return new String[] { "/org/hibernate/orm/test/mapping/type/collection/custom/basic/UserPermissions.hbm.xml" }; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/UserCollectionTypeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/UserCollectionTypeTest.java similarity index 91% rename from hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/UserCollectionTypeTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/UserCollectionTypeTest.java index 356b0a4238..40bd2173be 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/UserCollectionTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/UserCollectionTypeTest.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.collection.custom.basic; +package org.hibernate.orm.test.mapping.type.collection.custom.basic; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; @@ -29,7 +29,7 @@ public abstract class UserCollectionTypeTest extends BaseNonConfigCoreFunctional @Test public void testBasicOperation() { - User u = new User("max"); + User u = new User( 1, "max" ); inTransaction( s -> { u.getEmailAddresses().add( new Email("max@hibernate.org") ); @@ -53,7 +53,7 @@ public abstract class UserCollectionTypeTest extends BaseNonConfigCoreFunctional inTransaction( s -> { - User u2 = s.get( User.class, u.getUserName() ); + User u2 = s.get( User.class, u.getId() ); u2.getEmailAddresses().size(); assertEquals( 2, MyListType.lastInstantiationRequest ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/UserPermissions.hbm.xml b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/UserPermissions.hbm.xml similarity index 78% rename from hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/UserPermissions.hbm.xml rename to hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/UserPermissions.hbm.xml index 74bef55376..02e3ab823a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/custom/basic/UserPermissions.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/type/collection/custom/basic/UserPermissions.hbm.xml @@ -13,13 +13,14 @@ This mapping is a basic example of how to write a UserCollectionType. --> - + - - + + +