HHH-7235 - Support null NaturalId values in loadEntityIdByNaturalId query
This commit is contained in:
parent
37b645999c
commit
d1a7495218
|
@ -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 ) );
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue