Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ public NonNullableTransientDependencies findNonNullableTransientEntities() {
*/
protected final void nullifyTransientReferencesIfNotAlready() {
if ( ! areTransientReferencesNullified ) {
new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), getSession() )
.nullifyTransientReferences( getState(), getPersister().getPropertyTypes() );
new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), getSession(), getPersister() )
.nullifyTransientReferences( getState() );
new Nullability( getSession() ).checkNullability( getState(), getPersister(), false );
areTransientReferencesNullified = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
import org.hibernate.TransientObjectException;
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.SelfDirtinessTracker;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
Expand All @@ -36,6 +38,7 @@ public static class Nullifier {
private final boolean isEarlyInsert;
private final SharedSessionContractImplementor session;
private final Object self;
private final EntityPersister persister;

/**
* Constructs a Nullifier
Expand All @@ -44,11 +47,18 @@ public static class Nullifier {
* @param isDelete Are we in the middle of a delete action?
* @param isEarlyInsert Is this an early insert (INSERT generated id strategy)?
* @param session The session
* @param persister The EntityPersister for {@code self}
*/
public Nullifier(Object self, boolean isDelete, boolean isEarlyInsert, SharedSessionContractImplementor session) {
public Nullifier(
final Object self,
final boolean isDelete,
final boolean isEarlyInsert,
final SharedSessionContractImplementor session,
final EntityPersister persister) {
this.isDelete = isDelete;
this.isEarlyInsert = isEarlyInsert;
this.session = session;
this.persister = persister;
this.self = self;
}

Expand All @@ -57,11 +67,12 @@ public Nullifier(Object self, boolean isDelete, boolean isEarlyInsert, SharedSes
* points toward that entity.
*
* @param values The entity attribute values
* @param types The entity attribute types
*/
public void nullifyTransientReferences(final Object[] values, final Type[] types) {
public void nullifyTransientReferences(final Object[] values) {
final String[] propertyNames = persister.getPropertyNames();
final Type[] types = persister.getPropertyTypes();
for ( int i = 0; i < types.length; i++ ) {
values[i] = nullifyTransientReferences( values[i], types[i] );
values[i] = nullifyTransientReferences( values[i], propertyNames[i], types[i] );
}
}

Expand All @@ -70,34 +81,53 @@ public void nullifyTransientReferences(final Object[] values, final Type[] types
* input argument otherwise. This is how Hibernate avoids foreign key constraint violations.
*
* @param value An entity attribute value
* @param propertyName An entity attribute name
* @param type An entity attribute type
*
* @return {@code null} if the argument is an unsaved entity; otherwise return the argument.
*/
private Object nullifyTransientReferences(final Object value, final Type type) {
private Object nullifyTransientReferences(final Object value, final String propertyName, final Type type) {
final Object returnedValue;
if ( value == null ) {
return null;
returnedValue = null;
}
else if ( type.isEntityType() ) {
final EntityType entityType = (EntityType) type;
if ( entityType.isOneToOne() ) {
return value;
returnedValue = value;
}
else {
final String entityName = entityType.getAssociatedEntityName();
return isNullifiable( entityName, value ) ? null : value;
// If value is lazy, it may need to be initialized to
// determine if the value is nullifiable.
final Object possiblyInitializedValue = initializeIfNecessary( value, propertyName, entityType );
if ( possiblyInitializedValue == null ) {
// The uninitialized value was initialized to null
returnedValue = null;
}
else {
// If the value is not nullifiable, make sure that the
// possibly initialized value is returned.
returnedValue = isNullifiable( entityType.getAssociatedEntityName(), possiblyInitializedValue )
? null
: possiblyInitializedValue;
}
}
}
else if ( type.isAnyType() ) {
return isNullifiable( null, value ) ? null : value;
returnedValue = isNullifiable( null, value ) ? null : value;
}
else if ( type.isComponentType() ) {
final CompositeType actype = (CompositeType) type;
final Object[] subvalues = actype.getPropertyValues( value, session );
final Type[] subtypes = actype.getSubtypes();
final String[] subPropertyNames = actype.getPropertyNames();
boolean substitute = false;
for ( int i = 0; i < subvalues.length; i++ ) {
final Object replacement = nullifyTransientReferences( subvalues[i], subtypes[i] );
final Object replacement = nullifyTransientReferences(
subvalues[i],
StringHelper.qualify( propertyName, subPropertyNames[i] ),
subtypes[i]
);
if ( replacement != subvalues[i] ) {
substitute = true;
subvalues[i] = replacement;
Expand All @@ -107,7 +137,50 @@ else if ( type.isComponentType() ) {
// todo : need to account for entity mode on the CompositeType interface :(
actype.setPropertyValues( value, subvalues, EntityMode.POJO );
}
return value;
returnedValue = value;
}
else {
returnedValue = value;
}
// value != returnedValue if either:
// 1) returnedValue was nullified (set to null);
// or 2) returnedValue was initialized, but not nullified.
// When bytecode-enhancement is used for dirty-checking, the change should
// only be tracked when returnedValue was nullified (1)).
if ( value != returnedValue && returnedValue == null && SelfDirtinessTracker.class.isInstance( self ) ) {
( (SelfDirtinessTracker) self ).$$_hibernate_trackChange( propertyName );
}
return returnedValue;
}

private Object initializeIfNecessary(
final Object value,
final String propertyName,
final Type type) {
if ( isDelete &&
value == LazyPropertyInitializer.UNFETCHED_PROPERTY &&
type.isEntityType() &&
!session.getPersistenceContext().getNullifiableEntityKeys().isEmpty() ) {
// IMPLEMENTATION NOTE: If cascade-remove was mapped for the attribute,
// then value should have been initialized previously, when the remove operation was
// cascaded to the property (because CascadingAction.DELETE.performOnLazyProperty()
// returns true). This particular situation can only arise when cascade-remove is not
// mapped for the association.

// There is at least one nullifiable entity. We don't know if the lazy
// associated entity is one of the nullifiable entities. If it is, and
// the property is not nullified, then a constraint violation will result.
// The only way to find out if the associated entity is nullifiable is
// to initialize it.
// TODO: there may be ways to fine-tune when initialization is necessary
// (e.g., only initialize when the associated entity type is a
// superclass or the same as the entity type of a nullifiable entity).
// It is unclear if a more complicated check would impact performance
// more than just initializing the associated entity.
return persister
.getInstrumentationMetadata()
.extractInterceptor( self )
.fetchAttribute( self, propertyName );
}
else {
return value;
Expand Down Expand Up @@ -162,9 +235,7 @@ private boolean isNullifiable(final String entityName, Object object)
else {
return entityEntry.isNullifiable( isEarlyInsert, session );
}

}

}

/**
Expand Down Expand Up @@ -307,8 +378,8 @@ public static NonNullableTransientDependencies findNonNullableTransientEntities(
Object[] values,
boolean isEarlyInsert,
SharedSessionContractImplementor session) {
final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session );
final EntityPersister persister = session.getEntityPersister( entityName, entity );
final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session, persister );
final String[] propertyNames = persister.getPropertyNames();
final Type[] types = persister.getPropertyTypes();
final boolean[] nullability = persister.getPropertyNullability();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,7 @@ protected final void deleteEntity(

cascadeBeforeDelete( session, persister, entity, entityEntry, transientEntities );

new ForeignKeys.Nullifier( entity, true, false, session )
.nullifyTransientReferences( entityEntry.getDeletedState(), propTypes );
new ForeignKeys.Nullifier( entity, true, false, session, persister ).nullifyTransientReferences( entityEntry.getDeletedState() );
new Nullability( session ).checkNullability( entityEntry.getDeletedState(), persister, Nullability.NullabilityCheckType.DELETE );
persistenceContext.getNullifiableEntityKeys().add( key );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1179,7 +1179,6 @@ private Object initializeLazyPropertiesFromDatastore(
rs = session.getJdbcCoordinator().getResultSetReturn().extract( ps );
rs.next();
}
final Object[] snapshot = entry.getLoadedState();
for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) {
final boolean previousInitialized = initializedLazyAttributeNames.contains( fetchGroupAttributeDescriptor.getName() );

Expand Down Expand Up @@ -1210,7 +1209,7 @@ private Object initializeLazyPropertiesFromDatastore(
fieldName,
entity,
session,
snapshot,
entry,
fetchGroupAttributeDescriptor.getLazyIndex(),
selectedValue
);
Expand Down Expand Up @@ -1259,7 +1258,6 @@ private Object initializeLazyPropertiesFromCache(

Object result = null;
Serializable[] disassembledValues = cacheEntry.getDisassembledState();
final Object[] snapshot = entry.getLoadedState();
for ( int j = 0; j < lazyPropertyNames.length; j++ ) {
final Serializable cachedValue = disassembledValues[lazyPropertyNumbers[j]];
final Type lazyPropertyType = lazyPropertyTypes[j];
Expand All @@ -1276,7 +1274,7 @@ private Object initializeLazyPropertiesFromCache(
session,
entity
);
if ( initializeLazyProperty( fieldName, entity, session, snapshot, j, propValue ) ) {
if ( initializeLazyProperty( fieldName, entity, session, entry, j, propValue ) ) {
result = propValue;
}
}
Expand All @@ -1291,13 +1289,17 @@ private boolean initializeLazyProperty(
final String fieldName,
final Object entity,
final SharedSessionContractImplementor session,
final Object[] snapshot,
final EntityEntry entry,
final int j,
final Object propValue) {
setPropertyValue( entity, lazyPropertyNumbers[j], propValue );
if ( snapshot != null ) {
if ( entry.getLoadedState() != null ) {
// object have been loaded with setReadOnly(true); HHH-2236
snapshot[lazyPropertyNumbers[j]] = lazyPropertyTypes[j].deepCopy( propValue, factory );
entry.getLoadedState()[lazyPropertyNumbers[j]] = lazyPropertyTypes[j].deepCopy( propValue, factory );
}
// If the entity has deleted state, then update that as well
if ( entry.getDeletedState() != null ) {
entry.getDeletedState()[lazyPropertyNumbers[j]] = lazyPropertyTypes[j].deepCopy( propValue, factory );
}
return fieldName.equals( lazyPropertyNames[j] );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,25 @@
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.persistence.EntityManager;
import javax.persistence.SharedCacheMode;
import javax.persistence.ValidationMode;
import javax.persistence.spi.PersistenceUnitTransactionType;

import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl;
import org.hibernate.bytecode.enhance.spi.EnhancementContext;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.jpa.AvailableSettings;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.hibernate.jpa.boot.spi.Bootstrap;
import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor;

import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.After;
import org.junit.Before;

import org.jboss.logging.Logger;

/**
* A base class for all ejb tests.
*
Expand Down Expand Up @@ -209,6 +207,8 @@ protected Map getConfig() {
Map<Object, Object> config = Environment.getProperties();
ArrayList<Class> classes = new ArrayList<Class>();

config.put( AvailableSettings.CLASSLOADERS, getClass().getClassLoader() );

classes.addAll( Arrays.asList( getAnnotatedClasses() ) );
config.put( AvailableSettings.LOADED_CLASSES, classes );
for ( Map.Entry<Class, String> entry : getCachedClasses().entrySet() ) {
Expand Down
Loading