Skip to content

Commit bfa7102

Browse files
committed
HHH-19544 Allow plugging a different cache implementation for internal needs
1 parent c6f5398 commit bfa7102

File tree

8 files changed

+194
-10
lines changed

8 files changed

+194
-10
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.internal.util.cache;
6+
7+
import java.util.function.Function;
8+
9+
/**
10+
* Contract for internal caches.
11+
* We call it "internal" Cache to disambiguate from the canonical entity 2LC, as that one is more commonly
12+
* used by users and therefore of public knowledge.
13+
* We highly prefer caches to be implemented by reputable caching libraries, this reduces our maintenance complexity
14+
* (as maintaining a proper cache implementation is not at all trivial) and allows for people to experiment with
15+
* various algorithms, including state-of-the-art that we might not be familiar with.
16+
* For these reasons, we rely on this internal interface and encourage plugging in an external implementation;
17+
* at the time of writing this we'll have a legacy implementation for backwards compatibility reasons but the general
18+
* idea is to deprecate it and eventually require a third party implementation.
19+
*/
20+
public interface InternalCache<K, V> {
21+
22+
/**
23+
* @return An estimate of the number of values contained in the cache.
24+
*/
25+
int heldElementsEstimate();
26+
27+
/**
28+
* Attempt to read from the cache. Will return null on cache miss.
29+
* It would typically be better to use {@link #computeIfAbsent(Object, Function)} instead.
30+
* @param key
31+
* @return
32+
*/
33+
V get(K key);
34+
35+
/**
36+
* Stores a key/value pair into the cache. Storage is not guaranteed, as the implementation
37+
* has liberty to cap internal storage or use various eviction strategies.
38+
* @param key
39+
* @param value
40+
*/
41+
void put(K key, V value);
42+
43+
/**
44+
* Attempts to clear the content of the cache. Note that in some cache implementations this
45+
* is not a trivial operation and should not be used on a performance critical path.
46+
* Also note that thorough cleanup is not guaranteed:
47+
* in some implementations it's a "best effort" strategy, or could be ignored altogether.
48+
* Essentially it's useful as a hint that the client will no longer likely need to stored entries,
49+
* so to save memory, if possible.
50+
*/
51+
void clear();
52+
53+
/**
54+
* This should be the preferred main strategy to benefit from the cache: it allows to implement
55+
* the general pattern of "try to read, or produce a value and then cache it" but avoiding
56+
* efficiency issues that would be caused by accessing the cache multiple times, not least
57+
* potentially a cache stampede, and concurrent need for generating the same value.
58+
* @param key
59+
* @param mappingFunction This function will be invoked to produce the value, and store it,
60+
* if a matching existing value couldn't be loaded from the cache.
61+
* @return Either the existing value, or the return from the provided function.
62+
*/
63+
V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction);
64+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.internal.util.cache;
6+
7+
import org.hibernate.service.Service;
8+
9+
/**
10+
* Internal components can use this factory to create an efficient cache for internal purposes.
11+
* The implementation is pluggable, therefore the exact eviction and sizing semantics are unspecified
12+
* and responsibility of the implementation.
13+
*/
14+
public interface InternalCacheFactory extends Service {
15+
16+
<K,V> InternalCache<K,V> createInternalCache(int intendedApproximateSize);
17+
18+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.internal.util.cache;
6+
7+
final class InternalCacheFactoryImpl implements InternalCacheFactory {
8+
9+
@Override
10+
public <K, V> InternalCache<K, V> createInternalCache(int intendedApproximateSize) {
11+
return new LegacyInternalCacheImplementation( intendedApproximateSize );
12+
}
13+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.internal.util.cache;
6+
7+
import org.hibernate.boot.registry.StandardServiceInitiator;
8+
import org.hibernate.service.spi.ServiceRegistryImplementor;
9+
10+
import java.util.Map;
11+
12+
public class InternalCacheFactoryInitiator implements StandardServiceInitiator<InternalCacheFactory> {
13+
14+
/**
15+
* Singleton access
16+
*/
17+
public static final InternalCacheFactoryInitiator INSTANCE = new InternalCacheFactoryInitiator();
18+
19+
private InternalCacheFactoryInitiator() {}
20+
21+
@Override
22+
public InternalCacheFactory initiateService(Map<String, Object> configurationValues, ServiceRegistryImplementor registry) {
23+
return new InternalCacheFactoryImpl();
24+
}
25+
26+
@Override
27+
public Class<InternalCacheFactory> getServiceInitiated() {
28+
return InternalCacheFactory.class;
29+
}
30+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.internal.util.cache;
6+
7+
import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
8+
9+
import java.io.Serializable;
10+
import java.util.function.Function;
11+
12+
/**
13+
* An implementation of {@link InternalCache} based on the deprecated {@link BoundedConcurrentHashMap}.
14+
* @param <K>
15+
* @param <V>
16+
*/
17+
final class LegacyInternalCacheImplementation<K,V> implements InternalCache<K,V>, Serializable {
18+
19+
private final BoundedConcurrentHashMap<K,V> map;
20+
21+
public LegacyInternalCacheImplementation(int intendedApproximateSize) {
22+
map = new BoundedConcurrentHashMap<>(
23+
intendedApproximateSize, 20, BoundedConcurrentHashMap.Eviction.LIRS );
24+
}
25+
26+
@Override
27+
public int heldElementsEstimate() {
28+
return map.size();
29+
}
30+
31+
@Override
32+
public V get(K key) {
33+
return map.get( key );
34+
}
35+
36+
@Override
37+
public void put(K key, V value) {
38+
map.put( key, value );
39+
}
40+
41+
@Override
42+
public void clear() {
43+
map.clear();
44+
}
45+
46+
@Override
47+
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
48+
return map.computeIfAbsent( key, mappingFunction );
49+
}
50+
51+
}

hibernate-core/src/main/java/org/hibernate/internal/util/collections/BoundedConcurrentHashMap.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,10 @@
8484
* @param <V> the type of mapped values
8585
*
8686
* @author Doug Lea
87+
* @deprecated We shouldn't maintain a cache implementation in the Hibernate ORM codebase,
88+
* so this will be eventually removed as we encourage to plug-in external implementations for all caching needs.
8789
*/
90+
@Deprecated(forRemoval = true)
8891
public class BoundedConcurrentHashMap<K, V> extends AbstractMap<K, V>
8992
implements ConcurrentMap<K, V>, Serializable {
9093

hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
import java.util.function.Function;
99
import java.util.function.Supplier;
1010

11-
import org.hibernate.internal.util.collections.BoundedConcurrentHashMap;
11+
import org.hibernate.internal.util.cache.InternalCache;
12+
import org.hibernate.internal.util.cache.InternalCacheFactory;
1213
import org.hibernate.query.QueryLogging;
1314
import org.hibernate.query.hql.HqlTranslator;
1415
import org.hibernate.query.spi.HqlInterpretation;
@@ -37,31 +38,31 @@ public class QueryInterpretationCacheStandardImpl implements QueryInterpretation
3738
/**
3839
* the cache of the actual plans...
3940
*/
40-
private final BoundedConcurrentHashMap<Key, QueryPlan> queryPlanCache;
41+
private final InternalCache<Key, QueryPlan> queryPlanCache;
4142

4243
private final ServiceRegistry serviceRegistry;
43-
private final BoundedConcurrentHashMap<Object, HqlInterpretation<?>> hqlInterpretationCache;
44-
private final BoundedConcurrentHashMap<String, ParameterInterpretation> nativeQueryParamCache;
44+
private final InternalCache<Object, HqlInterpretation<?>> hqlInterpretationCache;
45+
private final InternalCache<String, ParameterInterpretation> nativeQueryParamCache;
4546

4647
private StatisticsImplementor statistics;
4748

4849
public QueryInterpretationCacheStandardImpl(int maxQueryPlanCount, ServiceRegistry serviceRegistry) {
4950
log.debugf( "Starting QueryInterpretationCache(%s)", maxQueryPlanCount );
50-
51-
this.queryPlanCache = new BoundedConcurrentHashMap<>( maxQueryPlanCount, 20, BoundedConcurrentHashMap.Eviction.LIRS );
52-
this.hqlInterpretationCache = new BoundedConcurrentHashMap<>( maxQueryPlanCount, 20, BoundedConcurrentHashMap.Eviction.LIRS );
53-
this.nativeQueryParamCache = new BoundedConcurrentHashMap<>( maxQueryPlanCount, 20, BoundedConcurrentHashMap.Eviction.LIRS );
51+
final InternalCacheFactory cacheFactory = serviceRegistry.requireService( InternalCacheFactory.class );
52+
this.queryPlanCache = cacheFactory.createInternalCache( maxQueryPlanCount );
53+
this.hqlInterpretationCache = cacheFactory.createInternalCache( maxQueryPlanCount );
54+
this.nativeQueryParamCache = cacheFactory.createInternalCache( maxQueryPlanCount );
5455
this.serviceRegistry = serviceRegistry;
5556
}
5657

5758
@Override
5859
public int getNumberOfCachedHqlInterpretations() {
59-
return hqlInterpretationCache.size();
60+
return hqlInterpretationCache.heldElementsEstimate();
6061
}
6162

6263
@Override
6364
public int getNumberOfCachedQueryPlans() {
64-
return queryPlanCache.size();
65+
return queryPlanCache.heldElementsEstimate();
6566
}
6667

6768
private StatisticsImplementor getStatistics() {

hibernate-core/src/main/java/org/hibernate/service/StandardServiceInitiators.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator;
2929
import org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformResolverInitiator;
3030
import org.hibernate.event.internal.EntityCopyObserverFactoryInitiator;
31+
import org.hibernate.internal.util.cache.InternalCacheFactoryInitiator;
3132
import org.hibernate.loader.ast.internal.BatchLoaderFactoryInitiator;
3233
import org.hibernate.persister.internal.PersisterClassResolverInitiator;
3334
import org.hibernate.persister.internal.PersisterFactoryInitiator;
@@ -156,6 +157,9 @@ private static List<StandardServiceInitiator<?>> buildStandardServiceInitiatorLi
156157
serviceInitiators.add( ParameterMarkerStrategyInitiator.INSTANCE );
157158
serviceInitiators.add( BatchLoaderFactoryInitiator.INSTANCE );
158159

160+
// InternalCacheFactoryService
161+
serviceInitiators.add( InternalCacheFactoryInitiator.INSTANCE );
162+
159163
serviceInitiators.trimToSize();
160164

161165
return unmodifiableList( serviceInitiators );

0 commit comments

Comments
 (0)