HHH-7085 - Entities marked as @Immutable that have a @NaturalId fail to be inserted with NPE
This commit is contained in:
parent
f1a54aeadf
commit
9f4fd48603
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.engine.internal;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
|
||||
import org.hibernate.cache.spi.NaturalIdCacheKey;
|
||||
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
|
||||
import org.hibernate.cache.spi.access.SoftLock;
|
||||
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SessionImplementor;
|
||||
import org.hibernate.event.spi.EventSource;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
||||
/**
|
||||
* 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. Additionally coordinates
|
||||
* actions related to the shared caching of the entity's natural id.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class NaturalIdXrefDelegate {
|
||||
private static final Logger LOG = Logger.getLogger( NaturalIdXrefDelegate.class );
|
||||
|
||||
private final StatefulPersistenceContext persistenceContext;
|
||||
private final Map<EntityPersister, NaturalIdResolutionCache> naturalIdResolutionCacheMap = new ConcurrentHashMap<EntityPersister, NaturalIdResolutionCache>();
|
||||
|
||||
public NaturalIdXrefDelegate(StatefulPersistenceContext persistenceContext) {
|
||||
this.persistenceContext = persistenceContext;
|
||||
}
|
||||
|
||||
protected SessionImplementor session() {
|
||||
return persistenceContext.getSession();
|
||||
}
|
||||
|
||||
public void cacheNaturalIdResolution(
|
||||
EntityPersister persister,
|
||||
final Serializable pk,
|
||||
Object[] naturalIdValues,
|
||||
CachedNaturalIdValueSource valueSource) {
|
||||
validateNaturalId( persister, naturalIdValues );
|
||||
|
||||
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
||||
if ( entityNaturalIdResolutionCache == null ) {
|
||||
entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister );
|
||||
naturalIdResolutionCacheMap.put( persister, entityNaturalIdResolutionCache );
|
||||
}
|
||||
|
||||
final CachedNaturalId cachedNaturalId = new CachedNaturalId( persister, naturalIdValues );
|
||||
entityNaturalIdResolutionCache.pkToNaturalIdMap.put( pk, cachedNaturalId );
|
||||
entityNaturalIdResolutionCache.naturalIdToPkMap.put( cachedNaturalId, pk );
|
||||
|
||||
//If second-level caching is enabled cache the resolution there as well
|
||||
if ( persister.hasNaturalIdCache() ) {
|
||||
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
|
||||
final NaturalIdCacheKey naturalIdCacheKey = new NaturalIdCacheKey( naturalIdValues, persister, session() );
|
||||
|
||||
final SessionFactoryImplementor factory = session().getFactory();
|
||||
|
||||
switch ( valueSource ) {
|
||||
case LOAD: {
|
||||
final boolean put = naturalIdCacheAccessStrategy.putFromLoad(
|
||||
naturalIdCacheKey,
|
||||
pk,
|
||||
session().getTimestamp(),
|
||||
null
|
||||
);
|
||||
|
||||
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
|
||||
factory.getStatisticsImplementor()
|
||||
.naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() );
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case INSERT: {
|
||||
naturalIdCacheAccessStrategy.insert( naturalIdCacheKey, pk );
|
||||
|
||||
( (EventSource) session() ).getActionQueue().registerProcess(
|
||||
new AfterTransactionCompletionProcess() {
|
||||
@Override
|
||||
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
|
||||
final boolean put = naturalIdCacheAccessStrategy.afterInsert( naturalIdCacheKey, pk );
|
||||
|
||||
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
|
||||
factory.getStatisticsImplementor().naturalIdCachePut(
|
||||
naturalIdCacheAccessStrategy.getRegion().getName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
case UPDATE: {
|
||||
final SoftLock lock = naturalIdCacheAccessStrategy.lockItem( naturalIdCacheKey, null );
|
||||
naturalIdCacheAccessStrategy.update( naturalIdCacheKey, pk );
|
||||
|
||||
( (EventSource) session() ).getActionQueue().registerProcess(
|
||||
new AfterTransactionCompletionProcess() {
|
||||
@Override
|
||||
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
|
||||
final boolean put = naturalIdCacheAccessStrategy.afterUpdate( naturalIdCacheKey, pk, lock );
|
||||
|
||||
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
|
||||
factory.getStatisticsImplementor().naturalIdCachePut(
|
||||
naturalIdCacheAccessStrategy.getRegion().getName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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." );
|
||||
}
|
||||
}
|
||||
|
||||
public void evictNaturalIdResolution(EntityPersister persister, final Serializable pk, Object[] deletedNaturalIdValues) {
|
||||
validateNaturalId( persister, deletedNaturalIdValues );
|
||||
|
||||
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 NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister
|
||||
.getNaturalIdCacheAccessStrategy();
|
||||
final NaturalIdCacheKey naturalIdCacheKey = new NaturalIdCacheKey( deletedNaturalIdValues, persister, session() );
|
||||
naturalIdCacheAccessStrategy.evict( naturalIdCacheKey );
|
||||
|
||||
if ( sessionCachedNaturalIdValues != null
|
||||
&& !Arrays.equals( sessionCachedNaturalIdValues, deletedNaturalIdValues ) ) {
|
||||
final NaturalIdCacheKey sessionNaturalIdCacheKey = new NaturalIdCacheKey( sessionCachedNaturalIdValues, persister, session() );
|
||||
naturalIdCacheAccessStrategy.evict( sessionNaturalIdCacheKey );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Object[] findCachedNaturalId(EntityPersister persister, Serializable pk) {
|
||||
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();
|
||||
}
|
||||
|
||||
public Serializable findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Session cache miss, see if second-level caching is enabled
|
||||
if ( !persister.hasNaturalIdCache() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try resolution from second-level cache
|
||||
final NaturalIdCacheKey naturalIdCacheKey = new NaturalIdCacheKey( naturalIdValues, persister, session() );
|
||||
|
||||
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
|
||||
pk = (Serializable) naturalIdCacheAccessStrategy.get( naturalIdCacheKey, session().getTimestamp() );
|
||||
|
||||
// Found in second-level cache, store in session cache
|
||||
final SessionFactoryImplementor factory = session().getFactory();
|
||||
if ( pk != null ) {
|
||||
if ( factory.getStatistics().isStatisticsEnabled() ) {
|
||||
factory.getStatisticsImplementor().naturalIdCacheHit(
|
||||
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 );
|
||||
naturalIdResolutionCacheMap.put( persister, entityNaturalIdResolutionCache );
|
||||
}
|
||||
|
||||
entityNaturalIdResolutionCache.pkToNaturalIdMap.put( pk, cachedNaturalId );
|
||||
entityNaturalIdResolutionCache.naturalIdToPkMap.put( cachedNaturalId, pk );
|
||||
}
|
||||
else if ( factory.getStatistics().isStatisticsEnabled() ) {
|
||||
factory.getStatisticsImplementor().naturalIdCacheMiss( naturalIdCacheAccessStrategy.getRegion().getName() );
|
||||
}
|
||||
|
||||
return pk;
|
||||
}
|
||||
|
||||
|
||||
private static class CachedNaturalId {
|
||||
private final EntityPersister persister;
|
||||
private final Object[] values;
|
||||
private int hashCode;
|
||||
|
||||
public CachedNaturalId(EntityPersister persister, Object[] values) {
|
||||
this.persister = persister;
|
||||
this.values = values;
|
||||
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ( ( persister == null ) ? 0 : persister.hashCode() );
|
||||
result = prime * result + Arrays.hashCode( values );
|
||||
this.hashCode = result;
|
||||
}
|
||||
|
||||
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 )
|
||||
&& Arrays.equals( values, other.values );
|
||||
}
|
||||
}
|
||||
|
||||
private static class NaturalIdResolutionCache implements Serializable {
|
||||
private final EntityPersister persister;
|
||||
|
||||
private NaturalIdResolutionCache(EntityPersister persister) {
|
||||
this.persister = persister;
|
||||
}
|
||||
|
||||
public EntityPersister getPersister() {
|
||||
return persister;
|
||||
}
|
||||
|
||||
private Map<Serializable, CachedNaturalId> pkToNaturalIdMap = new ConcurrentHashMap<Serializable, CachedNaturalId>();
|
||||
private Map<CachedNaturalId, Serializable> naturalIdToPkMap = new ConcurrentHashMap<CachedNaturalId, Serializable>();
|
||||
}
|
||||
}
|
|
@ -29,7 +29,6 @@ import java.io.ObjectInputStream;
|
|||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
|
@ -37,10 +36,11 @@ import java.util.Iterator;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.collections.map.AbstractReferenceMap;
|
||||
import org.apache.commons.collections.map.ReferenceMap;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.HibernateException;
|
||||
|
@ -49,24 +49,19 @@ import org.hibernate.MappingException;
|
|||
import org.hibernate.NonUniqueObjectException;
|
||||
import org.hibernate.PersistentObjectException;
|
||||
import org.hibernate.TransientObjectException;
|
||||
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
|
||||
import org.hibernate.cache.spi.NaturalIdCacheKey;
|
||||
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
|
||||
import org.hibernate.cache.spi.access.SoftLock;
|
||||
import org.hibernate.collection.spi.PersistentCollection;
|
||||
import org.hibernate.engine.loading.internal.LoadContexts;
|
||||
import org.hibernate.engine.spi.AssociationKey;
|
||||
import org.hibernate.engine.spi.BatchFetchQueue;
|
||||
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
|
||||
import org.hibernate.engine.spi.CollectionEntry;
|
||||
import org.hibernate.engine.spi.CollectionKey;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.EntityKey;
|
||||
import org.hibernate.engine.spi.EntityUniqueKey;
|
||||
import org.hibernate.engine.spi.PersistenceContext;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SessionImplementor;
|
||||
import org.hibernate.engine.spi.Status;
|
||||
import org.hibernate.event.spi.EventSource;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.internal.util.MarkerObject;
|
||||
import org.hibernate.internal.util.collections.IdentityMap;
|
||||
|
@ -76,7 +71,6 @@ import org.hibernate.pretty.MessageHelper;
|
|||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.proxy.LazyInitializer;
|
||||
import org.hibernate.tuple.ElementWrapper;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* A <strong>stateful</strong> implementation of the {@link PersistenceContext} contract meaning that we maintain this
|
||||
|
@ -1704,8 +1698,10 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
|
||||
// NATURAL ID RESOLUTION HANDLING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
private NaturalIdXrefDelegate naturalIdXrefDelegate = new NaturalIdXrefDelegate( this );
|
||||
|
||||
@Override
|
||||
public void loadedStateInsertedNotification(EntityEntry entityEntry, Object[] state) {
|
||||
public void entityStateInsertedNotification(EntityEntry entityEntry, Object[] state) {
|
||||
final EntityPersister persister = entityEntry.getPersister();
|
||||
if ( !persister.hasNaturalIdentifier() ) {
|
||||
// nothing to do
|
||||
|
@ -1715,11 +1711,16 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
final Object[] naturalIdValues = getNaturalIdValues( state, persister );
|
||||
|
||||
// cache
|
||||
cacheNaturalIdResolution( persister, entityEntry.getId(), naturalIdValues, CachedNaturalIdValueSource.INSERT );
|
||||
naturalIdXrefDelegate.cacheNaturalIdResolution(
|
||||
persister,
|
||||
entityEntry.getId(),
|
||||
naturalIdValues,
|
||||
CachedNaturalIdValueSource.INSERT
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadedStateUpdatedNotification(EntityEntry entityEntry, Object[] state) {
|
||||
public void entityStateUpdatedNotification(EntityEntry entityEntry, Object[] state) {
|
||||
final EntityPersister persister = entityEntry.getPersister();
|
||||
if ( !persister.hasNaturalIdentifier() ) {
|
||||
// nothing to do
|
||||
|
@ -1729,11 +1730,16 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
final Object[] naturalIdValues = getNaturalIdValues( state, persister );
|
||||
|
||||
// re-cache
|
||||
cacheNaturalIdResolution( persister, entityEntry.getId(), naturalIdValues, CachedNaturalIdValueSource.UPDATE );
|
||||
naturalIdXrefDelegate.cacheNaturalIdResolution(
|
||||
persister,
|
||||
entityEntry.getId(),
|
||||
naturalIdValues,
|
||||
CachedNaturalIdValueSource.UPDATE
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadedStateDeletedNotification(EntityEntry entityEntry, Object[] deletedState) {
|
||||
public void entityStateDeletedNotification(EntityEntry entityEntry, Object[] deletedState) {
|
||||
final EntityPersister persister = entityEntry.getPersister();
|
||||
if ( !persister.hasNaturalIdentifier() ) {
|
||||
// nothing to do
|
||||
|
@ -1743,7 +1749,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
final Object[] naturalIdValues = getNaturalIdValues( deletedState, persister );
|
||||
|
||||
// evict from cache
|
||||
evictNaturalIdResolution( persister, entityEntry.getId(), naturalIdValues );
|
||||
naturalIdXrefDelegate.evictNaturalIdResolution( persister, entityEntry.getId(), naturalIdValues );
|
||||
}
|
||||
|
||||
private Object[] getNaturalIdValues(Object[] state, EntityPersister persister) {
|
||||
|
@ -1757,270 +1763,23 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
return naturalIdValues;
|
||||
}
|
||||
|
||||
private static class LocalNaturalIdCacheKey {
|
||||
private final EntityPersister persister;
|
||||
private final Object[] values;
|
||||
private int hashCode;
|
||||
|
||||
public LocalNaturalIdCacheKey(EntityPersister persister, Object[] values) {
|
||||
this.persister = persister;
|
||||
this.values = values;
|
||||
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ( ( persister == null ) ? 0 : persister.hashCode() );
|
||||
result = prime * result + Arrays.hashCode( values );
|
||||
this.hashCode = result;
|
||||
}
|
||||
|
||||
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 LocalNaturalIdCacheKey other = (LocalNaturalIdCacheKey) obj;
|
||||
return persister.equals( other.persister )
|
||||
&& Arrays.equals( values, other.values );
|
||||
}
|
||||
}
|
||||
|
||||
private static class NaturalIdResolutionCache implements Serializable {
|
||||
private final EntityPersister persister;
|
||||
|
||||
private NaturalIdResolutionCache(EntityPersister persister) {
|
||||
this.persister = persister;
|
||||
}
|
||||
|
||||
public EntityPersister getPersister() {
|
||||
return persister;
|
||||
}
|
||||
|
||||
private Map<Serializable, LocalNaturalIdCacheKey> pkToNaturalIdMap = new ConcurrentHashMap<Serializable, LocalNaturalIdCacheKey>();
|
||||
private Map<LocalNaturalIdCacheKey, Serializable> naturalIdToPkMap = new ConcurrentHashMap<LocalNaturalIdCacheKey, Serializable>();
|
||||
}
|
||||
|
||||
private 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." );
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<EntityPersister, NaturalIdResolutionCache> naturalIdResolutionCacheMap = new ConcurrentHashMap<EntityPersister, NaturalIdResolutionCache>();
|
||||
|
||||
@Override
|
||||
public Object[] findCachedNaturalId(EntityPersister persister, Serializable pk) {
|
||||
final NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
||||
if ( entityNaturalIdResolutionCache == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final LocalNaturalIdCacheKey localNaturalIdCacheKey = entityNaturalIdResolutionCache.pkToNaturalIdMap.get( pk );
|
||||
if ( localNaturalIdCacheKey == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return localNaturalIdCacheKey.getValues();
|
||||
return naturalIdXrefDelegate.findCachedNaturalId( persister, pk );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Serializable findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues) {
|
||||
validateNaturalId( persister, naturalIdValues );
|
||||
|
||||
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
||||
|
||||
Serializable pk;
|
||||
final LocalNaturalIdCacheKey localNaturalIdCacheKey = new LocalNaturalIdCacheKey( persister, naturalIdValues );
|
||||
if ( entityNaturalIdResolutionCache != null ) {
|
||||
pk = entityNaturalIdResolutionCache.naturalIdToPkMap.get( localNaturalIdCacheKey );
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Session cache miss, see if second-level caching is enabled
|
||||
if ( !persister.hasNaturalIdCache() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try resolution from second-level cache
|
||||
final NaturalIdCacheKey naturalIdCacheKey = new NaturalIdCacheKey( naturalIdValues, persister, session );
|
||||
|
||||
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
|
||||
pk = (Serializable) naturalIdCacheAccessStrategy.get( naturalIdCacheKey, session.getTimestamp() );
|
||||
|
||||
// Found in second-level cache, store in session cache
|
||||
final SessionFactoryImplementor factory = getSession().getFactory();
|
||||
if ( pk != null ) {
|
||||
if ( factory.getStatistics().isStatisticsEnabled() ) {
|
||||
factory.getStatisticsImplementor().naturalIdCacheHit(
|
||||
naturalIdCacheAccessStrategy.getRegion().getName()
|
||||
);
|
||||
}
|
||||
|
||||
if ( LOG.isTraceEnabled() )
|
||||
LOG.trace(
|
||||
"Resolved natural key -> primary key resolution in second-level cache: " +
|
||||
persister.getRootEntityName() + "#[" +
|
||||
Arrays.toString( naturalIdValues ) + "]"
|
||||
);
|
||||
|
||||
if ( entityNaturalIdResolutionCache == null ) {
|
||||
entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister );
|
||||
naturalIdResolutionCacheMap.put( persister, entityNaturalIdResolutionCache );
|
||||
}
|
||||
|
||||
entityNaturalIdResolutionCache.pkToNaturalIdMap.put( pk, localNaturalIdCacheKey );
|
||||
entityNaturalIdResolutionCache.naturalIdToPkMap.put( localNaturalIdCacheKey, pk );
|
||||
}
|
||||
else if ( factory.getStatistics().isStatisticsEnabled() ) {
|
||||
factory.getStatisticsImplementor().naturalIdCacheMiss( naturalIdCacheAccessStrategy.getRegion().getName() );
|
||||
}
|
||||
|
||||
return pk;
|
||||
return naturalIdXrefDelegate.findCachedNaturalIdResolution( persister, naturalIdValues );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cacheNaturalIdResolution(EntityPersister persister, final Serializable pk, Object[] naturalIdValues,
|
||||
public void cacheNaturalIdResolution(
|
||||
EntityPersister persister,
|
||||
final Serializable pk,
|
||||
Object[] naturalIdValues,
|
||||
CachedNaturalIdValueSource valueSource) {
|
||||
validateNaturalId( persister, naturalIdValues );
|
||||
|
||||
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
||||
if ( entityNaturalIdResolutionCache == null ) {
|
||||
entityNaturalIdResolutionCache = new NaturalIdResolutionCache( persister );
|
||||
naturalIdResolutionCacheMap.put( persister, entityNaturalIdResolutionCache );
|
||||
}
|
||||
|
||||
final LocalNaturalIdCacheKey localNaturalIdCacheKey = new LocalNaturalIdCacheKey( persister, naturalIdValues );
|
||||
entityNaturalIdResolutionCache.pkToNaturalIdMap.put( pk, localNaturalIdCacheKey );
|
||||
entityNaturalIdResolutionCache.naturalIdToPkMap.put( localNaturalIdCacheKey, pk );
|
||||
|
||||
//If second-level caching is enabled cache the resolution there as well
|
||||
if ( persister.hasNaturalIdCache() ) {
|
||||
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister.getNaturalIdCacheAccessStrategy();
|
||||
final NaturalIdCacheKey naturalIdCacheKey = new NaturalIdCacheKey( naturalIdValues, persister, session );
|
||||
|
||||
final SessionFactoryImplementor factory = getSession().getFactory();
|
||||
|
||||
switch ( valueSource ) {
|
||||
case LOAD: {
|
||||
final boolean put = naturalIdCacheAccessStrategy.putFromLoad(
|
||||
naturalIdCacheKey,
|
||||
pk,
|
||||
session.getTimestamp(),
|
||||
null
|
||||
);
|
||||
|
||||
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
|
||||
factory.getStatisticsImplementor()
|
||||
.naturalIdCachePut( naturalIdCacheAccessStrategy.getRegion().getName() );
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case INSERT: {
|
||||
naturalIdCacheAccessStrategy.insert( naturalIdCacheKey, pk );
|
||||
|
||||
( (EventSource) this.session ).getActionQueue().registerProcess(
|
||||
new AfterTransactionCompletionProcess() {
|
||||
@Override
|
||||
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
|
||||
final boolean put = naturalIdCacheAccessStrategy.afterInsert( naturalIdCacheKey, pk );
|
||||
|
||||
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
|
||||
factory.getStatisticsImplementor().naturalIdCachePut(
|
||||
naturalIdCacheAccessStrategy.getRegion().getName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
case UPDATE: {
|
||||
final SoftLock lock = naturalIdCacheAccessStrategy.lockItem( naturalIdCacheKey, null );
|
||||
naturalIdCacheAccessStrategy.update( naturalIdCacheKey, pk );
|
||||
|
||||
( (EventSource) this.session ).getActionQueue().registerProcess(
|
||||
new AfterTransactionCompletionProcess() {
|
||||
@Override
|
||||
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
|
||||
final boolean put = naturalIdCacheAccessStrategy.afterUpdate( naturalIdCacheKey, pk, lock );
|
||||
|
||||
if ( put && factory.getStatistics().isStatisticsEnabled() ) {
|
||||
factory.getStatisticsImplementor().naturalIdCachePut(
|
||||
naturalIdCacheAccessStrategy.getRegion().getName() );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void evictNaturalIdResolution(EntityPersister persister, final Serializable pk, Object[] deletedNaturalIdValues) {
|
||||
if ( !persister.hasNaturalIdentifier() ) {
|
||||
throw new IllegalArgumentException( "Entity did not define a natrual-id" );
|
||||
}
|
||||
if ( persister.getNaturalIdentifierProperties().length != deletedNaturalIdValues.length ) {
|
||||
throw new IllegalArgumentException( "Mismatch between expected number of natural-id values and found." );
|
||||
}
|
||||
|
||||
NaturalIdResolutionCache entityNaturalIdResolutionCache = naturalIdResolutionCacheMap.get( persister );
|
||||
Object[] sessionCachedNaturalIdValues = null;
|
||||
if ( entityNaturalIdResolutionCache != null ) {
|
||||
final LocalNaturalIdCacheKey localNaturalIdCacheKey = entityNaturalIdResolutionCache.pkToNaturalIdMap
|
||||
.remove( pk );
|
||||
if ( localNaturalIdCacheKey != null ) {
|
||||
entityNaturalIdResolutionCache.naturalIdToPkMap.remove( localNaturalIdCacheKey );
|
||||
sessionCachedNaturalIdValues = localNaturalIdCacheKey.getValues();
|
||||
}
|
||||
}
|
||||
|
||||
if ( persister.hasNaturalIdCache() ) {
|
||||
final NaturalIdRegionAccessStrategy naturalIdCacheAccessStrategy = persister
|
||||
.getNaturalIdCacheAccessStrategy();
|
||||
final NaturalIdCacheKey naturalIdCacheKey = new NaturalIdCacheKey( deletedNaturalIdValues, persister,
|
||||
session );
|
||||
naturalIdCacheAccessStrategy.evict( naturalIdCacheKey );
|
||||
|
||||
if ( sessionCachedNaturalIdValues != null
|
||||
&& !Arrays.equals( sessionCachedNaturalIdValues, deletedNaturalIdValues ) ) {
|
||||
final NaturalIdCacheKey sessionNaturalIdCacheKey = new NaturalIdCacheKey( sessionCachedNaturalIdValues,
|
||||
persister, session );
|
||||
naturalIdCacheAccessStrategy.evict( sessionNaturalIdCacheKey );
|
||||
}
|
||||
}
|
||||
naturalIdXrefDelegate.cacheNaturalIdResolution( persister, pk, naturalIdValues, valueSource );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.engine.spi;
|
||||
|
||||
/**
|
||||
* The type of action from which the cache call is originating.
|
||||
*/
|
||||
public enum CachedNaturalIdValueSource {
|
||||
LOAD,
|
||||
INSERT,
|
||||
UPDATE
|
||||
}
|
|
@ -379,7 +379,7 @@ public final class EntityEntry implements Serializable {
|
|||
throw new HibernateException( "PersistenceContext was null on attempt to update loaded state" );
|
||||
}
|
||||
|
||||
persistenceContext.loadedStateUpdatedNotification( this, state );
|
||||
persistenceContext.entityStateUpdatedNotification( this, state );
|
||||
}
|
||||
|
||||
private void notifyLoadedStateInserted(Object[] state) {
|
||||
|
@ -387,7 +387,7 @@ public final class EntityEntry implements Serializable {
|
|||
throw new HibernateException( "PersistenceContext was null on attempt to insert loaded state" );
|
||||
}
|
||||
|
||||
persistenceContext.loadedStateInsertedNotification( this, state );
|
||||
persistenceContext.entityStateInsertedNotification( this, state );
|
||||
}
|
||||
|
||||
private void notifyLoadedStateDeleted(Object[] deletedState) {
|
||||
|
@ -395,7 +395,7 @@ public final class EntityEntry implements Serializable {
|
|||
throw new HibernateException( "PersistenceContext was null on attempt to delete loaded state" );
|
||||
}
|
||||
|
||||
persistenceContext.loadedStateDeletedNotification( this, deletedState );
|
||||
persistenceContext.entityStateDeletedNotification( this, deletedState );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -683,25 +683,34 @@ public interface PersistenceContext {
|
|||
*/
|
||||
public boolean wasInsertedDuringTransaction(EntityPersister persister, Serializable id);
|
||||
|
||||
public void loadedStateUpdatedNotification(EntityEntry entityEntry, Object[] state);
|
||||
|
||||
public void loadedStateInsertedNotification(EntityEntry entityEntry, Object[] state);
|
||||
|
||||
public void loadedStateDeletedNotification(EntityEntry entityEntry, Object[] deletedState);
|
||||
/**
|
||||
* Callback used to signal that loaded entity state has changed.
|
||||
*
|
||||
* @param entityEntry The entry of the entity that has changed.
|
||||
* @param state The new state.
|
||||
*/
|
||||
public void entityStateUpdatedNotification(EntityEntry entityEntry, Object[] state);
|
||||
|
||||
/**
|
||||
* Callback used to signal that entity state has been inserted.
|
||||
*
|
||||
* @param entityEntry The entry of the inserted entity
|
||||
* @param state The new state
|
||||
*/
|
||||
public void entityStateInsertedNotification(EntityEntry entityEntry, Object[] state);
|
||||
|
||||
/**
|
||||
* Callback used to signal that entity state has been deleted.
|
||||
*
|
||||
* @param entityEntry The entry of the inserted entity
|
||||
* @param deletedState The state of the entity at the time of deletion
|
||||
*/
|
||||
public void entityStateDeletedNotification(EntityEntry entityEntry, Object[] deletedState);
|
||||
|
||||
public Object[] findCachedNaturalId(EntityPersister persister, Serializable pk);
|
||||
|
||||
public Serializable findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalId);
|
||||
|
||||
/**
|
||||
* The type of action the cache call is originating from
|
||||
*/
|
||||
public enum CachedNaturalIdValueSource {
|
||||
LOAD,
|
||||
INSERT,
|
||||
UPDATE
|
||||
}
|
||||
|
||||
public void cacheNaturalIdResolution(EntityPersister persister, Serializable pk, Object[] naturalId, CachedNaturalIdValueSource valueSource);
|
||||
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import java.io.Serializable;
|
|||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
|
||||
import org.hibernate.engine.spi.PersistenceContext.CachedNaturalIdValueSource;
|
||||
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.event.spi.ResolveNaturalIdEvent;
|
||||
import org.hibernate.event.spi.ResolveNaturalIdEventListener;
|
||||
|
|
|
@ -1,9 +1,32 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
//$Id$
|
||||
package org.hibernate.test.annotations.naturalid;
|
||||
package org.hibernate.test.naturalid.immutableentity;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
import org.hibernate.annotations.Immutable;
|
||||
import org.hibernate.annotations.NaturalId;
|
||||
|
@ -11,7 +34,6 @@ import org.hibernate.annotations.NaturalIdCache;
|
|||
|
||||
/**
|
||||
* @author Eric Dalquist
|
||||
* @see https://hibernate.onjira.com/browse/HHH-7085
|
||||
*/
|
||||
@Entity
|
||||
@Immutable
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
|
||||
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
|
@ -21,7 +21,7 @@
|
|||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.test.annotations.naturalid;
|
||||
package org.hibernate.test.naturalid.immutableentity;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
@ -35,6 +35,8 @@ import org.hibernate.annotations.Immutable;
|
|||
import org.hibernate.cfg.Configuration;
|
||||
import org.hibernate.metadata.ClassMetadata;
|
||||
import org.hibernate.stat.Statistics;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -42,9 +44,9 @@ import org.junit.Test;
|
|||
* Test case for NaturalId annotation on an {@link Immutable} entity
|
||||
*
|
||||
* @author Eric Dalquist
|
||||
* @see https://hibernate.onjira.com/browse/HHH-7085
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@TestForIssue( jiraKey = "HHH-7085" )
|
||||
public class ImmutableEntityNaturalIdTest extends BaseCoreFunctionalTestCase {
|
||||
@Test
|
||||
public void testMappingProperties() {
|
Loading…
Reference in New Issue