diff --git a/hibernate-models-common/src/main/java/org/hibernate/models/internal/StringHelper.java b/hibernate-models-common/src/main/java/org/hibernate/models/internal/StringHelper.java index 1d59d04..6ed69a4 100644 --- a/hibernate-models-common/src/main/java/org/hibernate/models/internal/StringHelper.java +++ b/hibernate-models-common/src/main/java/org/hibernate/models/internal/StringHelper.java @@ -10,6 +10,7 @@ * @author Steve Ebersole */ public class StringHelper { + public static final String[] EMPTY_STRINGS = new String[0]; public static boolean isEmpty(String string) { return string == null || string.isEmpty(); diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/AbstractManagedTypeMetadata.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/AbstractManagedTypeMetadata.java index 03ecffe..a790fbb 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/AbstractManagedTypeMetadata.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/AbstractManagedTypeMetadata.java @@ -17,10 +17,10 @@ import org.hibernate.boot.model.source.spi.AttributeRole; import org.hibernate.boot.model.source.spi.NaturalIdMutability; import org.hibernate.models.internal.IndexedConsumer; -import org.hibernate.models.orm.MultipleAttributeNaturesException; -import org.hibernate.models.orm.spi.AttributeMetadata; import org.hibernate.models.orm.HibernateAnnotations; import org.hibernate.models.orm.JpaAnnotations; +import org.hibernate.models.orm.MultipleAttributeNaturesException; +import org.hibernate.models.orm.spi.AttributeMetadata; import org.hibernate.models.orm.spi.ManagedTypeMetadata; import org.hibernate.models.orm.spi.OrmModelBuildingContext; import org.hibernate.models.source.spi.AnnotationUsage; @@ -148,11 +148,9 @@ public void forEachAttribute(IndexedConsumer consumer) { } protected List resolveAttributes() { - final List backingMembers = StandardPersistentAttributeMemberResolver.INSTANCE.resolveAttributesMembers( - classDetails, - getAccessType(), - modelContext - ); + final List backingMembers = getModelContext() + .getPersistentAttributeMemberResolver() + .resolveAttributesMembers( classDetails, getAccessType(), modelContext ); final List attributeList = arrayList( backingMembers.size() ); diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/EntityHierarchyBuilder.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/EntityHierarchyBuilder.java index 0b959ca..65e3e28 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/EntityHierarchyBuilder.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/EntityHierarchyBuilder.java @@ -86,6 +86,7 @@ private Set process( hierarchies.add( new EntityHierarchyImpl( rootEntity, defaultAccessType, + org.hibernate.cache.spi.access.AccessType.TRANSACTIONAL, typeConsumer, modelContext ) ); diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/EntityHierarchyImpl.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/EntityHierarchyImpl.java index d4822f4..017a238 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/EntityHierarchyImpl.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/EntityHierarchyImpl.java @@ -9,10 +9,15 @@ import java.util.Locale; import java.util.function.Consumer; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.NaturalIdCache; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.models.orm.JpaAnnotations; +import org.hibernate.models.orm.spi.CacheRegion; import org.hibernate.models.orm.spi.EntityHierarchy; import org.hibernate.models.orm.spi.EntityTypeMetadata; import org.hibernate.models.orm.spi.IdentifiableTypeMetadata; -import org.hibernate.models.orm.JpaAnnotations; +import org.hibernate.models.orm.spi.NaturalIdCacheRegion; import org.hibernate.models.orm.spi.OrmModelBuildingContext; import org.hibernate.models.source.spi.AnnotationUsage; import org.hibernate.models.source.spi.ClassDetails; @@ -29,12 +34,15 @@ public class EntityHierarchyImpl implements EntityHierarchy { private final EntityTypeMetadata rootEntityTypeMetadata; private final InheritanceType inheritanceType; + private final CacheRegion cacheRegion; + private final NaturalIdCacheRegion naturalIdCacheRegion; - // todo (models) : version? row-id? tenant-id? others? + // todo : version? optimistic-locking? row-id? tenant-id? others? public EntityHierarchyImpl( ClassDetails rootEntityClassDetails, jakarta.persistence.AccessType defaultAccessType, + AccessType defaultCacheAccessType, Consumer typeConsumer, OrmModelBuildingContext processingContext) { this.rootEntityTypeMetadata = new EntityTypeMetadataImpl( @@ -46,6 +54,12 @@ public EntityHierarchyImpl( ); this.inheritanceType = determineInheritanceType( rootEntityTypeMetadata ); + + final AnnotationUsage cacheAnnotation = rootEntityClassDetails.getAnnotationUsage( Cache.class ); + final AnnotationUsage naturalIdCacheAnnotation = rootEntityClassDetails.getAnnotationUsage( NaturalIdCache.class ); + + cacheRegion = new CacheRegion( cacheAnnotation, defaultCacheAccessType, rootEntityClassDetails.getName() ); + naturalIdCacheRegion = new NaturalIdCacheRegion( naturalIdCacheAnnotation, cacheRegion ); } private InheritanceType determineInheritanceType(EntityTypeMetadata root) { @@ -104,6 +118,16 @@ public InheritanceType getInheritanceType() { return inheritanceType; } + @Override + public CacheRegion getCacheRegion() { + return cacheRegion; + } + + @Override + public NaturalIdCacheRegion getNaturalIdCacheRegion() { + return naturalIdCacheRegion; + } + @Override public String toString() { return String.format( diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/EntityTypeMetadataImpl.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/EntityTypeMetadataImpl.java index 8bfc652..14bbdde 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/EntityTypeMetadataImpl.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/EntityTypeMetadataImpl.java @@ -9,21 +9,37 @@ import java.util.List; import java.util.function.Consumer; +import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.annotations.Immutable; +import org.hibernate.annotations.Proxy; +import org.hibernate.annotations.ResultCheckStyle; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLInsert; +import org.hibernate.annotations.SQLUpdate; +import org.hibernate.annotations.SelectBeforeUpdate; +import org.hibernate.annotations.Synchronize; +import org.hibernate.boot.model.CustomSql; import org.hibernate.boot.model.naming.EntityNaming; -import org.hibernate.models.internal.StringHelper; +import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; +import org.hibernate.models.orm.JpaAnnotations; import org.hibernate.models.orm.spi.AttributeMetadata; import org.hibernate.models.orm.spi.EntityHierarchy; import org.hibernate.models.orm.spi.EntityTypeMetadata; import org.hibernate.models.orm.spi.IdentifiableTypeMetadata; -import org.hibernate.models.orm.JpaAnnotations; import org.hibernate.models.orm.spi.OrmModelBuildingContext; import org.hibernate.models.source.spi.AnnotationUsage; import org.hibernate.models.source.spi.ClassDetails; import jakarta.persistence.AccessType; +import jakarta.persistence.Cacheable; +import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; -import static org.hibernate.internal.util.StringHelper.unqualify; +import static org.hibernate.models.internal.StringHelper.EMPTY_STRINGS; +import static org.hibernate.models.internal.StringHelper.isNotEmpty; +import static org.hibernate.models.internal.StringHelper.unqualify; /** * @author Steve Ebersole @@ -38,19 +54,19 @@ public class EntityTypeMetadataImpl private final List attributeList; -// private final String proxy; -// -// private final String customLoaderQueryName; -// private final String[] synchronizedTableNames; -// private final int batchSize; -// private final boolean isDynamicInsert; -// private final boolean isDynamicUpdate; -// private final boolean isSelectBeforeUpdate; -// private final CustomSql customInsert; -// private final CustomSql customUpdate; -// private final CustomSql customDelete; -// private final String discriminatorMatchValue; -// private final boolean isLazy; + private final boolean mutable; + private final boolean cacheable; + private final boolean isLazy; + private final String proxy; + private final int batchSize; + private final String discriminatorMatchValue; + private final boolean isSelectBeforeUpdate; + private final boolean isDynamicInsert; + private final boolean isDynamicUpdate; + private final CustomSql customInsert; + private final CustomSql customUpdate; + private final CustomSql customDelete; + private final String[] synchronizedTableNames; /** * This form is intended for construction of root Entity. @@ -73,60 +89,54 @@ public EntityTypeMetadataImpl( final AnnotationUsage entityAnnotation = classDetails.getAnnotationUsage( JpaAnnotations.ENTITY ); this.jpaEntityName = determineJpaEntityName( entityAnnotation, entityName ); -// this.synchronizedTableNames = determineSynchronizedTableNames(); -// this.batchSize = determineBatchSize(); -// -// this.customLoaderQueryName = determineCustomLoader(); -// this.customInsert = extractCustomSql( classDetails.getAnnotation( HibernateAnnotations.SQL_INSERT ) ); -// this.customUpdate = extractCustomSql( classDetails.getAnnotation( HibernateAnnotations.SQL_UPDATE ) ); -// this.customDelete = extractCustomSql( classDetails.getAnnotation( HibernateAnnotations.SQL_DELETE ) ); -// -// this.isDynamicInsert = decodeDynamicInsert(); -// this.isDynamicUpdate = decodeDynamicUpdate(); -// this.isSelectBeforeUpdate = decodeSelectBeforeUpdate(); -// -// // Proxy generation -// final AnnotationUsage proxyAnnotation = classDetails.getAnnotation( HibernateAnnotations.PROXY ); -// if ( proxyAnnotation != null ) { -// final AnnotationUsage.AnnotationAttributeValue lazyValue = proxyAnnotation.getAttributeValue( "lazy" ); -// if ( lazyValue != null ) { -// this.isLazy = lazyValue.asBoolean(); -// } -// else { -// this.isLazy = true; -// } -// -// if ( this.isLazy ) { -// final AnnotationUsage.AnnotationAttributeValue proxyClassValue = proxyAnnotation.getAttributeValue( "proxyClass" ); -// if ( proxyClassValue != null && !proxyClassValue.isDefaultValue() ) { -// this.proxy = proxyClassValue.asString(); -// } -// else { -// this.proxy = null; -// } -// } -// else { -// this.proxy = null; -// } -// } -// else { -// // defaults are that it is lazy and that the class itself is the proxy class -// this.isLazy = true; -// this.proxy = getName(); -// } -// -// final AnnotationUsage discriminatorValueAnnotation = classDetails.getAnnotation( JpaAnnotations.DISCRIMINATOR_VALUE ); -// if ( discriminatorValueAnnotation != null ) { -// final AnnotationUsage.AnnotationAttributeValue discriminatorValueValue = discriminatorValueAnnotation.getValueAttributeValue(); -// this.discriminatorMatchValue = discriminatorValueValue.asString(); -// } -// else { -// this.discriminatorMatchValue = null; -// } - this.attributeList = resolveAttributes(); + + this.mutable = determineMutability( classDetails, modelContext ); + this.cacheable = determineCacheability( classDetails, modelContext ); + this.synchronizedTableNames = determineSynchronizedTableNames(); + this.batchSize = determineBatchSize(); + this.isSelectBeforeUpdate = decodeSelectBeforeUpdate(); + this.isDynamicInsert = decodeDynamicInsert(); + this.isDynamicUpdate = decodeDynamicUpdate(); + this.customInsert = extractCustomSql( classDetails.getAnnotationUsage( SQLInsert.class ) ); + this.customUpdate = extractCustomSql( classDetails.getAnnotationUsage( SQLUpdate.class ) ); + this.customDelete = extractCustomSql( classDetails.getAnnotationUsage( SQLDelete.class ) ); + + //noinspection deprecation + final AnnotationUsage proxyAnnotation = classDetails.getAnnotationUsage( Proxy.class ); + if ( proxyAnnotation != null ) { + final Boolean lazyValue = proxyAnnotation.getAttributeValue( "lazy" ); + this.isLazy = lazyValue == null || lazyValue; + + if ( this.isLazy ) { + final ClassDetails proxyClassDetails = proxyAnnotation.getAttributeValue( "proxyClass" ); + if ( proxyClassDetails != null ) { + this.proxy = proxyClassDetails.getName(); + } + else { + this.proxy = null; + } + } + else { + this.proxy = null; + } + } + else { + // defaults are that it is lazy and that the class itself is the proxy class + this.isLazy = true; + this.proxy = getEntityName(); + } + + final AnnotationUsage discriminatorValueAnn = classDetails.getAnnotationUsage( DiscriminatorValue.class ); + if ( discriminatorValueAnn != null ) { + this.discriminatorMatchValue = discriminatorValueAnn.getAttributeValue( "value" ); + } + else { + this.discriminatorMatchValue = null; + } } + /** * This form is intended for construction of non-root Entity. */ @@ -148,14 +158,50 @@ public EntityTypeMetadataImpl( this.jpaEntityName = determineJpaEntityName( entityAnnotation, entityName ); this.attributeList = resolveAttributes(); - } - private String determineJpaEntityName(AnnotationUsage entityAnnotation, String entityName) { - final String name = entityAnnotation.getAttributeValue( "name" ); - if ( StringHelper.isNotEmpty( name ) ) { - return name; + this.mutable = determineMutability( classDetails, modelContext ); + this.cacheable = determineCacheability( classDetails, modelContext ); + this.synchronizedTableNames = determineSynchronizedTableNames(); + this.batchSize = determineBatchSize(); + this.isSelectBeforeUpdate = decodeSelectBeforeUpdate(); + this.isDynamicInsert = decodeDynamicInsert(); + this.isDynamicUpdate = decodeDynamicUpdate(); + this.customInsert = extractCustomSql( classDetails.getAnnotationUsage( SQLInsert.class ) ); + this.customUpdate = extractCustomSql( classDetails.getAnnotationUsage( SQLUpdate.class ) ); + this.customDelete = extractCustomSql( classDetails.getAnnotationUsage( SQLDelete.class ) ); + + //noinspection deprecation + final AnnotationUsage proxyAnnotation = classDetails.getAnnotationUsage( Proxy.class ); + if ( proxyAnnotation != null ) { + final Boolean lazyValue = proxyAnnotation.getAttributeValue( "lazy" ); + this.isLazy = lazyValue == null || lazyValue; + + if ( this.isLazy ) { + final ClassDetails proxyClassDetails = proxyAnnotation.getAttributeValue( "proxyClass" ); + if ( proxyClassDetails != null ) { + this.proxy = proxyClassDetails.getName(); + } + else { + this.proxy = null; + } + } + else { + this.proxy = null; + } + } + else { + // defaults are that it is lazy and that the class itself is the proxy class + this.isLazy = true; + this.proxy = getEntityName(); + } + + final AnnotationUsage discriminatorValueAnn = classDetails.getAnnotationUsage( DiscriminatorValue.class ); + if ( discriminatorValueAnn != null ) { + this.discriminatorMatchValue = discriminatorValueAnn.getAttributeValue( "value" ); + } + else { + this.discriminatorMatchValue = null; } - return unqualify( entityName ); } @Override @@ -163,66 +209,11 @@ protected List attributeList() { return attributeList; } -// private String determineCustomLoader() { -// final AnnotationUsage loaderAnnotation = getManagedClass().getAnnotation( HibernateAnnotations.LOADER ); -// if ( loaderAnnotation != null ) { -// final AnnotationUsage.AnnotationAttributeValue namedQueryValue = loaderAnnotation.getAttributeValue( "namedQuery" ); -// return namedQueryValue.asString(); -// } -// return null; -// } -// -// private String[] determineSynchronizedTableNames() { -// final AnnotationUsage synchronizeAnnotation = getManagedClass().getAnnotation( HibernateAnnotations.SYNCHRONIZE ); -// if ( synchronizeAnnotation != null ) { -// return synchronizeAnnotation.getValueAttributeValue().getValue(); -// } -// return StringHelper.EMPTY_STRINGS; -// } -// -// private int determineBatchSize() { -// final AnnotationUsage batchSizeAnnotation = getManagedClass().getAnnotation( HibernateAnnotations.BATCH_SIZE ); -// if ( batchSizeAnnotation != null ) { -// return batchSizeAnnotation.getAttributeValue( "size" ).asInt(); -// } -// return -1; -// } -// -// private boolean decodeDynamicInsert() { -// final AnnotationUsage dynamicInsertAnnotation = getManagedClass().getAnnotation( HibernateAnnotations.DYNAMIC_INSERT ); -// if ( dynamicInsertAnnotation == null ) { -// return false; -// } -// -// return dynamicInsertAnnotation.getValueAttributeValue().asBoolean(); -// } -// -// private boolean decodeDynamicUpdate() { -// final AnnotationUsage dynamicUpdateAnnotation = getManagedClass().getAnnotation( HibernateAnnotations.DYNAMIC_UPDATE ); -// if ( dynamicUpdateAnnotation == null ) { -// return false; -// } -// return dynamicUpdateAnnotation.getValueAttributeValue().asBoolean(); -// } -// -// private boolean decodeSelectBeforeUpdate() { -// final AnnotationUsage selectBeforeUpdateAnnotation = getManagedClass().getAnnotation( HibernateAnnotations.SELECT_BEFORE_UPDATE ); -// if ( selectBeforeUpdateAnnotation == null ) { -// return false; -// } -// return selectBeforeUpdateAnnotation.getValueAttributeValue().asBoolean(); -// } - @Override public EntityHierarchy getHierarchy() { return hierarchy; } -// @Override -// public EntityNaming getEntityNaming() { -// return this; -// } - @Override public String getEntityName() { return entityName; @@ -238,58 +229,188 @@ public String getClassName() { return getClassDetails().getClassName(); } -// @Override -// public String getCustomLoaderQueryName() { -// return customLoaderQueryName; -// } -// -// public String[] getSynchronizedTableNames() { -// return synchronizedTableNames; -// } -// -// public int getBatchSize() { -// return batchSize; -// } -// -// public boolean isDynamicInsert() { -// return isDynamicInsert; -// } -// -// public boolean isDynamicUpdate() { -// return isDynamicUpdate; -// } -// -// public boolean isSelectBeforeUpdate() { -// return isSelectBeforeUpdate; -// } -// -// public CustomSql getCustomInsert() { -// return customInsert; -// } -// -// public CustomSql getCustomUpdate() { -// return customUpdate; -// } -// -// public CustomSql getCustomDelete() { -// return customDelete; -// } -// -// public String getDiscriminatorMatchValue() { -// return discriminatorMatchValue; -// } -// -// public boolean isLazy() { -// return isLazy; -// } -// -// public String getProxy() { -// return proxy; -// } - - -// @Override -// public MetadataBuildingContext getBuildingContext() { -// return getModelContext()getModelProcessingContext().getMetadataBuildingContext(); -// } + @Override + public boolean isMutable() { + return mutable; + } + + @Override + public boolean isCacheable() { + return cacheable; + } + + @Override + public String[] getSynchronizedTableNames() { + return synchronizedTableNames; + } + + @Override + public int getBatchSize() { + return batchSize; + } + + @Override + public boolean isSelectBeforeUpdate() { + return isSelectBeforeUpdate; + } + + @Override + public boolean isDynamicInsert() { + return isDynamicInsert; + } + + @Override + public boolean isDynamicUpdate() { + return isDynamicUpdate; + } + + @Override + public CustomSql getCustomInsert() { + return customInsert; + } + + @Override + public CustomSql getCustomUpdate() { + return customUpdate; + } + + @Override + public CustomSql getCustomDelete() { + return customDelete; + } + + public String getDiscriminatorMatchValue() { + return discriminatorMatchValue; + } + + public boolean isLazy() { + return isLazy; + } + + public String getProxy() { + return proxy; + } + + + private String determineJpaEntityName(AnnotationUsage entityAnnotation, String entityName) { + final String name = entityAnnotation.getAttributeValue( "name" ); + if ( isNotEmpty( name ) ) { + return name; + } + return unqualify( entityName ); + } + + private boolean determineMutability(ClassDetails classDetails, OrmModelBuildingContext modelContext) { + final AnnotationUsage immutableAnn = classDetails.getAnnotationUsage( Immutable.class ); + return immutableAnn == null; + } + + private boolean determineCacheability( + ClassDetails classDetails, + OrmModelBuildingContext modelContext) { + final AnnotationUsage cacheableAnn = classDetails.getAnnotationUsage( Cacheable.class ); + switch ( modelContext.getSharedCacheMode() ) { + case NONE: { + return false; + } + case ALL: { + return true; + } + case DISABLE_SELECTIVE: { + // Disable caching for all `@Cacheable(false)`, enabled otherwise (including no annotation) + //noinspection RedundantIfStatement + if ( cacheableAnn == null || cacheableAnn.getBoolean( "value" ) ) { + // not disabled + return true; + } + else { + // disable, there was an explicit `@Cacheable(false)` + return false; + } + } + default: { + // ENABLE_SELECTIVE + // UNSPECIFIED + + // Enable caching for all `@Cacheable(true)`, disable otherwise (including no annotation) + //noinspection RedundantIfStatement + if ( cacheableAnn != null && cacheableAnn.getBoolean( "value" ) ) { + // enable, there was an explicit `@Cacheable(true)` + return true; + } + else { + return false; + } + } + } + } + + /** + * Build a CustomSql reference from {@link org.hibernate.annotations.SQLInsert}, + * {@link org.hibernate.annotations.SQLUpdate}, {@link org.hibernate.annotations.SQLDelete} + * or {@link org.hibernate.annotations.SQLDeleteAll} annotations + */ + public static CustomSql extractCustomSql(AnnotationUsage customSqlAnnotation) { + if ( customSqlAnnotation == null ) { + return null; + } + + final String sql = customSqlAnnotation.getAttributeValue( "sql" ); + final boolean isCallable = customSqlAnnotation.getAttributeValue( "value" ); + + final ResultCheckStyle checkValue = customSqlAnnotation.getAttributeValue( "check" ); + final ExecuteUpdateResultCheckStyle checkStyle; + if ( checkValue == null ) { + checkStyle = isCallable + ? ExecuteUpdateResultCheckStyle.NONE + : ExecuteUpdateResultCheckStyle.COUNT; + } + else { + checkStyle = ExecuteUpdateResultCheckStyle.fromResultCheckStyle( checkValue ); + } + + return new CustomSql( sql, isCallable, checkStyle ); + } + + private String[] determineSynchronizedTableNames() { + final AnnotationUsage synchronizeAnnotation = getClassDetails().getAnnotationUsage( Synchronize.class ); + if ( synchronizeAnnotation != null ) { + return synchronizeAnnotation.getList( "value" ).toArray( new String[0] ); + } + return EMPTY_STRINGS; + } + + private int determineBatchSize() { + final AnnotationUsage batchSizeAnnotation = getClassDetails().getAnnotationUsage( BatchSize.class ); + if ( batchSizeAnnotation != null ) { + return batchSizeAnnotation.getAttributeValue( "size" ); + } + return -1; + } + + private boolean decodeSelectBeforeUpdate() { + //noinspection deprecation + final AnnotationUsage selectBeforeUpdateAnnotation = getClassDetails().getAnnotationUsage( SelectBeforeUpdate.class ); + if ( selectBeforeUpdateAnnotation == null ) { + return false; + } + return selectBeforeUpdateAnnotation.getBoolean( "value" ); + } + + private boolean decodeDynamicInsert() { + final AnnotationUsage dynamicInsertAnnotation = getClassDetails().getAnnotationUsage( DynamicInsert.class ); + if ( dynamicInsertAnnotation == null ) { + return false; + } + + return dynamicInsertAnnotation.getBoolean( "value" ); + } + + private boolean decodeDynamicUpdate() { + final AnnotationUsage dynamicUpdateAnnotation = getClassDetails().getAnnotationUsage( DynamicUpdate.class ); + if ( dynamicUpdateAnnotation == null ) { + return false; + } + return dynamicUpdateAnnotation.getBoolean( "value" ); + } } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/OrmModelBuildingContextImpl.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/OrmModelBuildingContextImpl.java index d897b16..c1aa430 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/OrmModelBuildingContextImpl.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/OrmModelBuildingContextImpl.java @@ -15,6 +15,8 @@ import org.jboss.jandex.IndexView; +import jakarta.persistence.SharedCacheMode; + /** * @author Steve Ebersole */ @@ -24,6 +26,7 @@ public class OrmModelBuildingContextImpl implements OrmModelBuildingContext { private final ClassLoading classLoading; private final IndexView jandexIndex; private final ClassmateContext classmateContext; + private final SharedCacheMode sharedCacheMode; public OrmModelBuildingContextImpl( SourceModelBuildingContext sourceModelBuildingContext, @@ -33,7 +36,8 @@ public OrmModelBuildingContextImpl( sourceModelBuildingContext.getAnnotationDescriptorRegistry(), sourceModelBuildingContext.getClassLoading(), sourceModelBuildingContext.getJandexIndex(), - classmateContext + classmateContext, + SharedCacheMode.UNSPECIFIED ); } @@ -42,12 +46,14 @@ public OrmModelBuildingContextImpl( AnnotationDescriptorRegistry annotationDescriptorRegistry, ClassLoading classLoading, IndexView jandexIndex, - ClassmateContext classmateContext) { + ClassmateContext classmateContext, + SharedCacheMode sharedCacheMode) { this.classDetailsRegistry = classDetailsRegistry; this.annotationDescriptorRegistry = annotationDescriptorRegistry; this.classLoading = classLoading; this.jandexIndex = jandexIndex; this.classmateContext = classmateContext; + this.sharedCacheMode = sharedCacheMode; } @Override @@ -74,4 +80,9 @@ public IndexView getJandexIndex() { public ClassmateContext getClassmateContext() { return classmateContext; } + + @Override + public SharedCacheMode getSharedCacheMode() { + return sharedCacheMode; + } } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/CacheRegion.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/CacheRegion.java new file mode 100644 index 0000000..fa3dcc2 --- /dev/null +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/CacheRegion.java @@ -0,0 +1,108 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.orm.spi; + +import org.hibernate.annotations.Cache; +import org.hibernate.boot.CacheRegionDefinition; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.models.source.spi.AnnotationUsage; + +/** + * Models the caching options for an entity, natural-id, or collection. + * + * @author Steve Ebersole + * @author Hardy Ferentschik + */ +public class CacheRegion { + private String regionName; + private AccessType accessType; + private boolean cacheLazyProperties; + + public CacheRegion( + AnnotationUsage cacheAnnotation, + AccessType implicitCacheAccessType, + String implicitRegionName) { + if ( cacheAnnotation == null ) { + regionName = implicitRegionName; + accessType = implicitCacheAccessType; + cacheLazyProperties = true; + } + else { + final String explicitRegionName = cacheAnnotation.getString( "region" ); + regionName = StringHelper.isEmpty( explicitRegionName ) ? implicitRegionName : explicitRegionName; + + accessType = cacheAnnotation.getAttributeValue( "usage" ); + + final Boolean explicitIncludeLazy = cacheAnnotation.getBoolean( "includeLazy" ); + if ( explicitIncludeLazy != null ) { + cacheLazyProperties = explicitIncludeLazy; + } + else { + final String include = cacheAnnotation.getAttributeValue( "include" ); + assert "all".equals( include ) || "non-lazy".equals( include ); + cacheLazyProperties = include.equals( "all" ); + } + } + } + + public String getRegionName() { + return regionName; + } + + public void setRegionName(String regionName) { + this.regionName = regionName; + } + + public AccessType getAccessType() { + return accessType; + } + + public void setAccessType(AccessType accessType) { + this.accessType = accessType; + } + + public boolean isCacheLazyProperties() { + return cacheLazyProperties; + } + + public void setCacheLazyProperties(boolean cacheLazyProperties) { + this.cacheLazyProperties = cacheLazyProperties; + } + + public void overlay(CacheRegionDefinition overrides) { + if ( overrides == null ) { + return; + } + + accessType = AccessType.fromExternalName( overrides.getUsage() ); + if ( StringHelper.isEmpty( overrides.getRegion() ) ) { + regionName = overrides.getRegion(); + } + // ugh, primitive boolean + cacheLazyProperties = overrides.isCacheLazy(); + } + + public void overlay(CacheRegion overrides) { + if ( overrides == null ) { + return; + } + + this.accessType = overrides.accessType; + this.regionName = overrides.regionName; + this.cacheLazyProperties = overrides.cacheLazyProperties; + } + + @Override + public String toString() { + return "Caching{" + + "region='" + regionName + '\'' + + ", accessType=" + accessType + + ", cacheLazyProperties=" + cacheLazyProperties + '}'; + } + +} diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/EntityHierarchy.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/EntityHierarchy.java index 9de5c13..13544f7 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/EntityHierarchy.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/EntityHierarchy.java @@ -24,4 +24,14 @@ public interface EntityHierarchy { * The inheritance strategy for the hierarchy. */ InheritanceType getInheritanceType(); + + /** + * The caching configuration for entities in this hierarchy. + */ + CacheRegion getCacheRegion(); + + /** + * The caching configuration for this hierarchy's {@linkplain org.hibernate.annotations.NaturalId natural-id} + */ + NaturalIdCacheRegion getNaturalIdCacheRegion(); } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/EntityTypeMetadata.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/EntityTypeMetadata.java index 747891b..3b69506 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/EntityTypeMetadata.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/EntityTypeMetadata.java @@ -6,12 +6,84 @@ */ package org.hibernate.models.orm.spi; +import org.hibernate.boot.model.CustomSql; + /** * Metadata about an {@linkplain jakarta.persistence.metamodel.EntityType entity type} * * @author Steve Ebersole */ public interface EntityTypeMetadata extends IdentifiableTypeMetadata { + /** + * The Hibernate notion of entity-name, used for dynamic models + */ String getEntityName(); + + /** + * The JPA notion of entity-name, used for HQL references (import) + */ String getJpaEntityName(); + + /** + * Whether the state of the entity is written to the database (mutable) or not (immutable) + */ + boolean isMutable(); + + /** + * Whether this entity is cacheable. + * + * @see jakarta.persistence.Cacheable + * @see org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor#getSharedCacheMode() + */ + boolean isCacheable(); + + /** + * Any tables to which this entity maps that Hibernate does not know about. + * + * @see org.hibernate.annotations.View + * @see org.hibernate.annotations.Subselect + */ + String[] getSynchronizedTableNames(); + + /** + * A size to use for the entity with batch loading + */ + int getBatchSize(); + + /** + * Whether to perform a select prior to performing a {@linkplain org.hibernate.Session#update} + * + * @deprecated Because {@linkplain org.hibernate.Session#update} itself is deprecated + */ + @Deprecated + boolean isSelectBeforeUpdate(); + + /** + * Whether to perform dynamic inserts. + * + * @see org.hibernate.annotations.DynamicInsert + */ + boolean isDynamicInsert(); + + /** + * Whether to perform dynamic updates. + * + * @see org.hibernate.annotations.DynamicUpdate + */ + boolean isDynamicUpdate(); + + /** + * Custom SQL to perform an INSERT of this entity + */ + CustomSql getCustomInsert(); + + /** + * Custom SQL to perform an UPDATE of this entity + */ + CustomSql getCustomUpdate(); + + /** + * Custom SQL to perform an DELETE of this entity + */ + CustomSql getCustomDelete(); } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/NaturalIdCacheRegion.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/NaturalIdCacheRegion.java new file mode 100644 index 0000000..b8714be --- /dev/null +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/NaturalIdCacheRegion.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.orm.spi; + +import org.hibernate.annotations.NaturalIdCache; +import org.hibernate.models.internal.StringHelper; +import org.hibernate.models.source.spi.AnnotationUsage; + +/** + * Details about caching related to the natural-id of an entity + * + * @see CacheRegion + * + * @author Steve Ebersole + */ +public class NaturalIdCacheRegion { + private final String regionName; + + public NaturalIdCacheRegion(AnnotationUsage cacheAnnotation, CacheRegion cacheRegion) { + this.regionName = determineRegionName( cacheAnnotation, cacheRegion ); + } + + private static String determineRegionName(AnnotationUsage cacheAnnotation, CacheRegion cacheRegion) { + if ( cacheAnnotation != null ) { + final String explicitRegionName = cacheAnnotation.getAttributeValue( "region" ); + if ( StringHelper.isNotEmpty( explicitRegionName ) ) { + return explicitRegionName; + } + } + + // use the default value + return cacheRegion.getRegionName() + "##NaturalId"; + } + + public String getRegionName() { + return regionName; + } +} diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/OrmModelBuildingContext.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/OrmModelBuildingContext.java index b459c76..f76ac01 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/OrmModelBuildingContext.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/OrmModelBuildingContext.java @@ -7,12 +7,15 @@ package org.hibernate.models.orm.spi; import org.hibernate.boot.internal.ClassmateContext; +import org.hibernate.models.orm.internal.StandardPersistentAttributeMemberResolver; import org.hibernate.models.source.spi.AnnotationDescriptorRegistry; import org.hibernate.models.source.spi.ClassDetailsRegistry; import org.hibernate.models.spi.ClassLoading; import org.jboss.jandex.IndexView; +import jakarta.persistence.SharedCacheMode; + /** * Contextual information used while building {@linkplain ManagedTypeMetadata} and friends. * @@ -28,4 +31,11 @@ public interface OrmModelBuildingContext { IndexView getJandexIndex(); ClassmateContext getClassmateContext(); + + default PersistentAttributeMemberResolver getPersistentAttributeMemberResolver() { + return StandardPersistentAttributeMemberResolver.INSTANCE; + } + + SharedCacheMode getSharedCacheMode(); + } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/Processor.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/Processor.java index b803fa7..320c095 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/Processor.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/Processor.java @@ -44,6 +44,7 @@ import org.jboss.jandex.IndexView; import jakarta.persistence.Embeddable; +import jakarta.persistence.SharedCacheMode; import static org.hibernate.models.orm.internal.EntityHierarchyBuilder.createEntityHierarchies; import static org.hibernate.models.orm.xml.internal.ManagedTypeProcessor.processOverrideEmbeddable; @@ -59,8 +60,17 @@ */ public class Processor { public interface Options { - boolean areGeneratorsGlobal(); - boolean shouldIgnoreUnlistedClasses(); + default boolean areGeneratorsGlobal() { + return false; + } + + default boolean shouldIgnoreUnlistedClasses() { + return false; + } + + default SharedCacheMode getSharedCacheMode() { + return SharedCacheMode.UNSPECIFIED; + } } public static ProcessResult process( @@ -70,17 +80,7 @@ public static ProcessResult process( return process( managedResources, explicitlyListedClasses, - new Options() { - @Override - public boolean areGeneratorsGlobal() { - return false; - } - - @Override - public boolean shouldIgnoreUnlistedClasses() { - return false; - } - }, + new Options() { }, sourceModelBuildingContext ); } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/xml/internal/AttributeProcessor.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/xml/internal/AttributeProcessor.java index 6e49bce..e65999e 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/xml/internal/AttributeProcessor.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/xml/internal/AttributeProcessor.java @@ -6,12 +6,14 @@ */ package org.hibernate.models.orm.xml.internal; +import org.hibernate.annotations.Bag; import org.hibernate.annotations.CollectionId; import org.hibernate.annotations.Fetch; import org.hibernate.annotations.Nationalized; import org.hibernate.annotations.SortComparator; import org.hibernate.annotations.SortNatural; import org.hibernate.boot.internal.CollectionClassification; +import org.hibernate.boot.internal.LimitedCollectionClassification; import org.hibernate.boot.internal.Target; import org.hibernate.boot.jaxb.mapping.spi.JaxbAnyMappingImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbAttributesContainer; @@ -485,8 +487,13 @@ public static MutableMemberDetails processElementCollectionAttribute( memberDetails ); setIf( jaxbElementCollection.getClassification(), "value", collectionClassificationAnn ); + if ( jaxbElementCollection.getClassification() == LimitedCollectionClassification.BAG ) { + getOrMakeAnnotation( Bag.class, memberDetails ); + } } + XmlAnnotationHelper.applyCollectionUserType( jaxbElementCollection.getCollectionType(), memberDetails, sourceModelBuildingContext ); + if ( StringHelper.isNotEmpty( jaxbElementCollection.getSort() ) ) { final MutableAnnotationUsage sortAnn = getOrMakeAnnotation( SortComparator.class, diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/xml/internal/XmlAnnotationHelper.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/xml/internal/XmlAnnotationHelper.java index 3421dac..72a924f 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/xml/internal/XmlAnnotationHelper.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/xml/internal/XmlAnnotationHelper.java @@ -15,6 +15,7 @@ import java.util.UUID; import org.hibernate.annotations.AttributeAccessor; +import org.hibernate.annotations.CollectionType; import org.hibernate.annotations.Formula; import org.hibernate.annotations.JavaType; import org.hibernate.annotations.JdbcType; @@ -32,6 +33,7 @@ import org.hibernate.boot.jaxb.mapping.spi.JaxbBasicImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbBasicMapping; import org.hibernate.boot.jaxb.mapping.spi.JaxbCachingImpl; +import org.hibernate.boot.jaxb.mapping.spi.JaxbCollectionUserTypeImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbColumnImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbConfigurationParameterImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbConvertImpl; @@ -258,12 +260,12 @@ public static void applyUserType( final DynamicAnnotationUsage typeAnn = new DynamicAnnotationUsage<>( Type.class, memberDetails ); memberDetails.addAnnotationUsage( typeAnn ); - final ClassDetails userTypeImpl = resolveJavaType( jaxbType.getValue(), buildingContext ); + final ClassDetails userTypeImpl = resolveJavaType( jaxbType.getType(), buildingContext ); typeAnn.setAttributeValue( "value", userTypeImpl ); typeAnn.setAttributeValue( "parameters", collectParameters( jaxbType.getParameters(), memberDetails ) ); } - private static List> collectParameters( + public static List> collectParameters( List jaxbParameters, AnnotationTarget target) { if ( CollectionHelper.isEmpty( jaxbParameters ) ) { @@ -280,6 +282,22 @@ private static List> collectParameters( return parameterAnnList; } + public static void applyCollectionUserType( + JaxbCollectionUserTypeImpl jaxbType, + MutableMemberDetails memberDetails, + SourceModelBuildingContext buildingContext) { + if ( jaxbType == null ) { + return; + } + + final DynamicAnnotationUsage typeAnn = new DynamicAnnotationUsage<>( CollectionType.class, memberDetails ); + memberDetails.addAnnotationUsage( typeAnn ); + + final ClassDetails userTypeImpl = resolveJavaType( jaxbType.getType(), buildingContext ); + typeAnn.setAttributeValue( "type", userTypeImpl ); + typeAnn.setAttributeValue( "parameters", collectParameters( jaxbType.getParameters(), memberDetails ) ); + } + public static void applyTargetClass( String name, MutableMemberDetails memberDetails, diff --git a/hibernate-models-source/hibernate-models-source.gradle b/hibernate-models-source/hibernate-models-source.gradle index d3b5ffa..cb0550a 100644 --- a/hibernate-models-source/hibernate-models-source.gradle +++ b/hibernate-models-source/hibernate-models-source.gradle @@ -14,7 +14,6 @@ dependencies { testImplementation platform( libs.hibernatePlatform ) testImplementation project( ":hibernate-models-testing" ) - testImplementation project( ":hibernate-models-orm" ) testImplementation libs.hibernateCore testRuntimeOnly testLibs.log4j diff --git a/hibernate-orm/src/main/resources/org/hibernate/xsd/mapping/mapping-3.1.0.xsd b/hibernate-orm/src/main/resources/org/hibernate/xsd/mapping/mapping-3.1.0.xsd index 7330fab..ba44010 100644 --- a/hibernate-orm/src/main/resources/org/hibernate/xsd/mapping/mapping-3.1.0.xsd +++ b/hibernate-orm/src/main/resources/org/hibernate/xsd/mapping/mapping-3.1.0.xsd @@ -831,6 +831,7 @@ See `jakarta.persistence.MapKeyClass` See `jakarta.persistence.MapKeyColumn` (other MapKey* annotations) + See `@org.hibernate.annotations.CollectionType See `@org.hibernate.annotations.AttributeAccessor` See `@org.hibernate.annotations.OptimisticLock` See `@org.hibernate.annotations.SortComparator` @@ -845,42 +846,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1818,8 +1783,22 @@ + + + + See `@org.hibernate.annotations.CollectionType` + + + + + + + + + + @@ -3071,7 +3050,7 @@ - + @@ -3097,5 +3076,4 @@ - diff --git a/todos.adoc b/todos.adoc index 4e17ad0..d006954 100644 --- a/todos.adoc +++ b/todos.adoc @@ -3,8 +3,26 @@ == Open Questions * Hierarchical packages? E.g. should things defined on the `com.acme` package apply to things in the `com.acme.model` package? Should `PackageDetails` have reference to its "parent" `PackageDetails`? -* Allow mapped-superclass in dynamic models? +* Allow in dynamic models? * Support for `@Comment` +* `@LazyGroup` in XSD? +* `@ListIndexBase` in XSD? +* `@Mutability` in XSD? +* `@OnDelete` +* `@Parent` +* `@PartitionKey` +* `@RowId` +* `@SecondaryRow` +* `@SQLInsert`, `@SQLUpdate` and `@SQLDelete` for secondary tables in XSD +* `@SoftDelete` +* `@Struct` +* `@TenantId` +* `@TimeZoneStorage` +* `@TimeZoneColumn` +* `@CreationTimestamp` +* `@UpdateTimestamp` +* `@View` +* generated values == XML Annotations