| /* |
| * Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation |
| // 05/19/2014-2.6 Tomas Kraus |
| // - 437578: Added cacheable field and updated setting isolation from parent. |
| package org.eclipse.persistence.descriptors; |
| |
| import java.io.Serializable; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.persistence.annotations.CacheCoordinationType; |
| import org.eclipse.persistence.annotations.CacheKeyType; |
| import org.eclipse.persistence.annotations.DatabaseChangeNotificationType; |
| import org.eclipse.persistence.config.CacheIsolationType; |
| import org.eclipse.persistence.exceptions.DescriptorException; |
| import org.eclipse.persistence.exceptions.ValidationException; |
| import org.eclipse.persistence.expressions.Expression; |
| import org.eclipse.persistence.internal.helper.ClassConstants; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.identitymaps.CacheId; |
| import org.eclipse.persistence.internal.identitymaps.CacheKey; |
| import org.eclipse.persistence.internal.identitymaps.IdentityMap; |
| import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.sessions.ObjectChangeSet; |
| import org.eclipse.persistence.logging.SessionLog; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| import org.eclipse.persistence.sessions.DatabaseSession; |
| import org.eclipse.persistence.sessions.interceptors.CacheInterceptor; |
| |
| /** |
| * <p><b>Purpose</b>: |
| * CachePolicy defines the cache configuration. |
| * EclipseLink supports an integrated shared (L2) object cache. |
| * Caching is enabled by default.<br> |
| * To disable caching use:<br> |
| * <code>setCacheIsolation(CacheIsolationType.ISOLATED)</code> |
| * |
| * @see ClassDescriptor |
| */ |
| public class CachePolicy implements Cloneable, Serializable { |
| protected Class<? extends IdentityMap> identityMapClass; |
| protected int identityMapSize; |
| protected boolean shouldAlwaysRefreshCache; |
| protected boolean shouldOnlyRefreshCacheIfNewerVersion; |
| protected boolean shouldDisableCacheHits; |
| |
| protected Class<? extends IdentityMap> remoteIdentityMapClass; |
| protected int remoteIdentityMapSize; |
| protected boolean shouldAlwaysRefreshCacheOnRemote; |
| protected boolean shouldDisableCacheHitsOnRemote; |
| |
| // Used only to evaluate cache isolation type in this class methods. |
| /** |
| * Entity @Cacheable annotation value. |
| * This value contains Boolean value equal to annotation value or null when |
| * no annotation was set for entity. Parent values are ignored, value refers |
| * to current class only. |
| * This value is set only when SharedCacheMode allows to override caching |
| * on entity level (DISABLE_SELECTIVE or ENABLE_SELECTIVE). Default value |
| * is <code>null</code> what means no annotation is present in current class. |
| */ |
| private Boolean cacheable = null; |
| // this attribute is used to determine what classes should be isolated from the shared cache |
| // and the severity of their isolation. |
| protected CacheIsolationType cacheIsolation; |
| |
| /** Configures how objects will be sent via cache synchronization, if synchronization is enabled. */ |
| protected int cacheSynchronizationType = UNDEFINED_OBJECT_CHANGE_BEHAVIOR; |
| public static final int UNDEFINED_OBJECT_CHANGE_BEHAVIOR = 0; |
| public static final int SEND_OBJECT_CHANGES = 1; |
| public static final int INVALIDATE_CHANGED_OBJECTS = 2; |
| public static final int SEND_NEW_OBJECTS_WITH_CHANGES = 3; |
| public static final int DO_NOT_SEND_CHANGES = 4; |
| |
| /** Configures how the unit of work uses the session cache. */ |
| protected int unitOfWorkCacheIsolationLevel = UNDEFINED_ISOLATATION; |
| /* Required to resolve initialization. */ |
| protected boolean wasDefaultUnitOfWorkCacheIsolationLevel; |
| public static final int UNDEFINED_ISOLATATION = -1; |
| public static final int USE_SESSION_CACHE_AFTER_TRANSACTION = 0; |
| public static final int ISOLATE_NEW_DATA_AFTER_TRANSACTION = 1; // this is the default behaviour even when undefined. |
| public static final int ISOLATE_CACHE_AFTER_TRANSACTION = 2; |
| public static final int ISOLATE_FROM_CLIENT_SESSION = 3; // Entity Instances only exist in UOW and shared cache. |
| public static final int ISOLATE_CACHE_ALWAYS = 4; |
| |
| /** Allow cache key type to be configured. */ |
| protected CacheKeyType cacheKeyType; |
| |
| //Added for interceptor support. |
| protected Class<? extends CacheInterceptor> cacheInterceptorClass; |
| //Added for interceptor support. |
| protected String cacheInterceptorClassName; |
| |
| /** This flag controls how the MergeManager should merge an Entity when merging into the shared cache.*/ |
| protected boolean fullyMergeEntity; |
| |
| /** |
| * In certain cases and cache types it is more efficient to preFetch the cache keys from the cache when |
| * building the results of the query. Set this flag to true to prefetch the results. |
| */ |
| protected boolean prefetchCacheKeys; |
| |
| protected Map<List<DatabaseField>, CacheIndex> cacheIndexes; |
| |
| /** Allows configuration of database change event notification. */ |
| protected DatabaseChangeNotificationType databaseChangeNotificationType; |
| |
| /** |
| * PUBLIC: |
| * Return a new descriptor. |
| */ |
| public CachePolicy() { |
| this.identityMapSize = -1; |
| this.remoteIdentityMapSize = -1; |
| } |
| |
| /** |
| * Returns what type of database change notification an entity/descriptor should use. |
| * This is only relevant if the persistence unit/session has been configured with a DatabaseEventListener, |
| * such as the OracleChangeNotificationListener that receives database change events. |
| * This allows for the EclipseLink cache to be invalidated or updated from database changes. |
| */ |
| public DatabaseChangeNotificationType getDatabaseChangeNotificationType() { |
| return databaseChangeNotificationType; |
| } |
| |
| /** |
| * Configures what type of database change notification an entity/descriptor should use. |
| * This is only relevant if the persistence unit/session has been configured with a DatabaseEventListener, |
| * such as the OracleChangeNotificationListener that receives database change events. |
| * This allows for the EclipseLink cache to be invalidated or updated from database changes. |
| */ |
| public void setDatabaseChangeNotificationType(DatabaseChangeNotificationType databaseChangeNotificationType) { |
| this.databaseChangeNotificationType = databaseChangeNotificationType; |
| } |
| |
| /** |
| * INTERNAL: |
| * Allow the inheritance properties of the descriptor to be initialized. |
| * The descriptor's parent must first be initialized. |
| */ |
| public void initializeFromParent(CachePolicy parentPolicy, ClassDescriptor descriptor, ClassDescriptor descriptorDescriptor, AbstractSession session) throws DescriptorException { |
| // If the parent is isolated, then the child must also be isolated. |
| if (!parentPolicy.isSharedIsolation()) { |
| // Do not override cache isolation when explicitly enabled by @Cacheable(true) annotation in current class. |
| boolean copyParrent = cacheable == null || !cacheable; |
| if (!isIsolated() && (getCacheIsolation() != parentPolicy.getCacheIsolation()) && copyParrent) { |
| session.log(SessionLog.WARNING, SessionLog.METADATA, "overriding_cache_isolation", |
| new Object[]{descriptorDescriptor.getAlias(), |
| parentPolicy.getCacheIsolation(), descriptor.getAlias(), |
| getCacheIsolation()}); |
| setCacheIsolation(parentPolicy.getCacheIsolation()); |
| } |
| } |
| // Child must maintain the same indexes as the parent. |
| for (CacheIndex index : parentPolicy.getCacheIndexes().values()) { |
| addCacheIndex(index); |
| } |
| if ((getDatabaseChangeNotificationType() == null) && (parentPolicy.getDatabaseChangeNotificationType() != null)) { |
| setDatabaseChangeNotificationType(parentPolicy.getDatabaseChangeNotificationType()); |
| } |
| if ((getCacheSynchronizationType() == UNDEFINED_OBJECT_CHANGE_BEHAVIOR) |
| && (parentPolicy.getCacheSynchronizationType() != UNDEFINED_OBJECT_CHANGE_BEHAVIOR)) { |
| setCacheSynchronizationType(parentPolicy.getCacheSynchronizationType()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Initialize the cache isolation setting. |
| * This may need to be called multiple times as notifyReferencingDescriptorsOfIsolation() can change the cache isolation. |
| */ |
| public void postInitialize(ClassDescriptor descriptor, AbstractSession session) throws DescriptorException { |
| if (!isSharedIsolation()) { |
| descriptor.notifyReferencingDescriptorsOfIsolation(session); |
| } |
| |
| // PERF: If using isolated cache, then default uow isolation to always (avoids merge/double build). |
| if ((getUnitOfWorkCacheIsolationLevel() == UNDEFINED_ISOLATATION) || this.wasDefaultUnitOfWorkCacheIsolationLevel) { |
| this.wasDefaultUnitOfWorkCacheIsolationLevel = true; |
| if (isIsolated()) { |
| setUnitOfWorkCacheIsolationLevel(ISOLATE_CACHE_ALWAYS); |
| } else if (isProtectedIsolation()) { |
| setUnitOfWorkCacheIsolationLevel(ISOLATE_FROM_CLIENT_SESSION); |
| } else { |
| setUnitOfWorkCacheIsolationLevel(ISOLATE_NEW_DATA_AFTER_TRANSACTION); |
| } |
| } |
| |
| // Record that there is an isolated class in the project. |
| if (!isSharedIsolation()) { |
| session.getProject().setHasIsolatedClasses(true); |
| } |
| if (!shouldIsolateObjectsInUnitOfWork() && !descriptor.shouldBeReadOnly()) { |
| session.getProject().setHasNonIsolatedUOWClasses(true); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Allow the inheritance properties of the descriptor to be initialized. |
| * The descriptor's parent must first be initialized. |
| */ |
| public void initialize(ClassDescriptor descriptor, AbstractSession session) throws DescriptorException { |
| if (hasCacheIndexes()) { |
| for (CacheIndex index : getCacheIndexes().values()) { |
| for (int count = 0; count < index.getFields().size(); count++) { |
| index.getFields().set(count, descriptor.buildField(index.getFields().get(count))); |
| } |
| } |
| } |
| for (DatabaseMapping mapping : descriptor.getMappings()) { |
| if (!mapping.isCacheable()) { |
| if (isSharedIsolation()) { |
| setCacheIsolation(CacheIsolationType.PROTECTED); |
| } |
| } |
| |
| if (mapping.isForeignReferenceMapping()) { |
| ClassDescriptor referencedDescriptor = mapping.getReferenceDescriptor(); |
| if (referencedDescriptor!= null) { |
| if (isSharedIsolation() && !referencedDescriptor.getCachePolicy().isSharedIsolation()) { |
| setCacheIsolation(CacheIsolationType.PROTECTED); |
| } |
| } |
| } |
| |
| if (mapping.isAggregateObjectMapping()) { |
| ClassDescriptor referencedDescriptor = mapping.getReferenceDescriptor(); |
| if (referencedDescriptor != null) { |
| if (isSharedIsolation() && !referencedDescriptor.getCachePolicy().isSharedIsolation()) { |
| setCacheIsolation(CacheIsolationType.PROTECTED); |
| } |
| } |
| } |
| } |
| |
| if (this.databaseChangeNotificationType == null) { |
| this.databaseChangeNotificationType = DatabaseChangeNotificationType.INVALIDATE; |
| } |
| if (this.cacheSynchronizationType == UNDEFINED_OBJECT_CHANGE_BEHAVIOR) { |
| this.cacheSynchronizationType = SEND_OBJECT_CHANGES; |
| } |
| if ((this.databaseChangeNotificationType != DatabaseChangeNotificationType.NONE) |
| && session.isDatabaseSession() && ((DatabaseSession)session).getDatabaseEventListener() != null) { |
| ((DatabaseSession)session).getDatabaseEventListener().initialize(descriptor, session); |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the cache index for the field names. |
| */ |
| public CacheIndex getCacheIndex(List<DatabaseField> fields) { |
| return getCacheIndexes().get(fields); |
| } |
| |
| /** |
| * PUBLIC: |
| * Add the cache index to the descriptor's cache settings. |
| * This allows for cache hits to be obtained on non-primary key fields. |
| * The primary key is always indexed in the cache. |
| * Cache indexes are defined by their database column names. |
| */ |
| public void addCacheIndex(CacheIndex index) { |
| getCacheIndexes().put(index.getFields(), index); |
| } |
| |
| /** |
| * PUBLIC: |
| * Add the cache index to the descriptor's cache settings. |
| * This allows for cache hits to be obtained on non-primary key fields. |
| * The primary key is always indexed in the cache. |
| * Cache indexes are defined by their database column names. |
| */ |
| public void addCacheIndex(String... fields) { |
| addCacheIndex(new CacheIndex(fields)); |
| } |
| |
| /** |
| * PUBLIC: |
| * Add the cache index to the descriptor's cache settings. |
| * This allows for cache hits to be obtained on non-primary key fields. |
| * The primary key is always indexed in the cache. |
| * Cache indexes are defined by their database column names. |
| */ |
| public void addCacheIndex(DatabaseField[] fields) { |
| addCacheIndex(new CacheIndex(fields)); |
| } |
| |
| public boolean hasCacheIndexes() { |
| return (this.cacheIndexes != null) && (!this.cacheIndexes.isEmpty()); |
| } |
| |
| public Map<List<DatabaseField>, CacheIndex> getCacheIndexes() { |
| if (this.cacheIndexes == null) { |
| this.cacheIndexes = new HashMap<>(); |
| } |
| return this.cacheIndexes; |
| } |
| |
| public void setCacheIndexes(Map<List<DatabaseField>, CacheIndex> cacheIndexes) { |
| this.cacheIndexes = cacheIndexes; |
| } |
| |
| @Override |
| public CachePolicy clone() { |
| try { |
| return (CachePolicy)super.clone(); |
| } catch (CloneNotSupportedException cnse) { |
| throw new InternalError(cnse.getMessage()); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Some attributes have default values defined in Project. |
| * If such the value for the attribute hasn't been set then the default value is assigned. |
| */ |
| public void assignDefaultValues(AbstractSession session) { |
| if(this.identityMapSize == -1) { |
| this.identityMapSize = session.getProject().getDefaultIdentityMapSize(); |
| } |
| if(this.identityMapClass == null) { |
| this.identityMapClass = session.getProject().getDefaultIdentityMapClass(); |
| } |
| if(this.cacheIsolation == null) { |
| this.cacheIsolation = session.getProject().getDefaultCacheIsolation(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Convert all the class-name-based settings in this Descriptor to actual class-based |
| * settings. This method is used when converting a project that has been built |
| * with class names to a project with classes. |
| */ |
| public void convertClassNamesToClasses(ClassLoader classLoader) { |
| if (this.cacheInterceptorClass == null && this.cacheInterceptorClassName != null) { |
| this.cacheInterceptorClass = PrivilegedAccessHelper.callDoPrivilegedWithException( |
| () -> PrivilegedAccessHelper.getClassForName(this.cacheInterceptorClassName, true, classLoader), |
| (ex) -> ValidationException.classNotFoundWhileConvertingClassNames(this.cacheInterceptorClassName, ex) |
| ); |
| } |
| } |
| |
| /** |
| * @return the fullyMergeEntity |
| */ |
| public boolean getFullyMergeEntity() { |
| return fullyMergeEntity; |
| } |
| |
| /** |
| * ADVANCED: |
| * Set what cache key type to use to store the object in the cache. |
| */ |
| public void setCacheKeyType(CacheKeyType cacheKeyType) { |
| this.cacheKeyType = cacheKeyType; |
| } |
| |
| /** |
| * ADVANCED: |
| * Return what cache key type to use to store the object in the cache. |
| */ |
| public CacheKeyType getCacheKeyType() { |
| return cacheKeyType; |
| } |
| |
| /** |
| * A CacheInterceptor is an adaptor that when overridden and assigned to a Descriptor all interaction |
| * between EclipseLink and the internal cache for that class will pass through the Interceptor. |
| * Advanced users could use this interceptor to audit, profile or log cache access. This Interceptor |
| * could also be used to redirect or augment the TopLink cache with an alternate cache mechanism. |
| * EclipseLink's configurated IdentityMaps will be passed to the Interceptor constructor. |
| * |
| * As with IdentityMaps an entire class inheritance hierarchy will share the same interceptor. |
| * @see org.eclipse.persistence.sessions.interceptors.CacheInterceptor |
| */ |
| @SuppressWarnings({"unchecked"}) |
| public <T extends CacheInterceptor> Class<T> getCacheInterceptorClass(){ |
| return (Class<T>) this.cacheInterceptorClass; |
| } |
| |
| /** |
| * A CacheInterceptor is an adaptor that when overridden and assigned to a Descriptor all interaction |
| * between EclipseLink and the internal cache for that class will pass through the Interceptor. |
| * Advanced users could use this interceptor to audit, profile or log cache access. This Interceptor |
| * could also be used to redirect or augment the TopLink cache with an alternate cache mechanism. |
| * EclipseLink's configurated IdentityMaps will be passed to the Interceptor constructor. |
| * |
| * As with IdentityMaps an entire class inheritance hierarchy will share the same interceptor. |
| * @see org.eclipse.persistence.sessions.interceptors.CacheInterceptor |
| */ |
| public String getCacheInterceptorClassName(){ |
| return this.cacheInterceptorClassName; |
| } |
| |
| /** |
| * PUBLIC: |
| * Get a value indicating the type of cache synchronization that will be used on objects of |
| * this type. Possible values are: |
| * SEND_OBJECT_CHANGES |
| * INVALIDATE_CHANGED_OBJECTS |
| * SEND_NEW_OBJECTS+WITH_CHANGES |
| * DO_NOT_SEND_CHANGES |
| * @return int |
| * |
| */ |
| public int getCacheSynchronizationType() { |
| return cacheSynchronizationType; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the class of identity map to be used by this descriptor. |
| * The default is the "SoftCacheWeakIdentityMap". |
| */ |
| @SuppressWarnings({"unchecked"}) |
| public <T extends IdentityMap> Class<T> getIdentityMapClass() { |
| return (Class<T>) identityMapClass; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the size of the identity map. |
| */ |
| public int getIdentityMapSize() { |
| return identityMapSize; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the class of identity map to be used by this descriptor. |
| * The default is the "SoftCacheWeakIdentityMap". |
| */ |
| @SuppressWarnings({"unchecked"}) |
| public <T extends IdentityMap> Class<T> getRemoteIdentityMapClass() { |
| if (remoteIdentityMapClass == null) { |
| remoteIdentityMapClass = getIdentityMapClass(); |
| } |
| return (Class<T>) remoteIdentityMapClass; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return the size of the remote identity map. |
| */ |
| public int getRemoteIdentityMapSize() { |
| if (remoteIdentityMapSize == -1) { |
| remoteIdentityMapSize = getIdentityMapSize(); |
| } |
| return remoteIdentityMapSize; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return if for cache hits on primary key read object queries to be disabled. |
| * |
| * @see ClassDescriptor#disableCacheHits() |
| */ |
| public boolean shouldDisableCacheHits() { |
| return shouldDisableCacheHits; |
| } |
| |
| /** |
| * PUBLIC: |
| * Return if the remote server session cache hits on primary key read object queries is aloowed or not. |
| * |
| * @see ClassDescriptor#disableCacheHitsOnRemote() |
| */ |
| public boolean shouldDisableCacheHitsOnRemote() { |
| return shouldDisableCacheHitsOnRemote; |
| } |
| |
| /** |
| * PUBLIC: |
| * This method returns <CODE>true</CODE> if the <CODE>ClassDescriptor</CODE> is configured to only refresh the cache |
| * if the data received from the database by a query is newer than the data in the cache (as determined by the |
| * optimistic locking field). Otherwise, it returns <CODE>false</CODE>. |
| * |
| * @see #setShouldOnlyRefreshCacheIfNewerVersion |
| */ |
| public boolean shouldOnlyRefreshCacheIfNewerVersion() { |
| return shouldOnlyRefreshCacheIfNewerVersion; |
| } |
| |
| /** |
| * PUBLIC: |
| * This method returns <CODE>true</CODE> if the <CODE>ClassDescriptor</CODE> is configured to always refresh |
| * the cache if data is received from the database by any query. Otherwise, it returns <CODE>false</CODE>. |
| * |
| * @see #setShouldAlwaysRefreshCache |
| */ |
| public boolean shouldAlwaysRefreshCache() { |
| return shouldAlwaysRefreshCache; |
| } |
| |
| /** |
| * PUBLIC: |
| * This method returns <CODE>true</CODE> if the <CODE>ClassDescriptor</CODE> is configured to always remotely |
| * refresh the cache if data is received from the database by any query in a {@link org.eclipse.persistence.sessions.remote.RemoteSession}. |
| * Otherwise, it returns <CODE>false</CODE>. |
| * |
| * @see #setShouldAlwaysRefreshCacheOnRemote |
| */ |
| public boolean shouldAlwaysRefreshCacheOnRemote() { |
| return shouldAlwaysRefreshCacheOnRemote; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set if cache hits on primary key read object queries should be disabled. |
| * |
| * @see ClassDescriptor#alwaysRefreshCache() |
| */ |
| public void setShouldDisableCacheHits(boolean shouldDisableCacheHits) { |
| this.shouldDisableCacheHits = shouldDisableCacheHits; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set if the remote session cache hits on primary key read object queries is allowed or not. |
| * |
| * @see ClassDescriptor#disableCacheHitsOnRemote() |
| */ |
| public void setShouldDisableCacheHitsOnRemote(boolean shouldDisableCacheHitsOnRemote) { |
| this.shouldDisableCacheHitsOnRemote = shouldDisableCacheHitsOnRemote; |
| } |
| |
| /** |
| * PUBLIC: |
| * When the <CODE>shouldOnlyRefreshCacheIfNewerVersion</CODE> argument passed into this method is <CODE>true</CODE>, |
| * this method configures a <CODE>ClassDescriptor</CODE> to only refresh the cache if the data received from the database |
| * by a query is newer than the data in the cache (as determined by the optimistic locking field) and as long as one of the following is true: |
| * |
| * <UL> |
| * <LI>the <CODE>ClassDescriptor</CODE> was configured by calling {@link ClassDescriptor#alwaysRefreshCache} or {@link ClassDescriptor#alwaysRefreshCacheOnRemote},</LI> |
| * <LI>the query was configured by calling {@link org.eclipse.persistence.queries.ObjectLevelReadQuery#refreshIdentityMapResult}, or</LI> |
| * <LI>the query was a call to {@link org.eclipse.persistence.sessions.Session#refreshObject}</LI> |
| * </UL> |
| * <P> |
| * |
| * However, if a query hits the cache, data is not refreshed regardless of how this setting is configured. For example, by default, |
| * when a query for a single object based on its primary key is executed, OracleAS TopLink will first look in the cache for the object. |
| * If the object is in the cache, the cached object is returned and data is not refreshed. To avoid cache hits, use |
| * the {@link ClassDescriptor#disableCacheHits} method.<P> |
| * |
| * Also note that the {@link org.eclipse.persistence.sessions.UnitOfWork} will not refresh its registered objects.<P> |
| * |
| * When the <CODE>shouldOnlyRefreshCacheIfNewerVersion</CODE> argument passed into this method is <CODE>false</CODE>, this method |
| * ensures that a <CODE>ClassDescriptor</CODE> is not configured to only refresh the cache if the data received from the database by a |
| * query is newer than the data in the cache (as determined by the optimistic locking field). |
| * |
| * @see ClassDescriptor#onlyRefreshCacheIfNewerVersion |
| * @see ClassDescriptor#dontOnlyRefreshCacheIfNewerVersion |
| */ |
| public void setShouldOnlyRefreshCacheIfNewerVersion(boolean shouldOnlyRefreshCacheIfNewerVersion) { |
| this.shouldOnlyRefreshCacheIfNewerVersion = shouldOnlyRefreshCacheIfNewerVersion; |
| } |
| |
| /** |
| * PUBLIC: |
| * When the <CODE>shouldAlwaysRefreshCache</CODE> argument passed into this method is <CODE>true</CODE>, |
| * this method configures a <CODE>ClassDescriptor</CODE> to always refresh the cache if data is received from |
| * the database by any query.<P> |
| * |
| * However, if a query hits the cache, data is not refreshed regardless of how this setting is configured. |
| * For example, by default, when a query for a single object based on its primary key is executed, OracleAS TopLink |
| * will first look in the cache for the object. If the object is in the cache, the cached object is returned and |
| * data is not refreshed. To avoid cache hits, use the {@link ClassDescriptor#disableCacheHits} method.<P> |
| * |
| * Also note that the {@link org.eclipse.persistence.sessions.UnitOfWork} will not refresh its registered objects.<P> |
| * |
| * Use this property with caution because it can lead to poor performance and may refresh on queries when it is not desired. |
| * Normally, if you require fresh data, it is better to configure a query with {@link org.eclipse.persistence.queries.ObjectLevelReadQuery#refreshIdentityMapResult}. |
| * To ensure that refreshes are only done when required, use this method in conjunction with {@link ClassDescriptor#onlyRefreshCacheIfNewerVersion}.<P> |
| * |
| * When the <CODE>shouldAlwaysRefreshCache</CODE> argument passed into this method is <CODE>false</CODE>, this method |
| * ensures that a <CODE>ClassDescriptor</CODE> is not configured to always refresh the cache if data is received from the database by any query. |
| * |
| * @see ClassDescriptor#alwaysRefreshCache |
| * @see ClassDescriptor#dontAlwaysRefreshCache |
| */ |
| public void setShouldAlwaysRefreshCache(boolean shouldAlwaysRefreshCache) { |
| this.shouldAlwaysRefreshCache = shouldAlwaysRefreshCache; |
| } |
| |
| /** |
| * PUBLIC: |
| * When the <CODE>shouldAlwaysRefreshCacheOnRemote</CODE> argument passed into this method is <CODE>true</CODE>, |
| * this method configures a <CODE>ClassDescriptor</CODE> to always remotely refresh the cache if data is received from |
| * the database by any query in a {@link org.eclipse.persistence.sessions.remote.RemoteSession}. |
| * |
| * However, if a query hits the cache, data is not refreshed regardless of how this setting is configured. For |
| * example, by default, when a query for a single object based on its primary key is executed, OracleAS TopLink |
| * will first look in the cache for the object. If the object is in the cache, the cached object is returned and |
| * data is not refreshed. To avoid cache hits, use the {@link ClassDescriptor#disableCacheHitsOnRemote} method.<P> |
| * |
| * Also note that the {@link org.eclipse.persistence.sessions.UnitOfWork} will not refresh its registered objects.<P> |
| * |
| * Use this property with caution because it can lead to poor performance and may refresh on queries when it is |
| * not desired. Normally, if you require fresh data, it is better to configure a query with {@link org.eclipse.persistence.queries.ObjectLevelReadQuery#refreshIdentityMapResult}. |
| * To ensure that refreshes are only done when required, use this method in conjunction with {@link ClassDescriptor#onlyRefreshCacheIfNewerVersion}.<P> |
| * |
| * When the <CODE>shouldAlwaysRefreshCacheOnRemote</CODE> argument passed into this method is <CODE>false</CODE>, |
| * this method ensures that a <CODE>ClassDescriptor</CODE> is not configured to always remotely refresh the cache if data |
| * is received from the database by any query in a {@link org.eclipse.persistence.sessions.remote.RemoteSession}. |
| * |
| * @see ClassDescriptor#alwaysRefreshCacheOnRemote |
| * @see ClassDescriptor#dontAlwaysRefreshCacheOnRemote |
| */ |
| public void setShouldAlwaysRefreshCacheOnRemote(boolean shouldAlwaysRefreshCacheOnRemote) { |
| this.shouldAlwaysRefreshCacheOnRemote = shouldAlwaysRefreshCacheOnRemote; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the class of identity map to be used by this descriptor. |
| * The default is the "FullIdentityMap". |
| */ |
| public void setRemoteIdentityMapClass(Class<? extends IdentityMap> theIdentityMapClass) { |
| remoteIdentityMapClass = theIdentityMapClass; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the size of the identity map to be used by this descriptor. |
| * The default is the 100. |
| */ |
| public void setRemoteIdentityMapSize(int identityMapSize) { |
| remoteIdentityMapSize = identityMapSize; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set entity @Cacheable annotation value. |
| * @param cacheable Entity @Cacheable annotation value for current class |
| * or <code>null</code> if @Cacheable annotation is not set. Parent |
| * values are ignored, value shall refer to current class only. |
| */ |
| public void setCacheable(Boolean cacheable) { |
| this.cacheable = cacheable; |
| } |
| |
| /** |
| * PUBLIC: |
| * Controls how the Entity instances will be cached. See the CacheIsolationType for details on the options. |
| * @return the isolationType |
| */ |
| public CacheIsolationType getCacheIsolation() { |
| return cacheIsolation; |
| } |
| |
| /** |
| * PUBLIC: |
| * Controls how the Entity instances and data will be cached. See the CacheIsolationType for details on the options. |
| * To disable all second level caching simply set CacheIsolationType.ISOLATED. Note that setting the isolation |
| * will automatically set the corresponding cacheSynchronizationType. |
| * ISOLATED = DO_NOT_SEND_CHANGES, PROTECTED and SHARED = SEND_OBJECT_CHANGES |
| */ |
| public void setCacheIsolation(CacheIsolationType isolationType) { |
| this.cacheIsolation = isolationType; |
| if (CacheIsolationType.ISOLATED == isolationType) { |
| // bug 3587273 - set the cache synchronization type so isolated objects are not sent |
| // do not call the setter method because it does not allow changing the cache synchronization |
| // type of isolated objects |
| cacheSynchronizationType = DO_NOT_SEND_CHANGES; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the unit of work should by-pass the session cache. |
| * Objects will be built in the unit of work, and never merged into the session cache. |
| */ |
| public boolean shouldIsolateObjectsInUnitOfWork() { |
| return this.unitOfWorkCacheIsolationLevel == ISOLATE_CACHE_ALWAYS; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the unit of work should by-pass the IsolatedSession cache. |
| * Objects will be built/merged into the unit of work and into the session cache. |
| * but not built/merge into the IsolatedClientSession cache. |
| */ |
| public boolean shouldIsolateProtectedObjectsInUnitOfWork() { |
| return this.unitOfWorkCacheIsolationLevel == ISOLATE_FROM_CLIENT_SESSION; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the unit of work should by-pass the session cache after an early transaction. |
| */ |
| public boolean shouldIsolateObjectsInUnitOfWorkEarlyTransaction() { |
| return this.unitOfWorkCacheIsolationLevel == ISOLATE_CACHE_AFTER_TRANSACTION; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return if the unit of work should use the session cache after an early transaction. |
| */ |
| public boolean shouldUseSessionCacheInUnitOfWorkEarlyTransaction() { |
| return this.unitOfWorkCacheIsolationLevel == USE_SESSION_CACHE_AFTER_TRANSACTION; |
| } |
| |
| /** |
| * ADVANCED: |
| * Return the unit of work cache isolation setting. |
| * This setting configures how the session cache will be used in a unit of work. |
| * @see #setUnitOfWorkCacheIsolationLevel(int) |
| */ |
| public int getUnitOfWorkCacheIsolationLevel() { |
| return unitOfWorkCacheIsolationLevel; |
| } |
| |
| /** |
| * ADVANCED: |
| * This setting configures how the session cache will be used in a unit of work. |
| * Most of the options only apply to a unit of work in an early transaction, |
| * such as a unit of work that was flushed (writeChanges), issued a modify query, or acquired a pessimistic lock. |
| * <p> USE_SESSION_CACHE_AFTER_TRANSACTION - Objects built from new data accessed after a unit of work early transaction are stored in the session cache. |
| * This options is the most efficient as it allows the cache to be used after an early transaction. |
| * This should only be used if it is known that this class is not modified in the transaction, |
| * otherwise this could cause uncommitted data to be loaded into the session cache. |
| * ISOLATE_NEW_DATA_AFTER_TRANSACTION - Default (when using caching): Objects built from new data accessed after a unit of work early transaction are only stored in the unit of work. |
| * This still allows previously cached objects to be accessed in the unit of work after an early transaction, |
| * but ensures uncommitted data will never be put in the session cache by storing any object built from new data only in the unit of work. |
| * ISOLATE_CACHE_AFTER_TRANSACTION - After a unit of work early transaction the session cache is no longer used for this class. |
| * Objects will be directly built from the database data and only stored in the unit of work, even if previously cached. |
| * Note that this may lead to poor performance as the session cache is bypassed after an early transaction. |
| * ISOLATE_CACHE_ALWAYS - Default (when using isolated cache): The session cache will never be used for this class. |
| * Objects will be directly built from the database data and only stored in the unit of work. |
| * New objects and changes will also never be merged into the session cache. |
| * Note that this may lead to poor performance as the session cache is bypassed, |
| * however if this class is isolated or pessimistic locked and always accessed in a transaction, this can avoid having to build two copies of the object. |
| */ |
| public void setUnitOfWorkCacheIsolationLevel(int unitOfWorkCacheIsolationLevel) { |
| this.unitOfWorkCacheIsolationLevel = unitOfWorkCacheIsolationLevel; |
| } |
| |
| /** |
| * @param fullyMergeEntity the fullyMergeEntity to set |
| */ |
| public void setFullyMergeEntity(boolean fullyMergeEntity) { |
| this.fullyMergeEntity = fullyMergeEntity; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the class of identity map to be used by this descriptor. |
| * The default is the "FullIdentityMap". |
| */ |
| public void setIdentityMapClass(Class<? extends IdentityMap> theIdentityMapClass) { |
| identityMapClass = theIdentityMapClass; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the size of the identity map to be used by this descriptor. |
| * The default is the 100. |
| */ |
| public void setIdentityMapSize(int identityMapSize) { |
| this.identityMapSize = identityMapSize; |
| } |
| |
| /** |
| * OBSOLETE: |
| * Set the type of cache coordination that will be used on objects of this type. Possible values |
| * are:<ul> |
| * <li>SEND_OBJECT_CHANGES |
| * <li>INVALIDATE_CHANGED_OBJECTS |
| * <li>SEND_NEW_OBJECTS_WITH_CHANGES |
| * <li>DO_NOT_SEND_CHANGES</ul> |
| * Note: Cache Synchronization type cannot be altered for descriptors that are set as isolated using |
| * the setIsIsolated method.<p> |
| * This has been replaced by setCacheCoordinationType(). |
| * @see #setCacheCoordinationType(CacheCoordinationType) |
| * @param type int The synchronization type for this descriptor |
| */ |
| public void setCacheSynchronizationType(int type) { |
| // bug 3587273 |
| if (!isIsolated()) { |
| cacheSynchronizationType = type; |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the type of cache coordination that will be used on objects of this type. |
| * Valid values defined in CacheCoordinationType. |
| */ |
| public void setCacheCoordinationType(CacheCoordinationType type) { |
| if (type == null) { |
| setCacheSynchronizationType(UNDEFINED_OBJECT_CHANGE_BEHAVIOR); |
| } else if (type == CacheCoordinationType.SEND_OBJECT_CHANGES) { |
| setCacheSynchronizationType(SEND_OBJECT_CHANGES); |
| } else if (type == CacheCoordinationType.INVALIDATE_CHANGED_OBJECTS) { |
| setCacheSynchronizationType(INVALIDATE_CHANGED_OBJECTS); |
| } else if (type == CacheCoordinationType.SEND_NEW_OBJECTS_WITH_CHANGES) { |
| setCacheSynchronizationType(SEND_NEW_OBJECTS_WITH_CHANGES); |
| } else if (type == CacheCoordinationType.NONE) { |
| setCacheSynchronizationType(DO_NOT_SEND_CHANGES); |
| } |
| } |
| |
| /** |
| * PUBLIC: |
| * A CacheInterceptor is an adaptor that when overridden and assigned to a Descriptor all interaction |
| * between EclipseLink and the internal cache for that class will pass through the Interceptor. |
| * Advanced users could use this interceptor to audit, profile or log cache access. This Interceptor |
| * could also be used to redirect or augment the TopLink cache with an alternate cache mechanism. |
| * EclipseLink's configurated IdentityMaps will be passed to the Interceptor constructor. |
| |
| * As with IdentityMaps an entire class inheritance hierarchy will share the same interceptor. |
| * @see org.eclipse.persistence.sessions.interceptors.CacheInterceptor |
| */ |
| public void setCacheInterceptorClass(Class<? extends CacheInterceptor> cacheInterceptorClass){ |
| this.cacheInterceptorClass = cacheInterceptorClass; |
| } |
| |
| /** |
| * PUBLIC: |
| * A CacheInterceptor is an adaptor that when overridden and assigned to a Descriptor all interaction |
| * between EclipseLink and the internal cache for that class will pass through the Interceptor. |
| * Advanced users could use this interceptor to audit, profile or log cache access. This Interceptor |
| * could also be used to redirect or augment the TopLink cache with an alternate cache mechanism. |
| * EclipseLink's configurated IdentityMaps will be passed to the Interceptor constructor. |
| |
| * As with IdentityMaps an entire class inheritance hierarchy will share the same interceptor. |
| * @see org.eclipse.persistence.sessions.interceptors.CacheInterceptor |
| */ |
| public void setCacheInterceptorClassName(String cacheInterceptorClassName){ |
| this.cacheInterceptorClassName = cacheInterceptorClassName; |
| } |
| |
| /** |
| * PUBLIC: |
| * Returns true if the descriptor represents an isolated class |
| */ |
| public boolean isIsolated() { |
| return CacheIsolationType.ISOLATED == this.cacheIsolation; |
| } |
| |
| /** |
| * PUBLIC: |
| * Returns true if the descriptor represents a protected class. |
| */ |
| public boolean isProtectedIsolation() { |
| return CacheIsolationType.PROTECTED ==this.cacheIsolation; |
| } |
| |
| /** |
| * PUBLIC: |
| * Returns true if the descriptor represents a shared class. |
| */ |
| public boolean isSharedIsolation() { |
| return this.cacheIsolation == null || CacheIsolationType.SHARED == this.cacheIsolation; |
| } |
| |
| /** |
| * INTERNAL: |
| * Index the object by index in the cache using its row. |
| */ |
| public void indexObjectInCache(CacheKey cacheKey, AbstractRecord databaseRow, Object domainObject, ClassDescriptor descriptor, AbstractSession session, boolean refresh) { |
| if (!hasCacheIndexes()) { |
| return; |
| } |
| for (CacheIndex index : this.cacheIndexes.values()) { |
| if (!refresh || index.isUpdateable()) { |
| List<DatabaseField> fields = index.getFields(); |
| int size = fields.size(); |
| Object[] values = new Object[size]; |
| for (int count = 0; count < size; count++) { |
| values[count] = databaseRow.get(fields.get(count)); |
| } |
| CacheId indexValues = new CacheId(values); |
| session.getIdentityMapAccessorInstance().putCacheKeyByIndex(index, indexValues, cacheKey, descriptor); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Index the object by index in the cache using the object. |
| */ |
| public void indexObjectInCache(CacheKey cacheKey, Object object, ClassDescriptor descriptor, AbstractSession session, boolean refresh) { |
| if (!hasCacheIndexes()) { |
| return; |
| } |
| for (CacheIndex index : this.cacheIndexes.values()) { |
| if (!refresh || index.isUpdateable()) { |
| List<DatabaseField> fields = index.getFields(); |
| int size = fields.size(); |
| Object[] values = new Object[size]; |
| for (int count = 0; count < size; count++) { |
| values[count] = descriptor.getObjectBuilder().extractValueFromObjectForField(object, fields.get(count), session); |
| } |
| CacheId indexValues = new CacheId(values); |
| session.getIdentityMapAccessorInstance().putCacheKeyByIndex(index, indexValues, cacheKey, descriptor); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Index the object by index in the cache using its changeSet. |
| */ |
| public void indexObjectInCache(ObjectChangeSet changeSet, Object object, ClassDescriptor descriptor, AbstractSession session) { |
| if (!hasCacheIndexes()) { |
| return; |
| } |
| for (CacheIndex index : this.cacheIndexes.values()) { |
| if ((changeSet == null) || (changeSet.isNew() && index.isInsertable()) || (!changeSet.isNew() && index.isUpdateable())) { |
| List<DatabaseField> fields = index.getFields(); |
| int size = fields.size(); |
| Object[] values = new Object[size]; |
| for (int count = 0; count < size; count++) { |
| values[count] = descriptor.getObjectBuilder().extractValueFromObjectForField(object, fields.get(count), session); |
| } |
| CacheId indexValues = new CacheId(values); |
| CacheKey cacheKey = null; |
| Object id = null; |
| if (changeSet != null) { |
| cacheKey = changeSet.getActiveCacheKey(); |
| if (cacheKey == null) { |
| id = changeSet.getId(); |
| } |
| } else { |
| id = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(object, session); |
| } |
| if (cacheKey == null) { |
| cacheKey = session.getIdentityMapAccessorInstance().getCacheKeyForObject(id, descriptor.getJavaClass(), descriptor, true); |
| } |
| session.getIdentityMapAccessorInstance().putCacheKeyByIndex(index, indexValues, cacheKey, descriptor); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Lookup the expression in the cache if it contains any indexes. |
| */ |
| public CacheKey checkCacheByIndex(Expression expression, AbstractRecord translationRow, ClassDescriptor descriptor, AbstractSession session) { |
| if (!hasCacheIndexes()) { |
| return null; |
| } |
| AbstractRecord record = descriptor.getObjectBuilder().extractRowFromExpression(expression, translationRow, session); |
| if (record == null) { |
| return null; |
| } |
| for (CacheIndex index : this.cacheIndexes.values()) { |
| List<DatabaseField> fields = index.getFields(); |
| int size = fields.size(); |
| Object[] values = new Object[size]; |
| for (int count = 0; count < size; count++) { |
| Object value = record.get(fields.get(count)); |
| if (value == null) { |
| break; |
| } |
| values[count] = value; |
| } |
| CacheId indexValues = new CacheId(values); |
| CacheKey cacheKey = session.getIdentityMapAccessorInstance().getCacheKeyByIndex(index, indexValues, true, descriptor); |
| if (cacheKey != null) { |
| return cacheKey; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Lookup the expression in the cache if it contains any indexes. |
| */ |
| public boolean isIndexableExpression(Expression expression, ClassDescriptor descriptor, AbstractSession session) { |
| if (!hasCacheIndexes()) { |
| return false; |
| } |
| for (CacheIndex index : this.cacheIndexes.values()) { |
| List<DatabaseField> searchFields = index.getFields(); |
| int size = searchFields.size(); |
| Set<DatabaseField> foundFields = new HashSet<>(size); |
| boolean isValid = expression.extractFields(true, false, descriptor, searchFields, foundFields); |
| if (isValid && (foundFields.size() == size)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the class of identity map to be the full identity map. |
| * This map caches all instances read and grows to accomodate them. |
| * The default is the "SoftCacheWeakIdentityMap". |
| */ |
| public void useFullIdentityMap() { |
| setIdentityMapClass(ClassConstants.FullIdentityMap_Class); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the class of identity map to be the hard cache weak identity map. |
| * This map uses weak references to only cache object in-memory. |
| * It also includes a secondary fixed sized hard cache to improve caching performance. |
| * This is provided because some Java VM's implement soft references differently. |
| * The default is the "SoftCacheWeakIdentityMap". |
| */ |
| public void useHardCacheWeakIdentityMap() { |
| setIdentityMapClass(ClassConstants.HardCacheWeakIdentityMap_Class); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the class of identity map to be the soft identity map. |
| * This map uses soft references to only cache all object in-memory, until memory is low. |
| * Note that "low" is interpreted differently by different JVM's. |
| * The default is the "SoftCacheWeakIdentityMap". |
| */ |
| public void useSoftIdentityMap() { |
| setIdentityMapClass(ClassConstants.SoftIdentityMap_Class); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the class of identity map to be the no identity map. |
| * This map does no caching. |
| * Note: This map does not maintain object identity. |
| * In general if caching is not desired a WeakIdentityMap should be used with an isolated descriptor. |
| * The default is the "SoftCacheWeakIdentityMap". |
| * @see ClassDescriptor#setCacheIsolation(CacheIsolationType) |
| */ |
| public void useNoIdentityMap() { |
| setIdentityMapClass(ClassConstants.NoIdentityMap_Class); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the class of identity map to be the weak identity map. |
| * The default is the "SoftCacheWeakIdentityMap". |
| */ |
| public void useWeakIdentityMap() { |
| setIdentityMapClass(ClassConstants.WeakIdentityMap_Class); |
| } |
| |
| public void setPrefetchCacheKeys(boolean prefetchCacheKeys) { |
| this.prefetchCacheKeys = prefetchCacheKeys; |
| } |
| |
| public boolean shouldPrefetchCacheKeys() { |
| return this.prefetchCacheKeys ; |
| } |
| } |