HHH-13241 : Constraint violation when deleting entites in bi-directional, lazy OneToMany association with bytecode enhancement
(cherry picked from commit 980f24916c
)
This commit is contained in:
parent
eb8db53e21
commit
d4c47d46f2
|
@ -110,8 +110,8 @@ public abstract class AbstractEntityInsertAction extends EntityAction {
|
||||||
*/
|
*/
|
||||||
protected final void nullifyTransientReferencesIfNotAlready() {
|
protected final void nullifyTransientReferencesIfNotAlready() {
|
||||||
if ( ! areTransientReferencesNullified ) {
|
if ( ! areTransientReferencesNullified ) {
|
||||||
new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), getSession() )
|
new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), getSession(), getPersister() )
|
||||||
.nullifyTransientReferences( getState(), getPersister().getPropertyTypes() );
|
.nullifyTransientReferences( getState() );
|
||||||
new Nullability( getSession() ).checkNullability( getState(), getPersister(), false );
|
new Nullability( getSession() ).checkNullability( getState(), getPersister(), false );
|
||||||
areTransientReferencesNullified = true;
|
areTransientReferencesNullified = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,9 @@ import org.hibernate.HibernateException;
|
||||||
import org.hibernate.TransientObjectException;
|
import org.hibernate.TransientObjectException;
|
||||||
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
|
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
|
||||||
import org.hibernate.engine.spi.EntityEntry;
|
import org.hibernate.engine.spi.EntityEntry;
|
||||||
|
import org.hibernate.engine.spi.SelfDirtinessTracker;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
|
import org.hibernate.internal.util.StringHelper;
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
import org.hibernate.proxy.HibernateProxy;
|
import org.hibernate.proxy.HibernateProxy;
|
||||||
import org.hibernate.proxy.LazyInitializer;
|
import org.hibernate.proxy.LazyInitializer;
|
||||||
|
@ -36,6 +38,7 @@ public final class ForeignKeys {
|
||||||
private final boolean isEarlyInsert;
|
private final boolean isEarlyInsert;
|
||||||
private final SharedSessionContractImplementor session;
|
private final SharedSessionContractImplementor session;
|
||||||
private final Object self;
|
private final Object self;
|
||||||
|
private final EntityPersister persister;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a Nullifier
|
* Constructs a Nullifier
|
||||||
|
@ -44,11 +47,18 @@ public final class ForeignKeys {
|
||||||
* @param isDelete Are we in the middle of a delete action?
|
* @param isDelete Are we in the middle of a delete action?
|
||||||
* @param isEarlyInsert Is this an early insert (INSERT generated id strategy)?
|
* @param isEarlyInsert Is this an early insert (INSERT generated id strategy)?
|
||||||
* @param session The session
|
* @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.isDelete = isDelete;
|
||||||
this.isEarlyInsert = isEarlyInsert;
|
this.isEarlyInsert = isEarlyInsert;
|
||||||
this.session = session;
|
this.session = session;
|
||||||
|
this.persister = persister;
|
||||||
this.self = self;
|
this.self = self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,11 +67,12 @@ public final class ForeignKeys {
|
||||||
* points toward that entity.
|
* points toward that entity.
|
||||||
*
|
*
|
||||||
* @param values The entity attribute values
|
* @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++ ) {
|
for ( int i = 0; i < types.length; i++ ) {
|
||||||
values[i] = nullifyTransientReferences( values[i], types[i] );
|
values[i] = nullifyTransientReferences( values[i], propertyNames[i], types[i] );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,34 +81,47 @@ public final class ForeignKeys {
|
||||||
* input argument otherwise. This is how Hibernate avoids foreign key constraint violations.
|
* input argument otherwise. This is how Hibernate avoids foreign key constraint violations.
|
||||||
*
|
*
|
||||||
* @param value An entity attribute value
|
* @param value An entity attribute value
|
||||||
|
* @param propertyName An entity attribute name
|
||||||
* @param type An entity attribute type
|
* @param type An entity attribute type
|
||||||
*
|
*
|
||||||
* @return {@code null} if the argument is an unsaved entity; otherwise return the argument.
|
* @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 ) {
|
if ( value == null ) {
|
||||||
return null;
|
returnedValue = null;
|
||||||
}
|
}
|
||||||
else if ( type.isEntityType() ) {
|
else if ( type.isEntityType() ) {
|
||||||
final EntityType entityType = (EntityType) type;
|
final EntityType entityType = (EntityType) type;
|
||||||
if ( entityType.isOneToOne() ) {
|
if ( entityType.isOneToOne() ) {
|
||||||
return value;
|
returnedValue = value;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
final String entityName = entityType.getAssociatedEntityName();
|
// If value is lazy, it may need to be initialized to
|
||||||
return isNullifiable( entityName, value ) ? null : value;
|
// determine if the value is nullifiable.
|
||||||
|
final Object possiblyInitializedValue = initializeIfNecessary( value, propertyName, entityType );
|
||||||
|
// 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() ) {
|
else if ( type.isAnyType() ) {
|
||||||
return isNullifiable( null, value ) ? null : value;
|
returnedValue = isNullifiable( null, value ) ? null : value;
|
||||||
}
|
}
|
||||||
else if ( type.isComponentType() ) {
|
else if ( type.isComponentType() ) {
|
||||||
final CompositeType actype = (CompositeType) type;
|
final CompositeType actype = (CompositeType) type;
|
||||||
final Object[] subvalues = actype.getPropertyValues( value, session );
|
final Object[] subvalues = actype.getPropertyValues( value, session );
|
||||||
final Type[] subtypes = actype.getSubtypes();
|
final Type[] subtypes = actype.getSubtypes();
|
||||||
|
final String[] subPropertyNames = actype.getPropertyNames();
|
||||||
boolean substitute = false;
|
boolean substitute = false;
|
||||||
for ( int i = 0; i < subvalues.length; i++ ) {
|
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] ) {
|
if ( replacement != subvalues[i] ) {
|
||||||
substitute = true;
|
substitute = true;
|
||||||
subvalues[i] = replacement;
|
subvalues[i] = replacement;
|
||||||
|
@ -107,7 +131,50 @@ public final class ForeignKeys {
|
||||||
// todo : need to account for entity mode on the CompositeType interface :(
|
// todo : need to account for entity mode on the CompositeType interface :(
|
||||||
actype.setPropertyValues( value, subvalues, EntityMode.POJO );
|
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 {
|
else {
|
||||||
return value;
|
return value;
|
||||||
|
@ -162,9 +229,7 @@ public final class ForeignKeys {
|
||||||
else {
|
else {
|
||||||
return entityEntry.isNullifiable( isEarlyInsert, session );
|
return entityEntry.isNullifiable( isEarlyInsert, session );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -307,8 +372,8 @@ public final class ForeignKeys {
|
||||||
Object[] values,
|
Object[] values,
|
||||||
boolean isEarlyInsert,
|
boolean isEarlyInsert,
|
||||||
SharedSessionContractImplementor session) {
|
SharedSessionContractImplementor session) {
|
||||||
final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session );
|
|
||||||
final EntityPersister persister = session.getEntityPersister( entityName, entity );
|
final EntityPersister persister = session.getEntityPersister( entityName, entity );
|
||||||
|
final Nullifier nullifier = new Nullifier( entity, false, isEarlyInsert, session, persister );
|
||||||
final String[] propertyNames = persister.getPropertyNames();
|
final String[] propertyNames = persister.getPropertyNames();
|
||||||
final Type[] types = persister.getPropertyTypes();
|
final Type[] types = persister.getPropertyTypes();
|
||||||
final boolean[] nullability = persister.getPropertyNullability();
|
final boolean[] nullability = persister.getPropertyNullability();
|
||||||
|
|
|
@ -285,8 +285,7 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback
|
||||||
|
|
||||||
cascadeBeforeDelete( session, persister, entity, entityEntry, transientEntities );
|
cascadeBeforeDelete( session, persister, entity, entityEntry, transientEntities );
|
||||||
|
|
||||||
new ForeignKeys.Nullifier( entity, true, false, session )
|
new ForeignKeys.Nullifier( entity, true, false, session, persister ).nullifyTransientReferences( entityEntry.getDeletedState() );
|
||||||
.nullifyTransientReferences( entityEntry.getDeletedState(), propTypes );
|
|
||||||
new Nullability( session ).checkNullability( entityEntry.getDeletedState(), persister, Nullability.NullabilityCheckType.DELETE );
|
new Nullability( session ).checkNullability( entityEntry.getDeletedState(), persister, Nullability.NullabilityCheckType.DELETE );
|
||||||
persistenceContext.getNullifiableEntityKeys().add( key );
|
persistenceContext.getNullifiableEntityKeys().add( key );
|
||||||
|
|
||||||
|
|
|
@ -1179,7 +1179,6 @@ public abstract class AbstractEntityPersister
|
||||||
rs = session.getJdbcCoordinator().getResultSetReturn().extract( ps );
|
rs = session.getJdbcCoordinator().getResultSetReturn().extract( ps );
|
||||||
rs.next();
|
rs.next();
|
||||||
}
|
}
|
||||||
final Object[] snapshot = entry.getLoadedState();
|
|
||||||
for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) {
|
for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) {
|
||||||
final boolean previousInitialized = initializedLazyAttributeNames.contains( fetchGroupAttributeDescriptor.getName() );
|
final boolean previousInitialized = initializedLazyAttributeNames.contains( fetchGroupAttributeDescriptor.getName() );
|
||||||
|
|
||||||
|
@ -1210,7 +1209,7 @@ public abstract class AbstractEntityPersister
|
||||||
fieldName,
|
fieldName,
|
||||||
entity,
|
entity,
|
||||||
session,
|
session,
|
||||||
snapshot,
|
entry,
|
||||||
fetchGroupAttributeDescriptor.getLazyIndex(),
|
fetchGroupAttributeDescriptor.getLazyIndex(),
|
||||||
selectedValue
|
selectedValue
|
||||||
);
|
);
|
||||||
|
@ -1259,7 +1258,6 @@ public abstract class AbstractEntityPersister
|
||||||
|
|
||||||
Object result = null;
|
Object result = null;
|
||||||
Serializable[] disassembledValues = cacheEntry.getDisassembledState();
|
Serializable[] disassembledValues = cacheEntry.getDisassembledState();
|
||||||
final Object[] snapshot = entry.getLoadedState();
|
|
||||||
for ( int j = 0; j < lazyPropertyNames.length; j++ ) {
|
for ( int j = 0; j < lazyPropertyNames.length; j++ ) {
|
||||||
final Serializable cachedValue = disassembledValues[lazyPropertyNumbers[j]];
|
final Serializable cachedValue = disassembledValues[lazyPropertyNumbers[j]];
|
||||||
final Type lazyPropertyType = lazyPropertyTypes[j];
|
final Type lazyPropertyType = lazyPropertyTypes[j];
|
||||||
|
@ -1276,7 +1274,7 @@ public abstract class AbstractEntityPersister
|
||||||
session,
|
session,
|
||||||
entity
|
entity
|
||||||
);
|
);
|
||||||
if ( initializeLazyProperty( fieldName, entity, session, snapshot, j, propValue ) ) {
|
if ( initializeLazyProperty( fieldName, entity, session, entry, j, propValue ) ) {
|
||||||
result = propValue;
|
result = propValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1291,13 +1289,17 @@ public abstract class AbstractEntityPersister
|
||||||
final String fieldName,
|
final String fieldName,
|
||||||
final Object entity,
|
final Object entity,
|
||||||
final SharedSessionContractImplementor session,
|
final SharedSessionContractImplementor session,
|
||||||
final Object[] snapshot,
|
final EntityEntry entry,
|
||||||
final int j,
|
final int j,
|
||||||
final Object propValue) {
|
final Object propValue) {
|
||||||
setPropertyValue( entity, lazyPropertyNumbers[j], propValue );
|
setPropertyValue( entity, lazyPropertyNumbers[j], propValue );
|
||||||
if ( snapshot != null ) {
|
if ( entry.getLoadedState() != null ) {
|
||||||
// object have been loaded with setReadOnly(true); HHH-2236
|
// 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] );
|
return fieldName.equals( lazyPropertyNames[j] );
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,354 @@
|
||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.test.bytecode.enhancement.lazy;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
|
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.annotations.LazyToOne;
|
||||||
|
import org.hibernate.annotations.LazyToOneOption;
|
||||||
|
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
|
||||||
|
import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
|
||||||
|
import org.hibernate.bytecode.enhance.spi.UnloadedClass;
|
||||||
|
import org.hibernate.bytecode.enhance.spi.UnloadedField;
|
||||||
|
import org.hibernate.engine.spi.EntityEntry;
|
||||||
|
import org.hibernate.engine.spi.SessionImplementor;
|
||||||
|
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
|
||||||
|
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
|
||||||
|
import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext;
|
||||||
|
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests removing non-owning side of the bidirectional association,
|
||||||
|
* with and without dirty-checking using enhancement.
|
||||||
|
*
|
||||||
|
* @author Gail Badner
|
||||||
|
*/
|
||||||
|
@TestForIssue(jiraKey = "HHH-13241")
|
||||||
|
@RunWith(BytecodeEnhancerRunner.class)
|
||||||
|
@CustomEnhancementContext({
|
||||||
|
EnhancerTestContext.class, // supports laziness and dirty-checking
|
||||||
|
BidirectionalLazyTest.NoDirtyCheckEnhancementContext.class // supports laziness; does not support dirty-checking
|
||||||
|
})
|
||||||
|
public class BidirectionalLazyTest extends BaseCoreFunctionalTestCase {
|
||||||
|
|
||||||
|
public Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class[] { Employer.class, Employee.class, Unrelated.class };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveWithDeletedAssociatedEntity() {
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
|
||||||
|
Employer employer = new Employer( "RedHat" );
|
||||||
|
session.persist( employer );
|
||||||
|
employer.addEmployee( new Employee( "Jack" ) );
|
||||||
|
employer.addEmployee( new Employee( "Jill" ) );
|
||||||
|
employer.addEmployee( new Employee( "John" ) );
|
||||||
|
for ( Employee employee : employer.getEmployees() ) {
|
||||||
|
session.persist( employee );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
Employer employer = session.get( Employer.class, "RedHat" );
|
||||||
|
// Delete the associated entity first
|
||||||
|
session.remove( employer );
|
||||||
|
for ( Employee employee : employer.getEmployees() ) {
|
||||||
|
assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) );
|
||||||
|
session.remove( employee );
|
||||||
|
// Should be initialized because at least one entity was deleted beforehand
|
||||||
|
assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) );
|
||||||
|
assertSame( employer, employee.getEmployer() );
|
||||||
|
// employee.getEmployer was initialized, and should be nullified in EntityEntry#deletedState
|
||||||
|
checkEntityEntryState( session, employee, employer, true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
assertNull( session.find( Employer.class, "RedHat" ) );
|
||||||
|
assertTrue( session.createQuery( "from Employee e", Employee.class ).getResultList().isEmpty() );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveWithNonAssociatedRemovedEntity() {
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
Employer employer = new Employer( "RedHat" );
|
||||||
|
session.persist( employer );
|
||||||
|
Employee employee = new Employee( "Jack" );
|
||||||
|
employer.addEmployee( employee );
|
||||||
|
session.persist( employee );
|
||||||
|
session.persist( new Unrelated( 1 ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
// Delete an entity that is not associated with Employee
|
||||||
|
session.remove( session.get( Unrelated.class, 1 ) );
|
||||||
|
final Employee employee = session.get( Employee.class, "Jack" );
|
||||||
|
assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) );
|
||||||
|
session.remove( employee );
|
||||||
|
// Should be initialized because at least one entity was deleted beforehand
|
||||||
|
assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) );
|
||||||
|
// employee.getEmployer was initialized, and should not be nullified in EntityEntry#deletedState
|
||||||
|
checkEntityEntryState( session, employee, employee.getEmployer(), false );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
assertNull( session.find( Unrelated.class, 1 ) );
|
||||||
|
assertNull( session.find( Employee.class, "Jack" ) );
|
||||||
|
session.remove( session.find( Employer.class, "RedHat" ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveWithNoRemovedEntities() {
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
Employer employer = new Employer( "RedHat" );
|
||||||
|
session.persist( employer );
|
||||||
|
Employee employee = new Employee( "Jack" );
|
||||||
|
employer.addEmployee( employee );
|
||||||
|
session.persist( employee );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
// Don't delete any entities before deleting the Employee
|
||||||
|
final Employee employee = session.get( Employee.class, "Jack" );
|
||||||
|
assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) );
|
||||||
|
session.remove( employee );
|
||||||
|
// There were no other deleted entities before employee was deleted,
|
||||||
|
// so there was no need to initialize employee.employer.
|
||||||
|
assertFalse( Hibernate.isPropertyInitialized( employee, "employer" ) );
|
||||||
|
// employee.getEmployer was not initialized, and should not be nullified in EntityEntry#deletedState
|
||||||
|
checkEntityEntryState( session, employee, LazyPropertyInitializer.UNFETCHED_PROPERTY, false );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
assertNull( session.find( Employee.class, "Jack" ) );
|
||||||
|
session.remove( session.find( Employer.class, "RedHat" ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkEntityEntryState(
|
||||||
|
final Session session,
|
||||||
|
final Employee employee,
|
||||||
|
final Object employer,
|
||||||
|
final boolean isEmployerNullified) {
|
||||||
|
final SessionImplementor sessionImplementor = (SessionImplementor) session;
|
||||||
|
final EntityEntry entityEntry = sessionImplementor.getPersistenceContext().getEntry( employee );
|
||||||
|
final int propertyNumber = entityEntry.getPersister().getEntityMetamodel().getPropertyIndex( "employer" );
|
||||||
|
assertEquals(
|
||||||
|
employer,
|
||||||
|
entityEntry.getLoadedState()[propertyNumber]
|
||||||
|
);
|
||||||
|
if ( isEmployerNullified ) {
|
||||||
|
assertEquals( null, entityEntry.getDeletedState()[propertyNumber] );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assertEquals(
|
||||||
|
employer,
|
||||||
|
entityEntry.getDeletedState()[propertyNumber]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Employer")
|
||||||
|
public static class Employer {
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private Set<Employee> employees;
|
||||||
|
|
||||||
|
public Employer(String name) {
|
||||||
|
this();
|
||||||
|
setName( name );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Id
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "employer", fetch = FetchType.LAZY)
|
||||||
|
public Set<Employee> getEmployees() {
|
||||||
|
return employees;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEmployee(Employee employee) {
|
||||||
|
if ( getEmployees() == null ) {
|
||||||
|
setEmployees( new HashSet<>() );
|
||||||
|
}
|
||||||
|
employees.add( employee );
|
||||||
|
employee.setEmployer( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Employer() {
|
||||||
|
// this form used by Hibernate
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setEmployees(Set<Employee> employees) {
|
||||||
|
this.employees = employees;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Employee")
|
||||||
|
public static class Employee {
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private Employer employer;
|
||||||
|
|
||||||
|
public Employee(String name) {
|
||||||
|
this();
|
||||||
|
setName( name );
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Id
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@LazyToOne(LazyToOneOption.NO_PROXY)
|
||||||
|
@JoinColumn(name = "employer_name")
|
||||||
|
public Employer getEmployer() {
|
||||||
|
return employer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Employee() {
|
||||||
|
// this form used by Hibernate
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setId(long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setEmployer(Employer employer) {
|
||||||
|
this.employer = employer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
if ( name != null ) {
|
||||||
|
return name.hashCode();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if ( this == o ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if ( o instanceof Employee ) {
|
||||||
|
Employee other = Employee.class.cast( o );
|
||||||
|
if ( name != null ) {
|
||||||
|
return getName().equals( other.getName() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return other.getName() == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Manager")
|
||||||
|
public static class Manager extends Employee {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Unrelated")
|
||||||
|
public static class Unrelated {
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
public Unrelated() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Unrelated(int id) {
|
||||||
|
setId( id );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Id
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
public void setId(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NoDirtyCheckEnhancementContext extends DefaultEnhancementContext {
|
||||||
|
@Override
|
||||||
|
public boolean hasLazyLoadableAttributes(UnloadedClass classDescriptor) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLazyLoadable(UnloadedField field) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,225 @@
|
||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.test.bytecode.enhancement.lazy.group;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.persistence.CascadeType;
|
||||||
|
import javax.persistence.Embeddable;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.LazyGroup;
|
||||||
|
import org.hibernate.annotations.LazyToOne;
|
||||||
|
import org.hibernate.annotations.LazyToOneOption;
|
||||||
|
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
|
||||||
|
import org.hibernate.bytecode.enhance.spi.UnloadedClass;
|
||||||
|
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
|
||||||
|
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
|
||||||
|
import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext;
|
||||||
|
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests removing non-owning side of the bidirectional association,
|
||||||
|
* where owning side is in an embeddable.
|
||||||
|
*
|
||||||
|
* Tests with and without dirty-checking using enhancement.
|
||||||
|
*
|
||||||
|
* @author Gail Badner
|
||||||
|
*/
|
||||||
|
@TestForIssue(jiraKey = "HHH-13241")
|
||||||
|
@RunWith(BytecodeEnhancerRunner.class)
|
||||||
|
@CustomEnhancementContext({
|
||||||
|
EnhancerTestContext.class,
|
||||||
|
BidirectionalLazyGroupsInEmbeddableTest.NoDirtyCheckEnhancementContext.class
|
||||||
|
})
|
||||||
|
public class BidirectionalLazyGroupsInEmbeddableTest extends BaseCoreFunctionalTestCase {
|
||||||
|
|
||||||
|
public Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class[] { Employer.class, Employee.class };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
|
||||||
|
Employer employer = new Employer( "RedHat" );
|
||||||
|
session.persist( employer );
|
||||||
|
employer.addEmployee( new Employee( "Jack" ) );
|
||||||
|
employer.addEmployee( new Employee( "Jill" ) );
|
||||||
|
employer.addEmployee( new Employee( "John" ) );
|
||||||
|
for ( Employee employee : employer.getEmployees() ) {
|
||||||
|
session.persist( employee );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
Employer employer = session.createQuery( "from Employer e", Employer.class ).getSingleResult();
|
||||||
|
session.remove( employer );
|
||||||
|
for ( Employee employee : employer.getEmployees() ) {
|
||||||
|
session.remove( employee );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Employer")
|
||||||
|
public static class Employer {
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private Set<Employee> employees;
|
||||||
|
|
||||||
|
public Employer(String name) {
|
||||||
|
this();
|
||||||
|
setName( name );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Id
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "employerContainer.employer", fetch = FetchType.LAZY)
|
||||||
|
@LazyGroup("Employees")
|
||||||
|
public Set<Employee> getEmployees() {
|
||||||
|
return employees;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEmployee(Employee employee) {
|
||||||
|
if ( getEmployees() == null ) {
|
||||||
|
setEmployees( new HashSet<>() );
|
||||||
|
}
|
||||||
|
employees.add( employee );
|
||||||
|
if ( employee.getEmployerContainer() == null ) {
|
||||||
|
employee.setEmployerContainer( new EmployerContainer() );
|
||||||
|
}
|
||||||
|
employee.getEmployerContainer().setEmployer( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Employer() {
|
||||||
|
// this form used by Hibernate
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setEmployees(Set<Employee> employees) {
|
||||||
|
this.employees = employees;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Employee")
|
||||||
|
public static class Employee {
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public Employee(String name) {
|
||||||
|
this();
|
||||||
|
setName( name );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmployerContainer employerContainer;
|
||||||
|
|
||||||
|
protected Employee() {
|
||||||
|
// this form used by Hibernate
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmployerContainer getEmployerContainer() {
|
||||||
|
return employerContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setId(long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setEmployerContainer(EmployerContainer employerContainer) {
|
||||||
|
this.employerContainer = employerContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
if ( name != null ) {
|
||||||
|
return name.hashCode();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if ( this == o ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if ( o instanceof Employee ) {
|
||||||
|
Employee other = Employee.class.cast( o );
|
||||||
|
if ( name != null ) {
|
||||||
|
return getName().equals( other.getName() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return other.getName() == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Embeddable
|
||||||
|
public static class EmployerContainer {
|
||||||
|
private Employer employer;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
|
||||||
|
@LazyToOne(LazyToOneOption.NO_PROXY)
|
||||||
|
@LazyGroup("EmployerForEmployee")
|
||||||
|
@JoinColumn(name = "employer_name")
|
||||||
|
public Employer getEmployer() {
|
||||||
|
return employer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setEmployer(Employer employer) {
|
||||||
|
this.employer = employer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NoDirtyCheckEnhancementContext extends DefaultEnhancementContext {
|
||||||
|
@Override
|
||||||
|
public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,218 @@
|
||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.test.bytecode.enhancement.lazy.group;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
|
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
|
import org.hibernate.annotations.LazyGroup;
|
||||||
|
import org.hibernate.annotations.LazyToOne;
|
||||||
|
import org.hibernate.annotations.LazyToOneOption;
|
||||||
|
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
|
||||||
|
import org.hibernate.bytecode.enhance.spi.UnloadedClass;
|
||||||
|
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
|
||||||
|
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
|
||||||
|
import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext;
|
||||||
|
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests removing non-owning side of the bidirectional association,
|
||||||
|
* with and without dirty-checking using enhancement.
|
||||||
|
*
|
||||||
|
* @author Gail Badner
|
||||||
|
*/
|
||||||
|
@TestForIssue(jiraKey = "HHH-13241")
|
||||||
|
@RunWith(BytecodeEnhancerRunner.class)
|
||||||
|
@CustomEnhancementContext({
|
||||||
|
EnhancerTestContext.class,
|
||||||
|
BidirectionalLazyGroupsTest.NoDirtyCheckEnhancementContext.class
|
||||||
|
})
|
||||||
|
public class BidirectionalLazyGroupsTest extends BaseCoreFunctionalTestCase {
|
||||||
|
|
||||||
|
public Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class[] { Employer.class, Employee.class };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveCollectionOwnerNoCascade() {
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
|
||||||
|
Employer employer = new Employer( "RedHat" );
|
||||||
|
session.persist( employer );
|
||||||
|
employer.addEmployee( new Employee( "Jack" ) );
|
||||||
|
employer.addEmployee( new Employee( "Jill" ) );
|
||||||
|
employer.addEmployee( new Employee( "John" ) );
|
||||||
|
for ( Employee employee : employer.getEmployees() ) {
|
||||||
|
session.persist( employee );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
Employer employer = session.createQuery( "from Employer e", Employer.class ).getSingleResult();
|
||||||
|
session.remove( employer );
|
||||||
|
for ( Employee employee : employer.getEmployees() ) {
|
||||||
|
assertFalse( Hibernate.isPropertyInitialized( employee, "employer") );
|
||||||
|
session.remove( employee );
|
||||||
|
assertTrue( Hibernate.isPropertyInitialized( employee, "employer" ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory, session -> {
|
||||||
|
assertNull( session.find( Employer.class, "RedHat" ) );
|
||||||
|
assertNull( session.createQuery( "from Employee e", Employee.class ).uniqueResult() );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Employer")
|
||||||
|
public static class Employer {
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private Set<Employee> employees;
|
||||||
|
|
||||||
|
public Employer(String name) {
|
||||||
|
this();
|
||||||
|
setName( name );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Id
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "employer", fetch = FetchType.LAZY)
|
||||||
|
@LazyGroup("Employees")
|
||||||
|
public Set<Employee> getEmployees() {
|
||||||
|
return employees;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEmployee(Employee employee) {
|
||||||
|
if ( getEmployees() == null ) {
|
||||||
|
setEmployees( new HashSet<>() );
|
||||||
|
}
|
||||||
|
employees.add( employee );
|
||||||
|
employee.setEmployer( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Employer() {
|
||||||
|
// this form used by Hibernate
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setEmployees(Set<Employee> employees) {
|
||||||
|
this.employees = employees;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Employee")
|
||||||
|
public static class Employee {
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private Employer employer;
|
||||||
|
|
||||||
|
public Employee(String name) {
|
||||||
|
this();
|
||||||
|
setName( name );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@LazyToOne(LazyToOneOption.NO_PROXY)
|
||||||
|
@LazyGroup("EmployerForEmployee")
|
||||||
|
@JoinColumn(name = "employer_name")
|
||||||
|
public Employer getEmployer() {
|
||||||
|
return employer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Employee() {
|
||||||
|
// this form used by Hibernate
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setId(long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setEmployer(Employer employer) {
|
||||||
|
this.employer = employer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
if ( name != null ) {
|
||||||
|
return name.hashCode();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if ( this == o ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if ( o instanceof Employee ) {
|
||||||
|
Employee other = Employee.class.cast( o );
|
||||||
|
if ( name != null ) {
|
||||||
|
return getName().equals( other.getName() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return other.getName() == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NoDirtyCheckEnhancementContext extends DefaultEnhancementContext {
|
||||||
|
@Override
|
||||||
|
public boolean doDirtyCheckingInline(UnloadedClass classDescriptor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue