All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.engine.internal.NaturalIdXrefDelegate Maven / Gradle / Ivy

There is a newer version: 7.0.0.Alpha1
Show newest version
/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or .
 */
package org.hibernate.engine.internal;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.hibernate.AssertionFailure;
import org.hibernate.cache.spi.access.NaturalIdDataAccess;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.stat.internal.StatsHelper;
import org.hibernate.type.Type;

import org.jboss.logging.Logger;

/**
 * Maintains a {@link org.hibernate.engine.spi.PersistenceContext}-level 2-way cross-reference (xref) between the 
 * identifiers and natural ids of entities associated with the PersistenceContext.
 * 

* Most operations resolve the proper {@link NaturalIdResolutionCache} to use based on the persister and * simply delegate calls there. * * @author Steve Ebersole */ public class NaturalIdXrefDelegate { private static final Logger LOG = Logger.getLogger( NaturalIdXrefDelegate.class ); private final StatefulPersistenceContext persistenceContext; private final ConcurrentHashMap naturalIdResolutionCacheMap = new ConcurrentHashMap<>(); /** * Constructs a NaturalIdXrefDelegate * * @param persistenceContext The persistence context that owns this delegate */ public NaturalIdXrefDelegate(StatefulPersistenceContext persistenceContext) { this.persistenceContext = persistenceContext; } /** * Access to the session (via the PersistenceContext) to which this delegate ultimately belongs. * * @return The session */ protected SharedSessionContractImplementor session() { return persistenceContext.getSession(); } /** * Creates needed cross-reference entries between the given primary (pk) and natural (naturalIdValues) key values * for the given persister. Returns an indication of whether entries were actually made. If those values already * existed as an entry, {@code false} would be returned here. * * @param persister The persister representing the entity type. * @param pk The primary key value * @param naturalIdValues The natural id value(s) * * @return {@code true} if a new entry was actually added; {@code false} otherwise. */ public boolean cacheNaturalIdCrossReference(EntityPersister persister, Serializable pk, Object[] naturalIdValues) { validateNaturalId( persister, naturalIdValues ); NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister ); if ( entityNaturalIdResolutionCache == null ) { entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister ); NaturalIdResolutionCache previousInstance = naturalIdResolutionCacheMap.putIfAbsent( persister, entityNaturalIdResolutionCache ); if ( previousInstance != null ) { entityNaturalIdResolutionCache = previousInstance; } } return entityNaturalIdResolutionCache.cache( pk, naturalIdValues ); } /** * Handle removing cross reference entries for the given natural-id/pk combo * * @param persister The persister representing the entity type. * @param pk The primary key value * @param naturalIdValues The natural id value(s) * * @return The cached values, if any. May be different from incoming values. */ public Object[] removeNaturalIdCrossReference(EntityPersister persister, Serializable pk, Object[] naturalIdValues) { persister = locatePersisterForKey( persister ); validateNaturalId( persister, naturalIdValues ); final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister ); Object[] sessionCachedNaturalIdValues = null; if ( entityNaturalIdResolutionCache != null ) { final CachedNaturalId cachedNaturalId = entityNaturalIdResolutionCache.pkToNaturalIdMap .remove( pk ); if ( cachedNaturalId != null ) { entityNaturalIdResolutionCache.naturalIdToPkMap.remove( cachedNaturalId ); sessionCachedNaturalIdValues = cachedNaturalId.getValues(); } } if ( persister.hasNaturalIdCache() ) { final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister .getNaturalIdCacheAccessStrategy(); final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session() ); naturalIdCacheAccessStrategy.evict( naturalIdCacheKey ); if ( sessionCachedNaturalIdValues != null && !Arrays.equals( sessionCachedNaturalIdValues, naturalIdValues ) ) { final Object sessionNaturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( sessionCachedNaturalIdValues, persister, session() ); naturalIdCacheAccessStrategy.evict( sessionNaturalIdCacheKey ); } } return sessionCachedNaturalIdValues; } /** * Are the naturals id values cached here (if any) for the given persister+pk combo the same as the given values? * * @param persister The persister representing the entity type. * @param pk The primary key value * @param naturalIdValues The natural id value(s) to check * * @return {@code true} if the given naturalIdValues match the current cached values; {@code false} otherwise. */ public boolean sameAsCached(EntityPersister persister, Serializable pk, Object[] naturalIdValues) { final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister ); return entityNaturalIdResolutionCache != null && entityNaturalIdResolutionCache.sameAsCached( pk, naturalIdValues ); } /** * It is only valid to define natural ids at the root of an entity hierarchy. This method makes sure we are * using the root persister. * * @param persister The persister representing the entity type. * * @return The root persister. */ protected EntityPersister locatePersisterForKey(EntityPersister persister) { return persistenceContext.getSession().getFactory().getEntityPersister( persister.getRootEntityName() ); } /** * Invariant validate of the natural id. Checks include

    *
  • that the entity defines a natural id
  • *
  • the number of natural id values matches the expected number
  • *
* * @param persister The persister representing the entity type. * @param naturalIdValues The natural id values */ protected void validateNaturalId(EntityPersister persister, Object[] naturalIdValues) { if ( !persister.hasNaturalIdentifier() ) { throw new IllegalArgumentException( "Entity did not define a natrual-id" ); } if ( persister.getNaturalIdentifierProperties().length != naturalIdValues.length ) { throw new IllegalArgumentException( "Mismatch between expected number of natural-id values and found." ); } } /** * Given a persister and primary key, find the locally cross-referenced natural id. * * @param persister The persister representing the entity type. * @param pk The entity primary key * * @return The corresponding cross-referenced natural id values, or {@code null} if none */ public Object[] findCachedNaturalId(EntityPersister persister, Serializable pk) { persister = locatePersisterForKey( persister ); final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister ); if ( entityNaturalIdResolutionCache == null ) { return null; } final CachedNaturalId cachedNaturalId = entityNaturalIdResolutionCache.pkToNaturalIdMap.get( pk ); if ( cachedNaturalId == null ) { return null; } return cachedNaturalId.getValues(); } /** * Given a persister and natural-id value(s), find the locally cross-referenced primary key. Will return * {@link PersistenceContext.NaturalIdHelper#INVALID_NATURAL_ID_REFERENCE} if the given natural ids are known to * be invalid (see {@link #stashInvalidNaturalIdReference}). * * @param persister The persister representing the entity type. * @param naturalIdValues The natural id value(s) * * @return The corresponding cross-referenced primary key, * {@link PersistenceContext.NaturalIdHelper#INVALID_NATURAL_ID_REFERENCE}, * or {@code null} if none */ public Serializable findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues) { persister = locatePersisterForKey( persister ); validateNaturalId( persister, naturalIdValues ); NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister ); Serializable pk; final CachedNaturalId cachedNaturalId = new CachedNaturalId( persister, naturalIdValues ); if ( entityNaturalIdResolutionCache != null ) { pk = entityNaturalIdResolutionCache.naturalIdToPkMap.get( cachedNaturalId ); // Found in session cache if ( pk != null ) { if ( LOG.isTraceEnabled() ) { LOG.trace( "Resolved natural key -> primary key resolution in session cache: " + persister.getRootEntityName() + "#[" + Arrays.toString( naturalIdValues ) + "]" ); } return pk; } // if we did not find a hit, see if we know about these natural ids as invalid... if ( entityNaturalIdResolutionCache.containsInvalidNaturalIdReference( naturalIdValues ) ) { return PersistenceContext.NaturalIdHelper.INVALID_NATURAL_ID_REFERENCE; } } // Session cache miss, see if second-level caching is enabled if ( !persister.hasNaturalIdCache() ) { return null; } // Try resolution from second-level cache final NaturalIdDataAccess naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy(); final Object naturalIdCacheKey = naturalIdCacheAccessStrategy.generateCacheKey( naturalIdValues, persister, session() ); pk = CacheHelper.fromSharedCache( session(), naturalIdCacheKey, naturalIdCacheAccessStrategy ); // Found in second-level cache, store in session cache final SessionFactoryImplementor factory = session().getFactory(); if ( pk != null ) { if ( factory.getStatistics().isStatisticsEnabled() ) { factory.getStatistics().naturalIdCacheHit( StatsHelper.INSTANCE.getRootEntityRole( persister ), naturalIdCacheAccessStrategy.getRegion().getName() ); } if ( LOG.isTraceEnabled() ) { // protected to avoid Arrays.toString call unless needed LOG.tracef( "Found natural key [%s] -> primary key [%s] xref in second-level cache for %s", Arrays.toString( naturalIdValues ), pk, persister.getRootEntityName() ); } if ( entityNaturalIdResolutionCache == null ) { entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister ); NaturalIdResolutionCache existingCache = naturalIdResolutionCacheMap.putIfAbsent( persister, entityNaturalIdResolutionCache ); if ( existingCache != null ) { entityNaturalIdResolutionCache = existingCache; } } entityNaturalIdResolutionCache.pkToNaturalIdMap.put( pk, cachedNaturalId ); entityNaturalIdResolutionCache.naturalIdToPkMap.put( cachedNaturalId, pk ); } else if ( factory.getStatistics().isStatisticsEnabled() ) { factory.getStatistics().naturalIdCacheMiss( StatsHelper.INSTANCE.getRootEntityRole( persister ), naturalIdCacheAccessStrategy.getRegion().getName() ); } return pk; } /** * Return all locally cross-referenced primary keys for the given persister. Used as part of load * synchronization process. * * @param persister The persister representing the entity type. * * @return The primary keys * * @see org.hibernate.NaturalIdLoadAccess#setSynchronizationEnabled */ public Collection getCachedPkResolutions(EntityPersister persister) { persister = locatePersisterForKey( persister ); Collection pks = null; final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister ); if ( entityNaturalIdResolutionCache != null ) { pks = entityNaturalIdResolutionCache.pkToNaturalIdMap.keySet(); } if ( pks == null || pks.isEmpty() ) { return java.util.Collections.emptyList(); } else { return java.util.Collections.unmodifiableCollection( pks ); } } /** * As part of "load synchronization process", if a particular natural id is found to have changed we need to track * its invalidity until after the next flush. This method lets the "load synchronization process" indicate * when it has encountered such changes. * * @param persister The persister representing the entity type. * @param invalidNaturalIdValues The "old" natural id values. * * @see org.hibernate.NaturalIdLoadAccess#setSynchronizationEnabled */ public void stashInvalidNaturalIdReference(EntityPersister persister, Object[] invalidNaturalIdValues) { persister = locatePersisterForKey( persister ); final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister ); if ( entityNaturalIdResolutionCache == null ) { throw new AssertionFailure( "Expecting NaturalIdResolutionCache to exist already for entity " + persister.getEntityName() ); } entityNaturalIdResolutionCache.stashInvalidNaturalIdReference( invalidNaturalIdValues ); } /** * Again, as part of "load synchronization process" we need to also be able to clear references to these * known-invalid natural-ids after flush. This method exposes that capability. */ public void unStashInvalidNaturalIdReferences() { for ( NaturalIdResolutionCache naturalIdResolutionCache : naturalIdResolutionCacheMap.values() ) { naturalIdResolutionCache.unStashInvalidNaturalIdReferences(); } } /** * Used to put natural id values into collections. Useful mainly to apply equals/hashCode implementations. */ private static class CachedNaturalId implements Serializable { private final EntityPersister persister; private final Object[] values; private final Type[] naturalIdTypes; private int hashCode; public CachedNaturalId(EntityPersister persister, Object[] values) { this.persister = persister; this.values = values; final int prime = 31; int hashCodeCalculation = 1; hashCodeCalculation = prime * hashCodeCalculation + persister.hashCode(); final int[] naturalIdPropertyIndexes = persister.getNaturalIdentifierProperties(); naturalIdTypes = new Type[ naturalIdPropertyIndexes.length ]; int i = 0; for ( int naturalIdPropertyIndex : naturalIdPropertyIndexes ) { final Type type = persister.getPropertyType( persister.getPropertyNames()[ naturalIdPropertyIndex ] ); naturalIdTypes[i] = type; final int elementHashCode = values[i] == null ? 0 :type.getHashCode( values[i], persister.getFactory() ); hashCodeCalculation = prime * hashCodeCalculation + elementHashCode; i++; } this.hashCode = hashCodeCalculation; } public Object[] getValues() { return values; } @Override public int hashCode() { return this.hashCode; } @Override public boolean equals(Object obj) { if ( this == obj ) { return true; } if ( obj == null ) { return false; } if ( getClass() != obj.getClass() ) { return false; } final CachedNaturalId other = (CachedNaturalId) obj; return persister.equals( other.persister ) && isSame( other.values ); } private boolean isSame(Object[] otherValues) { // lengths have already been verified at this point for ( int i = 0; i < naturalIdTypes.length; i++ ) { if ( ! naturalIdTypes[i].isEqual( values[i], otherValues[i], persister.getFactory() ) ) { return false; } } return true; } } /** * Represents the persister-specific cross-reference cache. */ private static class NaturalIdResolutionCache implements Serializable { private final EntityPersister persister; private Map pkToNaturalIdMap = new ConcurrentHashMap<>(); private Map naturalIdToPkMap = new ConcurrentHashMap<>(); private List invalidNaturalIdList; private NaturalIdResolutionCache(EntityPersister persister) { this.persister = persister; } public EntityPersister getPersister() { return persister; } public boolean sameAsCached(Serializable pk, Object[] naturalIdValues) { if ( pk == null ) { return false; } final CachedNaturalId initial = pkToNaturalIdMap.get( pk ); if ( initial != null ) { if ( initial.isSame( naturalIdValues ) ) { return true; } } return false; } public boolean cache(Serializable pk, Object[] naturalIdValues) { if ( pk == null ) { return false; } final CachedNaturalId initial = pkToNaturalIdMap.get( pk ); if ( initial != null ) { if ( initial.isSame( naturalIdValues ) ) { return false; } naturalIdToPkMap.remove( initial ); } final CachedNaturalId cachedNaturalId = new CachedNaturalId( persister, naturalIdValues ); pkToNaturalIdMap.put( pk, cachedNaturalId ); naturalIdToPkMap.put( cachedNaturalId, pk ); return true; } public void stashInvalidNaturalIdReference(Object[] invalidNaturalIdValues) { if ( invalidNaturalIdList == null ) { invalidNaturalIdList = new ArrayList<>(); } invalidNaturalIdList.add( new CachedNaturalId( persister, invalidNaturalIdValues ) ); } public boolean containsInvalidNaturalIdReference(Object[] naturalIdValues) { return invalidNaturalIdList != null && invalidNaturalIdList.contains( new CachedNaturalId( persister, naturalIdValues ) ); } public void unStashInvalidNaturalIdReferences() { if ( invalidNaturalIdList != null ) { invalidNaturalIdList.clear(); } } } /** * Clear the resolution cache */ public void clear() { naturalIdResolutionCacheMap.clear(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy