HHH-4726 - Add support for delete-orphan cascading to <one-to-one/>

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18568 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Steve Ebersole 2010-01-16 20:27:04 +00:00
parent 7fa50f7a18
commit 29152a8390
17 changed files with 726 additions and 110 deletions

View File

@ -1064,10 +1064,10 @@ public final class HbmBinder {
// COLUMN(S) // COLUMN(S)
Attribute columnAttribute = node.attribute( "column" ); Attribute columnAttribute = node.attribute( "column" );
if ( columnAttribute == null ) { if ( columnAttribute == null ) {
Iterator iter = node.elementIterator(); Iterator itr = node.elementIterator();
int count = 0; int count = 0;
while ( iter.hasNext() ) { while ( itr.hasNext() ) {
Element columnElement = (Element) iter.next(); Element columnElement = (Element) itr.next();
if ( columnElement.getName().equals( "column" ) ) { if ( columnElement.getName().equals( "column" ) ) {
Column column = new Column(); Column column = new Column();
column.setValue( simpleValue ); column.setValue( simpleValue );
@ -1115,6 +1115,9 @@ public final class HbmBinder {
Column column = new Column(); Column column = new Column();
column.setValue( simpleValue ); column.setValue( simpleValue );
bindColumn( node, column, isNullable ); bindColumn( node, column, isNullable );
if ( column.isUnique() && ManyToOne.class.isInstance( simpleValue ) ) {
( (ManyToOne) simpleValue ).markAsLogicalOneToOne();
}
final String columnName = columnAttribute.getValue(); final String columnName = columnAttribute.getValue();
String logicalColumnName = mappings.getNamingStrategy().logicalColumnName( String logicalColumnName = mappings.getNamingStrategy().logicalColumnName(
columnName, propertyPath columnName, propertyPath
@ -1617,7 +1620,9 @@ public final class HbmBinder {
String cascade = node.attributeValue( "cascade" ); String cascade = node.attributeValue( "cascade" );
if ( cascade != null && cascade.indexOf( "delete-orphan" ) >= 0 ) { if ( cascade != null && cascade.indexOf( "delete-orphan" ) >= 0 ) {
throw new MappingException( "many-to-one attributes do not support orphan delete: " + path ); if ( !manyToOne.isLogicalOneToOne() ) {
throw new MappingException( "many-to-one attributes do not support orphan delete: " + path );
}
} }
} }

View File

@ -1,10 +1,10 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * Hibernate, Relational Persistence for Idiomatic Java
* *
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as * Copyright (c) 2010, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution * indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are * statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC. * distributed under license by Red Hat Inc.
* *
* This copyrighted material is made available to anyone wishing to use, modify, * 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 * copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,13 +20,14 @@
* Free Software Foundation, Inc. * Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor * 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA * Boston, MA 02110-1301 USA
*
*/ */
package org.hibernate.engine; package org.hibernate.engine;
import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Stack;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -36,6 +37,7 @@ import org.hibernate.collection.PersistentCollection;
import org.hibernate.event.EventSource; import org.hibernate.event.EventSource;
import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.type.AbstractComponentType; import org.hibernate.type.AbstractComponentType;
import org.hibernate.type.AssociationType; import org.hibernate.type.AssociationType;
import org.hibernate.type.CollectionType; import org.hibernate.type.CollectionType;
@ -148,7 +150,8 @@ public final class Cascade {
EntityMode entityMode = eventSource.getEntityMode(); EntityMode entityMode = eventSource.getEntityMode();
boolean hasUninitializedLazyProperties = persister.hasUninitializedLazyProperties( parent, entityMode ); boolean hasUninitializedLazyProperties = persister.hasUninitializedLazyProperties( parent, entityMode );
for ( int i=0; i<types.length; i++) { for ( int i=0; i<types.length; i++) {
CascadeStyle style = cascadeStyles[i]; final CascadeStyle style = cascadeStyles[i];
final String propertyName = persister.getPropertyNames()[i];
if ( hasUninitializedLazyProperties && persister.getPropertyLaziness()[i] && ! action.performOnLazyProperty() ) { if ( hasUninitializedLazyProperties && persister.getPropertyLaziness()[i] && ! action.performOnLazyProperty() ) {
//do nothing to avoid a lazy property initialization //do nothing to avoid a lazy property initialization
continue; continue;
@ -160,6 +163,7 @@ public final class Cascade {
persister.getPropertyValue( parent, i, entityMode ), persister.getPropertyValue( parent, i, entityMode ),
types[i], types[i],
style, style,
propertyName,
anything, anything,
false false
); );
@ -189,6 +193,7 @@ public final class Cascade {
final Object child, final Object child,
final Type type, final Type type,
final CascadeStyle style, final CascadeStyle style,
final String propertyName,
final Object anything, final Object anything,
final boolean isCascadeDeleteEnabled) throws HibernateException { final boolean isCascadeDeleteEnabled) throws HibernateException {
@ -207,7 +212,7 @@ public final class Cascade {
} }
} }
else if ( type.isComponentType() ) { else if ( type.isComponentType() ) {
cascadeComponent( parent, child, (AbstractComponentType) type, anything ); cascadeComponent( parent, child, (AbstractComponentType) type, propertyName, anything );
} }
} }
else { else {
@ -222,11 +227,34 @@ public final class Cascade {
final EntityEntry entry = eventSource.getPersistenceContext().getEntry( parent ); final EntityEntry entry = eventSource.getPersistenceContext().getEntry( parent );
if ( entry != null ) { if ( entry != null ) {
final EntityType entityType = (EntityType) type; final EntityType entityType = (EntityType) type;
final Object loadedValue = entry.getLoadedValue( entityType.getPropertyName() ); final Object loadedValue;
if ( componentPathStack.isEmpty() ) {
// association defined on entity
loadedValue = entry.getLoadedValue( propertyName );
}
else {
// association defined on component
// todo : this is currently unsupported because of the fact that
// we do not know the loaded state of this value properly
// and doing so would be very difficult given how components and
// entities are loaded (and how 'loaded state' is put into the
// EntityEntry). Solutions here are to either:
// 1) properly account for components as a 2-phase load construct
// 2) just assume the association was just now orphaned and
// issue the orphan delete. This would require a special
// set of SQL statements though since we do not know the
// orphaned value, something a delete with a subquery to
// match the owner.
// final String propertyPath = composePropertyPath( entityType.getPropertyName() );
loadedValue = null;
}
if ( loadedValue != null ) { if ( loadedValue != null ) {
final String entityName = entityType.getAssociatedEntityName(); final String entityName = entry.getPersister().getEntityName();
if ( log.isTraceEnabled() ) { if ( log.isTraceEnabled() ) {
log.trace( "deleting orphaned entity instance: " + entityName ); final Serializable id = entry.getPersister()
.getIdentifier( loadedValue, eventSource.getEntityMode() );
final String description = MessageHelper.infoString( entityName, id );
log.trace( "deleting orphaned entity instance: " + description );
} }
eventSource.delete( entityName, loadedValue, false, new HashSet() ); eventSource.delete( entityName, loadedValue, false, new HashSet() );
} }
@ -245,21 +273,26 @@ public final class Cascade {
* @return True if the attribute represents a logical one to one association * @return True if the attribute represents a logical one to one association
*/ */
private boolean isLogicalOneToOne(Type type) { private boolean isLogicalOneToOne(Type type) {
if ( ! type.isEntityType() ) { return type.isEntityType() && ( (EntityType) type ).isLogicalOneToOne();
return false;
}
final EntityType entityType = (EntityType) type;
if ( entityType.isOneToOne() ) {
// physical one-to-one
return true;
}
// todo : still need to handle the many-to-one w/ property-ref
// actually there is a question about whether the constrained side
// can declare the orphan-delete. If not, then the side declaring
// the orphan-delete can only ever be a <one-to-one/>
return false;
} }
private String composePropertyPath(String propertyName) {
if ( componentPathStack.isEmpty() ) {
return propertyName;
}
else {
StringBuffer buffer = new StringBuffer();
Iterator itr = componentPathStack.iterator();
while ( itr.hasNext() ) {
buffer.append( itr.next() ).append( '.' );
}
buffer.append( propertyName );
return buffer.toString();
}
}
private Stack componentPathStack = new Stack();
private boolean cascadeAssociationNow(AssociationType associationType) { private boolean cascadeAssociationNow(AssociationType associationType) {
return associationType.getForeignKeyDirection().cascadeNow(cascadeTo) && return associationType.getForeignKeyDirection().cascadeNow(cascadeTo) &&
( eventSource.getEntityMode()!=EntityMode.DOM4J || associationType.isEmbeddedInXML() ); ( eventSource.getEntityMode()!=EntityMode.DOM4J || associationType.isEmbeddedInXML() );
@ -269,22 +302,27 @@ public final class Cascade {
final Object parent, final Object parent,
final Object child, final Object child,
final AbstractComponentType componentType, final AbstractComponentType componentType,
final String componentPropertyName,
final Object anything) { final Object anything) {
Object[] children = componentType.getPropertyValues(child, eventSource); componentPathStack.push( componentPropertyName );
Object[] children = componentType.getPropertyValues( child, eventSource );
Type[] types = componentType.getSubtypes(); Type[] types = componentType.getSubtypes();
for ( int i=0; i<types.length; i++ ) { for ( int i=0; i<types.length; i++ ) {
CascadeStyle componentPropertyStyle = componentType.getCascadeStyle(i); final CascadeStyle componentPropertyStyle = componentType.getCascadeStyle(i);
final String subPropertyName = componentType.getPropertyNames()[i];
if ( componentPropertyStyle.doCascade(action) ) { if ( componentPropertyStyle.doCascade(action) ) {
cascadeProperty( cascadeProperty(
parent, parent,
children[i], children[i],
types[i], types[i],
componentPropertyStyle, componentPropertyStyle,
subPropertyName,
anything, anything,
false false
); );
} }
} }
componentPathStack.pop();
} }
private void cascadeAssociation( private void cascadeAssociation(
@ -389,7 +427,8 @@ public final class Cascade {
parent, parent,
iter.next(), iter.next(),
elemType, elemType,
style, style,
null,
anything, anything,
isCascadeDeleteEnabled isCascadeDeleteEnabled
); );

View File

@ -1,10 +1,10 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * Hibernate, Relational Persistence for Idiomatic Java
* *
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as * Copyright (c) 2010, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution * indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are * statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC. * distributed under license by Red Hat Inc.
* *
* This copyrighted material is made available to anyone wishing to use, modify, * 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 * copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,7 +20,6 @@
* Free Software Foundation, Inc. * Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor * 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA * Boston, MA 02110-1301 USA
*
*/ */
package org.hibernate.engine; package org.hibernate.engine;
@ -29,7 +28,6 @@ import java.io.ObjectOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import org.hibernate.EntityMode; import org.hibernate.EntityMode;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.LockMode; import org.hibernate.LockMode;
@ -84,13 +82,9 @@ public final class EntityEntry implements Serializable {
this.loadedWithLazyPropertiesUnfetched = lazyPropertiesAreUnfetched; this.loadedWithLazyPropertiesUnfetched = lazyPropertiesAreUnfetched;
this.persister=persister; this.persister=persister;
this.entityMode = entityMode; this.entityMode = entityMode;
this.entityName = persister == null ? this.entityName = persister == null ? null : persister.getEntityName();
null : persister.getEntityName();
} }
/**
* Used during custom deserialization
*/
private EntityEntry( private EntityEntry(
final SessionFactoryImplementor factory, final SessionFactoryImplementor factory,
final String entityName, final String entityName,
@ -104,6 +98,7 @@ public final class EntityEntry implements Serializable {
final boolean existsInDatabase, final boolean existsInDatabase,
final boolean isBeingReplicated, final boolean isBeingReplicated,
final boolean loadedWithLazyPropertiesUnfetched) { final boolean loadedWithLazyPropertiesUnfetched) {
// Used during custom deserialization
this.entityName = entityName; this.entityName = entityName;
this.persister = ( factory == null ? null : factory.getEntityPersister( entityName ) ); this.persister = ( factory == null ? null : factory.getEntityPersister( entityName ) );
this.id = id; this.id = id;
@ -181,10 +176,6 @@ public final class EntityEntry implements Serializable {
return cachedEntityKey; return cachedEntityKey;
} }
void afterDeserialize(SessionFactoryImplementor factory) {
persister = factory.getEntityPersister( entityName );
}
public String getEntityName() { public String getEntityName() {
return entityName; return entityName;
} }
@ -198,24 +189,29 @@ public final class EntityEntry implements Serializable {
} }
/** /**
* After actually updating the database, update the snapshot information, * Handle updating the internal state of the entry after actually performing
* and escalate the lock mode * the database update. Specifically we update the snapshot information and
* escalate the lock mode
*
* @param entity The entity instance
* @param updatedState The state calculated after the update (becomes the
* new {@link #getLoadedState() loaded state}.
* @param nextVersion The new version.
*/ */
public void postUpdate(Object entity, Object[] updatedState, Object nextVersion) { public void postUpdate(Object entity, Object[] updatedState, Object nextVersion) {
this.loadedState = updatedState; this.loadedState = updatedState;
setLockMode(LockMode.WRITE); setLockMode(LockMode.WRITE);
if ( getPersister().isVersioned() ) { if ( getPersister().isVersioned() ) {
this.version = nextVersion; this.version = nextVersion;
getPersister().setPropertyValue( getPersister().setPropertyValue(
entity, entity,
getPersister().getVersionProperty(), getPersister().getVersionProperty(),
nextVersion, nextVersion,
entityMode entityMode
); );
} }
FieldInterceptionHelper.clearDirty( entity ); FieldInterceptionHelper.clearDirty( entity );
} }
@ -249,8 +245,7 @@ public final class EntityEntry implements Serializable {
int propertyIndex = ( (UniqueKeyLoadable) persister ).getPropertyIndex(propertyName); int propertyIndex = ( (UniqueKeyLoadable) persister ).getPropertyIndex(propertyName);
return loadedState[propertyIndex]; return loadedState[propertyIndex];
} }
public boolean requiresDirtyCheck(Object entity) { public boolean requiresDirtyCheck(Object entity) {
boolean isMutableInstance = boolean isMutableInstance =
@ -268,6 +263,7 @@ public final class EntityEntry implements Serializable {
public void forceLocked(Object entity, Object nextVersion) { public void forceLocked(Object entity, Object nextVersion) {
version = nextVersion; version = nextVersion;
loadedState[ persister.getVersionProperty() ] = version; loadedState[ persister.getVersionProperty() ] = version;
//noinspection deprecation
setLockMode( LockMode.FORCE ); // TODO: use LockMode.PESSIMISTIC_FORCE_INCREMENT setLockMode( LockMode.FORCE ); // TODO: use LockMode.PESSIMISTIC_FORCE_INCREMENT
persister.setPropertyValue( persister.setPropertyValue(
entity, entity,
@ -309,13 +305,13 @@ public final class EntityEntry implements Serializable {
return loadedWithLazyPropertiesUnfetched; return loadedWithLazyPropertiesUnfetched;
} }
/** /**
* Custom serialization routine used during serialization of a * Custom serialization routine used during serialization of a
* Session/PersistenceContext for increased performance. * Session/PersistenceContext for increased performance.
* *
* @param oos The stream to which we should write the serial data. * @param oos The stream to which we should write the serial data.
* @throws java.io.IOException *
* @throws IOException If a stream error occurs
*/ */
void serialize(ObjectOutputStream oos) throws IOException { void serialize(ObjectOutputStream oos) throws IOException {
oos.writeObject( entityName ); oos.writeObject( entityName );
@ -338,9 +334,12 @@ public final class EntityEntry implements Serializable {
* *
* @param ois The stream from which to read the entry. * @param ois The stream from which to read the entry.
* @param session The session being deserialized. * @param session The session being deserialized.
*
* @return The deserialized EntityEntry * @return The deserialized EntityEntry
* @throws IOException *
* @throws ClassNotFoundException * @throws IOException If a stream error occurs
* @throws ClassNotFoundException If any of the classes declared in the stream
* cannot be found
*/ */
static EntityEntry deserialize( static EntityEntry deserialize(
ObjectInputStream ois, ObjectInputStream ois,
@ -353,7 +352,7 @@ public final class EntityEntry implements Serializable {
Status.parse( ( String ) ois.readObject() ), Status.parse( ( String ) ois.readObject() ),
( Object[] ) ois.readObject(), ( Object[] ) ois.readObject(),
( Object[] ) ois.readObject(), ( Object[] ) ois.readObject(),
( Object ) ois.readObject(), ois.readObject(),
LockMode.parse( ( String ) ois.readObject() ), LockMode.parse( ( String ) ois.readObject() ),
ois.readBoolean(), ois.readBoolean(),
ois.readBoolean(), ois.readBoolean(),

View File

@ -38,8 +38,8 @@ import org.hibernate.type.TypeFactory;
* @author Gavin King * @author Gavin King
*/ */
public class ManyToOne extends ToOne { public class ManyToOne extends ToOne {
private boolean ignoreNotFound; private boolean ignoreNotFound;
private boolean isLogicalOneToOne;
public ManyToOne(Table table) { public ManyToOne(Table table) {
super(table); super(table);
@ -52,8 +52,9 @@ public class ManyToOne extends ToOne {
isLazy(), isLazy(),
isUnwrapProxy(), isUnwrapProxy(),
isEmbedded(), isEmbedded(),
isIgnoreNotFound() isIgnoreNotFound(),
); isLogicalOneToOne
);
} }
public void createForeignKey() throws MappingException { public void createForeignKey() throws MappingException {
@ -110,5 +111,11 @@ public class ManyToOne extends ToOne {
this.ignoreNotFound = ignoreNotFound; this.ignoreNotFound = ignoreNotFound;
} }
public void markAsLogicalOneToOne() {
this.isLogicalOneToOne = true;
}
public boolean isLogicalOneToOne() {
return isLogicalOneToOne;
}
} }

View File

@ -1,10 +1,10 @@
/* /*
* Hibernate, Relational Persistence for Idiomatic Java * Hibernate, Relational Persistence for Idiomatic Java
* *
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as * Copyright (c) 2010, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution * indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are * statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC. * distributed under license by Red Hat Inc.
* *
* This copyrighted material is made available to anyone wishing to use, modify, * 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 * copy, or redistribute it subject to the terms and conditions of the GNU
@ -20,7 +20,6 @@
* Free Software Foundation, Inc. * Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor * 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA * Boston, MA 02110-1301 USA
*
*/ */
package org.hibernate.tuple.entity; package org.hibernate.tuple.entity;
@ -29,7 +28,6 @@ import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.hibernate.EntityMode;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.tuple.Instantiator; import org.hibernate.tuple.Instantiator;
@ -132,12 +130,12 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
getters = new Getter[propertySpan]; getters = new Getter[propertySpan];
setters = new Setter[propertySpan]; setters = new Setter[propertySpan];
Iterator iter = mappingInfo.getPropertyClosureIterator(); Iterator itr = mappingInfo.getPropertyClosureIterator();
boolean foundCustomAccessor=false; boolean foundCustomAccessor=false;
int i=0; int i=0;
while ( iter.hasNext() ) { while ( itr.hasNext() ) {
//TODO: redesign how PropertyAccessors are acquired... //TODO: redesign how PropertyAccessors are acquired...
Property property = (Property) iter.next(); Property property = (Property) itr.next();
getters[i] = buildPropertyGetter(property, mappingInfo); getters[i] = buildPropertyGetter(property, mappingInfo);
setters[i] = buildPropertySetter(property, mappingInfo); setters[i] = buildPropertySetter(property, mappingInfo);
if ( !property.isBasicPropertyAccessor() ) { if ( !property.isBasicPropertyAccessor() ) {
@ -172,7 +170,7 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
} }
/** /**
* Retreives the defined entity-names for any subclasses defined for this * Retrieves the defined entity-names for any subclasses defined for this
* entity. * entity.
* *
* @return Any subclass entity-names. * @return Any subclass entity-names.
@ -208,7 +206,7 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
catch ( ClassCastException cce ) { catch ( ClassCastException cce ) {
StringBuffer msg = new StringBuffer( "Identifier classes must be serializable. " ); StringBuffer msg = new StringBuffer( "Identifier classes must be serializable. " );
if ( id != null ) { if ( id != null ) {
msg.append( id.getClass().getName() + " is not serializable. " ); msg.append( id.getClass().getName() ).append( " is not serializable. " );
} }
if ( cce.getMessage() != null ) { if ( cce.getMessage() != null ) {
msg.append( cce.getMessage() ); msg.append( cce.getMessage() );
@ -296,16 +294,21 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
} }
public Object getPropertyValue(Object entity, String propertyPath) throws HibernateException { public Object getPropertyValue(Object entity, String propertyPath) throws HibernateException {
final int loc = propertyPath.indexOf('.');
int loc = propertyPath.indexOf('.'); final String basePropertyName = loc > 0
String basePropertyName = loc>0 ? ? propertyPath.substring( 0, loc )
propertyPath.substring(0, loc) : propertyPath; : propertyPath;
final int index = entityMetamodel.getPropertyIndex( basePropertyName );
int index = entityMetamodel.getPropertyIndex( basePropertyName ); final Object baseValue = getPropertyValue( entity, index );
Object baseValue = getPropertyValue( entity, index ); if ( loc > 0 ) {
if ( loc>0 ) { if ( baseValue == null ) {
ComponentType type = (ComponentType) entityMetamodel.getPropertyTypes()[index]; return null;
return getComponentValue( type, baseValue, propertyPath.substring(loc+1) ); }
return getComponentValue(
(ComponentType) entityMetamodel.getPropertyTypes()[index],
baseValue,
propertyPath.substring(loc+1)
);
} }
else { else {
return baseValue; return baseValue;
@ -321,25 +324,21 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
* @return The property value extracted. * @return The property value extracted.
*/ */
protected Object getComponentValue(ComponentType type, Object component, String propertyPath) { protected Object getComponentValue(ComponentType type, Object component, String propertyPath) {
final int loc = propertyPath.indexOf( '.' );
int loc = propertyPath.indexOf('.'); final String basePropertyName = loc > 0
String basePropertyName = loc>0 ? ? propertyPath.substring( 0, loc )
propertyPath.substring(0, loc) : propertyPath; : propertyPath;
final int index = findSubPropertyIndex( type, basePropertyName );
String[] propertyNames = type.getPropertyNames(); final Object baseValue = type.getPropertyValue( component, index, getEntityMode() );
int index=0; if ( loc > 0 ) {
for ( ; index<propertyNames.length; index++ ) { if ( baseValue == null ) {
if ( basePropertyName.equals( propertyNames[index] ) ) break; return null;
} }
if (index==propertyNames.length) { return getComponentValue(
throw new MappingException( "component property not found: " + basePropertyName ); (ComponentType) type.getSubtypes()[index],
} baseValue,
propertyPath.substring(loc+1)
Object baseValue = type.getPropertyValue( component, index, getEntityMode() ); );
if ( loc>0 ) {
ComponentType subtype = (ComponentType) type.getSubtypes()[index];
return getComponentValue( subtype, baseValue, propertyPath.substring(loc+1) );
} }
else { else {
return baseValue; return baseValue;
@ -347,6 +346,16 @@ public abstract class AbstractEntityTuplizer implements EntityTuplizer {
} }
private int findSubPropertyIndex(ComponentType type, String subPropertyName) {
final String[] propertyNames = type.getPropertyNames();
for ( int index = 0; index<propertyNames.length; index++ ) {
if ( subPropertyName.equals( propertyNames[index] ) ) {
return index;
}
}
throw new MappingException( "component property not found: " + subPropertyName );
}
public void setPropertyValues(Object entity, Object[] values) throws HibernateException { public void setPropertyValues(Object entity, Object[] values) throws HibernateException {
boolean setAll = !entityMetamodel.hasLazyProperties(); boolean setAll = !entityMetamodel.hasLazyProperties();

View File

@ -508,8 +508,22 @@ public abstract class EntityType extends AbstractType implements AssociationType
return result.toString(); return result.toString();
} }
/**
* Is the association modeled here defined as a 1-1 in the database (physical model)?
*
* @return True if a 1-1 in the database; false otherwise.
*/
public abstract boolean isOneToOne(); public abstract boolean isOneToOne();
/**
* Is the association modeled here a 1-1 according to the logical moidel?
*
* @return True if a 1-1 in the logical model; false otherwise.
*/
public boolean isLogicalOneToOne() {
return isOneToOne();
}
/** /**
* Convenience method to locate the identifier type of the associated entity. * Convenience method to locate the identifier type of the associated entity.
* *

View File

@ -45,27 +45,54 @@ import org.hibernate.persister.entity.EntityPersister;
* @author Gavin King * @author Gavin King
*/ */
public class ManyToOneType extends EntityType { public class ManyToOneType extends EntityType {
private final boolean ignoreNotFound; private final boolean ignoreNotFound;
private boolean isLogicalOneToOne;
public ManyToOneType(String className) { /**
this( className, false ); * Creates a many-to-one association type with the given referenced entity.
*
* @param referencedEntityName The name iof the referenced entity
*/
public ManyToOneType(String referencedEntityName) {
this( referencedEntityName, false );
} }
public ManyToOneType(String className, boolean lazy) { /**
super( className, null, !lazy, true, false ); * Creates a many-to-one association type with the given referenced entity and the
this.ignoreNotFound = false; * given laziness characteristic
*
* @param referencedEntityName The name iof the referenced entity
* @param lazy Should the association be handled lazily
*/
public ManyToOneType(String referencedEntityName, boolean lazy) {
this( referencedEntityName, null, !lazy, true, false, false );
} }
/**
* @deprecated use {@link #ManyToOneType(String, String, boolean, boolean, boolean, boolean, boolean)}
* @noinspection JavaDoc
*/
public ManyToOneType( public ManyToOneType(
String entityName, String referencedEntityName,
String uniqueKeyPropertyName, String uniqueKeyPropertyName,
boolean lazy, boolean lazy,
boolean unwrapProxy, boolean unwrapProxy,
boolean isEmbeddedInXML, boolean isEmbeddedInXML,
boolean ignoreNotFound) { boolean ignoreNotFound) {
super( entityName, uniqueKeyPropertyName, !lazy, isEmbeddedInXML, unwrapProxy ); this( referencedEntityName, uniqueKeyPropertyName, !lazy, isEmbeddedInXML, unwrapProxy, ignoreNotFound, false );
}
public ManyToOneType(
String referencedEntityName,
String uniqueKeyPropertyName,
boolean lazy,
boolean unwrapProxy,
boolean isEmbeddedInXML,
boolean ignoreNotFound,
boolean isLogicalOneToOne) {
super( referencedEntityName, uniqueKeyPropertyName, !lazy, isEmbeddedInXML, unwrapProxy );
this.ignoreNotFound = ignoreNotFound; this.ignoreNotFound = ignoreNotFound;
this.isLogicalOneToOne = isLogicalOneToOne;
} }
protected boolean isNullable() { protected boolean isNullable() {
@ -82,7 +109,11 @@ public class ManyToOneType extends EntityType {
public boolean isOneToOne() { public boolean isOneToOne() {
return false; return false;
} }
public boolean isLogicalOneToOne() {
return isLogicalOneToOne;
}
public int getColumnSpan(Mapping mapping) throws MappingException { public int getColumnSpan(Mapping mapping) throws MappingException {
// our column span is the number of columns in the PK // our column span is the number of columns in the PK
return getIdentifierOrUniqueKeyType( mapping ).getColumnSpan( mapping ); return getIdentifierOrUniqueKeyType( mapping ).getColumnSpan( mapping );

View File

@ -201,6 +201,8 @@ public final class TypeFactory {
/** /**
* A many-to-one association type for the given class * A many-to-one association type for the given class
*
* @deprecated Use {@link #manyToOne(String, String, boolean, boolean, boolean, boolean, boolean)}
*/ */
public static EntityType manyToOne( public static EntityType manyToOne(
String persistentClass, String persistentClass,
@ -208,8 +210,8 @@ public final class TypeFactory {
boolean lazy, boolean lazy,
boolean unwrapProxy, boolean unwrapProxy,
boolean isEmbeddedInXML, boolean isEmbeddedInXML,
boolean ignoreNotFound boolean ignoreNotFound) {
) { //noinspection deprecation
return new ManyToOneType( return new ManyToOneType(
persistentClass, persistentClass,
uniqueKeyPropertyName, uniqueKeyPropertyName,
@ -217,7 +219,29 @@ public final class TypeFactory {
unwrapProxy, unwrapProxy,
isEmbeddedInXML, isEmbeddedInXML,
ignoreNotFound ignoreNotFound
); );
}
/**
* A many-to-one association type for the given class
*/
public static EntityType manyToOne(
String persistentClass,
String uniqueKeyPropertyName,
boolean lazy,
boolean unwrapProxy,
boolean isEmbeddedInXML,
boolean ignoreNotFound,
boolean isLogicalOneToOne) {
return new ManyToOneType(
persistentClass,
uniqueKeyPropertyName,
lazy,
unwrapProxy,
isEmbeddedInXML,
ignoreNotFound,
isLogicalOneToOne
);
} }
/** /**

View File

@ -629,6 +629,10 @@ public class CustomPersister implements EntityPersister {
return false; return false;
} }
public String[] getOrphanRemovalOneToOnePaths() {
return null;
}
public Object[] getNaturalIdentifierSnapshot(Serializable id, SessionImplementor session) throws HibernateException { public Object[] getNaturalIdentifierSnapshot(Serializable id, SessionImplementor session) throws HibernateException {
return null; return null;
} }

View File

@ -0,0 +1,92 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, 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.orphan.one2one.fk.reversed.bidirectional;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.junit.functional.FunctionalTestCase;
/**
* TODO : javadoc
*
* @author Steve Ebersole
*/
public class DeleteOneToOneOrphansTest extends FunctionalTestCase {
public DeleteOneToOneOrphansTest(String string) {
super( string );
}
public String[] getMappings() {
return new String[] { "orphan/one2one/fk/reversed/bidirectional/Mapping.hbm.xml" };
}
private void createData() {
Session session = openSession();
session.beginTransaction();
Employee emp = new Employee();
emp.setInfo( new EmployeeInfo( emp ) );
session.save( emp );
session.getTransaction().commit();
session.close();
}
private void cleanupData() {
Session session = openSession();
session.beginTransaction();
session.createQuery( "delete EmployeeInfo" ).executeUpdate();
session.createQuery( "delete Employee" ).executeUpdate();
session.getTransaction().commit();
session.close();
}
public void testOrphanedWhileManaged() {
createData();
Session session = openSession();
session.beginTransaction();
List results = session.createQuery( "from EmployeeInfo" ).list();
assertEquals( 1, results.size() );
results = session.createQuery( "from Employee" ).list();
assertEquals( 1, results.size() );
Employee emp = ( Employee ) results.get( 0 );
assertNotNull( emp.getInfo() );
emp.setInfo( null );
session.getTransaction().commit();
session.close();
session = openSession();
session.beginTransaction();
emp = ( Employee ) session.get( Employee.class, emp.getId() );
assertNull( emp.getInfo() );
results = session.createQuery( "from EmployeeInfo" ).list();
assertEquals( 0, results.size() );
results = session.createQuery( "from Employee" ).list();
assertEquals( 1, results.size() );
session.getTransaction().commit();
session.close();
cleanupData();
}
}

View File

@ -0,0 +1,50 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, 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.orphan.one2one.fk.reversed.bidirectional;
/**
* TODO : javadoc
*
* @author Steve Ebersole
*/
public class Employee {
private Long id;
private EmployeeInfo info;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public EmployeeInfo getInfo() {
return info;
}
public void setInfo(EmployeeInfo info) {
this.info = info;
}
}

View File

@ -0,0 +1,57 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, 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.orphan.one2one.fk.reversed.bidirectional;
/**
* TODO : javadoc
*
* @author Steve Ebersole
*/
public class EmployeeInfo {
private Long id;
private Employee employee;
public EmployeeInfo() {
}
public EmployeeInfo(Employee employee) {
this.employee = employee;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
}

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Hibernate, Relational Persistence for Idiomatic Java
~
~ Copyright (c) 2010, 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
-->
<!DOCTYPE hibernate-mapping PUBLIC
'-//Hibernate/Hibernate Mapping DTD 3.0//EN'
'http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd'>
<hibernate-mapping package="org.hibernate.test.orphan.one2one.fk.reversed.bidirectional" >
<class name="Employee">
<id name="id" type="long" column="id">
<generator class="increment" />
</id>
<many-to-one name="info"
column="info_id"
unique="true"
cascade="all,delete-orphan"/>
</class>
<class name="EmployeeInfo">
<id name="id" type="long" column="id">
<generator class="increment" />
</id>
<one-to-one name="employee"
property-ref="info"
class="Employee" />
</class>
</hibernate-mapping>

View File

@ -0,0 +1,92 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, 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.orphan.one2one.fk.reversed.unidirectional;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.junit.functional.FunctionalTestCase;
/**
* TODO : javadoc
*
* @author Steve Ebersole
*/
public class DeleteOneToOneOrphansTest extends FunctionalTestCase {
public DeleteOneToOneOrphansTest(String string) {
super( string );
}
public String[] getMappings() {
return new String[] { "orphan/one2one/fk/reversed/unidirectional/Mapping.hbm.xml" };
}
private void createData() {
Session session = openSession();
session.beginTransaction();
Employee emp = new Employee();
emp.setInfo( new EmployeeInfo() );
session.save( emp );
session.getTransaction().commit();
session.close();
}
private void cleanupData() {
Session session = openSession();
session.beginTransaction();
session.createQuery( "delete EmployeeInfo" ).executeUpdate();
session.createQuery( "delete Employee" ).executeUpdate();
session.getTransaction().commit();
session.close();
}
public void testOrphanedWhileManaged() {
createData();
Session session = openSession();
session.beginTransaction();
List results = session.createQuery( "from EmployeeInfo" ).list();
assertEquals( 1, results.size() );
results = session.createQuery( "from Employee" ).list();
assertEquals( 1, results.size() );
Employee emp = ( Employee ) results.get( 0 );
assertNotNull( emp.getInfo() );
emp.setInfo( null );
session.getTransaction().commit();
session.close();
session = openSession();
session.beginTransaction();
emp = ( Employee ) session.get( Employee.class, emp.getId() );
assertNull( emp.getInfo() );
results = session.createQuery( "from EmployeeInfo" ).list();
assertEquals( 0, results.size() );
results = session.createQuery( "from Employee" ).list();
assertEquals( 1, results.size() );
session.getTransaction().commit();
session.close();
cleanupData();
}
}

View File

@ -0,0 +1,50 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, 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.orphan.one2one.fk.reversed.unidirectional;
/**
* TODO : javadoc
*
* @author Steve Ebersole
*/
public class Employee {
private Long id;
private EmployeeInfo info;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public EmployeeInfo getInfo() {
return info;
}
public void setInfo(EmployeeInfo info) {
this.info = info;
}
}

View File

@ -0,0 +1,44 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, 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.orphan.one2one.fk.reversed.unidirectional;
/**
* TODO : javadoc
*
* @author Steve Ebersole
*/
public class EmployeeInfo {
private Long id;
public EmployeeInfo() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Hibernate, Relational Persistence for Idiomatic Java
~
~ Copyright (c) 2010, 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
-->
<!DOCTYPE hibernate-mapping PUBLIC
'-//Hibernate/Hibernate Mapping DTD 3.0//EN'
'http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd'>
<hibernate-mapping package="org.hibernate.test.orphan.one2one.fk.reversed.unidirectional" >
<class name="Employee">
<id name="id" type="long" column="id">
<generator class="increment" />
</id>
<many-to-one name="info"
column="info_id"
unique="true"
cascade="all,delete-orphan"/>
</class>
<class name="EmployeeInfo">
<id name="id" type="long" column="id">
<generator class="increment" />
</id>
</class>
</hibernate-mapping>