HHH-7235 - Support null NaturalId values in loadEntityIdByNaturalId query

This commit is contained in:
Steve Ebersole 2012-04-23 14:26:45 -05:00
parent 37b645999c
commit d1a7495218
3 changed files with 143 additions and 13 deletions

View File

@ -4531,7 +4531,8 @@ public abstract class AbstractEntityPersister
);
}
final String sqlEntityIdByNaturalIdString = determinePkByNaturalIdQuery();
final boolean[] valueNullness = determineValueNullness( naturalIdValues );
final String sqlEntityIdByNaturalIdString = determinePkByNaturalIdQuery( valueNullness );
try {
PreparedStatement ps = session.getTransactionCoordinator()
@ -4542,9 +4543,12 @@ public abstract class AbstractEntityPersister
int positions = 1;
int loop = 0;
for ( int idPosition : getNaturalIdentifierProperties() ) {
final Type type = getPropertyTypes()[idPosition];
type.nullSafeSet( ps, naturalIdValues[loop++], positions, session );
positions += type.getColumnSpan( session.getFactory() );
final Object naturalIdValue = naturalIdValues[loop++];
if ( naturalIdValue != null ) {
final Type type = getPropertyTypes()[idPosition];
type.nullSafeSet( ps, naturalIdValue, positions, session );
positions += type.getColumnSpan( session.getFactory() );
}
}
ResultSet rs = ps.executeQuery();
try {
@ -4576,24 +4580,62 @@ public abstract class AbstractEntityPersister
}
}
private String pkByNaturalIdQuery;
private boolean[] determineValueNullness(Object[] naturalIdValues) {
boolean[] nullness = new boolean[ naturalIdValues.length ];
for ( int i = 0; i < naturalIdValues.length; i++ ) {
nullness[i] = naturalIdValues[i] == null;
}
return nullness;
}
private String determinePkByNaturalIdQuery() {
private Boolean naturalIdIsNonNullable;
private String cachedPkByNonNullableNaturalIdQuery;
private String determinePkByNaturalIdQuery(boolean[] valueNullness) {
if ( ! hasNaturalIdentifier() ) {
throw new HibernateException( "Attempt to build natural-id -> PK resolution query for entity that does not define natural id" );
}
if ( pkByNaturalIdQuery == null ) {
pkByNaturalIdQuery = generateEntityIdByNaturalIdSql();
// performance shortcut for cases where the natural-id is defined as completely non-nullable
if ( isNaturalIdNonNullable() ) {
if ( valueNullness != null && ! ArrayHelper.isAllFalse( valueNullness ) ) {
throw new HibernateException( "Null value(s) passed to lookup by non-nullable natural-id" );
}
if ( cachedPkByNonNullableNaturalIdQuery == null ) {
cachedPkByNonNullableNaturalIdQuery = generateEntityIdByNaturalIdSql( null );
}
return cachedPkByNonNullableNaturalIdQuery;
}
return pkByNaturalIdQuery;
// Otherwise, regenerate it each time
return generateEntityIdByNaturalIdSql( valueNullness );
}
private String generateEntityIdByNaturalIdSql() {
@SuppressWarnings("UnnecessaryUnboxing")
protected boolean isNaturalIdNonNullable() {
if ( naturalIdIsNonNullable == null ) {
naturalIdIsNonNullable = determineNaturalIdNullability();
}
return naturalIdIsNonNullable.booleanValue();
}
private boolean determineNaturalIdNullability() {
boolean[] nullability = getPropertyNullability();
for ( int position : getNaturalIdentifierProperties() ) {
// if any individual property is nullable, return false
if ( nullability[position] ) {
return false;
}
}
// return true if we found no individually nullable properties
return true;
}
private String generateEntityIdByNaturalIdSql(boolean[] valueNullness) {
EntityPersister rootPersister = getFactory().getEntityPersister( getRootEntityName() );
if ( rootPersister != this ) {
if ( rootPersister instanceof AbstractEntityPersister ) {
return ( (AbstractEntityPersister) rootPersister ).generateEntityIdByNaturalIdSql();
return ( (AbstractEntityPersister) rootPersister ).generateEntityIdByNaturalIdSql( valueNullness );
}
}
@ -4610,7 +4652,9 @@ public abstract class AbstractEntityPersister
final StringBuilder whereClause = new StringBuilder();
final int[] propertyTableNumbers = getPropertyTableNumbers();
final int[] naturalIdPropertyIndexes = this.getNaturalIdentifierProperties();
int valuesIndex = -1;
for ( int propIdx = 0; propIdx < naturalIdPropertyIndexes.length; propIdx++ ) {
valuesIndex++;
if ( propIdx > 0 ) {
whereClause.append( " and " );
}
@ -4620,7 +4664,12 @@ public abstract class AbstractEntityPersister
final String[] propertyColumnNames = getPropertyColumnNames( naturalIdIdx );
final String[] aliasedPropertyColumns = StringHelper.qualify( tableAlias, propertyColumnNames );
whereClause.append( StringHelper.join( "=? and ", aliasedPropertyColumns ) ).append( "=?" );
if ( valueNullness != null && valueNullness[valuesIndex] ) {
whereClause.append( StringHelper.join( " is null and ", aliasedPropertyColumns ) ).append( " is null" );
}
else {
whereClause.append( StringHelper.join( "=? and ", aliasedPropertyColumns ) ).append( "=?" );
}
}
whereClause.append( whereJoinFragment( getRootAlias(), true, false ) );

View File

@ -0,0 +1,48 @@
/*
* 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.test.naturalid.nullable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.hibernate.annotations.NaturalId;
/**
* @author Guenther Demetz
*/
@Entity
public class D {
@Id @GeneratedValue(strategy = GenerationType.TABLE)
public long oid;
@NaturalId(mutable=true)
public String name;
@NaturalId(mutable=true)
@ManyToOne
public C associatedC;
}

View File

@ -37,7 +37,7 @@ import static org.junit.Assert.assertNull;
public class NullableNaturalIdTest extends BaseCoreFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] { A.class, B.class, C.class };
return new Class[] { A.class, B.class, C.class, D.class };
}
@Test
@ -87,4 +87,37 @@ public class NullableNaturalIdTest extends BaseCoreFunctionalTestCase {
session.getTransaction().commit();
session.close();
}
@Test
public void testNaturalIdQuerySupportingNullValues() {
Session session = openSession();
session.beginTransaction();
D d1 = new D();
d1.name = "Titi";
d1.associatedC = null;
D d2 = new D();
d2.name = null;
C c = new C();
d2.associatedC = c;
session.persist( d1 );
session.persist( d2 );
session.persist( c );
session.getTransaction().commit();
session.close();
session = openSession();
session.beginTransaction();
assertNotNull( session.byNaturalId( D.class ).using( "name", null ).using( "associatedC", c ).load() );
assertNotNull( session.byNaturalId( D.class ).using( "name", "Titi" ).using( "associatedC", null ).load() );
session.getTransaction().commit();
session.close();
session = openSession();
session.beginTransaction();
session.delete( c );
session.delete( d1 );
session.delete( d2 );
session.getTransaction().commit();
session.close();
}
}