HHH-6970 - Expand notion of "natural id mutability" to ternary value

This commit is contained in:
Steve Ebersole 2012-01-23 12:11:46 -06:00
parent a2a1775c87
commit d50a66bc20
9 changed files with 70 additions and 153 deletions

View File

@ -1,65 +0,0 @@
/*
* 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;
/**
* Possible values regarding the mutability of a natural id.
*
* @author Steve Ebersole
*
* @see org.hibernate.annotations.NaturalId
*/
public enum NaturalIdMutability {
/**
* The natural id is mutable. Hibernate will write changes in the natural id value when flushing updates to the
* the entity to the database. Also, it will invalidate any caching when such a change is detected.
*/
MUTABLE,
/**
* The natural id is immutable. Hibernate will ignore any changes in the natural id value when flushing updates
* to the entity to the database. Additionally Hibernate <b>will not</b> check with the database to check if the
* natural id values change there. Essentially the user is assuring Hibernate that the values will not change.
*/
IMMUTABLE,
/**
* The natural id is immutable. Hibernate will ignore any changes in the natural id value when flushing updates
* to the entity to the database. However, Hibernate <b>will</b> check with the database to check if the natural
* id values change there. This will ensure caching gets invalidated if the natural id value is changed in the
* database (outside of this Hibernate SessionFactory).
*
* Note however that frequently changing natural ids are really not natural ids and should really not be mapped
* as such. The overhead of maintaining caching of natural ids in these cases is far greater than the benefit
* from such caching. In such cases, a database index is a much better solution.
*/
IMMUTABLE_CHECKED,
/**
* @deprecated Added in deprecated form solely to allow seamless working until the deprecated attribute
* {@link org.hibernate.annotations.NaturalId#mutable()} can be removed.
*/
@Deprecated
UNSPECIFIED
}

View File

@ -26,8 +26,6 @@ package org.hibernate.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.hibernate.NaturalIdMutability;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ -36,31 +34,14 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
* This specifies that a property is part of the natural id of the entity.
*
* @author Nicol<EFBFBD>s Lichtmaier
* @author Steve Ebersole
*/
@Target( { METHOD, FIELD } )
@Retention( RUNTIME )
public @interface NaturalId {
/**
* @deprecated Use {@link #mutability()} instead. For {@code mutable == false} (the default) use
* {@link NaturalIdMutability#IMMUTABLE_CHECKED}; for {@code mutable == true} use
* {@link NaturalIdMutability#MUTABLE}.
* Is this natural id mutable (or immutable)?
*
* Note however the difference between {@link NaturalIdMutability#IMMUTABLE_CHECKED} which mimics the old behavior
* of {@code mutable == false} and the new behavior available via {@link NaturalIdMutability#IMMUTABLE}
* @return {@code true} indicates the natural id is mutable; {@code false} (the default) that it is immutable.
*/
@Deprecated
@SuppressWarnings( {"JavaDoc"})
boolean mutable() default false;
/**
* The mutability behavior of this natural id.
*
* Note: the current default value is the {@link NaturalIdMutability#UNSPECIFIED} value which was added
* in deprecated form until the deprecated {@link #mutable()} attribute here can be removed. This lets existing
* applications continue to work seamlessly using their existing natural id annotations.
*
* @return The mutability behavior.
*/
NaturalIdMutability mutability() default NaturalIdMutability.UNSPECIFIED;
}

View File

@ -104,7 +104,6 @@ public class BinderHelper {
clone.setName( property.getName() );
clone.setNodeName( property.getNodeName() );
clone.setNaturalIdentifier( property.isNaturalIdentifier() );
clone.setNaturalIdMutability( property.getNaturalIdMutability() );
clone.setOptimisticLocked( property.isOptimisticLocked() );
clone.setOptional( property.isOptional() );
clone.setPersistentClass( property.getPersistentClass() );

View File

@ -40,7 +40,6 @@ import org.hibernate.EntityMode;
import org.hibernate.FetchMode;
import org.hibernate.FlushMode;
import org.hibernate.MappingException;
import org.hibernate.NaturalIdMutability;
import org.hibernate.engine.internal.Versioning;
import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle;
import org.hibernate.engine.spi.FilterDefinition;
@ -2222,19 +2221,24 @@ public final class HbmBinder {
}
if ( value != null ) {
Property property = createProperty( value, propertyName, persistentClass
.getClassName(), subnode, mappings, inheritedMetas );
final Property property = createProperty(
value,
propertyName,
persistentClass.getClassName(),
subnode,
mappings,
inheritedMetas
);
if ( !mutable ) {
property.setUpdateable(false);
}
if ( naturalId ) {
property.setNaturalIdentifier(true);
property.setNaturalIdMutability(
mutable ? NaturalIdMutability.MUTABLE : NaturalIdMutability.IMMUTABLE_CHECKED
);
property.setNaturalIdentifier( true );
}
persistentClass.addProperty( property );
if ( uniqueKey!=null ) uniqueKey.addColumns( property.getColumnIterator() );
if ( uniqueKey!=null ) {
uniqueKey.addColumns( property.getColumnIterator() );
}
}
}

View File

@ -30,7 +30,6 @@ import javax.persistence.Id;
import org.jboss.logging.Logger;
import org.hibernate.AnnotationException;
import org.hibernate.NaturalIdMutability;
import org.hibernate.annotations.Generated;
import org.hibernate.annotations.GenerationTime;
import org.hibernate.annotations.Immutable;
@ -286,20 +285,14 @@ public class PropertyBinder {
}
NaturalId naturalId = property != null ? property.getAnnotation( NaturalId.class ) : null;
if ( naturalId != null ) {
NaturalIdMutability mutability = naturalId.mutability();
if ( mutability == NaturalIdMutability.UNSPECIFIED ) {
mutability = naturalId.mutable()
? NaturalIdMutability.MUTABLE
: NaturalIdMutability.IMMUTABLE_CHECKED;
}
if ( mutability != NaturalIdMutability.MUTABLE ) {
if ( ! naturalId.mutable() ) {
updatable = false;
}
prop.setNaturalIdentifier( true );
prop.setNaturalIdMutability( mutability );
}
prop.setInsertable( insertable );
prop.setUpdateable( updatable );
OptimisticLock lockAnn = property != null ?
property.getAnnotation( OptimisticLock.class ) :
null;

View File

@ -96,34 +96,57 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
Object[] loaded,
SessionImplementor session) {
if ( persister.hasNaturalIdentifier() && entry.getStatus() != Status.READ_ONLY ) {
Object[] snapshot = null;
Type[] types = persister.getPropertyTypes();
int[] props = persister.getNaturalIdentifierProperties();
boolean[] updateable = persister.getPropertyUpdateability();
for ( int i=0; i<props.length; i++ ) {
int prop = props[i];
if ( !updateable[prop] ) {
Object loadedVal;
if ( loaded == null ) {
if ( snapshot == null) {
snapshot = session.getPersistenceContext().getNaturalIdSnapshot( entry.getId(), persister );
}
loadedVal = snapshot[i];
} else {
loadedVal = loaded[prop];
}
if ( !types[prop].isEqual( current[prop], loadedVal ) ) {
throw new HibernateException(
"immutable natural identifier of an instance of " +
persister.getEntityName() +
" was altered"
);
}
if ( ! persister.getEntityMetamodel().hasImmutableNaturalId() ) {
// SHORT-CUT: if the natural id is mutable (!immutable), no need to do the below checks
// EARLY EXIT!!!
return;
}
final int[] naturalIdentifierPropertiesIndexes = persister.getNaturalIdentifierProperties();
final Type[] propertyTypes = persister.getPropertyTypes();
final boolean[] propertyUpdateability = persister.getPropertyUpdateability();
final Object[] snapshot = loaded == null
? session.getPersistenceContext().getNaturalIdSnapshot( entry.getId(), persister )
: extractNaturalIdValues( loaded, naturalIdentifierPropertiesIndexes );
for ( int i=0; i<naturalIdentifierPropertiesIndexes.length; i++ ) {
final int naturalIdentifierPropertyIndex = naturalIdentifierPropertiesIndexes[i];
if ( propertyUpdateability[ naturalIdentifierPropertyIndex ] ) {
// if the given natural id property is updatable (mutable), there is nothing to check
continue;
}
final Type propertyType = propertyTypes[naturalIdentifierPropertyIndex];
if ( ! propertyType.isEqual( current[naturalIdentifierPropertyIndex], snapshot[i] ) ) {
throw new HibernateException(
String.format(
"An immutable natural identifier of entity %s was altered from %s to %s",
persister.getEntityName(),
propertyTypes[naturalIdentifierPropertyIndex].toLoggableString(
snapshot[i],
session.getFactory()
),
propertyTypes[naturalIdentifierPropertyIndex].toLoggableString(
current[naturalIdentifierPropertyIndex],
session.getFactory()
)
)
);
}
}
}
}
public Object[] extractNaturalIdValues(Object[] entitySnapshot, int[] naturalIdPropertyIndexes) {
final Object[] naturalIdSnapshotSubSet = new Object[ naturalIdPropertyIndexes.length ];
for ( int i = 0; i < naturalIdPropertyIndexes.length; i++ ) {
naturalIdSnapshotSubSet[i] = entitySnapshot[ naturalIdPropertyIndexes[i] ];
}
return naturalIdSnapshotSubSet;
}
/**
* Flushes a single entity's state to the database, by scheduling
* an update action, if necessary

View File

@ -22,13 +22,13 @@
* Boston, MA 02110-1301 USA
*/
package org.hibernate.mapping;
import java.io.Serializable;
import java.util.Iterator;
import java.util.StringTokenizer;
import org.hibernate.EntityMode;
import org.hibernate.MappingException;
import org.hibernate.NaturalIdMutability;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.Mapping;
@ -61,7 +61,6 @@ public class Property implements Serializable, MetaAttributable {
private java.util.Map metaAttributes;
private PersistentClass persistentClass;
private boolean naturalIdentifier;
private NaturalIdMutability naturalIdMutability;
public boolean isBackRef() {
return false;
@ -147,20 +146,14 @@ public class Property implements Serializable, MetaAttributable {
}
public boolean isUpdateable() {
// if the property mapping consists of all formulas,
// if the property mapping consists of all formulas,
// make it non-updateable
final boolean[] columnUpdateability = value.getColumnUpdateability();
final boolean isImmutableNaturalId = isNaturalIdentifier()
&& ( NaturalIdMutability.IMMUTABLE.equals( getNaturalIdMutability() )
|| NaturalIdMutability.IMMUTABLE_CHECKED.equals( getNaturalIdMutability() ) );
return updateable
&& !isImmutableNaturalId
&& !ArrayHelper.isAllFalse(columnUpdateability);
return updateable && !ArrayHelper.isAllFalse( value.getColumnUpdateability() );
}
public boolean isInsertable() {
// if the property mapping consists of all formulas,
// make it insertable
// make it non-insertable
final boolean[] columnInsertability = value.getColumnInsertability();
return insertable && (
columnInsertability.length==0 ||
@ -176,8 +169,7 @@ public class Property implements Serializable, MetaAttributable {
this.generation = generation;
}
public void setUpdateable(
boolean mutable) {
public void setUpdateable(boolean mutable) {
this.updateable = mutable;
}
@ -299,7 +291,7 @@ public class Property implements Serializable, MetaAttributable {
// todo : remove
public Getter getGetter(Class clazz) throws PropertyNotFoundException, MappingException {
return getPropertyAccessor(clazz).getGetter(clazz, name);
return getPropertyAccessor(clazz).getGetter( clazz, name );
}
// todo : remove
@ -320,12 +312,4 @@ public class Property implements Serializable, MetaAttributable {
this.naturalIdentifier = naturalIdentifier;
}
public NaturalIdMutability getNaturalIdMutability() {
return naturalIdMutability;
}
public void setNaturalIdMutability(NaturalIdMutability naturalIdMutability) {
this.naturalIdMutability = naturalIdMutability;
}
}

View File

@ -37,7 +37,6 @@ import org.jboss.logging.Logger;
import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.NaturalIdMutability;
import org.hibernate.engine.OptimisticLockStyle;
import org.hibernate.engine.internal.Versioning;
import org.hibernate.engine.spi.CascadeStyle;
@ -196,7 +195,7 @@ public class EntityMetamodel implements Serializable {
if ( prop.isNaturalIdentifier() ) {
naturalIdNumbers.add( i );
if ( prop.isUpdateable() && NaturalIdMutability.MUTABLE.equals( prop.getNaturalIdMutability() ) ) {
if ( prop.isUpdateable() ) {
foundUpdateableNaturalIdProperty = true;
}
}

View File

@ -27,7 +27,6 @@ import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.NaturalIdMutability;
import org.hibernate.annotations.NaturalId;
/**
@ -56,7 +55,7 @@ public class Group {
this.id = id;
}
@NaturalId(mutability = NaturalIdMutability.MUTABLE)
@NaturalId(mutable = true)
public String getName() {
return name;
}