correct implementation of JPA SharedCacheMode for .hbm.xml

- even though I hate most of its members, I think NONE is pretty useful
- anyway we may as well make it work, since it's trivial
This commit is contained in:
Gavin 2022-12-31 11:44:32 +01:00 committed by Gavin King
parent 1db1c08d3b
commit 327342b39e
4 changed files with 69 additions and 127 deletions

View File

@ -9,9 +9,9 @@ package org.hibernate.boot.model;
/**
* An enumeration of truth values.
*
* @implNote Yes this *could* be handled with Boolean, but then
* you run into potential problems with unwanted auto-unboxing
* and {@linkplain NullPointerException NPE}.
* @implNote Sure, this could be handled with {@code Boolean}, but
* that option is vulnerable to unwanted auto-unboxing
* and {@link NullPointerException}s.
*
* @author Steve Ebersole
*/

View File

@ -1470,39 +1470,32 @@ public class EntityBinder {
return false;
}
switch ( effectiveCache.include().toLowerCase( Locale.ROOT ) ) {
case "all": {
case "all":
return true;
}
case "non-lazy": {
case "non-lazy":
return false;
}
default: {
default:
throw new AnnotationException( "Class '" + annotatedClass.getName()
+ "' has a '@Cache' with undefined option 'include=\"" + effectiveCache.include() + "\"'" );
}
}
}
private static boolean isCacheable(SharedCacheMode sharedCacheMode, Cacheable explicitCacheableAnn) {
switch (sharedCacheMode) {
case ALL: {
switch ( sharedCacheMode ) {
case ALL:
// all entities should be cached
return true;
}
case ENABLE_SELECTIVE: {
case ENABLE_SELECTIVE:
// only entities with @Cacheable(true) should be cached
return explicitCacheableAnn != null && explicitCacheableAnn.value();
}
case DISABLE_SELECTIVE: {
case DISABLE_SELECTIVE:
// only entities with @Cacheable(false) should not be cached
return explicitCacheableAnn == null || explicitCacheableAnn.value();
}
default: {
default:
// treat both NONE and UNSPECIFIED the same
return false;
}
}
}
private static String resolveCacheConcurrencyStrategy(CacheConcurrencyStrategy strategy) {
final org.hibernate.cache.spi.access.AccessType accessType = strategy.toAccessType();

View File

@ -35,6 +35,8 @@ import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.StringHelper;
import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
/**
* @author Steve Ebersole
* @author Gail Badner
@ -76,33 +78,32 @@ public class Helper {
public static Caching createCaching(JaxbHbmCacheType cacheElement) {
if ( cacheElement == null ) {
// I'd really rather this be UNKNOWN, but the annotation version resolves this to TRUE/FALSE
return new Caching( TruthValue.FALSE );
return new Caching( TruthValue.UNKNOWN );
}
final boolean cacheLazyProps = cacheElement.getInclude() == null
|| !"non-lazy".equals( cacheElement.getInclude().value() );
else {
return new Caching(
cacheElement.getRegion(),
cacheElement.getUsage(),
cacheLazyProps,
cacheElement.getInclude() == null
|| !"non-lazy".equals( cacheElement.getInclude().value() ),
TruthValue.TRUE
);
}
}
public static Caching createNaturalIdCaching(JaxbHbmNaturalIdCacheType cacheElement) {
if ( cacheElement == null ) {
return new Caching( TruthValue.UNKNOWN );
}
else {
return new Caching(
StringHelper.nullIfEmpty( cacheElement.getRegion() ),
nullIfEmpty( cacheElement.getRegion() ),
null,
false,
TruthValue.TRUE
);
}
}
public static String getPropertyAccessorName(String access, boolean isEmbedded, String defaultAccess) {
return getValue( access, isEmbedded ? "embedded" : defaultAccess );

View File

@ -96,6 +96,7 @@ import org.hibernate.boot.spi.InFlightMetadataCollector.EntityTableXref;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.NaturalIdUniqueKeyBinder;
import org.hibernate.boot.spi.SecondPass;
import org.hibernate.cache.spi.access.AccessType;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
@ -203,24 +204,20 @@ public class ModelBinder {
.addEntityBinding( rootEntityDescriptor );
switch ( hierarchySource.getHierarchyInheritanceType() ) {
case NO_INHERITANCE: {
case NO_INHERITANCE:
// nothing to do
break;
}
case DISCRIMINATED: {
case DISCRIMINATED:
bindDiscriminatorSubclassEntities( hierarchySource.getRoot(), rootEntityDescriptor );
break;
}
case JOINED: {
case JOINED:
bindJoinedSubclassEntities( hierarchySource.getRoot(), rootEntityDescriptor );
break;
}
case UNION: {
case UNION:
bindUnionSubclassEntities( hierarchySource.getRoot(), rootEntityDescriptor );
break;
}
}
}
private void bindRootEntity(EntityHierarchySourceImpl hierarchySource, RootClass rootEntityDescriptor) {
final MappingDocument mappingDocument = hierarchySource.getRoot().sourceMappingDocument();
@ -293,51 +290,42 @@ public class ModelBinder {
}
private void applyCaching(MappingDocument mappingDocument, Caching caching, RootClass rootEntityDescriptor) {
if ( caching == null || caching.getRequested() == TruthValue.UNKNOWN ) {
// see if JPA's SharedCacheMode indicates we should implicitly apply caching
//
// here we only really look for ALL. Ideally we could look at NONE too as a means
// to selectively disable all caching, but historically hbm.xml mappings were processed
// outside this concept and whether to cache or not was defined wholly by what
// is defined in the mapping document. So for backwards compatibility we
// do not consider ENABLE_SELECTIVE nor DISABLE_SELECTIVE here.
//
// Granted, ALL was not historically considered either, but I have a practical
// reason for wanting to support this... our legacy tests built using
// Configuration applied a similar logic but that capability is no longer
// accessible from Configuration
if ( isCacheEnabled( mappingDocument, caching ) ) {
rootEntityDescriptor.setCacheConcurrencyStrategy( getConcurrencyStrategy( mappingDocument, caching ).getExternalName() );
rootEntityDescriptor.setCacheRegionName( caching == null ? null : caching.getRegion() );
rootEntityDescriptor.setLazyPropertiesCacheable( caching == null || caching.isCacheLazyProperties() );
rootEntityDescriptor.setCached( true );
}
}
private static AccessType getConcurrencyStrategy(MappingDocument mappingDocument, Caching caching) {
return caching == null || caching.getAccessType() == null
? mappingDocument.getBuildingOptions().getImplicitCacheAccessType()
: caching.getAccessType();
}
private static boolean isCacheEnabled(MappingDocument mappingDocument, Caching caching) {
switch ( mappingDocument.getBuildingOptions().getSharedCacheMode() ) {
case UNSPECIFIED:
case ENABLE_SELECTIVE:
// this is default behavior for hbm.xml
return caching != null && caching.getRequested().toBoolean(false);
case NONE:
// this option is actually really useful
return false;
case ALL:
caching = new Caching(
null,
mappingDocument.getBuildingOptions().getImplicitCacheAccessType(),
false,
TruthValue.UNKNOWN
);
break;
case NONE: // Ideally we'd disable all caching...
case ENABLE_SELECTIVE: // this is default behavior for hbm.xml
case DISABLE_SELECTIVE: // really makes no sense for hbm.xml
default: // null or UNSPECIFIED, nothing to do. IMO for hbm.xml this is equivalent to ENABLE_SELECTIVE
// do nothing
// goes completely against the whole ideology we have for
// caching, and so it hurts me to support it here
return true;
case DISABLE_SELECTIVE:
// makes no sense for hbm.xml, and also goes against our
// ideology, and so it hurts me to support it here
return caching == null || caching.getRequested().toBoolean(true);
default:
throw new AssertionFailure( "unknown SharedCacheMode" );
}
}
if ( caching == null || caching.getRequested() == TruthValue.FALSE ) {
return;
}
if ( caching.getAccessType() != null ) {
rootEntityDescriptor.setCacheConcurrencyStrategy( caching.getAccessType().getExternalName() );
}
else {
rootEntityDescriptor.setCacheConcurrencyStrategy( mappingDocument.getBuildingOptions().getImplicitCacheAccessType().getExternalName() );
}
rootEntityDescriptor.setCacheRegionName( caching.getRegion() );
rootEntityDescriptor.setLazyPropertiesCacheable( caching.isCacheLazyProperties() );
rootEntityDescriptor.setCached( caching.getRequested() != TruthValue.UNKNOWN );
}
private void bindEntityIdentifier(
MappingDocument mappingDocument,
EntityHierarchySourceImpl hierarchySource,
@ -1553,50 +1541,10 @@ public class ModelBinder {
}
private void applyCaching(MappingDocument mappingDocument, Caching caching, Collection collection) {
if ( caching == null || caching.getRequested() == TruthValue.UNKNOWN ) {
// see if JPA's SharedCacheMode indicates we should implicitly apply caching
switch ( mappingDocument.getBuildingOptions().getSharedCacheMode() ) {
case ALL: {
caching = new Caching(
null,
mappingDocument.getBuildingOptions().getImplicitCacheAccessType(),
false,
TruthValue.UNKNOWN
);
break;
if ( isCacheEnabled( mappingDocument, caching ) ) {
collection.setCacheConcurrencyStrategy( getConcurrencyStrategy( mappingDocument, caching ).getExternalName() );
collection.setCacheRegionName( caching == null ? null : caching.getRegion() );
}
case NONE: {
// Ideally we'd disable all caching...
break;
}
case ENABLE_SELECTIVE: {
// this is default behavior for hbm.xml
break;
}
case DISABLE_SELECTIVE: {
// really makes no sense for hbm.xml
break;
}
default: {
// null or UNSPECIFIED, nothing to do. IMO for hbm.xml this is equivalent
// to ENABLE_SELECTIVE
break;
}
}
}
if ( caching == null || caching.getRequested() == TruthValue.FALSE ) {
return;
}
if ( caching.getAccessType() != null ) {
collection.setCacheConcurrencyStrategy( caching.getAccessType().getExternalName() );
}
else {
collection.setCacheConcurrencyStrategy( mappingDocument.getBuildingOptions().getImplicitCacheAccessType().getExternalName() );
}
collection.setCacheRegionName( caching.getRegion() );
// collection.setCachingExplicitlyRequested( caching.getRequested() != TruthValue.UNKNOWN );
}
private Identifier determineTable(