HHH-4552 - Support generated value within composite keys

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18601 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Steve Ebersole 2010-01-21 19:37:01 +00:00
parent 8577a68e69
commit 79b10cc810
4 changed files with 257 additions and 11 deletions

View File

@ -38,6 +38,7 @@ import org.hibernate.classic.Validatable;
import org.hibernate.engine.EntityEntry; import org.hibernate.engine.EntityEntry;
import org.hibernate.engine.EntityKey; import org.hibernate.engine.EntityKey;
import org.hibernate.engine.Nullability; import org.hibernate.engine.Nullability;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.SessionImplementor; import org.hibernate.engine.SessionImplementor;
import org.hibernate.engine.Status; import org.hibernate.engine.Status;
import org.hibernate.engine.Versioning; import org.hibernate.engine.Versioning;
@ -62,8 +63,12 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
/** /**
* make sure user didn't mangle the id * make sure user didn't mangle the id
*/ */
public void checkId(Object object, EntityPersister persister, Serializable id, EntityMode entityMode) public void checkId(
throws HibernateException { Object object,
EntityPersister persister,
Serializable id,
EntityMode entityMode,
SessionFactoryImplementor factory) throws HibernateException {
if ( id != null && id instanceof DelayedPostInsertIdentifier ) { if ( id != null && id instanceof DelayedPostInsertIdentifier ) {
// this is a situation where the entity id is assigned by a post-insert generator // this is a situation where the entity id is assigned by a post-insert generator
@ -77,7 +82,7 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
if (id==null) { if (id==null) {
throw new AssertionFailure("null id in " + persister.getEntityName() + " entry (don't flush the Session after an exception occurs)"); throw new AssertionFailure("null id in " + persister.getEntityName() + " entry (don't flush the Session after an exception occurs)");
} }
if ( !persister.getIdentifierType().isEqual(id, oid, entityMode) ) { if ( !persister.getIdentifierType().isEqual( id, oid, entityMode, factory ) ) {
throw new HibernateException( throw new HibernateException(
"identifier of an instance of " + "identifier of an instance of " +
persister.getEntityName() + persister.getEntityName() +
@ -184,7 +189,7 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
values = loadedState; values = loadedState;
} }
else { else {
checkId( entity, persister, entry.getId(), entityMode ); checkId( entity, persister, entry.getId(), entityMode, session.getFactory() );
// grab its current state // grab its current state
values = persister.getPropertyValues( entity, entityMode ); values = persister.getPropertyValues( entity, entityMode );

View File

@ -0,0 +1,96 @@
/*
* 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.id;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.engine.SessionImplementor;
/**
* For composite identifiers, defines a number of "nested" generations that
* need to happen to "fill" the identifier property(s).
* <p/>
* This generator is used implicitly for all composite identifier scenarios if an
* explicit generator is not in place. So it make sense to discuss the various
* potential scenarios:<ul>
* <li>
* <i>"embedded" composite identifier</i> - this is possible only in HBM mappings
* as {@code <composite-id/>} (notice the lack of both a name and class attribute
* declarations). The term {@link org.hibernate.mapping.Component#isEmbedded() "embedded"}
* here refers to the Hibernate usage which is actually the exact opposite of the JPA
* meaning of "embedded". Essentially this means that the entity class itself holds
* the named composite pk properties. This is very similar to the JPA {@code @IdClass}
* usage, though without a separate pk-class for loading.
* </li>
* <li>
* <i>pk-class as entity attribute</i> - this is possible in both annotations ({@code @EmbeddedId})
* and HBM mappings ({@code <composite-id name="idAttributeName" class="PkClassName"/>})
* </li>
* <li>
* <i>"embedded" composite identifier with a pk-class</i> - this is the JPA {@code @IdClass} use case
* and is only possible in annotations
* </li>
* </ul>
* <p/>
* Most of the grunt work is done in {@link org.hibernate.mapping.Component}.
*
* @author Steve Ebersole
*/
public class CompositeNestedGeneratedValueGenerator implements IdentifierGenerator, Serializable {
public static interface GenerationContextLocator {
public Serializable locateGenerationContext(SessionImplementor session, Object incomingObject);
}
public static interface GenerationPlan {
public void execute(SessionImplementor session, Object incomingObject);
}
private final GenerationContextLocator generationContextLocator;
private List generationPlans = new ArrayList();
public CompositeNestedGeneratedValueGenerator(GenerationContextLocator generationContextLocator) {
this.generationContextLocator = generationContextLocator;
}
public void addGeneratedValuePlan(GenerationPlan plan) {
generationPlans.add( plan );
}
public Serializable generate(SessionImplementor session, Object object) throws HibernateException {
final Serializable context = generationContextLocator.locateGenerationContext( session, object );
Iterator itr = generationPlans.iterator();
while ( itr.hasNext() ) {
final GenerationPlan plan = (GenerationPlan) itr.next();
plan.execute( session, context );
}
return context;
}
}

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,17 +20,30 @@
* 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.mapping; package org.hibernate.mapping;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.hibernate.EntityMode; import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.id.CompositeNestedGeneratedValueGenerator;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.property.Getter;
import org.hibernate.property.PropertyAccessor;
import org.hibernate.property.Setter;
import org.hibernate.tuple.component.ComponentMetamodel; import org.hibernate.tuple.component.ComponentMetamodel;
import org.hibernate.type.ComponentType; import org.hibernate.type.ComponentType;
import org.hibernate.type.EmbeddedComponentType; import org.hibernate.type.EmbeddedComponentType;
@ -41,10 +54,11 @@ import org.hibernate.util.ReflectHelper;
/** /**
* The mapping for a component, composite element, * The mapping for a component, composite element,
* composite identifier, etc. * composite identifier, etc.
*
* @author Gavin King * @author Gavin King
* @author Steve Ebersole
*/ */
public class Component extends SimpleValue implements MetaAttributable { public class Component extends SimpleValue implements MetaAttributable {
private ArrayList properties = new ArrayList(); private ArrayList properties = new ArrayList();
private String componentClassName; private String componentClassName;
private boolean embedded; private boolean embedded;
@ -295,4 +309,135 @@ public class Component extends SimpleValue implements MetaAttributable {
return getClass().getName() + '(' + properties.toString() + ')'; return getClass().getName() + '(' + properties.toString() + ')';
} }
private IdentifierGenerator builtIdentifierGenerator;
public IdentifierGenerator createIdentifierGenerator(
IdentifierGeneratorFactory identifierGeneratorFactory,
Dialect dialect,
String defaultCatalog,
String defaultSchema,
RootClass rootClass) throws MappingException {
if ( builtIdentifierGenerator == null ) {
builtIdentifierGenerator = buildIdentifierGenerator(
identifierGeneratorFactory,
dialect,
defaultCatalog,
defaultSchema,
rootClass
);
}
return builtIdentifierGenerator;
}
private IdentifierGenerator buildIdentifierGenerator(
IdentifierGeneratorFactory identifierGeneratorFactory,
Dialect dialect,
String defaultCatalog,
String defaultSchema,
RootClass rootClass) throws MappingException {
final boolean hasCustomGenerator = ! DEFAULT_ID_GEN_STRATEGY.equals( getIdentifierGeneratorStrategy() );
if ( hasCustomGenerator ) {
return super.createIdentifierGenerator(
identifierGeneratorFactory, dialect, defaultCatalog, defaultSchema, rootClass
);
}
final Class entityClass = rootClass.getMappedClass();
final Class attributeDeclarer; // what class is the declarer of the composite pk attributes
CompositeNestedGeneratedValueGenerator.GenerationContextLocator locator;
// IMPL NOTE : See the javadoc discussion on CompositeNestedGeneratedValueGenerator wrt the
// various scenarios for which we need to account here
if ( isEmbedded() ) {
// we have the "straight up" embedded (again the hibernate term) component identifier
attributeDeclarer = entityClass;
}
else if ( rootClass.getIdentifierProperty() != null ) {
// we have the "@EmbeddedId" / <composite-id name="idName"/> case
attributeDeclarer = resolveComponentClass();
}
else {
// we have the @IdClass / <composite-id mapped="true"/> case
attributeDeclarer = resolveComponentClass();
}
locator = new StandardGenerationContextLocator( rootClass.getEntityName() );
final CompositeNestedGeneratedValueGenerator generator = new CompositeNestedGeneratedValueGenerator( locator );
Iterator itr = getPropertyIterator();
while ( itr.hasNext() ) {
final Property property = (Property) itr.next();
if ( property.getValue().isSimpleValue() ) {
final SimpleValue value = (SimpleValue) property.getValue();
if ( DEFAULT_ID_GEN_STRATEGY.equals( value.getIdentifierGeneratorStrategy() ) ) {
// skip any 'assigned' generators, they would have been handled by
// the StandardGenerationContextLocator
continue;
}
final IdentifierGenerator valueGenerator = value.createIdentifierGenerator(
identifierGeneratorFactory,
dialect,
defaultCatalog,
defaultSchema,
rootClass
);
final Setter injector = property.getPropertyAccessor( attributeDeclarer )
.getSetter( attributeDeclarer, property.getName() );
generator.addGeneratedValuePlan(
new ValueGenerationPlan(
property.getName(),
valueGenerator,
injector
)
);
}
}
return generator;
}
private Class resolveComponentClass() {
try {
return getComponentClass();
}
catch ( Exception e ) {
return null;
}
}
public static class StandardGenerationContextLocator
implements CompositeNestedGeneratedValueGenerator.GenerationContextLocator {
private final String entityName;
public StandardGenerationContextLocator(String entityName) {
this.entityName = entityName;
}
public Serializable locateGenerationContext(SessionImplementor session, Object incomingObject) {
return session.getEntityPersister( entityName, incomingObject )
.getIdentifier( incomingObject, session.getEntityMode() );
}
}
public static class ValueGenerationPlan implements CompositeNestedGeneratedValueGenerator.GenerationPlan {
private final String propertyName;
private final IdentifierGenerator subGenerator;
private final Setter injector;
public ValueGenerationPlan(
String propertyName,
IdentifierGenerator subGenerator,
Setter injector) {
this.propertyName = propertyName;
this.subGenerator = subGenerator;
this.injector = injector;
}
public void execute(SessionImplementor session, Object incomingObject) {
final Object generatedValue = subGenerator.generate( session, incomingObject );
injector.set( incomingObject, generatedValue, session.getFactory() );
}
}
} }

View File

@ -46,11 +46,12 @@ import org.hibernate.util.ReflectHelper;
* @author Gavin King * @author Gavin King
*/ */
public class SimpleValue implements KeyValue { public class SimpleValue implements KeyValue {
public static final String DEFAULT_ID_GEN_STRATEGY = "assigned";
private final List columns = new ArrayList(); private final List columns = new ArrayList();
private String typeName; private String typeName;
private Properties identifierGeneratorProperties; private Properties identifierGeneratorProperties;
private String identifierGeneratorStrategy = "assigned"; private String identifierGeneratorStrategy = DEFAULT_ID_GEN_STRATEGY;
private String nullValue; private String nullValue;
private Table table; private Table table;
private String foreignKeyName; private String foreignKeyName;
@ -124,8 +125,7 @@ public class SimpleValue implements KeyValue {
Dialect dialect, Dialect dialect,
String defaultCatalog, String defaultCatalog,
String defaultSchema, String defaultSchema,
RootClass rootClass) RootClass rootClass) throws MappingException {
throws MappingException {
Properties params = new Properties(); Properties params = new Properties();