initial work on non-aggregated composite-id support

This commit is contained in:
Steve Ebersole 2020-01-07 15:34:29 -06:00
parent eab6107ec2
commit a9ee082128
26 changed files with 889 additions and 157 deletions

View File

@ -28,16 +28,17 @@ import org.hibernate.event.spi.LoadEventListener;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.loader.entity.CacheEntityLoaderHelper;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.tuple.IdentifierProperty;
import org.hibernate.tuple.entity.EntityMetamodel;
import org.hibernate.type.EmbeddedComponentType;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
/**
* Defines the default load event listeners used by hibernate for loading entities
@ -123,33 +124,32 @@ public class DefaultLoadEventListener implements LoadEventListener {
final LoadEvent event,
final LoadEventListener.LoadType loadType,
final Class idClass) {
// we may have the kooky jpa requirement of allowing find-by-id where
// "id" is the "simple pk value" of a dependent objects parent. This
// is part of its generally goofy "derived identity" "feature"
final IdentifierProperty identifierProperty = persister.getEntityMetamodel().getIdentifierProperty();
if ( identifierProperty.isEmbedded() ) {
final EmbeddedComponentType dependentIdType =
(EmbeddedComponentType) identifierProperty.getType();
if ( dependentIdType.getSubtypes().length == 1 ) {
final Type singleSubType = dependentIdType.getSubtypes()[0];
if ( singleSubType.isEntityType() ) {
final EntityType dependentParentType = (EntityType) singleSubType;
final SessionFactoryImplementor factory = event.getSession().getFactory();
final Type dependentParentIdType = dependentParentType.getIdentifierOrUniqueKeyType( factory );
if ( dependentParentIdType.getReturnedClass().isInstance( event.getEntityId() ) ) {
// we may have the jpa requirement of allowing find-by-id where id is the "simple pk value" of a
// dependent objects parent. This is part of its generally goofy derived identity "feature"
final EntityIdentifierMapping idMapping = persister.getIdentifierMapping();
if ( idMapping instanceof CompositeIdentifierMapping ) {
final CompositeIdentifierMapping cidMapping = (CompositeIdentifierMapping) idMapping;
if ( cidMapping.getAttributeCount() == 1 ) {
final AttributeMapping singleIdAttribute = cidMapping.getAttributes().iterator().next();
if ( singleIdAttribute.getMappedTypeDescriptor() instanceof EntityMappingType ) {
final EntityMappingType dependentIdTargetMapping = (EntityMappingType) singleIdAttribute.getMappedTypeDescriptor();
final EntityIdentifierMapping dependentIdTargetIdMapping = dependentIdTargetMapping.getIdentifierMapping();
final JavaTypeDescriptor dependentParentIdJtd = dependentIdTargetIdMapping.getMappedTypeDescriptor().getMappedJavaTypeDescriptor();
if ( dependentParentIdJtd.getJavaType().isInstance( event.getEntityId() ) ) {
// yep that's what we have...
loadByDerivedIdentitySimplePkValue(
event,
loadType,
persister,
dependentIdType,
factory.getMetamodel().entityPersister( dependentParentType.getAssociatedEntityName() )
(EntityPersister) dependentIdTargetMapping
);
return;
}
}
}
}
throw new TypeMismatchException(
"Provided id of the wrong type for class " + persister.getEntityName() + ". Expected: " + idClass
+ ", got " + event.getEntityId().getClass()
@ -160,14 +160,14 @@ public class DefaultLoadEventListener implements LoadEventListener {
LoadEvent event,
LoadEventListener.LoadType options,
EntityPersister dependentPersister,
EmbeddedComponentType dependentIdType,
// EmbeddedComponentType dependentIdType,
EntityPersister parentPersister) {
final EventSource session = event.getSession();
final EntityKey parentEntityKey = session.generateEntityKey( event.getEntityId(), parentPersister );
final Object parent = doLoad( event, parentPersister, parentEntityKey, options );
final Serializable dependent = (Serializable) dependentIdType.instantiate( parent, session );
dependentIdType.setPropertyValues( dependent, new Object[] {parent}, dependentPersister.getEntityMode() );
final Serializable dependent = (Serializable) dependentPersister.instantiate( parent, session );
dependentPersister.setPropertyValues( dependent, new Object[] {parent} );
final EntityKey dependentEntityKey = session.generateEntityKey( dependent, dependentPersister );
event.setEntityId( dependent );

View File

@ -15,7 +15,6 @@ import java.util.Set;
import org.hibernate.EntityNameResolver;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.metamodel.spi.Instantiator;
@ -58,7 +57,7 @@ public class DynamicMapInstantiator implements Instantiator<Map> {
}
@Override
public Map instantiate(SharedSessionContractImplementor session) {
public Map instantiate(SessionFactoryImplementor sessionFactory) {
Map map = generateMap();
if ( roleName != null ) {
//noinspection unchecked

View File

@ -7,7 +7,7 @@
package org.hibernate.metamodel.internal;
import org.hibernate.bytecode.spi.ReflectionOptimizer;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
/**
@ -22,7 +22,7 @@ public class OptimizedPojoInstantiatorImpl<J> extends AbstractPojoInstantiator {
}
@Override
public Object instantiate(SharedSessionContractImplementor session) {
public Object instantiate(SessionFactoryImplementor sessionFactory) {
return instantiationOptimizer.newInstance();
}
}

View File

@ -10,7 +10,7 @@ import java.lang.reflect.Constructor;
import org.hibernate.InstantiationException;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper;
@ -48,7 +48,7 @@ public class PojoInstantiatorImpl<J> extends AbstractPojoInstantiator {
@Override
@SuppressWarnings("unchecked")
public J instantiate(SharedSessionContractImplementor session) {
public J instantiate(SessionFactoryImplementor sessionFactory) {
if ( isAbstract() ) {
throw new InstantiationException( "Cannot instantiate abstract class or interface: ", getMappedPojoClass() );
}

View File

@ -6,8 +6,10 @@
*/
package org.hibernate.metamodel.mapping;
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
/**
* @author Steve Ebersole
*/
public interface BasicEntityIdentifierMapping extends EntityIdentifierMapping, BasicValuedModelPart {
public interface BasicEntityIdentifierMapping extends SingleAttributeIdentifierMapping, BasicValuedModelPart {
}

View File

@ -0,0 +1,26 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping;
import java.util.Collection;
/**
* Support for composite identifier mappings
*
* @author Andrea Boriero
*/
public interface CompositeIdentifierMapping extends EntityIdentifierMapping {
/**
* The number of attributes associated with this composite
*/
int getAttributeCount();
/**
* The attributes associated with this composite
*/
Collection<SingularAttributeMapping> getAttributes();
}

View File

@ -1,16 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
/**
* @author Andrea Boriero
*/
public interface EmbeddedIdentifierMapping extends EntityIdentifierMapping, EmbeddableValuedModelPart {
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.metamodel.mapping;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.property.access.spi.PropertyAccess;
/**
@ -14,7 +15,9 @@ import org.hibernate.property.access.spi.PropertyAccess;
public interface EntityIdentifierMapping extends ValueMapping, ModelPart {
String ROLE_LOCAL_NAME = "{id}";
PropertyAccess getPropertyAccess();
Object getIdentifier(Object entity, SharedSessionContractImplementor session);
void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session);
Object instantiate();
@Override
default String getPartName() {

View File

@ -15,6 +15,7 @@ import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.loader.ast.spi.Loadable;
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy;
@ -47,6 +48,10 @@ public interface EntityMappingType extends ManagedMappingType, Loadable {
*/
EntityPersister getEntityPersister();
default EntityRepresentationStrategy getRepresentationStrategy() {
return getEntityPersister().getRepresentationStrategy();
}
String getEntityName();
@Override

View File

@ -7,17 +7,19 @@
package org.hibernate.metamodel.mapping.internal;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EmbeddedIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
@ -51,35 +53,43 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Support for {@link javax.persistence.EmbeddedId}
*
* @author Andrea Boriero
*/
public class EmbeddedIdentifierMappingImpl
implements EmbeddedIdentifierMapping, EmbeddableValuedFetchable {
public class EmbeddedIdentifierMappingImpl implements CompositeIdentifierMapping, SingleAttributeIdentifierMapping, EmbeddableValuedFetchable {
private final EntityMappingType entityMapping;
private final String name;
private final MappingType type;
private final StateArrayContributorMetadataAccess attributeMetadataAccess;
private final PropertyAccess propertyAccess;
private final String tableExpression;
private final String[] attrColumnNames;
private final SessionFactoryImplementor sessionFactory;
@SuppressWarnings("WeakerAccess")
public EmbeddedIdentifierMappingImpl(
EntityMappingType entityMapping,
String name,
MappingType type,
StateArrayContributorMetadataAccess attributeMetadataAccess,
PropertyAccess propertyAccess,
String tableExpression,
String[] attrColumnNames) {
String[] attrColumnNames,
SessionFactoryImplementor sessionFactory) {
this.entityMapping = entityMapping;
this.name = name;
this.type = type;
this.attributeMetadataAccess = attributeMetadataAccess;
this.propertyAccess = propertyAccess;
this.tableExpression = tableExpression;
this.attrColumnNames = attrColumnNames;
this.sessionFactory = sessionFactory;
}
@Override
public MappingType getPartMappingType() {
return type;
public EmbeddableMappingType getPartMappingType() {
return (EmbeddableMappingType) type;
}
@Override
@ -93,8 +103,18 @@ public class EmbeddedIdentifierMappingImpl
}
@Override
public PropertyAccess getPropertyAccess() {
return propertyAccess;
public Object getIdentifier(Object entity, SharedSessionContractImplementor session) {
return propertyAccess.getGetter().get( entity );
}
@Override
public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) {
propertyAccess.getSetter().set( entity, id, session.getFactory() );
}
@Override
public Object instantiate() {
return entityMapping.getRepresentationStrategy().getInstantiator().instantiate( sessionFactory );
}
@Override
@ -271,4 +291,20 @@ public class EmbeddedIdentifierMappingImpl
public void visitColumns(ColumnConsumer consumer) {
getEmbeddableTypeDescriptor().visitColumns( consumer );
}
@Override
public int getAttributeCount() {
return getEmbeddableTypeDescriptor().getNumberOfAttributeMappings();
}
@Override
public Collection<SingularAttributeMapping> getAttributes() {
//noinspection unchecked
return (Collection) getEmbeddableTypeDescriptor().getAttributeMappings();
}
@Override
public PropertyAccess getPropertyAccess() {
return propertyAccess;
}
}

View File

@ -53,6 +53,7 @@ import org.hibernate.metamodel.mapping.CollectionIdentifierDescriptor;
import org.hibernate.metamodel.mapping.CollectionMappingType;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
@ -61,6 +62,7 @@ import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadata;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
@ -134,6 +136,23 @@ public class MappingModelCreationHelper {
return propertyAccess;
}
@Override
public Object getIdentifier(Object entity, SharedSessionContractImplementor session) {
return propertyAccess.getGetter().get( entity );
}
@Override
public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) {
propertyAccess.getSetter().set( entity, id, session.getFactory() );
}
@Override
public Object instantiate() {
return entityPersister.getRepresentationStrategy()
.getInstantiator()
.instantiate( creationProcess.getCreationContext().getSessionFactory() );
}
@Override
public MappingType getPartMappingType() {
return getBasicType();
@ -318,12 +337,14 @@ public class MappingModelCreationHelper {
(Component) bootProperty.getValue(),
cidType,
attributeMappingType -> new EmbeddedIdentifierMappingImpl(
entityPersister,
attributeName,
attributeMappingType,
attributeMetadataAccess,
propertyAccess,
rootTableName,
rootTableKeyColumnNames
rootTableKeyColumnNames,
creationProcess.getCreationContext().getSessionFactory()
),
creationProcess
);
@ -332,71 +353,24 @@ public class MappingModelCreationHelper {
return (EmbeddedIdentifierMappingImpl) embeddableMappingType.getEmbeddedValueMapping();
}
public static EntityIdentifierMapping buildNonEncapsulatedCompositeIdentifierMapping(
public static CompositeIdentifierMapping buildNonEncapsulatedCompositeIdentifierMapping(
EntityPersister entityPersister,
String rootTableName,
String[] rootTableKeyColumnNames,
List<SingularAttributeMapping> idAttributeMappings,
CompositeType cidType,
PersistentClass bootEntityDescriptor,
MappingModelCreationProcess creationProcess) {
final PersistentClass bootEntityDescriptor = creationProcess.getCreationContext()
.getBootModel()
.getEntityBinding( entityPersister.getEntityName() );
final PropertyAccess propertyAccess = entityPersister.getRepresentationStrategy()
.resolvePropertyAccess( bootEntityDescriptor.getIdentifierProperty() );
final Component bootCompositeDescriptor = (Component) bootEntityDescriptor.getIdentifier();
return new EntityIdentifierMapping() {
@Override
public MappingType getPartMappingType() {
// non-encapsulated means that the id attributes are directly defined on the entity
// - alternatively we could have the type here be the IdClass descriptor
return entityPersister;
}
@Override
public PropertyAccess getPropertyAccess() {
return propertyAccess;
}
@Override
public MappingType getMappedTypeDescriptor() {
return entityPersister;
}
@Override
public JavaTypeDescriptor getJavaTypeDescriptor() {
return getMappedTypeDescriptor().getMappedJavaTypeDescriptor();
}
@Override
public <T> DomainResult<T> createDomainResult(
NavigablePath navigablePath,
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
return ( (ModelPart) entityPersister.getIdentifierType() ).createDomainResult(
navigablePath,
tableGroup,
resultVariable,
creationState
return new NonAggregatedIdentifierMappingImpl(
entityPersister,
idAttributeMappings,
bootCompositeDescriptor,
cidType,
creationProcess
);
}
@Override
public void applySqlSelections(
NavigablePath navigablePath,
TableGroup tableGroup,
DomainResultCreationState creationState) {
( (ModelPart) entityPersister.getIdentifierType() ).applySqlSelections(
navigablePath,
tableGroup,
creationState
);
}
};
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Non-identifier attributes

View File

@ -0,0 +1,111 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.Property;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableResultImpl;
import org.hibernate.sql.results.graph.entity.internal.EntityResultImpl;
import org.hibernate.type.CompositeType;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
/**
* A "non-aggregated" composite identifier.
*
* This is an identifier mapped using JPA's {@link javax.persistence.MapsId} feature.
*
* @apiNote Technically a MapsId id does not have to be composite; we still handle that this class however
*
* @author Steve Ebersole
*/
public class NonAggregatedIdentifierMappingImpl implements CompositeIdentifierMapping {
private final EntityMappingType entityMapping;
private final List<SingularAttributeMapping> idAttributeMappings;
public NonAggregatedIdentifierMappingImpl(
EntityMappingType entityMapping,
List<SingularAttributeMapping> idAttributeMappings,
Component bootIdDescriptor,
CompositeType cidType,
MappingModelCreationProcess creationProcess) {
// todo (6.0) : handle MapsId and IdClass
// todo (6.0) : implement SQL AST apis (DomainResult, e.g.)
this.entityMapping = entityMapping;
this.idAttributeMappings = idAttributeMappings;
}
@Override
public EntityMappingType getPartMappingType() {
return entityMapping;
}
@Override
public JavaTypeDescriptor getJavaTypeDescriptor() {
return entityMapping.getJavaTypeDescriptor();
}
@Override
public EntityMappingType getMappedTypeDescriptor() {
return entityMapping;
}
@Override
public int getAttributeCount() {
return idAttributeMappings.size();
}
@Override
public Collection<SingularAttributeMapping> getAttributes() {
return idAttributeMappings;
}
@Override
public Object getIdentifier(Object entity, SharedSessionContractImplementor session) {
return entity;
}
@Override
public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) {
// nothing to do
}
@Override
public Object instantiate() {
return entityMapping;
}
@Override
public <T> DomainResult<T> createDomainResult(
NavigablePath navigablePath,
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
// we will need a specialized impl for this
throw new NotYetImplementedFor6Exception( getClass() );
}
}

View File

@ -0,0 +1,20 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.internal;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.property.access.spi.PropertyAccess;
/**
* @author Steve Ebersole
*/
public interface SingleAttributeIdentifierMapping extends EntityIdentifierMapping {
/**
* Access to the identifier attribute's PropertyAccess
*/
PropertyAccess getPropertyAccess();
}

View File

@ -14,11 +14,11 @@ import javax.persistence.metamodel.Bindable;
import javax.persistence.metamodel.IdentifiableType;
import javax.persistence.metamodel.SingularAttribute;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.model.domain.internal.AttributeContainer;
import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.NonAggregatedCompositeSqmPathSource;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
@ -385,7 +385,11 @@ public abstract class AbstractIdentifiableType<J>
}
else if ( idClassAttributes != null && ! idClassAttributes.isEmpty() ) {
// non-aggregate composite id
throw new NotYetImplementedFor6Exception( getClass() );
return new NonAggregatedCompositeSqmPathSource(
EntityIdentifierMapping.ROLE_LOCAL_NAME,
Bindable.BindableType.SINGULAR_ATTRIBUTE,
this
);
}
else {
if ( isIdMappingRequired() ) {

View File

@ -0,0 +1,15 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.model.domain.internal;
import org.hibernate.query.sqm.SqmPathSource;
/**
* @author Steve Ebersole
*/
public interface CompositeSqmPathSource<J> extends SqmPathSource<J> {
}

View File

@ -16,7 +16,10 @@ import org.hibernate.query.sqm.tree.domain.SqmPath;
/**
* @author Steve Ebersole
*/
public class EmbeddedSqmPathSource<J> extends AbstractSqmPathSource<J> implements AllowableParameterType<J> {
public class EmbeddedSqmPathSource<J>
extends AbstractSqmPathSource<J>
implements CompositeSqmPathSource<J>, AllowableParameterType<J> {
public EmbeddedSqmPathSource(
String localPathName,
EmbeddableDomainType<J> domainType,

View File

@ -697,7 +697,7 @@ public class MappingMetamodelImpl implements MappingMetamodel, MetamodelImplemen
return (BasicType) sqmExpressable;
}
if ( sqmExpressable instanceof EmbeddedSqmPathSource ) {
if ( sqmExpressable instanceof CompositeSqmPathSource ) {
throw new NotYetImplementedFor6Exception( "Resolution of embedded-valued SqmExpressable nodes not yet implemented" );
}

View File

@ -0,0 +1,44 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.model.domain.internal;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.metamodel.model.domain.ManagedDomainType;
import org.hibernate.query.hql.spi.SqmCreationState;
import org.hibernate.query.sqm.IllegalPathUsageException;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.tree.domain.SqmPath;
/**
* Support for non-aggregated composite values
*
* @author Steve Ebersole
*/
public class NonAggregatedCompositeSqmPathSource extends AbstractSqmPathSource implements CompositeSqmPathSource {
public NonAggregatedCompositeSqmPathSource(
String localName,
BindableType bindableType,
ManagedDomainType container) {
super( localName, container, bindableType );
}
@Override
public ManagedDomainType<?> getSqmPathType() {
return (ManagedDomainType<?>) super.getSqmPathType();
}
@Override
public SqmPathSource<?> findSubPathSource(String name) throws IllegalPathUsageException {
return (SqmPathSource<?>) getSqmPathType().findAttribute( name );
}
@Override
public SqmPath createSqmPath(SqmPath lhs, SqmCreationState creationState) {
// todo (6.0) : I think this will require a specialized SqmPath as well...
throw new NotYetImplementedFor6Exception( getClass() );
}
}

View File

@ -8,7 +8,6 @@ package org.hibernate.metamodel.spi;
import org.hibernate.Incubating;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
/**
* Strategy for instantiating representation structure instances.
@ -20,7 +19,7 @@ public interface Instantiator<J> {
/**
* Create an instance of the managed embedded value structure.
*/
J instantiate(SharedSessionContractImplementor session);
J instantiate(SessionFactoryImplementor sessionFactory);
/**
* Performs and "instance of" check to see if the given object is an

View File

@ -75,6 +75,7 @@ import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.CascadeStyles;
import org.hibernate.engine.spi.CollectionKey;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityEntryFactory;
@ -200,6 +201,7 @@ import org.hibernate.tuple.NonIdentifierAttribute;
import org.hibernate.tuple.ValueGeneration;
import org.hibernate.tuple.entity.EntityMetamodel;
import org.hibernate.tuple.entity.EntityTuplizer;
import org.hibernate.type.AnyType;
import org.hibernate.type.AssociationType;
import org.hibernate.type.BasicType;
import org.hibernate.type.CollectionType;
@ -5200,12 +5202,12 @@ public abstract class AbstractEntityPersister
@Override
public Object getIdentifier(Object entity, SharedSessionContractImplementor session) {
return identifierMapping.getPropertyAccess().getGetter().get( entity );
return identifierMapping.getIdentifier( entity, session );
}
@Override
public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) {
identifierMapping.getPropertyAccess().getSetter().set( entity, id, factory );
identifierMapping.setIdentifier( entity, id, session );
}
@Override
@ -5220,9 +5222,8 @@ public abstract class AbstractEntityPersister
@Override
public Object instantiate(Object id, SharedSessionContractImplementor session) {
Object instance = getRepresentationStrategy().getInstantiator().instantiate( session );
identifierMapping.getPropertyAccess().getSetter().set( instance, id, factory );
final Object instance = getRepresentationStrategy().getInstantiator().instantiate( session.getFactory() );
setIdentifier( instance, id, session );
return instance;
}
@ -5701,7 +5702,7 @@ public abstract class AbstractEntityPersister
identifierMapping = creationProcess.processSubPart(
EntityIdentifierMapping.ROLE_LOCAL_NAME,
(role, creationProcess1) ->
generateIdentifierMapping( creationProcess, bootEntityDescriptor.getIdentifierProperty() )
generateIdentifierMapping( creationProcess, bootEntityDescriptor )
);
if ( getVersionType() == null ) {
@ -5908,16 +5909,23 @@ public abstract class AbstractEntityPersister
}
private EntityIdentifierMapping generateIdentifierMapping(MappingModelCreationProcess creationProcess, Property identifierProperty) {
private EntityIdentifierMapping generateIdentifierMapping(MappingModelCreationProcess creationProcess, PersistentClass bootEntityDescriptor) {
final Type idType = getIdentifierType();
if ( idType instanceof CompositeType ) {
final CompositeType cidType = (CompositeType) idType;
if ( ! cidType.isEmbedded() ) {
// NOTE: the term `isEmbedded` here uses Hibernate's older (pre-JPA) naming for its "non-aggregated"
// composite-id support. It unfortunately conflicts with the JPA usage of "embedded". Here we normalize
// the legacy naming to the more descriptive encapsulated versus non-encapsulated phrasing
final boolean encapsulated = ! cidType.isEmbedded();
if ( encapsulated ) {
// we have an `@EmbeddedId`
return MappingModelCreationHelper.buildEncapsulatedCompositeIdentifierMapping(
this,
identifierProperty,
identifierProperty.getName(),
bootEntityDescriptor.getIdentifierProperty(),
bootEntityDescriptor.getIdentifierProperty().getName(),
getRootTableName(),
rootTableKeyColumnNames,
cidType,
@ -5925,13 +5933,8 @@ public abstract class AbstractEntityPersister
);
}
return MappingModelCreationHelper.buildNonEncapsulatedCompositeIdentifierMapping(
this,
getRootTableName(),
rootTableKeyColumnNames,
cidType,
creationProcess
);
// otherwise we have a non-encapsulated composite-identifier
return generateNonEncapsulatedCompositeIdentifierMapping( creationProcess, bootEntityDescriptor, cidType );
}
return MappingModelCreationHelper.buildSimpleIdentifierMapping(
@ -5943,6 +5946,89 @@ public abstract class AbstractEntityPersister
);
}
private EntityIdentifierMapping generateNonEncapsulatedCompositeIdentifierMapping(
MappingModelCreationProcess creationProcess,
PersistentClass bootEntityDescriptor, CompositeType cidType) {
// process all of the defined "id attributes" because they are declared on the entity
final Component bootIdDescriptor = (Component) bootEntityDescriptor.getIdentifier();
final List<SingularAttributeMapping> idAttributeMappings = new ArrayList<>( bootIdDescriptor.getPropertySpan() );
int columnsConsumedSoFar = 0;
if ( attributeMappings == null ) {
attributeMappings = new ArrayList<>(
bootEntityDescriptor.getPropertyClosureSpan() + bootIdDescriptor.getPropertySpan()
);
final Iterator bootPropertyIterator = bootIdDescriptor.getPropertyIterator();
while ( bootPropertyIterator.hasNext() ) {
final Property bootIdProperty = (Property) bootPropertyIterator.next();
final Type idPropertyType = bootIdProperty.getType();
if ( idPropertyType instanceof AnyType ) {
throw new HibernateException(
"AnyType property `" + getEntityName() + "#" + bootIdProperty.getName() +
"` cannot be used as part of entity identifier "
);
}
if ( idPropertyType instanceof CollectionType ) {
throw new HibernateException(
"Plural property `" + getEntityName() + "#" + bootIdProperty.getName() +
"` cannot be used as part of entity identifier "
);
}
final SingularAttributeMapping idAttributeMapping;
if ( idPropertyType instanceof BasicType ) {
idAttributeMapping = MappingModelCreationHelper.buildBasicAttributeMapping(
bootIdProperty.getName(),
attributeMappings.size(),
bootIdProperty,
this,
(BasicType) idPropertyType,
getRootTableName(),
rootTableKeyColumnNames[columnsConsumedSoFar],
getRepresentationStrategy().resolvePropertyAccess( bootIdProperty ),
CascadeStyles.ALL,
creationProcess
);
columnsConsumedSoFar++;
}
else {
// final String[] unconsumedColumnNames = Arrays.copyOfRange(
// rootTableKeyColumnNames,
// columnsConsumedSoFar,
// rootTableKeyColumnNames.length
// );
if ( idPropertyType instanceof CompositeType ) {
// nested composite
throw new NotYetImplementedFor6Exception( getClass() );
}
else if ( idPropertyType instanceof EntityType ) {
// key-many-to-one
throw new NotYetImplementedFor6Exception( getClass() );
}
else {
throw new UnsupportedOperationException();
}
}
idAttributeMappings.add( idAttributeMapping );
}
}
attributeMappings.addAll( idAttributeMappings );
return MappingModelCreationHelper.buildNonEncapsulatedCompositeIdentifierMapping(
this,
idAttributeMappings,
cidType,
bootEntityDescriptor,
creationProcess
);
}
private static EntityVersionMapping generateVersionMapping(
EntityPersister entityPersister,
MappingModelCreationProcess creationProcess) {

View File

@ -25,6 +25,7 @@ import org.hibernate.metamodel.mapping.MappingModelExpressable;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.internal.CompositeSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.BinaryArithmeticOperator;
@ -905,7 +906,7 @@ public abstract class BaseSqmToSqlAstConverter
return (BasicValuedMapping) parameterSqmType;
}
if ( parameterSqmType instanceof EmbeddedSqmPathSource ) {
if ( parameterSqmType instanceof CompositeSqmPathSource ) {
throw new NotYetImplementedFor6Exception( "Support for embedded-valued parameters not yet implemented" );
}

View File

@ -15,13 +15,13 @@ import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.StateArrayContributorMapping;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.results.internal.NullValueAssembler;
import org.hibernate.sql.results.graph.AbstractFetchParentAccess;
import org.hibernate.sql.results.graph.AssemblerCreationState;
import org.hibernate.sql.results.graph.DomainResultAssembler;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParentAccess;
import org.hibernate.sql.results.graph.Initializer;
import org.hibernate.sql.results.internal.NullValueAssembler;
import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
/**
@ -119,7 +119,7 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
compositeInstance = embeddedModelPartDescriptor.getEmbeddableTypeDescriptor()
.getRepresentationStrategy()
.getInstantiator()
.instantiate( rowProcessingState.getSession() );
.instantiate( rowProcessingState.getSession().getFactory() );
EmbeddableLoadingLogger.INSTANCE.debugf(
"Created composite instance [%s] : %s",
navigablePath,

View File

@ -0,0 +1,187 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.metamodel.mapping.cid.aggregated;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* Simple tests that aggregated id mappings work at a basic level
*
* @author Steve Ebersole
*/
@ServiceRegistry
@DomainModel(
annotatedClasses = {
SmokeTests.Order.class,
SmokeTests.LineItem.class,
SmokeTests.LineItemId.class
}
)
@SessionFactory
public class SmokeTests {
@Test
public void simpleTest(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "select i from LineItem i" ).list();
}
);
}
@BeforeEach
public void createTestData(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final Order order = new Order( 1, "123-abc" );
session.persist( order );
session.persist( new LineItem( order, 1, "xyz", 500 ) );
session.persist( new LineItem( order, 2, "tuv", 60 ) );
session.persist( new LineItem( order, 3, "def", 350 ) );
}
);
}
@AfterEach
public void cleanUpTestData(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "delete LineItem" ).executeUpdate();
session.createQuery( "delete Order" ).executeUpdate();
}
);
}
@Entity( name = "Order" )
@Table( name = "orders" )
public static class Order {
private Integer id;
private String invoice;
public Order() {
}
public Order(Integer id, String invoice) {
this.id = id;
this.invoice = invoice;
}
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getInvoice() {
return invoice;
}
public void setInvoice(String invoice) {
this.invoice = invoice;
}
}
@Embeddable
public static class LineItemId implements Serializable {
private Order order;
private Integer lineNumber;
public LineItemId() {
}
public LineItemId(Order order, Integer lineNumber) {
this.order = order;
this.lineNumber = lineNumber;
}
@ManyToOne
@JoinColumn( name = "order_id" )
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
@Column( name = "line_number" )
public Integer getLineNumber() {
return lineNumber;
}
public void setLineNumber(Integer lineNumber) {
this.lineNumber = lineNumber;
}
}
@Entity( name = "LineItem" )
@Table( name = "line_items" )
public static class LineItem {
private LineItemId id;
private String sku;
private int quantity;
public LineItem() {
}
public LineItem(LineItemId id, String sku, int quantity) {
this.id = id;
this.sku = sku;
this.quantity = quantity;
}
public LineItem(Order order, int lineNumber, String sku, int quantity) {
this.id = new LineItemId( order, lineNumber );
this.sku = sku;
this.quantity = quantity;
}
@EmbeddedId
public LineItemId getId() {
return id;
}
public void setId(LineItemId id) {
this.id = id;
}
public String getSku() {
return sku;
}
public void setSku(String sku) {
this.sku = sku;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
}
}

View File

@ -0,0 +1,222 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.metamodel.mapping.cid.nonaggregated;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.transaction.TransactionUtil2;
import org.junit.jupiter.api.Test;
/**
* Simple tests that aggregated id mappings work at a basic level
*
* @author Steve Ebersole
*/
@SuppressWarnings("WeakerAccess")
public class SmokeTests {
@Test
@FailureExpected( reason = "See org.hibernate.metamodel.mapping.internal.NonAggregatedIdentifierMappingImpl#createDomainResult" )
public void simpleTest() {
final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build();
try {
final SessionFactoryImplementor sessionFactory = (SessionFactoryImplementor) new MetadataSources( ssr )
.addAnnotatedClass( SystemAccess.class )
.buildMetadata()
.buildSessionFactory();
TransactionUtil2.inTransaction(
sessionFactory,
session -> {
session.createQuery( "select a from SystemAccess a" ).list();
}
);
}
finally {
StandardServiceRegistryBuilder.destroy( ssr );
}
}
@Test
@FailureExpected( reason = "Support for non-aggregated composite-ids not yet fully implemented" )
public void keyManyToOneTest() {
final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build();
try {
final SessionFactoryImplementor sessionFactory = (SessionFactoryImplementor) new MetadataSources( ssr )
.addAnnotatedClass( Order.class )
.addAnnotatedClass( LineItem.class )
.buildMetadata()
.buildSessionFactory();
TransactionUtil2.inTransaction(
sessionFactory,
session -> {
session.createQuery( "select i from LineItem i" ).list();
}
);
}
finally {
StandardServiceRegistryBuilder.destroy( ssr );
}
}
// @BeforeEach
// public void createTestData(SessionFactoryScope scope) {
// scope.inTransaction(
// session -> {
// final Order order = new Order( 1, "123-abc" );
// session.persist( order );
//
// session.persist( new LineItem( order, 1, "xyz", 500 ) );
// session.persist( new LineItem( order, 2, "tuv", 60 ) );
// session.persist( new LineItem( order, 3, "def", 350 ) );
// }
// );
// }
//
// @AfterEach
// public void cleanUpTestData(SessionFactoryScope scope) {
// scope.inTransaction(
// session -> {
// session.createQuery( "delete LineItem" ).executeUpdate();
// session.createQuery( "delete Order" ).executeUpdate();
// }
// );
// }
@Entity( name = "SystemAccess" )
@Table( name = "`access`" )
public static class SystemAccess implements Serializable {
private String system;
private String userId;
private String accessCode;
@Id
public String getSystem() {
return system;
}
public void setSystem(String system) {
this.system = system;
}
@Id
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getAccessCode() {
return accessCode;
}
public void setAccessCode(String accessCode) {
this.accessCode = accessCode;
}
}
@Entity( name = "Order" )
@Table( name = "orders" )
public static class Order {
private Integer id;
private String invoice;
public Order() {
}
public Order(Integer id, String invoice) {
this.id = id;
this.invoice = invoice;
}
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getInvoice() {
return invoice;
}
public void setInvoice(String invoice) {
this.invoice = invoice;
}
}
@Entity( name = "LineItem" )
@Table( name = "line_items" )
public static class LineItem implements Serializable {
private Order order;
private Integer lineNumber;
private String sku;
private int quantity;
public LineItem() {
}
public LineItem(Order order, int lineNumber, String sku, int quantity) {
this.order = order;
this.lineNumber = lineNumber;
this.sku = sku;
this.quantity = quantity;
}
@Id
@ManyToOne
@JoinColumn( name = "order_id" )
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
@Id
@Column( name = "line_number" )
public Integer getLineNumber() {
return lineNumber;
}
public void setLineNumber(Integer lineNumber) {
this.lineNumber = lineNumber;
}
public String getSku() {
return sku;
}
public void setSku(String sku) {
this.sku = sku;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
}
}

View File

@ -0,0 +1,11 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
/**
* Testing support of composite identifier mappings
*/
package org.hibernate.orm.test.metamodel.mapping.cid;

View File

@ -15,10 +15,11 @@ import javax.persistence.AccessType;
import org.hibernate.cfg.Configuration;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.property.access.spi.GetterFieldImpl;
import org.hibernate.property.access.spi.GetterMethodImpl;
import org.hibernate.tuple.entity.EntityTuplizer;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.Assert;
@ -179,10 +180,9 @@ public class XmlAccessTest extends BaseUnitTestCase {
// uses the first getter of the tupelizer for the assertions
private void assertAccessType(SessionFactoryImplementor factory, Class<?> classUnderTest, AccessType accessType) {
final Getter idGetter = factory.getDomainModel().findEntityDescriptor( classUnderTest.getName() )
.getIdentifierMapping()
.getPropertyAccess()
.getGetter();
final EntityPersister entityDescriptor = factory.getDomainModel().findEntityDescriptor( classUnderTest.getName() );
final SingleAttributeIdentifierMapping identifierMapping = (SingleAttributeIdentifierMapping) entityDescriptor.getIdentifierMapping();
final Getter idGetter = identifierMapping.getPropertyAccess().getGetter();
if ( AccessType.FIELD.equals( accessType ) ) {
Assert.assertTrue(