- HHH-14409 : Internal format of natural-id values

- org.hibernate.loader.access
- `ModelPart.breakDownJdbcValues`
- build/keep natural-id loaders per entity (inheritance)
- changes to NotImplementedYetExtension
- tests
This commit is contained in:
Steve Ebersole 2021-01-27 10:17:40 -06:00
parent 7e34535cfe
commit 9849ea5a1f
154 changed files with 5381 additions and 3726 deletions

View File

@ -853,8 +853,8 @@ public interface Session extends SharedSessionContract, EntityManager, AutoClose
*
* @return load delegate for loading the specified entity type by natural id
*
* @throws HibernateException If the specified entityClass cannot be resolved as a mapped entity, or if the
* entity does not define a natural-id or if its natural-id is made up of multiple attributes.
* @throws HibernateException If the specified entityClass cannot be resolved as a mapped entity or if the
* entity does not define a natural-id
*/
<T> SimpleNaturalIdLoadAccess<T> bySimpleNaturalId(String entityName);
@ -866,8 +866,8 @@ public interface Session extends SharedSessionContract, EntityManager, AutoClose
*
* @return load delegate for loading the specified entity type by natural id
*
* @throws HibernateException If the specified entityClass cannot be resolved as a mapped entity, or if the
* entity does not define a natural-id or if its natural-id is made up of multiple attributes.
* @throws HibernateException If the specified entityClass cannot be resolved as a mapped entity or if the
* entity does not define a natural-id
*/
<T> SimpleNaturalIdLoadAccess<T> bySimpleNaturalId(Class<T> entityClass);

View File

@ -43,13 +43,14 @@ public class EntityUpdateAction extends EntityAction {
private final int[] dirtyFields;
private final boolean hasDirtyCollection;
private final Object rowId;
private Object nextVersion;
private Object cacheEntry;
private SoftLock lock;
private final NaturalIdMapping naturalIdMapping;
private final Object previousNaturalIdValues;
private Object nextVersion;
private Object cacheEntry;
private SoftLock lock;
/**
* Constructs an EntityUpdateAction
* @param id The entity identifier
@ -90,9 +91,11 @@ public class EntityUpdateAction extends EntityAction {
previousNaturalIdValues = null;
}
else {
this.previousNaturalIdValues = determinePreviousNaturalIdValues( persister, id, previousState, session );
this.previousNaturalIdValues = determinePreviousNaturalIdValues( persister, naturalIdMapping, id, previousState, session );
session.getPersistenceContextInternal().getNaturalIdHelper().manageLocalResolution(
id, state, persister,
id,
naturalIdMapping.extractNaturalIdValues( state, session ),
persister,
CachedNaturalIdValueSource.UPDATE
);
}
@ -100,11 +103,13 @@ public class EntityUpdateAction extends EntityAction {
private static Object determinePreviousNaturalIdValues(
EntityPersister persister,
Object id, Object[] previousState,
NaturalIdMapping naturalIdMapping,
Object id,
Object[] previousState,
SharedSessionContractImplementor session) {
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
if ( previousState != null ) {
return persistenceContext.getNaturalIdHelper().extractNaturalIdValues( previousState, persister );
return naturalIdMapping.extractNaturalIdValues( previousState, session );
}
return persistenceContext.getNaturalIdSnapshot( id, persister );
@ -222,7 +227,7 @@ public class EntityUpdateAction extends EntityAction {
session.getPersistenceContextInternal().getNaturalIdHelper().manageSharedResolution(
id,
naturalIdMapping.extractNaturalIdValues( state, session ),
naturalIdMapping.extractNaturalIdValues( previousState, session ),
previousNaturalIdValues,
persister,
CachedNaturalIdValueSource.UPDATE
);

View File

@ -14,11 +14,13 @@ import java.util.Set;
import org.hibernate.EntityMode;
import org.hibernate.boot.MappingException;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmCompositeIdType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmEntityDiscriminatorType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmGeneratorSpecificationType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmMultiTenancyType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmPolymorphismEnum;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmRootEntityType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmSimpleIdType;
import org.hibernate.boot.model.Caching;
import org.hibernate.boot.model.IdentifierGeneratorDefinition;
import org.hibernate.boot.model.naming.EntityNaming;
@ -73,11 +75,13 @@ public class EntityHierarchySourceImpl implements EntityHierarchySource {
}
private static IdentifierSource interpretIdentifierSource(RootEntitySourceImpl rootEntitySource) {
if ( rootEntitySource.jaxbEntityMapping().getId() == null
&& rootEntitySource.jaxbEntityMapping().getCompositeId() == null ) {
final JaxbHbmSimpleIdType simpleId = rootEntitySource.jaxbEntityMapping().getId();
final JaxbHbmCompositeIdType compositeId = rootEntitySource.jaxbEntityMapping().getCompositeId();
if ( simpleId == null && compositeId == null ) {
throw new MappingException(
String.format(
Locale.ENGLISH,
Locale.ROOT,
"Entity [%s] did not define an identifier",
rootEntitySource.getEntityNamingSource().getEntityName()
),
@ -85,15 +89,15 @@ public class EntityHierarchySourceImpl implements EntityHierarchySource {
);
}
if ( rootEntitySource.jaxbEntityMapping().getId() != null ) {
if ( simpleId != null ) {
return new IdentifierSourceSimpleImpl( rootEntitySource );
}
else {
// if we get here, we should have a composite identifier. Just need
// to determine if it is aggregated, or non-aggregated...
if ( rootEntitySource.jaxbEntityMapping().getCompositeId().isMapped() ) {
if ( StringHelper.isEmpty( rootEntitySource.jaxbEntityMapping().getCompositeId().getClazz() ) ) {
if ( compositeId.isMapped() ) {
if ( StringHelper.isEmpty( compositeId.getClazz() ) ) {
throw new MappingException(
"mapped composite identifier must name component class to use.",
rootEntitySource.origin()
@ -101,23 +105,17 @@ public class EntityHierarchySourceImpl implements EntityHierarchySource {
}
}
if ( StringHelper.isEmpty( rootEntitySource.jaxbEntityMapping().getCompositeId().getClazz() ) ) {
if ( StringHelper.isEmpty( rootEntitySource.jaxbEntityMapping().getCompositeId().getName() ) ) {
if ( StringHelper.isEmpty( compositeId.getName() ) ) {
if ( compositeId.isMapped() && StringHelper.isEmpty( compositeId.getClazz() ) ) {
throw new MappingException(
"dynamic composite-id must specify name",
"mapped composite identifier must name component class to use.",
rootEntitySource.origin()
);
}
// we have a non-aggregated id without an IdClass
return new IdentifierSourceNonAggregatedCompositeImpl( rootEntitySource );
}
else if ( rootEntitySource.jaxbEntityMapping().getCompositeId().isMapped() ) {
// we have a non-aggregated id with an IdClass
return new IdentifierSourceNonAggregatedCompositeImpl( rootEntitySource );
}
else {
if ( rootEntitySource.jaxbEntityMapping().getCompositeId().isMapped() ) {
if ( compositeId.isMapped() ) {
throw new MappingException(
"cannot combine mapped=\"true\" with specified name",
rootEntitySource.origin()

View File

@ -2004,8 +2004,11 @@ public class StatefulPersistenceContext implements PersistenceContext {
}
persister = locateProperPersister( persister );
final Object naturalIdValues = extractNaturalIdValues( naturalId, persister );
final Object previousNaturalIdValues = previousNaturalId == null ? null : extractNaturalIdValues( previousNaturalId, persister );
// final Object naturalIdValues = extractNaturalIdValues( naturalId, persister );
final Object naturalIdValues = naturalId;
// final Object previousNaturalIdValues = previousNaturalId == null ? null : extractNaturalIdValues( previousNaturalId, persister );
final Object previousNaturalIdValues = previousNaturalId;
managedSharedResolutions( persister, id, naturalIdValues, previousNaturalIdValues, source );
}
@ -2182,7 +2185,7 @@ public class StatefulPersistenceContext implements PersistenceContext {
}
@Override
public Object findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues) {
public Object findCachedNaturalIdResolution(EntityPersister persister, Object naturalIdValues) {
return getNaturalIdXrefDelegate().findResolution( locateProperPersister( persister ), naturalIdValues );
}

View File

@ -898,7 +898,7 @@ public interface PersistenceContext {
* {@link PersistenceContext.NaturalIdHelper#INVALID_NATURAL_ID_REFERENCE},
* or {@code null}.
*/
Object findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues);
Object findCachedNaturalIdResolution(EntityPersister persister, Object naturalIdValues);
/**
* Find all the locally cached primary key cross-reference entries for the given persister.

View File

@ -112,7 +112,7 @@ public class NaturalIdMultiLoadAccessStandard<T> implements NaturalIdMultiLoadAc
}
try {
return entityDescriptor.getNaturalIdMapping().getMultiNaturalIdLoader().multiLoad( ids, this, session );
return (List<T>) entityDescriptor.getMultiNaturalIdLoader().multiLoad( ids, this, session );
}
finally {
if ( graphSemantic != null ) {

View File

@ -19,12 +19,9 @@ import java.sql.NClob;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import javax.persistence.CacheRetrieveMode;
import javax.persistence.CacheStoreMode;
import javax.persistence.EntityGraph;
@ -112,10 +109,8 @@ import org.hibernate.event.spi.ResolveNaturalIdEventListener;
import org.hibernate.event.spi.SaveOrUpdateEvent;
import org.hibernate.event.spi.SaveOrUpdateEventListener;
import org.hibernate.graph.GraphSemantic;
import org.hibernate.graph.RootGraph;
import org.hibernate.graph.internal.RootGraphImpl;
import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.jpa.AvailableSettings;
import org.hibernate.jpa.QueryHints;
import org.hibernate.jpa.internal.util.CacheModeHelper;
@ -123,7 +118,10 @@ import org.hibernate.jpa.internal.util.ConfigurationHelper;
import org.hibernate.jpa.internal.util.FlushModeTypeHelper;
import org.hibernate.jpa.internal.util.LockModeTypeHelper;
import org.hibernate.jpa.internal.util.LockOptionsHelper;
import org.hibernate.loader.ast.spi.NaturalIdLoader;
import org.hibernate.loader.access.IdentifierLoadAccessImpl;
import org.hibernate.loader.access.LoadAccessContext;
import org.hibernate.loader.access.NaturalIdLoadAccessImpl;
import org.hibernate.loader.access.SimpleNaturalIdLoadAccessImpl;
import org.hibernate.metamodel.spi.MetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper;
@ -164,7 +162,7 @@ import static org.hibernate.cfg.AvailableSettings.JPA_SHARED_CACHE_STORE_MODE;
*/
public class SessionImpl
extends AbstractSessionImpl
implements SessionImplementor, EventSource {
implements SessionImplementor, LoadAccessContext, EventSource {
private static final EntityManagerMessageLogger log = HEMLogging.messageLogger( SessionImpl.class );
// Defaults to null which means the properties are the default - as defined in FastSessionServices#defaultSessionProperties
@ -566,13 +564,23 @@ public class SessionImpl
}
@Override
protected void delayedAfterCompletion() {
public void delayedAfterCompletion() {
if ( getTransactionCoordinator() instanceof JtaTransactionCoordinatorImpl ) {
( (JtaTransactionCoordinatorImpl) getTransactionCoordinator() ).getSynchronizationCallbackCoordinator()
.processAnyDelayedAfterCompletion();
}
}
@Override
public void pulseTransactionCoordinator() {
super.pulseTransactionCoordinator();
}
@Override
public void checkOpenOrWaitingForAutoClose() {
super.checkOpenOrWaitingForAutoClose();
}
// saveOrUpdate() operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
@ -1111,12 +1119,12 @@ public class SessionImpl
@Override
public <T> IdentifierLoadAccessImpl<T> byId(String entityName) {
return new IdentifierLoadAccessImpl<>( entityName );
return new IdentifierLoadAccessImpl<>( this, requireEntityPersister( entityName ) );
}
@Override
public <T> IdentifierLoadAccessImpl<T> byId(Class<T> entityClass) {
return new IdentifierLoadAccessImpl<>( entityClass );
return new IdentifierLoadAccessImpl<>( this, requireEntityPersister( entityClass ) );
}
@Override
@ -1131,22 +1139,22 @@ public class SessionImpl
@Override
public <T> NaturalIdLoadAccess<T> byNaturalId(String entityName) {
return new NaturalIdLoadAccessImpl<>( entityName );
return new NaturalIdLoadAccessImpl<>( this, requireEntityPersister( entityName ) );
}
@Override
public <T> NaturalIdLoadAccess<T> byNaturalId(Class<T> entityClass) {
return new NaturalIdLoadAccessImpl<>( entityClass );
return new NaturalIdLoadAccessImpl<>( this, requireEntityPersister( entityClass ) );
}
@Override
public <T> SimpleNaturalIdLoadAccess<T> bySimpleNaturalId(String entityName) {
return new SimpleNaturalIdLoadAccessImpl<>( entityName );
return new SimpleNaturalIdLoadAccessImpl<>( this, requireEntityPersister( entityName ) );
}
@Override
public <T> SimpleNaturalIdLoadAccess<T> bySimpleNaturalId(Class<T> entityClass) {
return new SimpleNaturalIdLoadAccessImpl<>( entityClass );
return new SimpleNaturalIdLoadAccessImpl<>( this, requireEntityPersister( entityClass ) );
}
@Override
@ -1159,7 +1167,8 @@ public class SessionImpl
return new NaturalIdMultiLoadAccessStandard<>( requireEntityPersister( entityName ), this );
}
private void fireLoad(LoadEvent event, LoadType loadType) {
@Override
public void fireLoad(LoadEvent event, LoadType loadType) {
checkOpenOrWaitingForAutoClose();
fireLoadNoChecks( event, loadType );
delayedAfterCompletion();
@ -2111,147 +2120,6 @@ public class SessionImpl
transactionCoordinator.removeObserver( transactionObserver );
}
private class IdentifierLoadAccessImpl<T> implements IdentifierLoadAccess<T> {
private final EntityPersister entityPersister;
private LockOptions lockOptions;
private CacheMode cacheMode;
private RootGraphImplementor<T> rootGraph;
private GraphSemantic graphSemantic;
private IdentifierLoadAccessImpl(EntityPersister entityPersister) {
this.entityPersister = entityPersister;
}
private IdentifierLoadAccessImpl(String entityName) {
this( requireEntityPersister( entityName ) );
}
private IdentifierLoadAccessImpl(Class<T> entityClass) {
this( requireEntityPersister( entityClass ) );
}
@Override
public final IdentifierLoadAccessImpl<T> with(LockOptions lockOptions) {
this.lockOptions = lockOptions;
return this;
}
@Override
public IdentifierLoadAccess<T> with(CacheMode cacheMode) {
this.cacheMode = cacheMode;
return this;
}
@Override
public IdentifierLoadAccess<T> with(RootGraph<T> graph, GraphSemantic semantic) {
this.rootGraph = (RootGraphImplementor<T>) graph;
this.graphSemantic = semantic;
return this;
}
@Override
public final T getReference(Object id) {
return perform( () -> doGetReference( id ) );
}
protected T perform(Supplier<T> executor) {
CacheMode sessionCacheMode = getCacheMode();
boolean cacheModeChanged = false;
if ( cacheMode != null ) {
// naive check for now...
// todo : account for "conceptually equal"
if ( cacheMode != sessionCacheMode ) {
setCacheMode( cacheMode );
cacheModeChanged = true;
}
}
try {
if ( graphSemantic != null ) {
if ( rootGraph == null ) {
throw new IllegalArgumentException( "Graph semantic specified, but no RootGraph was supplied" );
}
loadQueryInfluencers.getEffectiveEntityGraph().applyGraph( rootGraph, graphSemantic );
}
try {
return executor.get();
}
finally {
if ( graphSemantic != null ) {
loadQueryInfluencers.getEffectiveEntityGraph().clear();
}
}
}
finally {
if ( cacheModeChanged ) {
// change it back
setCacheMode( sessionCacheMode );
}
}
}
@SuppressWarnings("unchecked")
protected T doGetReference(Object id) {
if ( this.lockOptions != null ) {
LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, SessionImpl.this, getReadOnlyFromLoadQueryInfluencers() );
fireLoad( event, LoadEventListener.LOAD );
return (T) event.getResult();
}
LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), false, SessionImpl.this, getReadOnlyFromLoadQueryInfluencers() );
boolean success = false;
try {
fireLoad( event, LoadEventListener.LOAD );
if ( event.getResult() == null ) {
getFactory().getEntityNotFoundDelegate().handleEntityNotFound(
entityPersister.getEntityName(),
id
);
}
success = true;
return (T) event.getResult();
}
finally {
afterOperation( success );
}
}
@Override
public final T load(Object id) {
return perform( () -> doLoad( id ) );
}
@Override
public Optional<T> loadOptional(Object id) {
return Optional.ofNullable( perform( () -> doLoad( id ) ) );
}
@SuppressWarnings("unchecked")
protected final T doLoad(Object id) {
if ( this.lockOptions != null ) {
LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, SessionImpl.this, getReadOnlyFromLoadQueryInfluencers() );
fireLoad( event, LoadEventListener.GET );
return (T) event.getResult();
}
LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), false, SessionImpl.this, getReadOnlyFromLoadQueryInfluencers() );
boolean success = false;
try {
fireLoad( event, LoadEventListener.GET );
success = true;
}
catch (ObjectNotFoundException e) {
// if session cache contains proxy for non-existing object
}
finally {
afterOperation( success );
}
return (T) event.getResult();
}
}
private EntityPersister requireEntityPersister(Class entityClass) {
return getFactory().getMetamodel().locateEntityPersister( entityClass );
}
@ -2260,261 +2128,6 @@ public class SessionImpl
return getFactory().getMetamodel().locateEntityPersister( entityName );
}
private abstract class BaseNaturalIdLoadAccessImpl<T> {
private final EntityPersister entityPersister;
private LockOptions lockOptions;
private boolean synchronizationEnabled = true;
private BaseNaturalIdLoadAccessImpl(EntityPersister entityPersister) {
this.entityPersister = entityPersister;
if ( !entityPersister.hasNaturalIdentifier() ) {
throw new HibernateException(
String.format( "Entity [%s] did not define a natural id", entityPersister.getEntityName() )
);
}
}
public LockOptions getLockOptions() {
return lockOptions;
}
public boolean isSynchronizationEnabled() {
return synchronizationEnabled;
}
public BaseNaturalIdLoadAccessImpl<T> with(LockOptions lockOptions) {
this.lockOptions = lockOptions;
return this;
}
protected void synchronizationEnabled(boolean synchronizationEnabled) {
this.synchronizationEnabled = synchronizationEnabled;
}
protected final Object resolveNaturalId(Map<String, Object> naturalIdParameters) {
performAnyNeededCrossReferenceSynchronizations();
final Object resolvedId = entityPersister()
.getNaturalIdMapping()
.getNaturalIdLoader()
.resolveNaturalIdToId( naturalIdParameters, SessionImpl.this );
return resolvedId == PersistenceContext.NaturalIdHelper.INVALID_NATURAL_ID_REFERENCE
? null
: resolvedId;
}
protected void performAnyNeededCrossReferenceSynchronizations() {
if ( !synchronizationEnabled ) {
// synchronization (this process) was disabled
return;
}
if ( entityPersister.getEntityMetamodel().hasImmutableNaturalId() ) {
// only mutable natural-ids need this processing
return;
}
if ( !isTransactionInProgress() ) {
// not in a transaction so skip synchronization
return;
}
final PersistenceContext persistenceContext = getPersistenceContextInternal();
final boolean debugEnabled = log.isDebugEnabled();
for ( Object pk : persistenceContext.getNaturalIdHelper()
.getCachedPkResolutions( entityPersister ) ) {
final EntityKey entityKey = generateEntityKey( pk, entityPersister );
final Object entity = persistenceContext.getEntity( entityKey );
final EntityEntry entry = persistenceContext.getEntry( entity );
if ( entry == null ) {
if ( debugEnabled ) {
log.debug(
"Cached natural-id/pk resolution linked to null EntityEntry in persistence context : "
+ MessageHelper.infoString( entityPersister, pk, getFactory() )
);
}
continue;
}
if ( !entry.requiresDirtyCheck( entity ) ) {
continue;
}
// MANAGED is the only status we care about here...
if ( entry.getStatus() != Status.MANAGED ) {
continue;
}
persistenceContext.getNaturalIdHelper().handleSynchronization(
entityPersister,
pk,
entity
);
}
}
protected final IdentifierLoadAccess getIdentifierLoadAccess() {
final IdentifierLoadAccessImpl identifierLoadAccess = new IdentifierLoadAccessImpl( entityPersister );
if ( this.lockOptions != null ) {
identifierLoadAccess.with( lockOptions );
}
return identifierLoadAccess;
}
protected EntityPersister entityPersister() {
return entityPersister;
}
}
private class NaturalIdLoadAccessImpl<T> extends BaseNaturalIdLoadAccessImpl<T> implements NaturalIdLoadAccess<T> {
private final Map<String, Object> naturalIdParameters = new LinkedHashMap<>();
private NaturalIdLoadAccessImpl(EntityPersister entityPersister) {
super( entityPersister );
}
private NaturalIdLoadAccessImpl(String entityName) {
this( requireEntityPersister( entityName ) );
}
private NaturalIdLoadAccessImpl(Class entityClass) {
this( requireEntityPersister( entityClass ) );
}
@Override
public NaturalIdLoadAccessImpl<T> with(LockOptions lockOptions) {
return (NaturalIdLoadAccessImpl<T>) super.with( lockOptions );
}
@Override
public NaturalIdLoadAccess<T> using(String attributeName, Object value) {
naturalIdParameters.put( attributeName, value );
return this;
}
@Override
public NaturalIdLoadAccess<T> using(Object... mappings) {
CollectionHelper.collectMapEntries( naturalIdParameters::put, mappings );
return this;
}
@Override
public NaturalIdLoadAccessImpl<T> setSynchronizationEnabled(boolean synchronizationEnabled) {
super.synchronizationEnabled( synchronizationEnabled );
return this;
}
@Override
@SuppressWarnings("unchecked")
public final T getReference() {
final Object entityId = resolveNaturalId( this.naturalIdParameters );
if ( entityId == null ) {
return null;
}
return (T) this.getIdentifierLoadAccess().getReference( entityId );
}
@Override
@SuppressWarnings("unchecked")
public final T load() {
autoFlushIfRequired( (Set) CollectionHelper.setOf( entityPersister().getQuerySpaces() ) );
final Object entityId = resolveNaturalId( this.naturalIdParameters );
if ( entityId == null ) {
return null;
}
try {
return (T) this.getIdentifierLoadAccess().load( entityId );
}
catch (EntityNotFoundException | ObjectNotFoundException enf) {
// OK
}
return null;
}
@Override
public Optional<T> loadOptional() {
return Optional.ofNullable( load() );
}
}
private class SimpleNaturalIdLoadAccessImpl<T>
extends BaseNaturalIdLoadAccessImpl<T>
implements SimpleNaturalIdLoadAccess<T>, NaturalIdLoader.LoadOptions {
private final String naturalIdAttributeName;
private SimpleNaturalIdLoadAccessImpl(EntityPersister entityPersister) {
super( entityPersister );
if ( entityPersister.getNaturalIdentifierProperties().length != 1 ) {
throw new HibernateException(
String.format(
"Entity [%s] did not define a simple natural id",
entityPersister.getEntityName()
)
);
}
final int naturalIdAttributePosition = entityPersister.getNaturalIdentifierProperties()[0];
this.naturalIdAttributeName = entityPersister.getPropertyNames()[naturalIdAttributePosition];
}
private SimpleNaturalIdLoadAccessImpl(String entityName) {
this( requireEntityPersister( entityName ) );
}
@Override
public LockOptions getLockOptions() {
return super.getLockOptions();
}
@Override
public boolean isSynchronizationEnabled() {
return super.isSynchronizationEnabled();
}
private SimpleNaturalIdLoadAccessImpl(Class entityClass) {
this( requireEntityPersister( entityClass ) );
}
@Override
public final SimpleNaturalIdLoadAccessImpl<T> with(LockOptions lockOptions) {
return (SimpleNaturalIdLoadAccessImpl<T>) super.with( lockOptions );
}
private Map<String, Object> getNaturalIdParameters(Object naturalIdValue) {
return Collections.singletonMap( naturalIdAttributeName, naturalIdValue );
}
@Override
public SimpleNaturalIdLoadAccessImpl<T> setSynchronizationEnabled(boolean synchronizationEnabled) {
super.synchronizationEnabled( synchronizationEnabled );
return this;
}
@Override
@SuppressWarnings("unchecked")
public T getReference(Object naturalIdValue) {
final Object entityId = entityPersister().getNaturalIdLoader().resolveNaturalIdToId( naturalIdValue, SessionImpl.this );
if ( entityId == null ) {
return null;
}
return (T) this.getIdentifierLoadAccess().getReference( entityId );
}
@Override
public T load(Object naturalIdValue) {
//noinspection unchecked
autoFlushIfRequired( (Set) CollectionHelper.setOf( entityPersister().getQuerySpaces() ) );
return (T) entityPersister().getNaturalIdLoader().load( naturalIdValue, this, SessionImpl.this );
}
@Override
public Optional<T> loadOptional(Serializable naturalIdValue) {
return Optional.ofNullable( load( naturalIdValue ) );
}
}
@Override
public void startTransactionBoundary() {
checkOpenOrWaitingForAutoClose();

View File

@ -0,0 +1,21 @@
/*
* 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.loader;
import org.jboss.logging.Logger;
/**
* Logging for loaders
*/
public interface LoaderLogging {
String LOGGER_NAME = "org.hibernate.orm.loader";
Logger LOADER_LOGGER = Logger.getLogger( LOGGER_NAME );
boolean DEBUG_ENABLED = LOADER_LOGGER.isDebugEnabled();
boolean TRACE_ENABLED = LOADER_LOGGER.isTraceEnabled();
}

View File

@ -0,0 +1,224 @@
package org.hibernate.loader.access;
import java.util.Map;
import org.hibernate.HibernateException;
import org.hibernate.IdentifierLoadAccess;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.loader.LoaderLogging;
import org.hibernate.loader.ast.spi.NaturalIdLoadOptions;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.persister.entity.EntityPersister;
import static org.hibernate.engine.spi.PersistenceContext.NaturalIdHelper.INVALID_NATURAL_ID_REFERENCE;
/**
* @author Steve Ebersole
*/
public abstract class BaseNaturalIdLoadAccessImpl<T> implements NaturalIdLoadOptions {
private final LoadAccessContext context;
private final EntityMappingType entityDescriptor;
private LockOptions lockOptions;
private boolean synchronizationEnabled = true;
protected BaseNaturalIdLoadAccessImpl(LoadAccessContext context, EntityMappingType entityDescriptor) {
this.context = context;
this.entityDescriptor = entityDescriptor;
if ( entityDescriptor.getNaturalIdMapping() == null ) {
throw new HibernateException(
String.format( "Entity [%s] did not define a natural id", entityDescriptor.getEntityName() )
);
}
}
public LockOptions getLockOptions() {
return lockOptions;
}
public boolean isSynchronizationEnabled() {
return synchronizationEnabled;
}
public BaseNaturalIdLoadAccessImpl<T> with(LockOptions lockOptions) {
this.lockOptions = lockOptions;
return this;
}
protected void synchronizationEnabled(boolean synchronizationEnabled) {
this.synchronizationEnabled = synchronizationEnabled;
}
protected final Object resolveNaturalId(Map<String, Object> naturalIdParameters) {
performAnyNeededCrossReferenceSynchronizations();
final Object resolvedId = entityPersister()
.getNaturalIdLoader()
.resolveNaturalIdToId( naturalIdParameters, context.getSession() );
return resolvedId == INVALID_NATURAL_ID_REFERENCE
? null
: resolvedId;
}
protected void performAnyNeededCrossReferenceSynchronizations() {
if ( !synchronizationEnabled ) {
// synchronization (this process) was disabled
return;
}
final NaturalIdMapping naturalIdMapping = entityDescriptor.getNaturalIdMapping();
if ( !naturalIdMapping.isMutable() ) {
// only mutable natural-ids need this processing
return;
}
final SessionImplementor session = context.getSession();
if ( ! session.isTransactionInProgress() ) {
// not in a transaction so skip synchronization
return;
}
final PersistenceContext persistenceContext = context.getSession().getPersistenceContextInternal();
for ( Object pk : persistenceContext.getNaturalIdHelper().getCachedPkResolutions( entityPersister() ) ) {
final EntityKey entityKey = context.getSession().generateEntityKey( pk, entityPersister() );
final Object entity = persistenceContext.getEntity( entityKey );
final EntityEntry entry = persistenceContext.getEntry( entity );
if ( entry == null ) {
if ( LoaderLogging.DEBUG_ENABLED ) {
LoaderLogging.LOADER_LOGGER.debugf(
"Cached natural-id/pk resolution linked to null EntityEntry in persistence context : %s#%s",
entityDescriptor.getEntityName(),
pk
);
}
continue;
}
if ( !entry.requiresDirtyCheck( entity ) ) {
continue;
}
// MANAGED is the only status we care about here...
if ( entry.getStatus() != Status.MANAGED ) {
continue;
}
persistenceContext.getNaturalIdHelper().handleSynchronization(
entityPersister(),
pk,
entity
);
}
}
@SuppressWarnings( "unchecked" )
protected final T doGetReference(Object normalizedNaturalIdValue) {
performAnyNeededCrossReferenceSynchronizations();
context.checkOpenOrWaitingForAutoClose();
context.pulseTransactionCoordinator();
final SessionImplementor session = context.getSession();
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
final Object cachedResolution = persistenceContext.getNaturalIdHelper().findCachedNaturalIdResolution(
entityPersister(),
normalizedNaturalIdValue
);
if ( cachedResolution == INVALID_NATURAL_ID_REFERENCE ) {
// the entity is deleted, although not yet flushed - return null
return null;
}
if ( cachedResolution != null ) {
return (T) getIdentifierLoadAccess().getReference( cachedResolution );
}
LoaderLogging.LOADER_LOGGER.debugf(
"Selecting entity identifier by natural-id for `#getReference` handling - %s : %s",
entityPersister().getEntityName(),
normalizedNaturalIdValue
);
final Object idFromDatabase = entityPersister().getNaturalIdLoader().resolveNaturalIdToId( normalizedNaturalIdValue, session );
if ( idFromDatabase != null ) {
return (T) getIdentifierLoadAccess().getReference( idFromDatabase );
}
return null;
}
protected final T doLoad(Object normalizedNaturalIdValue) {
performAnyNeededCrossReferenceSynchronizations();
context.checkOpenOrWaitingForAutoClose();
context.pulseTransactionCoordinator();
final SessionImplementor session = context.getSession();
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
final Object cachedResolution = persistenceContext.getNaturalIdHelper().findCachedNaturalIdResolution(
entityPersister(),
normalizedNaturalIdValue
);
if ( cachedResolution == INVALID_NATURAL_ID_REFERENCE ) {
return null;
}
try {
final T loaded;
if ( cachedResolution != null ) {
loaded = (T) getIdentifierLoadAccess().load( cachedResolution );
} else {
loaded = (T) entityPersister().getNaturalIdLoader().load( normalizedNaturalIdValue, this, session );
}
if ( loaded != null ) {
final EntityEntry entry = persistenceContext.getEntry( loaded );
assert entry != null;
if ( entry.getStatus() == Status.DELETED ) {
return null;
}
}
return loaded;
}
finally {
context.delayedAfterCompletion();
}
}
protected final IdentifierLoadAccess<?> getIdentifierLoadAccess() {
final IdentifierLoadAccessImpl<?> identifierLoadAccess = new IdentifierLoadAccessImpl<>( context, entityPersister() );
if ( this.lockOptions != null ) {
identifierLoadAccess.with( lockOptions );
}
return identifierLoadAccess;
}
protected LoadAccessContext getContext() {
return context;
}
public EntityMappingType getEntityDescriptor() {
return entityDescriptor;
}
protected EntityPersister entityPersister() {
return entityDescriptor.getEntityPersister();
}
}

View File

@ -0,0 +1,166 @@
package org.hibernate.loader.access;
import java.util.Optional;
import java.util.function.Supplier;
import org.hibernate.CacheMode;
import org.hibernate.IdentifierLoadAccess;
import org.hibernate.LockOptions;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.LoadEvent;
import org.hibernate.event.spi.LoadEventListener;
import org.hibernate.graph.GraphSemantic;
import org.hibernate.graph.RootGraph;
import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.persister.entity.EntityPersister;
/**
* @author Steve Ebersole
*/
public class IdentifierLoadAccessImpl<T> implements IdentifierLoadAccess<T> {
private final LoadAccessContext context;
private final EntityPersister entityPersister;
private LockOptions lockOptions;
private CacheMode cacheMode;
private RootGraphImplementor<T> rootGraph;
private GraphSemantic graphSemantic;
public IdentifierLoadAccessImpl(LoadAccessContext context, EntityPersister entityPersister) {
this.context = context;
this.entityPersister = entityPersister;
}
@Override
public final IdentifierLoadAccessImpl<T> with(LockOptions lockOptions) {
this.lockOptions = lockOptions;
return this;
}
@Override
public IdentifierLoadAccess<T> with(CacheMode cacheMode) {
this.cacheMode = cacheMode;
return this;
}
@Override
public IdentifierLoadAccess<T> with(RootGraph<T> graph, GraphSemantic semantic) {
this.rootGraph = (RootGraphImplementor<T>) graph;
this.graphSemantic = semantic;
return this;
}
@Override
public final T getReference(Object id) {
return perform( () -> doGetReference( id ) );
}
protected T perform(Supplier<T> executor) {
final SessionImplementor session = context.getSession();
CacheMode sessionCacheMode = session.getCacheMode();
boolean cacheModeChanged = false;
if ( cacheMode != null ) {
// naive check for now...
// todo : account for "conceptually equal"
if ( cacheMode != sessionCacheMode ) {
session.setCacheMode( cacheMode );
cacheModeChanged = true;
}
}
try {
if ( graphSemantic != null ) {
if ( rootGraph == null ) {
throw new IllegalArgumentException( "Graph semantic specified, but no RootGraph was supplied" );
}
session.getLoadQueryInfluencers().getEffectiveEntityGraph().applyGraph( rootGraph, graphSemantic );
}
try {
return executor.get();
}
finally {
if ( graphSemantic != null ) {
session.getLoadQueryInfluencers().getEffectiveEntityGraph().clear();
}
}
}
finally {
if ( cacheModeChanged ) {
// change it back
session.setCacheMode( sessionCacheMode );
}
}
}
@SuppressWarnings( "unchecked" )
protected T doGetReference(Object id) {
final SessionImplementor session = context.getSession();
final EventSource eventSource = (EventSource) session;
final LoadQueryInfluencers loadQueryInfluencers = session.getLoadQueryInfluencers();
if ( this.lockOptions != null ) {
LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, eventSource, loadQueryInfluencers.getReadOnly() );
context.fireLoad( event, LoadEventListener.LOAD );
return (T) event.getResult();
}
LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), false, eventSource, loadQueryInfluencers.getReadOnly() );
boolean success = false;
try {
context.fireLoad( event, LoadEventListener.LOAD );
if ( event.getResult() == null ) {
session.getFactory().getEntityNotFoundDelegate().handleEntityNotFound(
entityPersister.getEntityName(),
id
);
}
success = true;
return (T) event.getResult();
}
finally {
context.afterOperation( success );
}
}
@Override
public final T load(Object id) {
return perform( () -> doLoad( id ) );
}
@Override
public Optional<T> loadOptional(Object id) {
return Optional.ofNullable( perform( () -> doLoad( id ) ) );
}
@SuppressWarnings( "unchecked" )
protected final T doLoad(Object id) {
final SessionImplementor session = context.getSession();
final EventSource eventSource = (EventSource) session;
final LoadQueryInfluencers loadQueryInfluencers = session.getLoadQueryInfluencers();
if ( this.lockOptions != null ) {
LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, eventSource, loadQueryInfluencers.getReadOnly() );
context.fireLoad( event, LoadEventListener.GET );
return (T) event.getResult();
}
LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), false, eventSource, loadQueryInfluencers.getReadOnly() );
boolean success = false;
try {
context.fireLoad( event, LoadEventListener.GET );
success = true;
}
catch (ObjectNotFoundException e) {
// if session cache contains proxy for non-existing object
}
finally {
context.afterOperation( success );
}
return (T) event.getResult();
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.loader.access;
import org.hibernate.Incubating;
import org.hibernate.Internal;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.event.spi.LoadEvent;
import org.hibernate.event.spi.LoadEventListener;
/**
* Context for loader-access objects. Generally this is equivalent
* to the Session
*/
@Incubating
@Internal
public interface LoadAccessContext {
/**
* The session from which the load originates
*/
SessionImplementor getSession();
/**
* Callback to check whether the session is "active"
*/
void checkOpenOrWaitingForAutoClose();
/**
* Callback to pulse the transaction coo
*/
void pulseTransactionCoordinator();
void delayedAfterCompletion();
void afterOperation(boolean success);
void fireLoad(LoadEvent event, LoadEventListener.LoadType load);
}

View File

@ -0,0 +1,66 @@
package org.hibernate.loader.access;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import org.hibernate.LockOptions;
import org.hibernate.NaturalIdLoadAccess;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.EntityMappingType;
/**
* @author Steve Ebersole
*/
public class NaturalIdLoadAccessImpl<T> extends BaseNaturalIdLoadAccessImpl<T> implements NaturalIdLoadAccess<T> {
private final Map<String, Object> naturalIdParameters = new LinkedHashMap<>();
public NaturalIdLoadAccessImpl(LoadAccessContext context, EntityMappingType entityDescriptor) {
super( context, entityDescriptor );
}
@Override
public NaturalIdLoadAccessImpl<T> with(LockOptions lockOptions) {
return (NaturalIdLoadAccessImpl<T>) super.with( lockOptions );
}
@Override
public NaturalIdLoadAccess<T> using(String attributeName, Object value) {
naturalIdParameters.put( attributeName, value );
return this;
}
@Override
public NaturalIdLoadAccess<T> using(Object... mappings) {
CollectionHelper.collectMapEntries( naturalIdParameters::put, mappings );
return this;
}
@Override
public NaturalIdLoadAccessImpl<T> setSynchronizationEnabled(boolean synchronizationEnabled) {
super.synchronizationEnabled( synchronizationEnabled );
return this;
}
@Override
public final T getReference() {
final SessionImplementor session = getContext().getSession();
final Object normalizedValue = entityPersister().getNaturalIdMapping().normalizeInput( naturalIdParameters, session );
return doGetReference( normalizedValue );
}
@Override
public final T load() {
final SessionImplementor session = getContext().getSession();
final Object normalizedValue = entityPersister().getNaturalIdMapping().normalizeInput( naturalIdParameters, session );
return doLoad( normalizedValue );
}
@Override
public Optional<T> loadOptional() {
return Optional.ofNullable( load() );
}
}

View File

@ -0,0 +1,115 @@
package org.hibernate.loader.access;
import java.io.Serializable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import org.hibernate.HibernateException;
import org.hibernate.LockOptions;
import org.hibernate.SimpleNaturalIdLoadAccess;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.loader.LoaderLogging;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.internal.SimpleNaturalIdMapping;
/**
* @author Steve Ebersole
*/
public class SimpleNaturalIdLoadAccessImpl<T>
extends BaseNaturalIdLoadAccessImpl<T>
implements SimpleNaturalIdLoadAccess<T> {
private final boolean hasSimpleNaturalId;
public SimpleNaturalIdLoadAccessImpl(LoadAccessContext context, EntityMappingType entityDescriptor) {
super( context, entityDescriptor );
hasSimpleNaturalId = entityDescriptor.getNaturalIdMapping() instanceof SimpleNaturalIdMapping;
if ( !hasSimpleNaturalId ) {
// just log it - we allow this for composite natural-ids with the assumption
// that a singular representation of the natural-id (Map or array) will be passed
LoaderLogging.LOADER_LOGGER.debugf(
"Entity [%s] did not define a simple natural id",
entityDescriptor.getEntityName()
);
}
}
@Override
public LockOptions getLockOptions() {
return super.getLockOptions();
}
@Override
public boolean isSynchronizationEnabled() {
return super.isSynchronizationEnabled();
}
@Override
public final SimpleNaturalIdLoadAccessImpl<T> with(LockOptions lockOptions) {
return (SimpleNaturalIdLoadAccessImpl<T>) super.with( lockOptions );
}
@Override
public SimpleNaturalIdLoadAccessImpl<T> setSynchronizationEnabled(boolean synchronizationEnabled) {
super.synchronizationEnabled( synchronizationEnabled );
return this;
}
@Override
public T getReference(Object naturalIdValue) {
verifySimplicity( naturalIdValue );
final SessionImplementor session = getContext().getSession();
final Object normalizedNaturalIdValue = entityPersister().getNaturalIdMapping().normalizeInput( naturalIdValue, session );
return doGetReference( normalizedNaturalIdValue );
}
@Override
public T load(Object naturalIdValue) {
verifySimplicity( naturalIdValue );
final SessionImplementor session = getContext().getSession();
final Object normalizedNaturalIdValue = entityPersister().getNaturalIdMapping().normalizeInput( naturalIdValue, session );
return doLoad( normalizedNaturalIdValue );
}
private void verifySimplicity(Object naturalIdValue) {
assert naturalIdValue != null;
if ( hasSimpleNaturalId ) {
// implicitly
return;
}
if ( naturalIdValue.getClass().isArray() ) {
// we allow compound natural-id "simple" loading all the values are passed as an array
// (we assume the array is properly ordered following the mapping-model attribute ordering)
return;
}
if ( naturalIdValue instanceof List || naturalIdValue instanceof Map ) {
// also allowed. For Lists, just like arrays, we assume the user has ordered them properly;
// for Maps, the key is expected to be the attribute name
return;
}
throw new HibernateException(
String.format(
Locale.ROOT,
"Cannot interpret natural-id value [%s] for compound natural-id: %s",
naturalIdValue,
entityPersister().getEntityName()
)
);
}
@Override
public Optional<T> loadOptional(Serializable naturalIdValue) {
return Optional.ofNullable( load( naturalIdValue ) );
}
}

View File

@ -8,34 +8,52 @@ package org.hibernate.loader.ast.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.NaturalIdPostLoadListener;
import org.hibernate.loader.NaturalIdPreLoadListener;
import org.hibernate.loader.ast.spi.Loadable;
import org.hibernate.loader.ast.spi.NaturalIdLoadOptions;
import org.hibernate.loader.ast.spi.NaturalIdLoader;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.mapping.SelectionMapping;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.SimpleFromClauseAccessImpl;
import org.hibernate.sql.ast.spi.SqlAliasBaseManager;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl;
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
import org.hibernate.sql.exec.spi.Callback;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcParameterBinding;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.JdbcSelect;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.stat.spi.StatisticsImplementor;
/**
* Base support for NaturalIdLoader implementations
@ -48,18 +66,10 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
private final NaturalIdMapping naturalIdMapping;
private final EntityMappingType entityDescriptor;
private final NaturalIdPreLoadListener preLoadListener;
private final NaturalIdPostLoadListener postLoadListener;
public AbstractNaturalIdLoader(
NaturalIdMapping naturalIdMapping,
NaturalIdPreLoadListener preLoadListener,
NaturalIdPostLoadListener postLoadListener,
EntityMappingType entityDescriptor,
MappingModelCreationProcess creationProcess) {
EntityMappingType entityDescriptor) {
this.naturalIdMapping = naturalIdMapping;
this.preLoadListener = preLoadListener;
this.postLoadListener = postLoadListener;
this.entityDescriptor = entityDescriptor;
}
@ -77,35 +87,132 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
}
@Override
public T load(Object naturalIdValue, LoadOptions options, SharedSessionContractImplementor session) {
final Object bindValue = resolveNaturalIdBindValue( naturalIdValue, session );
preLoadListener.startingLoadByNaturalId( entityDescriptor, bindValue, session );
public T load(Object naturalIdValue, NaturalIdLoadOptions options, SharedSessionContractImplementor session) {
return selectByNaturalId(
naturalIdMapping().normalizeInput( naturalIdValue, session ),
options,
(tableGroup, creationState) -> entityDescriptor.createDomainResult(
new NavigablePath( entityDescriptor().getRootPathName() ),
tableGroup,
null,
creationState
),
(fetchParent, querySpec, creationState) -> {
final List<Fetch> fetches = new ArrayList<>( naturalIdMapping.getNaturalIdAttributes().size() );
fetchParent.getReferencedMappingContainer().visitFetchables(
fetchable -> {
final NavigablePath navigablePath = fetchParent.getNavigablePath().append( fetchable.getFetchableName() );
final Fetch fetch = fetchable.generateFetch(
fetchParent,
navigablePath,
fetchable.getMappedFetchOptions().getTiming(),
true,
options.getLockOptions() != null ? options.getLockOptions().getLockMode() : LockMode.READ,
null,
creationState
);
fetches.add( fetch );
},
entityDescriptor
);
return fetches;
},
(statsEnabled) -> {
// entityDescriptor().getPreLoadListener().startingLoad( entityDescriptor, naturalIdValue, KeyType.NATURAL_ID, LoadSource.DATABASE );
// return statsEnabled ? System.nanoTime() : -1;
return -1L;
},
(result,startToken) -> {
// entityDescriptor().getPostLoadListener().completedLoad( result, entityDescriptor(), naturalIdValue, KeyType.NATURAL_ID, LoadSource.DATABASE );
// if ( startToken > 0 ) {
// // todo (6.0) : need a "load-by-natural-id" stat
// // e.g.,
// // final Object identifier = entityDescriptor().getIdentifierMapping().getIdentifier( result, session );
// // session.getFactory().getStatistics().entityLoadedByNaturalId( entityDescriptor(), identifier );
// }
},
session
);
}
/**
* Perform a select, restricted by natural-id, based on `domainResultProducer` and `fetchProcessor`
*/
protected <L> L selectByNaturalId(
Object bindValue,
NaturalIdLoadOptions options,
BiFunction<TableGroup,LoaderSqlAstCreationState, DomainResult<?>> domainResultProducer,
LoaderSqlAstCreationState.FetchProcessor fetchProcessor,
Function<Boolean,Long> statementStartHandler,
BiConsumer<Object,Long> statementCompletionHandler,
SharedSessionContractImplementor session) {
final SessionFactoryImplementor sessionFactory = session.getFactory();
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
final List<JdbcParameter> jdbcParameters = new ArrayList<>();
final SelectStatement sqlSelect = LoaderSelectBuilder.createSelect(
entityDescriptor(),
Collections.emptyList(),
naturalIdMapping(),
null,
1,
session.getLoadQueryInfluencers(),
options.getLockOptions(),
jdbcParameters::add,
final LockOptions lockOptions;
if ( options.getLockOptions() != null ) {
lockOptions = options.getLockOptions();
}
else {
lockOptions = LockOptions.READ;
}
final LockMode lockMode = lockOptions.getLockMode();
final NavigablePath entityPath = new NavigablePath( entityDescriptor.getRootPathName() );
final QuerySpec rootQuerySpec = new QuerySpec( true );
final LoaderSqlAstCreationState sqlAstCreationState = new LoaderSqlAstCreationState(
rootQuerySpec,
new SqlAliasBaseManager(),
new SimpleFromClauseAccessImpl(),
lockOptions,
fetchProcessor,
true,
sessionFactory
);
final TableGroup rootTableGroup = entityDescriptor.createRootTableGroup(
entityPath,
null,
true,
lockMode,
() -> rootQuerySpec::applyPredicate,
sqlAstCreationState,
sessionFactory
);
rootQuerySpec.getFromClause().addRoot( rootTableGroup );
sqlAstCreationState.getFromClauseAccess().registerTableGroup( entityPath, rootTableGroup );
final DomainResult<?> domainResult = domainResultProducer.apply( rootTableGroup, sqlAstCreationState );
final SelectStatement sqlSelect = new SelectStatement( rootQuerySpec, Collections.singletonList( domainResult ) );
final List<JdbcParameter> jdbcParameters = new ArrayList<>( naturalIdMapping.getJdbcTypeCount() );
final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() );
applyNaturalIdAsJdbcParameters( bindValue, jdbcParameters, jdbcParamBindings, session );
applyNaturalIdRestriction(
bindValue,
rootTableGroup,
rootQuerySpec::applyPredicate,
(jdbcParameter, jdbcParameterBinding) -> {
jdbcParameters.add( jdbcParameter );
jdbcParamBindings.addBinding( jdbcParameter, jdbcParameterBinding );
},
sqlAstCreationState,
session
);
final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlSelect )
.translate( jdbcParamBindings, QueryOptions.NONE );
final StatisticsImplementor statistics = sessionFactory.getStatistics();
final Long startToken = statementStartHandler.apply( statistics.isStatisticsEnabled() );
//noinspection unchecked
final List<T> results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
final List<L> results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
jdbcSelect,
jdbcParamBindings,
new ExecutionContext() {
@ -126,11 +233,10 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
@Override
public Callback getCallback() {
return afterLoadAction -> {
};
return afterLoadAction -> {};
}
},
row -> (T) row[0],
row -> (L) row[0],
true
);
@ -143,91 +249,112 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
);
}
final T result = results.get( 0 );
postLoadListener.completedLoadByNaturalId( entityDescriptor, result, session );
if ( results.isEmpty() ) {
return null;
}
final L result = results.get( 0 );
statementCompletionHandler.accept( result, startToken );
return result;
}
protected abstract Object resolveNaturalIdBindValue(Object naturalIdToLoad, SharedSessionContractImplementor session);
/**
* Apply restriction necessary to match the given natural-id value. Should
* apply any predicates to `predicateConsumer` as well and any parameter / binding
* pairs
*/
protected abstract void applyNaturalIdRestriction(
Object bindValue,
TableGroup rootTableGroup,
Consumer<Predicate> predicateConsumer,
BiConsumer<JdbcParameter, JdbcParameterBinding> jdbcParameterConsumer,
LoaderSqlAstCreationState sqlAstCreationState,
SharedSessionContractImplementor session);
protected abstract void applyNaturalIdAsJdbcParameters(
Object naturalIdToLoad,
List<JdbcParameter> jdbcParameters,
JdbcParameterBindings jdbcParamBindings, SharedSessionContractImplementor session);
@Override
public Object resolveNaturalIdToId(Object naturalIdValue, SharedSessionContractImplementor session) {
final Object bindValue = resolveNaturalIdBindValue( naturalIdValue, session );
final SessionFactoryImplementor sessionFactory = session.getFactory();
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
final List<JdbcParameter> jdbcParameters = new ArrayList<>();
final SelectStatement sqlSelect = LoaderSelectBuilder.createSelect(
entityDescriptor(),
Collections.singletonList( entityDescriptor().getIdentifierMapping() ),
naturalIdMapping(),
null,
1,
session.getLoadQueryInfluencers(),
LockOptions.READ,
jdbcParameters::add,
sessionFactory
);
final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() );
applyNaturalIdAsJdbcParameters(
bindValue,
jdbcParameters,
jdbcParamBindings,
session
);
final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlSelect )
.translate( jdbcParamBindings, QueryOptions.NONE );
final List<?> results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
jdbcSelect,
jdbcParamBindings,
new ExecutionContext() {
@Override
public SharedSessionContractImplementor getSession() {
return session;
}
@Override
public QueryOptions getQueryOptions() {
return QueryOptions.NONE;
}
@Override
public QueryParameterBindings getQueryParameterBindings() {
return QueryParameterBindings.NO_PARAM_BINDINGS;
}
@Override
public Callback getCallback() {
return afterLoadAction -> {
};
}
},
row -> row[0],
true
);
if ( results.size() > 1 ) {
throw new HibernateException(
/**
* Helper to resolve ColumnReferences
*/
protected Expression resolveColumnReference(
TableGroup rootTableGroup,
SelectionMapping selectionMapping,
SqlExpressionResolver sqlExpressionResolver,
SessionFactoryImplementor sessionFactory) {
final TableReference tableReference = rootTableGroup.getTableReference( selectionMapping.getContainingTableExpression() );
if ( tableReference == null ) {
throw new IllegalStateException(
String.format(
"Resolving natural-id to id returned more that one row : %s [%s]",
entityDescriptor().getEntityName(),
bindValue
Locale.ROOT,
"Unable to locate TableReference for `%s` : %s",
selectionMapping.getContainingTableExpression(),
rootTableGroup
)
);
}
return sqlExpressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( tableReference, selectionMapping.getSelectionExpression() ),
(processingState) -> new ColumnReference(
tableReference,
selectionMapping.getSelectionExpression(),
selectionMapping.isFormula(),
selectionMapping.getCustomReadExpression(),
selectionMapping.getCustomWriteExpression(),
selectionMapping.getJdbcMapping(),
sessionFactory
)
);
}
return results.get( 0 );
@Override
public Object resolveNaturalIdToId(Object naturalIdValue, SharedSessionContractImplementor session) {
return selectByNaturalId(
naturalIdMapping().normalizeInput( naturalIdValue, session ),
NaturalIdLoadOptions.NONE,
(tableGroup, creationState) -> entityDescriptor.getIdentifierMapping().createDomainResult(
tableGroup.getNavigablePath().append( EntityIdentifierMapping.ROLE_LOCAL_NAME ),
tableGroup,
null,
creationState
),
(fetchParent, querySpec, creationState) -> {
final List<Fetch> fetches = new ArrayList<>();
fetchParent.getReferencedMappingContainer().visitFetchables(
(fetchable) -> {
final NavigablePath navigablePath = fetchParent.getNavigablePath().append( fetchable.getFetchableName() );
final Fetch fetch = fetchable.generateFetch(
fetchParent,
navigablePath,
fetchable.getMappedFetchOptions().getTiming(),
true,
LockMode.READ,
null,
creationState
);
fetches.add( fetch );
},
null
);
return fetches;
},
(statsEnabled) -> {
// entityDescriptor().getPreLoadListener().startingLoad( entityDescriptor, naturalIdValue, KeyType.NATURAL_ID, LoadSource.DATABASE );
// return statsEnabled ? System.nanoTime() : -1;
return -1L;
},
(result,startToken) -> {
// entityDescriptor().getPostLoadListener().completedLoad( result, entityDescriptor(), naturalIdValue, KeyType.NATURAL_ID, LoadSource.DATABASE );
// if ( startToken > 0 ) {
// // todo (6.0) : need a "load-by-natural-id" stat
// // e.g.,
// // final Object identifier = entityDescriptor().getIdentifierMapping().getIdentifier( result, session );
// // session.getFactory().getStatistics().entityLoadedByNaturalId( entityDescriptor(), identifier );
// }
},
session
);
}
@Override
@ -237,7 +364,7 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
final List<JdbcParameter> jdbcParameters = new ArrayList<>();
final SelectStatement sqlSelect = LoaderSelectBuilder.createSelect(
entityDescriptor(),
naturalIdMapping().getNaturalIdAttributes(),
Collections.singletonList( naturalIdMapping() ),
entityDescriptor().getIdentifierMapping(),
null,
1,
@ -263,7 +390,7 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlSelect )
.translate( jdbcParamBindings, QueryOptions.NONE );
final List<Object[]> results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
final List<Object> results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
jdbcSelect,
jdbcParamBindings,
new ExecutionContext() {
@ -288,10 +415,18 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
};
}
},
row -> row,
(row) -> {
// because we select the natural-id we want to "reduce" the result
assert row.length == 1;
return row[0];
},
true
);
if ( results.isEmpty() ) {
return null;
}
if ( results.size() > 1 ) {
throw new HibernateException(
String.format(
@ -302,13 +437,6 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
);
}
final Object[] objects = results.get( 0 );
if ( isSimple() ) {
return objects[0];
}
return objects;
return results.get( 0 );
}
protected abstract boolean isSimple();
}

View File

@ -6,21 +6,26 @@
*/
package org.hibernate.loader.ast.internal;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.NaturalIdPostLoadListener;
import org.hibernate.loader.NaturalIdPreLoadListener;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.internal.CompoundNaturalIdMapping;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.sql.ast.Clause;
import org.hibernate.query.ComparisonOperator;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.NullnessPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.exec.internal.JdbcParameterImpl;
import org.hibernate.sql.exec.spi.JdbcParameterBinding;
/**
* NaturalIdLoader implementation for compound natural-ids
@ -29,65 +34,70 @@ public class CompoundNaturalIdLoader<T> extends AbstractNaturalIdLoader<T> {
public CompoundNaturalIdLoader(
CompoundNaturalIdMapping naturalIdMapping,
NaturalIdPreLoadListener preLoadListener,
NaturalIdPostLoadListener postLoadListener,
EntityMappingType entityDescriptor,
MappingModelCreationProcess creationProcess) {
super( naturalIdMapping, preLoadListener, postLoadListener, entityDescriptor, creationProcess );
EntityMappingType entityDescriptor) {
super( naturalIdMapping, entityDescriptor );
}
@Override
protected Object resolveNaturalIdBindValue(Object naturalIdValue, SharedSessionContractImplementor session) {
// the "real" form as an array, although we also accept here a Map and reduce it to
// the appropriately ordered array
if ( naturalIdValue instanceof Object[] ) {
return naturalIdValue;
}
final List<SingularAttributeMapping> attributes = naturalIdMapping().getNaturalIdAttributes();
final Object[] naturalId = new Object[ attributes.size() ];
if ( naturalIdValue instanceof Map ) {
final Map<String,?> valueMap = (Map<String,?>) naturalIdValue;
for ( int i = 0; i < attributes.size(); i++ ) {
final SingularAttributeMapping attributeMapping = attributes.get( i );
naturalId[ i ] = valueMap.get( attributeMapping.getAttributeName() );
}
return naturalId;
}
throw new IllegalArgumentException( "Unexpected natural-id reference [" + naturalIdValue + "; expecting array or Map" );
}
@Override
protected void applyNaturalIdAsJdbcParameters(
Object naturalIdToLoad,
List<JdbcParameter> jdbcParameters,
JdbcParameterBindings jdbcParamBindings,
protected void applyNaturalIdRestriction(
Object bindValue,
TableGroup rootTableGroup,
Consumer<Predicate> predicateConsumer,
BiConsumer<JdbcParameter, JdbcParameterBinding> jdbcParameterConsumer,
LoaderSqlAstCreationState sqlAstCreationState,
SharedSessionContractImplementor session) {
assert naturalIdToLoad instanceof Object[];
final Object[] naturalIdValueArray = (Object[]) naturalIdToLoad;
final SqlExpressionResolver sqlExpressionResolver = sqlAstCreationState.getSqlExpressionResolver();
final SessionFactoryImplementor factory = session.getFactory();
int offset = 0;
if ( bindValue == null ) {
final List<SingularAttributeMapping> naturalIdAttributes = naturalIdMapping().getNaturalIdAttributes();
for ( int i = 0; i < naturalIdAttributes.size(); i++ ) {
naturalIdAttributes.get( i ).forEachSelection(
(selectionIndex, selectionMapping) -> {
final Expression columnRef = resolveColumnReference(
rootTableGroup,
selectionMapping,
sqlExpressionResolver,
factory
);
predicateConsumer.accept( new NullnessPredicate( columnRef ) );
}
);
}
for ( int i = 0; i < naturalIdMapping().getNaturalIdAttributes().size(); i++ ) {
final SingularAttributeMapping attrMapping = naturalIdMapping().getNaturalIdAttributes().get( i );
offset += jdbcParamBindings.registerParametersForEachJdbcValue(
naturalIdValueArray[i],
Clause.WHERE,
offset,
attrMapping,
jdbcParameters,
session
);
// EARLY EXIT!!
return;
}
// make sure we've exhausted all JDBC parameters
assert offset == jdbcParameters.size();
naturalIdMapping().breakDownJdbcValues(
bindValue,
(jdbcValue, jdbcValueMapping) -> {
final Expression columnReference = resolveColumnReference(
rootTableGroup,
jdbcValueMapping,
sqlExpressionResolver,
factory
);
if ( jdbcValue == null ) {
predicateConsumer.accept( new NullnessPredicate( columnReference ) );
}
else {
final JdbcParameter jdbcParameter = new JdbcParameterImpl( jdbcValueMapping.getJdbcMapping() );
final ComparisonPredicate predicate = new ComparisonPredicate(
columnReference,
ComparisonOperator.EQUAL,
jdbcParameter
);
predicateConsumer.accept( predicate );
jdbcParameterConsumer.accept(
jdbcParameter,
new JdbcParameterBindingImpl( jdbcValueMapping.getJdbcMapping(), jdbcValue )
);
}
},
session
);
}
@Override
protected boolean isSimple() {
return false;
}
}

View File

@ -82,9 +82,8 @@ class DatabaseSnapshotExecutor {
null,
true,
LockMode.NONE,
sqlAliasBaseManager,
state.getSqlExpressionResolver(),
() -> rootQuerySpec::applyPredicate,
state,
sessionFactory
);

View File

@ -240,11 +240,11 @@ public class LoaderSelectBuilder {
private final List<ModelPart> restrictedParts;
private final DomainResult cachedDomainResult;
private final int numberOfKeysToLoad;
private final boolean forceIdentifierSelection;
private final LoadQueryInfluencers loadQueryInfluencers;
private final LockOptions lockOptions;
private final Consumer<JdbcParameter> jdbcParameterConsumer;
private final EntityGraphTraversalState entityGraphTraversalState;
private boolean forceIdentifierSelection;
private int fetchDepth;
private Map<OrderByFragment, TableGroup> orderByFragments;
@ -378,9 +378,8 @@ public class LoaderSelectBuilder {
null,
true,
lockOptions.getLockMode(),
sqlAstCreationState.getSqlAliasBaseManager(),
sqlAstCreationState.getSqlExpressionResolver(),
() -> rootQuerySpec::applyPredicate,
sqlAstCreationState,
creationContext
);
@ -831,9 +830,8 @@ public class LoaderSelectBuilder {
null,
true,
lockOptions.getLockMode(),
sqlAstCreationState.getSqlAliasBaseManager(),
sqlAstCreationState.getSqlExpressionResolver(),
() -> rootQuerySpec::applyPredicate,
sqlAstCreationState,
creationContext
);

View File

@ -17,7 +17,6 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoadOptions;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.sql.results.LoadingLogger;
/**
@ -31,7 +30,7 @@ public class MultiNaturalIdLoaderStandard<E> implements MultiNaturalIdLoader<E>
private final EntityMappingType entityDescriptor;
public MultiNaturalIdLoaderStandard(EntityMappingType entityDescriptor, MappingModelCreationProcess creationProcess) {
public MultiNaturalIdLoaderStandard(EntityMappingType entityDescriptor) {
this.entityDescriptor = entityDescriptor;
}
@ -75,7 +74,7 @@ public class MultiNaturalIdLoaderStandard<E> implements MultiNaturalIdLoader<E>
(naturalId, session1) -> {
// `naturalId` here is the one passed in by the API as part of the values array
// todo (6.0) : use this to help create the ordered results
return entityDescriptor.getNaturalIdMapping().normalizeIncomingValue( naturalId, session );
return entityDescriptor.getNaturalIdMapping().normalizeInput( naturalId, session );
},
session.getLoadQueryInfluencers(),
lockOptions,

View File

@ -47,7 +47,7 @@ public class MultiNaturalIdLoadingBatcher {
* the "true" form - single value for simple natural-ids and an array for
* compound natural-ids.
*
* Generally delegates to {@link org.hibernate.metamodel.mapping.NaturalIdMapping#normalizeIncomingValue}
* Generally delegates to {@link org.hibernate.metamodel.mapping.NaturalIdMapping#normalizeInput}
*/
Object resolveKeyToLoad(Object incoming, SharedSessionContractImplementor session);
}

View File

@ -9,6 +9,8 @@ package org.hibernate.loader.ast.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.hibernate.HibernateException;
import org.hibernate.LockOptions;
@ -16,21 +18,27 @@ import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.NaturalIdPostLoadListener;
import org.hibernate.loader.NaturalIdPreLoadListener;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.mapping.internal.SimpleNaturalIdMapping;
import org.hibernate.query.ComparisonOperator;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.NullnessPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl;
import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl;
import org.hibernate.sql.exec.internal.JdbcParameterImpl;
import org.hibernate.sql.exec.spi.Callback;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcParameterBinding;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.JdbcSelect;
@ -41,11 +49,8 @@ public class SimpleNaturalIdLoader<T> extends AbstractNaturalIdLoader<T> {
public SimpleNaturalIdLoader(
SimpleNaturalIdMapping naturalIdMapping,
NaturalIdPreLoadListener preLoadListener,
NaturalIdPostLoadListener postLoadListener,
EntityMappingType entityDescriptor,
MappingModelCreationProcess creationProcess) {
super( naturalIdMapping, preLoadListener, postLoadListener, entityDescriptor, creationProcess );
EntityMappingType entityDescriptor) {
super( naturalIdMapping, entityDescriptor );
}
@Override
@ -54,114 +59,71 @@ public class SimpleNaturalIdLoader<T> extends AbstractNaturalIdLoader<T> {
}
@Override
protected void applyNaturalIdAsJdbcParameters(
Object naturalIdToLoad,
List<JdbcParameter> jdbcParameters,
JdbcParameterBindings jdbcParamBindings,
protected void applyNaturalIdRestriction(
Object bindValue,
TableGroup rootTableGroup,
Consumer<Predicate> predicateConsumer,
BiConsumer<JdbcParameter, JdbcParameterBinding> jdbcParameterConsumer,
LoaderSqlAstCreationState sqlAstCreationState,
SharedSessionContractImplementor session) {
assert jdbcParameters.size() == 1;
final Object bindableValue = naturalIdMapping().normalizeIncomingValue( naturalIdToLoad, session );
final SingularAttributeMapping attributeMapping = naturalIdMapping().getNaturalIdAttributes().get( 0 );
jdbcParamBindings.registerParametersForEachJdbcValue(
bindableValue,
Clause.WHERE,
attributeMapping,
jdbcParameters,
session
);
}
@Override
protected Object resolveNaturalIdBindValue(Object naturalIdToLoad, SharedSessionContractImplementor session) {
return naturalIdMapping().normalizeIncomingValue( naturalIdToLoad, session );
if ( bindValue == null ) {
naturalIdMapping().getAttribute().forEachSelection(
(selectionIndex, selectionMapping) -> {
final Expression columnReference = resolveColumnReference(
rootTableGroup,
selectionMapping,
sqlAstCreationState.getSqlExpressionResolver(),
session.getFactory()
);
predicateConsumer.accept( new NullnessPredicate( columnReference ) );
}
);
}
else {
naturalIdMapping().getAttribute().breakDownJdbcValues(
bindValue,
(jdbcValue, jdbcValueMapping) -> {
final Expression columnReference = resolveColumnReference(
rootTableGroup,
jdbcValueMapping,
sqlAstCreationState.getSqlExpressionResolver(),
session.getFactory()
);
if ( jdbcValue == null ) {
predicateConsumer.accept( new NullnessPredicate( columnReference ) );
}
else {
final JdbcParameter jdbcParameter = new JdbcParameterImpl( jdbcValueMapping.getJdbcMapping() );
final ComparisonPredicate predicate = new ComparisonPredicate(
columnReference,
ComparisonOperator.EQUAL,
jdbcParameter
);
predicateConsumer.accept( predicate );
jdbcParameterConsumer.accept(
jdbcParameter,
new JdbcParameterBindingImpl( jdbcValueMapping.getJdbcMapping(), jdbcValue )
);
}
},
session
);
}
}
@Override
public Object resolveIdToNaturalId(Object id, SharedSessionContractImplementor session) {
final SessionFactoryImplementor sessionFactory = session.getFactory();
final Object rawValue = super.resolveIdToNaturalId( id, session );
assert rawValue instanceof Object[];
final List<JdbcParameter> jdbcParameters = new ArrayList<>();
final SelectStatement sqlSelect = LoaderSelectBuilder.createSelect(
entityDescriptor(),
naturalIdMapping().getNaturalIdAttributes(),
entityDescriptor().getIdentifierMapping(),
null,
1,
session.getLoadQueryInfluencers(),
LockOptions.READ,
jdbcParameters::add,
sessionFactory
);
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() );
jdbcParamBindings.registerParametersForEachJdbcValue(
id,
Clause.WHERE,
entityDescriptor().getIdentifierMapping(),
jdbcParameters,
session
);
final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlSelect )
.translate( jdbcParamBindings, QueryOptions.NONE );
final List<Object[]> results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
jdbcSelect,
jdbcParamBindings,
new ExecutionContext() {
@Override
public SharedSessionContractImplementor getSession() {
return session;
}
@Override
public QueryOptions getQueryOptions() {
return QueryOptions.NONE;
}
@Override
public QueryParameterBindings getQueryParameterBindings() {
return QueryParameterBindings.NO_PARAM_BINDINGS;
}
@Override
public Callback getCallback() {
return afterLoadAction -> {
};
}
},
row -> row,
true
);
if ( results.size() > 1 ) {
throw new HibernateException(
String.format(
"Resolving id to natural-id returned more that one row : %s #%s",
entityDescriptor().getEntityName(),
id
)
);
}
return results.get( 0 );
}
@Override
protected boolean isSimple() {
return true;
return ( (Object[]) rawValue )[0];
}
@Override
public Object resolveNaturalIdToId(
Object naturalIdValue,
SharedSessionContractImplementor session) {
final Object bindValue = naturalIdMapping().normalizeIncomingValue( naturalIdValue, session );
final Object bindValue = naturalIdMapping().normalizeInput( naturalIdValue, session );
final SessionFactoryImplementor sessionFactory = session.getFactory();
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
@ -180,7 +142,6 @@ public class SimpleNaturalIdLoader<T> extends AbstractNaturalIdLoader<T> {
jdbcParameters::add,
sessionFactory
);
assert jdbcParameters.size() == 1;
final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() );

View File

@ -14,8 +14,7 @@ import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.tree.from.RootTableGroupProducer;
import org.hibernate.sql.ast.tree.from.TableGroup;
@ -43,10 +42,8 @@ public interface Loadable extends ModelPart, RootTableGroupProducer {
String explicitSourceAlias,
boolean canUseInnerJoins,
LockMode lockMode,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAstCreationContext creationContext) {
SqlAstCreationState creationState, SqlAstCreationContext creationContext) {
throw new NotYetImplementedFor6Exception( getClass() );
}
}

View File

@ -0,0 +1,34 @@
package org.hibernate.loader.ast.spi;
import org.hibernate.LockOptions;
/**
* Options for loading by natural-id
*/
public interface NaturalIdLoadOptions {
/**
* Singleton access
*/
NaturalIdLoadOptions NONE = new NaturalIdLoadOptions() {
@Override
public LockOptions getLockOptions() {
return LockOptions.READ;
}
@Override
public boolean isSynchronizationEnabled() {
return false;
}
};
/**
* The locking options for the loaded entity
*/
LockOptions getLockOptions();
/**
* Whether Hibernate should perform "synchronization" prior to performing
* look-ups?
*/
boolean isSynchronizationEnabled();
}

View File

@ -6,9 +6,6 @@
*/
package org.hibernate.loader.ast.spi;
import java.util.List;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
/**
@ -17,36 +14,6 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
* @author Steve Ebersole
*/
public interface NaturalIdLoader<T> extends Loader {
/**
* Options for the {@link #load} method
*/
interface LoadOptions {
/**
* Singleton access
*/
LoadOptions NONE = new LoadOptions() {
@Override
public LockOptions getLockOptions() {
return LockOptions.READ;
}
@Override
public boolean isSynchronizationEnabled() {
return false;
}
};
/**
* The locking options for the loaded entity
*/
LockOptions getLockOptions();
/**
* Whether Hibernate should perform "synchronization" prior to performing
* look-ups?
*/
boolean isSynchronizationEnabled();
}
/**
* Perform the load of the entity by its natural-id
@ -61,7 +28,7 @@ public interface NaturalIdLoader<T> extends Loader {
* @param options The options to apply to the load operation
* @param session The session into which the entity is being loaded
*/
T load(Object naturalIdToLoad, LoadOptions options, SharedSessionContractImplementor session);
T load(Object naturalIdToLoad, NaturalIdLoadOptions options, SharedSessionContractImplementor session);
/**
* Resolve the id from natural-id value

View File

@ -975,10 +975,20 @@ public abstract class PersistentClass implements AttributeContainer, Serializabl
return java.util.Collections.unmodifiableMap( tuplizerImpls );
}
private Boolean hasNaturalId;
public boolean hasNaturalId() {
Iterator props = getRootClass().getPropertyIterator();
if ( hasNaturalId == null ) {
hasNaturalId = determineIfNaturalIdDefined();
}
return hasNaturalId;
}
private boolean determineIfNaturalIdDefined() {
//noinspection unchecked
final Iterator<Property> props = getRootClass().getPropertyIterator();
while ( props.hasNext() ) {
if ( ( (Property) props.next() ).isNaturalIdentifier() ) {
if ( props.next().isNaturalIdentifier() ) {
return true;
}
}

View File

@ -16,7 +16,6 @@ import javax.persistence.OneToOne;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.Type;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.AssertionFailure;
import org.hibernate.internal.EntityManagerMessageLogger;
import org.hibernate.internal.HEMLogging;
@ -233,39 +232,40 @@ public class AttributeFactory {
final Component component = (Component) typeContext.getHibernateValue();
final EmbeddableTypeImpl<Y> embeddableType;
if ( component.getComponentClass() != null
|| component.getComponentClassName() != null ) {
final EmbeddableRepresentationStrategy representationStrategy = context.getTypeConfiguration()
.getMetadataBuildingContext()
.getBuildingOptions()
.getManagedTypeRepresentationResolver()
.resolveStrategy( component, context.getRuntimeModelCreationContext() );
if ( component.isDynamic() ) {
final JavaTypeDescriptor javaTypeDescriptor = context.getJavaTypeDescriptorRegistry().getDescriptor( Map.class );
embeddableType = new EmbeddableTypeImpl<>(
javaTypeDescriptor,
representationStrategy,
true,
context.getJpaMetamodel()
);
}
else {
// we should have a non-dynamic embeddable
assert component.getComponentClassName() != null;
final Class<Y> embeddableClass = component.getComponentClass();
final Class embeddableClass;
if ( component.getComponentClass() != null ) {
embeddableClass = component.getComponentClass();
}
else {
embeddableClass = context.getTypeConfiguration()
.getServiceRegistry()
.getService( ClassLoaderService.class )
.classForName( component.getComponentClassName() );
}
final EmbeddableDomainType cached = context.locateEmbeddable( embeddableClass );
final EmbeddableDomainType<Y> cached = context.locateEmbeddable( embeddableClass );
if ( cached != null ) {
return cached;
}
final JavaTypeDescriptorRegistry registry = context.getTypeConfiguration()
.getJavaTypeDescriptorRegistry();
final JavaTypeDescriptor javaTypeDescriptor = registry.resolveDescriptor( embeddableClass );
final EmbeddableRepresentationStrategy representationStrategy = context.getTypeConfiguration()
.getMetadataBuildingContext()
.getBuildingOptions()
.getManagedTypeRepresentationResolver()
.resolveStrategy( component, context.getRuntimeModelCreationContext() );
final JavaTypeDescriptor<Y> javaTypeDescriptor = registry.resolveDescriptor( embeddableClass );
embeddableType = new EmbeddableTypeImpl<>(
javaTypeDescriptor,
representationStrategy,
false,
context.getJpaMetamodel()
);
@ -273,12 +273,6 @@ public class AttributeFactory {
return embeddableType;
}
else {
embeddableType = new EmbeddableTypeImpl(
component.getRoleName(),
context.getJpaMetamodel()
);
}
final EmbeddableTypeImpl.InFlightAccess<Y> inFlightAccess = embeddableType.getInFlightAccess();
final Iterator<Property> subProperties = component.getPropertyIterator();

View File

@ -20,11 +20,9 @@ import javax.persistence.metamodel.IdentifiableType;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.Type;
import org.hibernate.AssertionFailure;
import org.hibernate.Internal;
import org.hibernate.MappingException;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.AssertionFailure;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.EntityManagerMessageLogger;
import org.hibernate.internal.HEMLogging;
import org.hibernate.internal.util.ReflectHelper;
@ -52,7 +50,6 @@ import org.hibernate.metamodel.model.domain.internal.MappedSuperclassTypeImpl;
import org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl;
import org.hibernate.metamodel.spi.EmbeddableRepresentationStrategy;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.type.EmbeddedComponentType;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry;
import org.hibernate.type.spi.TypeConfiguration;
@ -414,8 +411,8 @@ public class MetadataContext {
private void applyIdClassMetadata(Component identifier, Component idClass) {
final JavaTypeDescriptorRegistry registry = getTypeConfiguration()
.getJavaTypeDescriptorRegistry();
final Class componentClass = identifier.getComponentClass();
final JavaTypeDescriptor javaTypeDescriptor = registry.resolveDescriptor( componentClass );
final Class<?> componentClass = identifier.getComponentClass();
final JavaTypeDescriptor<?> javaTypeDescriptor = registry.resolveDescriptor( componentClass );
final EmbeddableRepresentationStrategy representationStrategy = getTypeConfiguration()
.getMetadataBuildingContext()
@ -423,22 +420,23 @@ public class MetadataContext {
.getManagedTypeRepresentationResolver()
.resolveStrategy( idClass, getRuntimeModelCreationContext() );
final EmbeddableTypeImpl embeddableType = new EmbeddableTypeImpl<>(
final EmbeddableTypeImpl<?> embeddableType = new EmbeddableTypeImpl<>(
javaTypeDescriptor,
representationStrategy,
false,
getJpaMetamodel()
);
registerEmbeddableType( embeddableType, idClass );
}
@SuppressWarnings({"unchecked", "rawtypes"})
private <X> void applyIdMetadata(MappedSuperclass mappingType, MappedSuperclassDomainType<X> jpaMappingType) {
if ( mappingType.hasIdentifierProperty() ) {
final Property declaredIdentifierProperty = mappingType.getDeclaredIdentifierProperty();
if ( declaredIdentifierProperty != null ) {
final SingularPersistentAttribute<X, Object> attribute = attributeFactory.buildIdAttribute( jpaMappingType, declaredIdentifierProperty );
//noinspection unchecked
( ( AttributeContainer) jpaMappingType ).getInFlightAccess().applyIdAttribute(
attributeFactory.buildIdAttribute( jpaMappingType, declaredIdentifierProperty )
);
( ( AttributeContainer) jpaMappingType ).getInFlightAccess().applyIdAttribute( attribute );
}
}
//a MappedSuperclass can have no identifier if the id is set below in the hierarchy

View File

@ -8,11 +8,13 @@ package org.hibernate.metamodel.mapping;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.sql.ast.Clause;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Contract for things at the domain/mapping level that can be bound into a JDBC

View File

@ -8,23 +8,25 @@ package org.hibernate.metamodel.mapping;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Locale;
import java.util.function.Consumer;
import java.util.function.Function;
import org.hibernate.MappingException;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.MutableInteger;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.mapping.Any;
import org.hibernate.mapping.BasicValue;
@ -34,13 +36,10 @@ import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.Table;
import org.hibernate.metamodel.mapping.internal.BasicValuedSingularAttributeMapping;
import org.hibernate.metamodel.mapping.internal.SelectionMappingsImpl;
import org.hibernate.metamodel.mapping.internal.DiscriminatedAssociationAttributeMapping;
import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.mapping.internal.SelectionMappingsImpl;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.metamodel.spi.EmbeddableRepresentationStrategy;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
@ -98,14 +97,35 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
);
creationProcess.registerInitializationCallback(
"EmbeddableMappingType(" + bootDescriptor.getRoleName() + ")#finishInitialization",
() -> mappingType.finishInitialization(
bootDescriptor,
compositeType,
rootTableExpression,
rootTableKeyColumnNames,
creationProcess
)
"EmbeddableMappingType(" + mappingType.getNavigableRole().getFullPath() + ")#finishInitialization",
() -> {
try {
final boolean finished = mappingType.finishInitialization(
bootDescriptor,
compositeType,
rootTableExpression,
rootTableKeyColumnNames,
creationProcess
);
if ( finished ) {
return finished;
}
}
catch (Exception e) {
MappingModelCreationLogger.LOGGER.debugf(
e,
"(DEBUG) Error finalizing EmbeddableMappingType(%s)",
mappingType.embeddedRole
);
}
MappingModelCreationLogger.LOGGER.debugf(
"EmbeddableMappingType(%s) finalization was not able to complete successfully",
mappingType.embeddedRole
);
return false;
}
);
return mappingType;
@ -116,9 +136,7 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
private final SessionFactoryImplementor sessionFactory;
// private final Map<String,AttributeMapping> attributeMappings = new TreeMap<>();
private final List<AttributeMapping> attributes = new ArrayList<>();
private final Map<String, AttributeMapping> attributeMap = new HashMap<>();
private final List<AttributeMapping> attributeMappings = new ArrayList<>();
private SelectionMappings selectionMappings;
private final EmbeddableValuedModelPart valueMapping;
@ -156,18 +174,20 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
MappingModelCreationProcess creationProcess) {
final SessionFactoryImplementor sessionFactory = creationProcess.getCreationContext().getSessionFactory();
final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration();
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final Dialect dialect = jdbcEnvironment.getDialect();
final String baseTableExpression = valueMapping.getContainingTableExpression();
final Dialect dialect = creationProcess.getCreationContext().getSessionFactory().getJdbcServices().getDialect();
final Type[] subtypes = compositeType.getSubtypes();
int attributeIndex = 0;
int columnPosition = 0;
//noinspection unchecked
final Iterator<Property> propertyIterator = bootDescriptor.getPropertyIterator();
while ( propertyIterator.hasNext() ) {
final Property bootPropertyDescriptor = propertyIterator.next();
final AttributeMapping attributeMapping;
final Type subtype = subtypes[attributeIndex];
if ( subtype instanceof BasicType ) {
@ -185,7 +205,7 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
if ( selectable instanceof Column ) {
containingTableExpression = getTableIdentifierExpression(
( (Column) selectable ).getValue().getTable(),
creationProcess
jdbcEnvironment
);
}
else {
@ -197,7 +217,7 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
columnExpression = rootTableKeyColumnNames[columnPosition];
}
BasicValuedSingularAttributeMapping attributeMapping = MappingModelCreationHelper.buildBasicAttributeMapping(
attributeMapping = MappingModelCreationHelper.buildBasicAttributeMapping(
bootPropertyDescriptor.getName(),
valueMapping.getNavigableRole().append( bootPropertyDescriptor.getName() ),
attributeIndex,
@ -206,14 +226,14 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
(BasicType<?>) subtype,
containingTableExpression,
columnExpression,
selectable.isFormula(),
false,
selectable.getCustomReadExpression(),
selectable.getCustomWriteExpression(),
representationStrategy.resolvePropertyAccess( bootPropertyDescriptor ),
compositeType.getCascadeStyle( attributeIndex ),
creationProcess
);
addAttribute( attributeMapping );
columnPosition++;
}
else if ( subtype instanceof AnyType ) {
@ -302,7 +322,7 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
}
};
DiscriminatedAssociationAttributeMapping attributeMapping = new DiscriminatedAssociationAttributeMapping(
attributeMapping = new DiscriminatedAssociationAttributeMapping(
valueMapping.getNavigableRole().append( bootPropertyDescriptor.getName() ),
typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Object.class ),
this,
@ -315,7 +335,6 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
bootValueMapping,
creationProcess
);
addAttribute( attributeMapping );
}
else if ( subtype instanceof CompositeType ) {
final CompositeType subCompositeType = (CompositeType) subtype;
@ -332,7 +351,7 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
System.arraycopy( rootTableKeyColumnNames, columnPosition, subRootTableKeyColumnNames, 0, columnSpan );
}
EmbeddedAttributeMapping attributeMapping = MappingModelCreationHelper.buildEmbeddedAttributeMapping(
attributeMapping = MappingModelCreationHelper.buildEmbeddedAttributeMapping(
bootPropertyDescriptor.getName(),
attributeIndex,
bootPropertyDescriptor,
@ -344,42 +363,51 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
compositeType.getCascadeStyle( attributeIndex ),
creationProcess
);
addAttribute( attributeMapping );
columnPosition += columnSpan;
}
else {
final EntityPersister entityPersister = creationProcess
.getEntityPersister( bootDescriptor.getOwner().getEntityName() );
if ( subtype instanceof CollectionType ) {
PluralAttributeMapping attributeMapping = MappingModelCreationHelper.buildPluralAttributeMapping(
bootPropertyDescriptor.getName(),
attributeIndex,
bootPropertyDescriptor,
entityPersister,
representationStrategy.resolvePropertyAccess( bootPropertyDescriptor ),
compositeType.getCascadeStyle( attributeIndex ),
compositeType.getFetchMode( attributeIndex ),
creationProcess
);
addAttribute( attributeMapping );
}
else if ( subtype instanceof EntityType ) {
final int columnSpan = subtype.getColumnSpan( sessionFactory );
final ToOneAttributeMapping attributeMapping = MappingModelCreationHelper.buildSingularAssociationAttributeMapping(
bootPropertyDescriptor.getName(),
valueMapping.getNavigableRole().append( bootPropertyDescriptor.getName() ),
attributeIndex,
bootPropertyDescriptor,
entityPersister,
(EntityType) subtype,
getRepresentationStrategy().resolvePropertyAccess( bootPropertyDescriptor ),
compositeType.getCascadeStyle( attributeIndex ),
creationProcess
);
addAttribute( attributeMapping );
columnPosition += columnSpan;
}
else if ( subtype instanceof CollectionType ) {
final EntityPersister entityPersister = creationProcess.getEntityPersister( bootDescriptor.getOwner().getEntityName() );
attributeMapping = MappingModelCreationHelper.buildPluralAttributeMapping(
bootPropertyDescriptor.getName(),
attributeIndex,
bootPropertyDescriptor,
entityPersister,
representationStrategy.resolvePropertyAccess( bootPropertyDescriptor ),
compositeType.getCascadeStyle( attributeIndex),
compositeType.getFetchMode( attributeIndex ),
creationProcess
);
}
else if ( subtype instanceof EntityType ) {
final EntityPersister entityPersister = creationProcess.getEntityPersister( bootDescriptor.getOwner().getEntityName() );
attributeMapping = MappingModelCreationHelper.buildSingularAssociationAttributeMapping(
bootPropertyDescriptor.getName(),
valueMapping.getNavigableRole().append( bootPropertyDescriptor.getName() ),
attributeIndex,
bootPropertyDescriptor,
entityPersister,
(EntityType) subtype,
getRepresentationStrategy().resolvePropertyAccess( bootPropertyDescriptor ),
compositeType.getCascadeStyle( attributeIndex ),
creationProcess
);
columnPosition += bootPropertyDescriptor.getColumnSpan();
}
else {
throw new MappingException(
String.format(
Locale.ROOT,
"Unable to determine attribute nature : %s#%s",
bootDescriptor.getOwner().getEntityName(),
bootPropertyDescriptor.getName()
)
);
}
addAttribute( attributeMapping );
attributeIndex++;
}
@ -392,11 +420,7 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
return true;
}
private static String getTableIdentifierExpression(Table table, MappingModelCreationProcess creationProcess) {
final JdbcEnvironment jdbcEnvironment = creationProcess.getCreationContext()
.getMetadata()
.getDatabase()
.getJdbcEnvironment();
private static String getTableIdentifierExpression(Table table, JdbcEnvironment jdbcEnvironment) {
return jdbcEnvironment
.getQualifiedObjectNameFormatter().format(
table.getQualifiedTableName(),
@ -410,18 +434,16 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
}
private void addAttribute(AttributeMapping attributeMapping) {
if ( attributeMap.put( attributeMapping.getAttributeName(), attributeMapping ) == null ) {
attributes.add( attributeMapping );
}
else {
for ( ListIterator<AttributeMapping> iterator = attributes.listIterator(); iterator.hasNext(); ) {
final AttributeMapping existingMapping = iterator.next();
if ( existingMapping.getAttributeName().equals( attributeMapping.getAttributeName() ) ) {
iterator.set( attributeMapping );
break;
}
// check if we've already seen this attribute...
for ( int i = 0; i < attributeMappings.size(); i++ ) {
final AttributeMapping previous = attributeMappings.get( i );
if ( attributeMapping.getAttributeName().equals( previous.getAttributeName() ) ) {
attributeMappings.set( i, attributeMapping );
return;
}
}
attributeMappings.add( attributeMapping );
}
public EmbeddableValuedModelPart getEmbeddedValueMapping() {
@ -471,7 +493,7 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
@Override
public int getNumberOfFetchables() {
return attributeMap.size();
return attributeMappings.size();
}
@Override
@ -506,14 +528,45 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
);
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
if ( domainValue instanceof Object[] ) {
final Object[] values = (Object[]) domainValue;
assert values.length == attributeMappings.size();
final MutableInteger positionRef = new MutableInteger();
attributeMappings.forEach(
(attributeMapping) -> {
final int position = positionRef.getAndIncrement();
final Object attributeValue = values[ position ];
attributeMapping.breakDownJdbcValues( attributeValue, valueConsumer, session );
}
);
}
else {
attributeMappings.forEach(
(attributeMapping) -> {
final Object attributeValue = attributeMapping.getPropertyAccess().getGetter().get( domainValue );
attributeMapping.breakDownJdbcValues( attributeValue, valueConsumer, session );
}
);
}
}
@Override
public Object disassemble(Object value, SharedSessionContractImplementor session) {
Object[] result = new Object[attributes.size()];
for ( int i = 0; i < attributes.size(); i++ ) {
AttributeMapping mapping = attributes.get( i );
final Collection<AttributeMapping> attributeMappings = getAttributeMappings();
Object[] result = new Object[attributeMappings.size()];
int i = 0;
final Iterator<AttributeMapping> iterator = attributeMappings.iterator();
while ( iterator.hasNext() ) {
AttributeMapping mapping = iterator.next();
Object o = mapping.getPropertyAccess().getGetter().get( value );
result[i] = mapping.disassemble( o, session );
i++;
}
return result;
}
@ -525,8 +578,9 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
JdbcValuesConsumer consumer,
SharedSessionContractImplementor session) {
int span = 0;
for ( int i = 0; i < attributes.size(); i++ ) {
final AttributeMapping attributeMapping = attributes.get( i );
for ( int i = 0; i < attributeMappings.size(); i++ ) {
final AttributeMapping attributeMapping = attributeMappings.get( i );
final Object o = attributeMapping.getPropertyAccess().getGetter().get( value );
span += attributeMapping.forEachJdbcValue( o, clause, span + offset, consumer, session );
}
@ -542,8 +596,8 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
SharedSessionContractImplementor session) {
final Object[] values = (Object[]) value;
int span = 0;
for ( int i = 0; i < attributes.size(); i++ ) {
final AttributeMapping mapping = attributes.get( i );
for ( int i = 0; i < attributeMappings.size(); i++ ) {
final AttributeMapping mapping = attributeMappings.get( i );
span += mapping.forEachDisassembledJdbcValue( values[i], clause, span + offset, valuesConsumer, session );
}
return span;
@ -566,34 +620,40 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
@Override
public int getNumberOfAttributeMappings() {
return attributeMap.size();
return attributeMappings.size();
}
@Override
public AttributeMapping findAttributeMapping(String name) {
return attributeMap.get( name );
for ( int i = 0; i < attributeMappings.size(); i++ ) {
final AttributeMapping attr = attributeMappings.get( i );
if ( name.equals( attr.getAttributeName() ) ) {
return attr;
}
}
return null;
}
@Override
public List<AttributeMapping> getAttributeMappings() {
return attributes;
return attributeMappings;
}
@Override
public void forEachAttributeMapping(IndexedConsumer<AttributeMapping> consumer) {
for ( int i = 0; i < attributes.size(); i++ ) {
consumer.accept( i, attributes.get( i ) );
for ( int i = 0; i < attributeMappings.size(); i++ ) {
consumer.accept( i, attributeMappings.get( i ) );
}
}
@Override
public void visitAttributeMappings(Consumer<AttributeMapping> action) {
attributes.forEach( action );
attributeMappings.forEach( action );
}
@Override
public ModelPart findSubPart(String name, EntityMappingType treatTargetType) {
return attributeMap.get( name );
return findAttributeMapping( name );
}
@Override
@ -605,8 +665,8 @@ public class EmbeddableMappingType implements ManagedMappingType, SelectionMappi
public void setPropertyValues(Object compositeInstance, Object[] resolvedValues) {
// todo (6.0) : reflection optimizer...
for ( int i = 0; i < attributes.size(); i++ ) {
attributes.get( i )
for ( int i = 0; i < attributeMappings.size(); i++ ) {
attributeMappings.get( i )
.getAttributeMetadataAccess()
.resolveAttributeMetadata( null )
.getPropertyAccess()

View File

@ -8,6 +8,7 @@ package org.hibernate.metamodel.mapping;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
@ -16,13 +17,15 @@ import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.loader.ast.spi.Loadable;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
import org.hibernate.loader.ast.spi.NaturalIdLoader;
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;
import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
@ -177,6 +180,14 @@ public interface EntityMappingType extends ManagedMappingType, EntityValuedModel
throw new NotYetImplementedFor6Exception( getClass() );
}
default EntityMappingType getRootEntityDescriptor() {
final EntityMappingType superMappingType = getSuperMappingType();
if ( superMappingType == null ) {
return this;
}
return superMappingType.getRootEntityDescriptor();
}
interface ConstraintOrderedTableConsumer {
void consume(String tableExpression, Supplier<Consumer<SelectionConsumer>> tableKeyColumnVisitationSupplier);
}
@ -215,6 +226,23 @@ public interface EntityMappingType extends ManagedMappingType, EntityValuedModel
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Loading
/**
* Access to performing natural-id database selection. This is per-entity in the hierarchy
*/
NaturalIdLoader<?> getNaturalIdLoader();
/**
* Access to performing multi-value natural-id database selection. This is per-entity in the hierarchy
*/
MultiNaturalIdLoader<?> getMultiNaturalIdLoader();
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Loadable
@ -239,18 +267,16 @@ public interface EntityMappingType extends ManagedMappingType, EntityValuedModel
String explicitSourceAlias,
boolean canUseInnerJoins,
LockMode lockMode,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAstCreationState creationState,
SqlAstCreationContext creationContext) {
return getEntityPersister().createRootTableGroup(
navigablePath,
explicitSourceAlias,
canUseInnerJoins,
lockMode,
aliasBaseGenerator,
sqlExpressionResolver,
additionalPredicateCollectorAccess,
creationState,
creationContext
);
}

View File

@ -104,6 +104,13 @@ public interface ModelPart extends MappingModelExpressable {
return 0;
}
@FunctionalInterface
interface JdbcValueConsumer {
void consume(Object value, SelectionMapping jdbcValueMapping);
}
void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session);
EntityMappingType findContainingEntityMapping();
default boolean areEqual(Object one, Object other, SharedSessionContractImplementor session) {

View File

@ -8,7 +8,7 @@ package org.hibernate.metamodel.mapping;
import java.util.List;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.cache.spi.access.NaturalIdDataAccess;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
import org.hibernate.loader.ast.spi.NaturalIdLoader;
@ -28,7 +28,18 @@ public interface NaturalIdMapping extends VirtualModelPart {
* Whether the natural-id is immutable. This is the same as saying that none of
* the attributes are mutable
*/
boolean isImmutable();
boolean isMutable();
@Override
default String getPartName() {
return PART_NAME;
}
/**
* Access to the natural-id's L2 cache access. Returns null if the natural-id is not
* configured for caching
*/
NaturalIdDataAccess getCacheAccess();
/**
* Verify the natural-id value(s) we are about to flush to the database
@ -39,14 +50,6 @@ public interface NaturalIdMapping extends VirtualModelPart {
Object[] loadedState,
SharedSessionContractImplementor session);
@Override
default String getPartName() {
return PART_NAME;
}
NaturalIdLoader getNaturalIdLoader();
MultiNaturalIdLoader getMultiNaturalIdLoader();
/**
* Given an array of "full entity state", extract the normalized natural id representation
*
@ -65,10 +68,15 @@ public interface NaturalIdMapping extends VirtualModelPart {
*/
Object extractNaturalIdValues(Object entity, SharedSessionContractImplementor session);
/**
* Normalize an incoming (user supplied) natural-id value.
* Normalize a user-provided natural-id value into the representation Hibernate uses internally
*
* @param incoming The user-supplied value
*
* @return The normalized, internal representation
*/
Object normalizeIncomingValue(Object incoming, SharedSessionContractImplementor session);
Object normalizeInput(Object incoming, SharedSessionContractImplementor session);
/**
* Validates a natural id value(s) for the described natural-id based on the expected internal representation
@ -83,4 +91,14 @@ public interface NaturalIdMapping extends VirtualModelPart {
* @return The hash-code
*/
int calculateHashCode(Object value, SharedSessionContractImplementor session);
/**
* Make a loader capable of loading a single entity by natural-id
*/
NaturalIdLoader<?> makeLoader(EntityMappingType entityDescriptor);
/**
* Make a loader capable of loading multiple entities by natural-id
*/
MultiNaturalIdLoader<?> makeMultiLoader(EntityMappingType entityDescriptor);
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.metamodel.mapping.internal;
import org.hibernate.cache.spi.access.NaturalIdDataAccess;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.model.domain.NavigableRole;
@ -15,26 +16,39 @@ import org.hibernate.metamodel.model.domain.NavigableRole;
*/
public abstract class AbstractNaturalIdMapping implements NaturalIdMapping {
private final EntityMappingType declaringType;
private final String cacheRegionName;
private final boolean mutable;
private final NaturalIdDataAccess cachesAccess;
private final NavigableRole role;
public AbstractNaturalIdMapping(EntityMappingType declaringType, String cacheRegionName) {
public AbstractNaturalIdMapping(EntityMappingType declaringType, boolean mutable) {
this.declaringType = declaringType;
this.cacheRegionName = cacheRegionName;
this.mutable = mutable;
this.cachesAccess = declaringType.getEntityPersister().getNaturalIdCacheAccessStrategy();
this.role = declaringType.getNavigableRole().append( PART_NAME );
}
@Override
public NavigableRole getNavigableRole() {
return role;
}
public EntityMappingType getDeclaringType() {
return declaringType;
}
@Override
public NavigableRole getNavigableRole() {
return role;
}
@Override
public boolean isMutable() {
return mutable;
}
@Override
public NaturalIdDataAccess getCacheAccess() {
return cachesAccess;
}
@Override
public EntityMappingType findContainingEntityMapping() {
return declaringType;

View File

@ -10,6 +10,7 @@ import org.hibernate.LockMode;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.DiscriminatedAssociationModelPart;
@ -122,6 +123,11 @@ public class AnyDiscriminatorPart implements BasicValuedModelPart, FetchOptions
return navigableRole;
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
valueConsumer.consume( domainValue, this );
}
@Override
public EntityMappingType findContainingEntityMapping() {
return declaringType.findContainingEntityMapping();

View File

@ -202,6 +202,11 @@ public class AnyKeyPart implements BasicValuedModelPart, FetchOptions {
return getJdbcTypeCount();
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
valueConsumer.consume( domainValue, this );
}
@Override
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) {
action.accept( offset, jdbcMapping );

View File

@ -134,6 +134,11 @@ public class BasicEntityIdentifierMappingImpl implements BasicEntityIdentifierMa
return getJdbcTypeCount();
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
valueConsumer.consume( domainValue, this );
}
@Override
public EntityMappingType findContainingEntityMapping() {
return entityPersister;

View File

@ -261,6 +261,11 @@ public class BasicValuedCollectionPart
return getJdbcTypeCount();
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
valueConsumer.consume( domainValue, this );
}
@Override
public int forEachDisassembledJdbcValue(
Object value,

View File

@ -268,4 +268,9 @@ public class BasicValuedSingularAttributeMapping
consumer.accept( offset, this );
return getJdbcTypeCount();
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
valueConsumer.consume( disassemble( domainValue, session ), this );
}
}

View File

@ -10,6 +10,7 @@ import org.hibernate.LockMode;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.CollectionIdentifierDescriptor;
import org.hibernate.metamodel.mapping.EntityMappingType;
@ -112,6 +113,11 @@ public class CollectionIdentifierDescriptorImpl implements CollectionIdentifierD
return navigableRole;
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
valueConsumer.consume( domainValue, this );
}
@Override
public EntityMappingType findContainingEntityMapping() {
return collectionDescriptor.getAttributeMapping().findContainingEntityMapping();

View File

@ -11,72 +11,100 @@ import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.hibernate.HibernateException;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.NaturalIdPostLoadListener;
import org.hibernate.loader.NaturalIdPreLoadListener;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.loader.ast.internal.CompoundNaturalIdLoader;
import org.hibernate.loader.ast.internal.MultiNaturalIdLoaderStandard;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
import org.hibernate.loader.ast.spi.NaturalIdLoader;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.SelectionConsumer;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.SelectionConsumer;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.graph.AssemblerCreationState;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultAssembler;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.FetchParentAccess;
import org.hibernate.sql.results.graph.Fetchable;
import org.hibernate.sql.results.graph.FetchableContainer;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions;
import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
/**
* Multi-attribute NaturalIdMapping implementation
*/
public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implements MappingType {
public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implements MappingType, FetchableContainer {
// todo (6.0) : create a composite MappingType for this descriptor's Object[]?
private final List<SingularAttributeMapping> attributes;
private final List<JdbcMapping> jdbcMappings;
private final boolean immutable;
private final JavaTypeDescriptor<?> jtd;
private final NaturalIdLoader<?> loader;
private final MultiNaturalIdLoader<?> multiLoader;
private List<JdbcMapping> jdbcMappings;
public CompoundNaturalIdMapping(
EntityMappingType declaringType,
List<SingularAttributeMapping> attributes,
String cacheRegionName,
MappingModelCreationProcess creationProcess) {
super( declaringType, cacheRegionName );
super( declaringType, isMutable( declaringType, attributes, creationProcess ) );
this.attributes = attributes;
boolean anyMutable = false;
final List<JdbcMapping> jdbcMappings = new ArrayList<>();
jtd = creationProcess.getCreationContext().getTypeConfiguration().getJavaTypeDescriptorRegistry().getDescriptor(
Object[].class
);
creationProcess.registerInitializationCallback(
"Determine compound natural-id JDBC mappings ( " + declaringType.getEntityName() + ")",
() -> {
final List<JdbcMapping> jdbcMappings = new ArrayList<>();
attributes.forEach(
(attribute) -> attribute.forEachJdbcType(
(index, jdbcMapping) -> jdbcMappings.add( jdbcMapping )
)
);
this.jdbcMappings = jdbcMappings;
return true;
}
);
}
private static boolean isMutable(
EntityMappingType entityDescriptor,
List<SingularAttributeMapping> attributes,
MappingModelCreationProcess creationProcess) {
for ( int i = 0; i < attributes.size(); i++ ) {
final SingularAttributeMapping attributeMapping = attributes.get( i );
attributeMapping.forEachJdbcType( (index, jdbcMapping) -> jdbcMappings.add( jdbcMapping ) );
anyMutable = anyMutable || attributeMapping.getAttributeMetadataAccess().resolveAttributeMetadata( null ).isUpdatable();
}
this.jdbcMappings = jdbcMappings;
this.immutable = ! anyMutable;
final StateArrayContributorMetadataAccess metadataAccess = attributeMapping.getAttributeMetadataAccess();
loader = new CompoundNaturalIdLoader<>(
this,
NaturalIdPreLoadListener.NO_OP,
NaturalIdPostLoadListener.NO_OP,
declaringType,
creationProcess
);
multiLoader = new MultiNaturalIdLoaderStandard<>( declaringType, creationProcess );
if ( ! metadataAccess.resolveAttributeMetadata( entityDescriptor ).isUpdatable() ) {
return false;
}
}
return true;
}
@Override
@ -113,7 +141,7 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
@Override
@SuppressWarnings( "rawtypes" )
public Object[] normalizeIncomingValue(Object incoming, SharedSessionContractImplementor session) {
public Object[] normalizeInput(Object incoming, SharedSessionContractImplementor session) {
if ( incoming instanceof Object[] ) {
return (Object[]) incoming;
}
@ -160,7 +188,7 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
@Override
public void verifyFlushState(Object id, Object[] currentState, Object[] loadedState, SharedSessionContractImplementor session) {
if ( ! immutable ) {
if ( isMutable() ) {
// EARLY EXIT!!!
// the natural id is mutable (!immutable), no need to do the checks
return;
@ -216,18 +244,13 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
}
@Override
public boolean isImmutable() {
return immutable;
public NaturalIdLoader<?> makeLoader(EntityMappingType entityDescriptor) {
return new CompoundNaturalIdLoader<>( this, entityDescriptor );
}
@Override
public NaturalIdLoader<?> getNaturalIdLoader() {
return loader;
}
@Override
public MultiNaturalIdLoader<?> getMultiNaturalIdLoader() {
return multiLoader;
public MultiNaturalIdLoader<?> makeMultiLoader(EntityMappingType entityDescriptor) {
return new MultiNaturalIdLoaderStandard<>( entityDescriptor );
}
@Override
@ -251,7 +274,28 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
@Override
public <T> DomainResult<T> createDomainResult(NavigablePath navigablePath, TableGroup tableGroup, String resultVariable, DomainResultCreationState creationState) {
throw new NotYetImplementedFor6Exception( getClass() );
assert navigablePath.getLocalName().equals( NaturalIdMapping.PART_NAME );
final SessionFactoryImplementor sessionFactory = creationState.getSqlAstCreationState().getCreationContext().getSessionFactory();
final JavaTypeDescriptor<Object[]> jtd = sessionFactory
.getTypeConfiguration()
.getJavaTypeDescriptorRegistry()
.getDescriptor( Object[].class );
// register the table group under `...{natural-id}` as well
creationState.getSqlAstCreationState().getFromClauseAccess().resolveTableGroup(
navigablePath,
(np) -> tableGroup
);
return (DomainResult<T>) new DomainResultImpl(
navigablePath,
this,
jtd,
resultVariable,
creationState
);
}
@Override
@ -268,6 +312,39 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
}
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
if ( domainValue == null ) {
attributes.forEach(
attributeMapping -> attributeMapping.breakDownJdbcValues( null, valueConsumer, session )
);
return;
}
assert domainValue instanceof Object[];
final Object[] values = (Object[]) domainValue;
assert values.length == attributes.size();
for ( int i = 0; i < attributes.size(); i++ ) {
final SingularAttributeMapping attributeMapping = attributes.get( i );
final Object value = values[ i ];
if ( attributeMapping instanceof ToOneAttributeMapping ) {
final ToOneAttributeMapping toOne = (ToOneAttributeMapping) attributeMapping;
final ForeignKeyDescriptor fKDescriptor = toOne.getForeignKeyDescriptor();
final EntityMappingType associatedEntityMapping = toOne.getEntityMappingType();
final EntityIdentifierMapping associatedEntityMappingIdentifierMapping = associatedEntityMapping.getIdentifierMapping();
final Object keyValue = value == null ? null : associatedEntityMappingIdentifierMapping.getIdentifier( value, session );
fKDescriptor.breakDownJdbcValues( keyValue, valueConsumer, session );
}
else {
attributeMapping.breakDownJdbcValues( value, valueConsumer, session );
}
}
}
@Override
public int forEachSelection(int offset, SelectionConsumer consumer) {
int span = 0;
@ -355,4 +432,222 @@ public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implement
}
return span;
}
@Override
public int getNumberOfFetchables() {
return attributes.size();
}
@Override
public ModelPart findSubPart(String name, EntityMappingType treatTargetType) {
for ( int i = 0; i < attributes.size(); i++ ) {
if ( name.equals( attributes.get( i ).getAttributeName() ) ) {
return attributes.get( i );
}
}
return null;
}
@Override
public void visitSubParts(Consumer<ModelPart> consumer, EntityMappingType treatTargetType) {
attributes.forEach( consumer );
}
public static class DomainResultImpl implements DomainResult<Object[]>, FetchParent {
private final NavigablePath navigablePath;
private final CompoundNaturalIdMapping naturalIdMapping;
private final JavaTypeDescriptor<Object[]> arrayJtd;
private final List<Fetch> fetches;
private final String resultVariable;
public DomainResultImpl(
NavigablePath navigablePath,
CompoundNaturalIdMapping naturalIdMapping,
JavaTypeDescriptor<Object[]> arrayJtd,
String resultVariable,
DomainResultCreationState creationState) {
this.navigablePath = navigablePath;
this.naturalIdMapping = naturalIdMapping;
this.arrayJtd = arrayJtd;
this.resultVariable = resultVariable;
this.fetches = creationState.visitFetches( this );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// DomainResult
@Override
public String getResultVariable() {
return resultVariable;
}
@Override
public DomainResultAssembler<Object[]> createResultAssembler(AssemblerCreationState creationState) {
return new AssemblerImpl(
fetches,
navigablePath,
naturalIdMapping,
arrayJtd,
creationState
);
}
@Override
public JavaTypeDescriptor<Object[]> getResultJavaTypeDescriptor() {
return arrayJtd;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// FetchParent
@Override
public FetchableContainer getReferencedMappingContainer() {
return getReferencedMappingType();
}
@Override
public FetchableContainer getReferencedMappingType() {
return naturalIdMapping;
}
@Override
public NavigablePath getNavigablePath() {
return navigablePath;
}
@Override
public List<Fetch> getFetches() {
return fetches;
}
@Override
public Fetch findFetch(Fetchable fetchable) {
assert fetchable != null;
for ( int i = 0; i < fetches.size(); i++ ) {
final Fetch fetch = fetches.get( i );
if ( fetchable.equals( fetch.getFetchedMapping() ) ) {
return fetch;
}
}
return null;
}
}
private static class AssemblerImpl implements DomainResultAssembler<Object[]> {
private final NavigablePath navigablePath;
private final CompoundNaturalIdMapping naturalIdMapping;
private final JavaTypeDescriptor<Object[]> jtd;
private final List<DomainResultAssembler<?>> subAssemblers;
private AssemblerImpl(
List<Fetch> fetches,
NavigablePath navigablePath,
CompoundNaturalIdMapping naturalIdMapping,
JavaTypeDescriptor<Object[]> jtd,
AssemblerCreationState creationState) {
this.navigablePath = navigablePath;
this.naturalIdMapping = naturalIdMapping;
this.jtd = jtd;
// we don't even register the Initializer here... its really no-op.
// we just "need it" as an impl detail for handling Fetches
final InitializerImpl initializer = new InitializerImpl( navigablePath, naturalIdMapping );
this.subAssemblers = CollectionHelper.arrayList( fetches.size() );
for ( int i = 0; i < fetches.size(); i++ ) {
final Fetch fetch = fetches.get( i );
final DomainResultAssembler<?> fetchAssembler = fetch.createAssembler( initializer, creationState );
subAssemblers.add( fetchAssembler );
}
}
@Override
public Object[] assemble(
RowProcessingState rowProcessingState,
JdbcValuesSourceProcessingOptions options) {
final Object[] result = new Object[ subAssemblers.size() ];
for ( int i = 0; i < subAssemblers.size(); i++ ) {
result[ i ] = subAssemblers.get( i ).assemble( rowProcessingState, options );
}
return result;
}
@Override
public JavaTypeDescriptor<Object[]> getAssembledJavaTypeDescriptor() {
return jtd;
}
}
private static class InitializerImpl implements FetchParentAccess {
private final NavigablePath navigablePath;
private final CompoundNaturalIdMapping naturalIdMapping;
public InitializerImpl(NavigablePath navigablePath, CompoundNaturalIdMapping naturalIdMapping) {
this.navigablePath = navigablePath;
this.naturalIdMapping = naturalIdMapping;
}
@Override
public FetchParentAccess findFirstEntityDescriptorAccess() {
return null;
}
@Override
public Object getParentKey() {
return null;
}
@Override
public Object getFetchParentInstance() {
return null;
}
@Override
public NavigablePath getNavigablePath() {
return navigablePath;
}
@Override
public ModelPart getInitializedPart() {
return naturalIdMapping;
}
@Override
public Object getInitializedInstance() {
return null;
}
@Override
public void resolveKey(RowProcessingState rowProcessingState) {
}
@Override
public void resolveInstance(RowProcessingState rowProcessingState) {
}
@Override
public void initializeInstance(RowProcessingState rowProcessingState) {
}
@Override
public void finishUpRow(RowProcessingState rowProcessingState) {
}
@Override
public void registerResolutionListener(Consumer<Object> resolvedParentConsumer) {
}
}
}

View File

@ -12,6 +12,7 @@ import org.hibernate.LockMode;
import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.Any;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.mapping.Property;
@ -135,6 +136,11 @@ public class DiscriminatedAssociationAttributeMapping
return getDiscriminatorPart().getJdbcTypeCount() + getKeyPart().getJdbcTypeCount();
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
discriminatorMapping.getDiscriminatorPart().breakDownJdbcValues( domainValue, valueConsumer, session );
}
@Override
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) {
int span = getDiscriminatorPart().forEachJdbcType( offset, action );

View File

@ -10,6 +10,7 @@ import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.Any;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
@ -172,6 +173,11 @@ public class DiscriminatedCollectionPart implements DiscriminatedAssociationMode
return getDiscriminatorPart().getJdbcTypeCount() + getKeyPart().getJdbcTypeCount();
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
discriminatorMapping.getDiscriminatorPart().breakDownJdbcValues( domainValue, valueConsumer, session );
}
@Override
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) {
int span = getDiscriminatorPart().forEachJdbcType( offset, action );

View File

@ -11,18 +11,16 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.SelectionConsumer;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SelectionConsumer;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.property.access.internal.PropertyAccessStrategyBasicImpl;
@ -126,6 +124,11 @@ public class EmbeddedAttributeMapping
return getEmbeddableTypeDescriptor().forEachSelection( offset, consumer );
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
getEmbeddableTypeDescriptor().breakDownJdbcValues( domainValue, valueConsumer, session );
}
@Override
public <T> DomainResult<T> createDomainResult(
NavigablePath navigablePath,

View File

@ -15,9 +15,7 @@ import org.hibernate.LockMode;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.SelectionConsumer;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
@ -265,6 +263,11 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF
return getEmbeddableTypeDescriptor().getNumberOfAttributeMappings();
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
getEmbeddableTypeDescriptor().breakDownJdbcValues( domainValue, valueConsumer, session );
}
@Override
public FetchStyle getStyle() {
return FetchStyle.JOIN;

View File

@ -14,14 +14,14 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.AssociationKey;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.SelectionConsumer;
import org.hibernate.metamodel.mapping.SelectionMappings;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.SelectionConsumer;
import org.hibernate.metamodel.mapping.SelectionMappings;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.query.ComparisonOperator;
import org.hibernate.query.NavigablePath;
@ -396,6 +396,19 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor, Model
);
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
assert domainValue instanceof Object[];
final Object[] values = (Object[]) domainValue;
keySelectionMappings.forEachSelection(
(selectionIndex, selectionMapping) -> {
valueConsumer.consume( values[ selectionIndex ], selectionMapping );
}
);
}
@Override
public EntityMappingType findContainingEntityMapping() {
throw new UnsupportedOperationException();

View File

@ -6,7 +6,6 @@
*/
package org.hibernate.metamodel.mapping.internal;
import java.util.Collection;
import java.util.List;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -25,7 +24,8 @@ import org.hibernate.sql.ast.Clause;
*
* @author Andrea Boriero
*/
public class EmbeddedIdentifierMappingImpl extends AbstractCompositeIdentifierMapping
public class EmbeddedIdentifierMappingImpl
extends AbstractCompositeIdentifierMapping
implements SingleAttributeIdentifierMapping {
private final String name;
private final PropertyAccess propertyAccess;
@ -108,6 +108,11 @@ public class EmbeddedIdentifierMappingImpl extends AbstractCompositeIdentifierMa
return name;
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
getEmbeddableTypeDescriptor().breakDownJdbcValues( domainValue, valueConsumer, session );
}
@Override
public int forEachDisassembledJdbcValue(
Object value,

View File

@ -12,6 +12,7 @@ import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.mapping.CollectionPart;
@ -178,6 +179,11 @@ public class EntityCollectionPart
return entityMappingType.forEachSelection( offset, consumer );
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
fkTargetModelPart.breakDownJdbcValues( domainValue, valueConsumer, session );
}
@Override
public void applySqlSelections(
NavigablePath navigablePath,

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.metamodel.mapping.internal;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
@ -71,6 +72,11 @@ public class EntityDiscriminatorMappingImpl extends AbstractEntityDiscriminatorM
return navigableRole;
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
valueConsumer.consume( domainValue, this );
}
@Override
public EntityMappingType findContainingEntityMapping() {
return getEntityDescriptor();

View File

@ -8,11 +8,13 @@ package org.hibernate.metamodel.mapping.internal;
import java.util.function.BiConsumer;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityRowIdMapping;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.SelectionMapping;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
@ -25,12 +27,13 @@ import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.type.JavaObjectType;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
/**
* @author Nathan Xu
*/
public class EntityRowIdMappingImpl implements EntityRowIdMapping {
public class EntityRowIdMappingImpl implements EntityRowIdMapping, SelectionMapping {
private final String rowIdName;
private final EntityMappingType declaringType;
private final String tableExpression;
@ -135,4 +138,38 @@ public class EntityRowIdMappingImpl implements EntityRowIdMapping {
BiConsumer<SqlSelection, JdbcMapping> selectionConsumer) {
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
valueConsumer.consume( domainValue, this );
}
@Override
public String getContainingTableExpression() {
return tableExpression;
}
@Override
public String getSelectionExpression() {
return rowIdName;
}
@Override
public String getCustomReadExpression() {
return rowIdName;
}
@Override
public String getCustomWriteExpression() {
return null;
}
@Override
public boolean isFormula() {
return true;
}
@Override
public JdbcMapping getJdbcMapping() {
return StandardBasicTypes.INTEGER;
}
}

View File

@ -11,6 +11,7 @@ import java.util.function.BiConsumer;
import org.hibernate.LockMode;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.SelectionConsumer;
import org.hibernate.metamodel.mapping.EntityMappingType;
@ -213,6 +214,11 @@ public class EntityVersionMappingImpl implements EntityVersionMapping, FetchOpti
selectionConsumer.accept( resolveSqlSelection( tableGroup, creationState ), getJdbcMapping() );
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
valueConsumer.consume( domainValue, this );
}
private SqlSelection resolveSqlSelection(TableGroup tableGroup, DomainResultCreationState creationState) {
final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState();

View File

@ -8,6 +8,7 @@ package org.hibernate.metamodel.mapping.internal;
import java.util.List;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
@ -77,6 +78,11 @@ public class JoinedSubclassDiscriminatorMappingImpl extends AbstractEntityDiscri
return navigableRole;
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
valueConsumer.consume( domainValue, this );
}
@Override
public EntityMappingType findContainingEntityMapping() {
return getEntityDescriptor();

View File

@ -7,7 +7,6 @@
package org.hibernate.metamodel.mapping.internal;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import org.hibernate.engine.spi.EntityKey;
@ -116,6 +115,19 @@ public class NonAggregatedIdentifierMappingImpl extends AbstractCompositeIdentif
);
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
assert domainValue instanceof Object[];
final Object[] values = (Object[]) domainValue;
assert values.length == idAttributeMappings.size();
for ( int i = 0; i < idAttributeMappings.size(); i++ ) {
final SingularAttributeMapping attribute = idAttributeMappings.get( i );
attribute.breakDownJdbcValues( values[ i ], valueConsumer, session );
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// EmbeddableValuedFetchable

View File

@ -81,7 +81,8 @@ import org.jboss.logging.Logger;
/**
* @author Steve Ebersole
*/
public class PluralAttributeMappingImpl extends AbstractAttributeMapping
public class PluralAttributeMappingImpl
extends AbstractAttributeMapping
implements PluralAttributeMapping, FetchOptions {
private static final Logger log = Logger.getLogger( PluralAttributeMappingImpl.class );
@ -922,17 +923,16 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping
String explicitSourceAlias,
boolean canUseInnerJoins,
LockMode lockMode,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAstCreationState creationState,
SqlAstCreationContext creationContext) {
if ( getCollectionDescriptor().isOneToMany() ) {
return createOneToManyTableGroup(
navigablePath,
canUseInnerJoins,
lockMode,
aliasBaseGenerator,
sqlExpressionResolver,
creationState.getSqlAliasBaseGenerator(),
creationState.getSqlExpressionResolver(),
creationContext
);
}
@ -941,8 +941,8 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping
navigablePath,
canUseInnerJoins,
lockMode,
aliasBaseGenerator,
sqlExpressionResolver,
creationState.getSqlAliasBaseGenerator(),
creationState.getSqlExpressionResolver(),
creationContext
);
}
@ -1005,6 +1005,11 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping
elementDescriptor.applySqlSelections( navigablePath, tableGroup, creationState, selectionConsumer );
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
throw new UnsupportedOperationException();
}
@Override
public void visitSubParts(Consumer<ModelPart> consumer, EntityMappingType treatTargetType) {
consumer.accept( elementDescriptor );

View File

@ -258,6 +258,27 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa
throw new UnsupportedOperationException();
}
@Override
public Object disassemble(Object value, SharedSessionContractImplementor session) {
return value;
}
@Override
public int forEachDisassembledJdbcValue(
Object value,
Clause clause,
int offset,
JdbcValuesConsumer valuesConsumer,
SharedSessionContractImplementor session) {
valuesConsumer.consume( offset, value, getJdbcMapping() );
return 1;
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
valueConsumer.consume( domainValue, keySelectionMapping );
}
@Override
public int visitReferringColumns(int offset, SelectionConsumer consumer) {
consumer.accept( offset, keySelectionMapping );

View File

@ -14,17 +14,15 @@ import java.util.function.BiConsumer;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.NaturalIdPostLoadListener;
import org.hibernate.loader.NaturalIdPreLoadListener;
import org.hibernate.loader.ast.internal.MultiNaturalIdLoaderStandard;
import org.hibernate.loader.ast.internal.SimpleNaturalIdLoader;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
import org.hibernate.loader.ast.spi.NaturalIdLoader;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.metamodel.mapping.SelectionConsumer;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.SelectionConsumer;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.NavigablePath;
@ -41,36 +39,20 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
private final SingularAttributeMapping attribute;
private final boolean immutable;
private final SimpleNaturalIdLoader<?> loader;
private final MultiNaturalIdLoader<?> multiLoader;
public SimpleNaturalIdMapping(
SingularAttributeMapping attribute,
EntityMappingType declaringType,
String cacheRegionName,
MappingModelCreationProcess creationProcess) {
super( declaringType, cacheRegionName );
this.attribute = attribute;
this.immutable = ! attribute.getAttributeMetadataAccess()
.resolveAttributeMetadata( null )
.isUpdatable();
this.loader = new SimpleNaturalIdLoader<>(
this,
NaturalIdPreLoadListener.NO_OP,
NaturalIdPostLoadListener.NO_OP,
super(
declaringType,
creationProcess
attribute.getAttributeMetadataAccess().resolveAttributeMetadata( declaringType ).isUpdatable()
);
this.multiLoader = new MultiNaturalIdLoaderStandard<>( declaringType, creationProcess );
this.attribute = attribute;
}
@Override
public void verifyFlushState(Object id, Object[] currentState, Object[] loadedState, SharedSessionContractImplementor session) {
if ( ! immutable ) {
if ( isMutable() ) {
// EARLY EXIT!!!
// the natural id is mutable (!immutable), no need to do the checks
return;
@ -134,7 +116,7 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
}
@Override
public Object normalizeIncomingValue(Object incoming, SharedSessionContractImplementor session) {
public Object normalizeInput(Object incoming, SharedSessionContractImplementor session) {
return normalizeIncomingValue( incoming );
}
@ -160,26 +142,11 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
return attribute;
}
@Override
public NaturalIdLoader<?> getNaturalIdLoader() {
return loader;
}
@Override
public MultiNaturalIdLoader<?> getMultiNaturalIdLoader() {
return multiLoader;
}
@Override
public List<SingularAttributeMapping> getNaturalIdAttributes() {
return Collections.singletonList( attribute );
}
@Override
public boolean isImmutable() {
return immutable;
}
@Override
public MappingType getPartMappingType() {
return attribute.getPartMappingType();
@ -238,6 +205,11 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
return attribute.disassemble( value, session );
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
attribute.breakDownJdbcValues( domainValue, valueConsumer, session );
}
@Override
public int forEachDisassembledJdbcValue(
Object value,
@ -257,4 +229,14 @@ public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
SharedSessionContractImplementor session) {
return attribute.forEachJdbcValue( value, clause, offset, valuesConsumer, session );
}
@Override
public NaturalIdLoader<?> makeLoader(EntityMappingType entityDescriptor) {
return new SimpleNaturalIdLoader<>( this, entityDescriptor );
}
@Override
public MultiNaturalIdLoader<?> makeMultiLoader(EntityMappingType entityDescriptor) {
return new MultiNaturalIdLoaderStandard<>( entityDescriptor );
}
}

View File

@ -11,17 +11,19 @@ import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.IndexedConsumer;
import org.hibernate.mapping.ManyToOne;
import org.hibernate.mapping.OneToOne;
import org.hibernate.mapping.ToOne;
import org.hibernate.metamodel.mapping.AssociationKey;
import org.hibernate.metamodel.mapping.SelectionConsumer;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SelectionConsumer;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.entity.EntityPersister;
@ -59,7 +61,8 @@ import org.hibernate.sql.results.internal.domain.CircularFetchImpl;
/**
* @author Steve Ebersole
*/
public class ToOneAttributeMapping extends AbstractSingularAttributeMapping
public class ToOneAttributeMapping
extends AbstractSingularAttributeMapping
implements EntityValuedFetchable, EntityAssociationMapping, TableGroupJoinProducer {
public enum Cardinality {
@ -214,24 +217,6 @@ public class ToOneAttributeMapping extends AbstractSingularAttributeMapping
return navigableRole;
}
@Override
public int forEachJdbcValue(
Object value,
Clause clause,
int offset,
JdbcValuesConsumer consumer,
SharedSessionContractImplementor session) {
return getForeignKeyDescriptor().forEachJdbcValue(
getAssociatedEntityMappingType()
.getIdentifierMapping()
.getIdentifier( value, session ),
clause,
offset,
consumer,
session
);
}
@Override
public Fetch resolveCircularFetch(
NavigablePath fetchablePath,
@ -628,6 +613,14 @@ public class ToOneAttributeMapping extends AbstractSingularAttributeMapping
return "SingularAssociationAttributeMapping {" + navigableRole + "}";
}
@Override
public void breakDownJdbcValues(
Object domainValue,
JdbcValueConsumer valueConsumer,
SharedSessionContractImplementor session) {
foreignKeyDescriptor.breakDownJdbcValues( domainValue, valueConsumer, session );
}
@Override
public int forEachSelection(int offset, SelectionConsumer consumer) {
if ( isKeyReferringSide ) {
@ -637,4 +630,29 @@ public class ToOneAttributeMapping extends AbstractSingularAttributeMapping
return 0;
}
}
@Override
public int getJdbcTypeCount() {
return foreignKeyDescriptor.getJdbcTypeCount();
}
@Override
public int forEachJdbcType(int offset, IndexedConsumer<JdbcMapping> action) {
return foreignKeyDescriptor.forEachJdbcType( offset, action );
}
@Override
public Object disassemble(Object value, SharedSessionContractImplementor session) {
return foreignKeyDescriptor.disassemble( value, session );
}
@Override
public int forEachDisassembledJdbcValue(Object value, Clause clause, int offset, JdbcValuesConsumer valuesConsumer, SharedSessionContractImplementor session) {
return foreignKeyDescriptor.forEachDisassembledJdbcValue( value, clause, offset, valuesConsumer, session );
}
@Override
public int forEachJdbcValue(Object value, Clause clause, int offset, JdbcValuesConsumer consumer, SharedSessionContractImplementor session) {
return foreignKeyDescriptor.forEachJdbcValue( value, clause, offset, consumer, session );
}
}

View File

@ -48,14 +48,17 @@ public class EmbeddableTypeImpl<J>
extends AbstractManagedType<J>
implements EmbeddableDomainType<J>, Serializable, MappingModelExpressable<J> {
private final boolean isDynamic;
private final EmbeddableRepresentationStrategy representationStrategy;
public EmbeddableTypeImpl(
JavaTypeDescriptor<J> javaTypeDescriptor,
EmbeddableRepresentationStrategy representationStrategy,
boolean isDynamic,
JpaMetamodel domainMetamodel) {
super( javaTypeDescriptor.getJavaType().getName(), javaTypeDescriptor, null, domainMetamodel );
this.representationStrategy = representationStrategy;
this.isDynamic = isDynamic;
}
public EmbeddableTypeImpl(

View File

@ -123,6 +123,7 @@ import org.hibernate.loader.ast.internal.SingleUniqueKeyEntityLoaderStandard;
import org.hibernate.loader.ast.spi.Loader;
import org.hibernate.loader.ast.spi.MultiIdEntityLoader;
import org.hibernate.loader.ast.spi.MultiIdLoadOptions;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
import org.hibernate.loader.ast.spi.NaturalIdLoader;
import org.hibernate.loader.ast.spi.SingleIdEntityLoader;
import org.hibernate.loader.ast.spi.SingleUniqueKeyEntityLoader;
@ -145,22 +146,22 @@ import org.hibernate.metamodel.RepresentationMode;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.AttributeMetadata;
import org.hibernate.metamodel.mapping.AttributeMetadataAccess;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.SelectionConsumer;
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityRowIdMapping;
import org.hibernate.metamodel.mapping.EntityVersionMapping;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.Queryable;
import org.hibernate.metamodel.mapping.SelectionConsumer;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.StateArrayContributorMapping;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadata;
import org.hibernate.metamodel.mapping.internal.DiscriminatedAssociationAttributeMapping;
import org.hibernate.metamodel.mapping.internal.BasicEntityIdentifierMappingImpl;
import org.hibernate.metamodel.mapping.internal.CompoundNaturalIdMapping;
import org.hibernate.metamodel.mapping.internal.DiscriminatedAssociationAttributeMapping;
import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping;
import org.hibernate.metamodel.mapping.internal.EntityDiscriminatorMappingImpl;
import org.hibernate.metamodel.mapping.internal.EntityRowIdMappingImpl;
@ -168,8 +169,8 @@ import org.hibernate.metamodel.mapping.internal.EntityVersionMappingImpl;
import org.hibernate.metamodel.mapping.internal.InFlightEntityMappingType;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.mapping.internal.SimpleNaturalIdMapping;
import org.hibernate.metamodel.mapping.internal.NonAggregatedIdentifierMappingImpl;
import org.hibernate.metamodel.mapping.internal.SimpleNaturalIdMapping;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
@ -203,6 +204,7 @@ import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAliasStemHelper;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
@ -256,9 +258,12 @@ public abstract class AbstractEntityPersister
public static final String ENTITY_CLASS = "class";
private final String sqlAliasStem;
private EntityMappingType rootEntityDescriptor;
private final SingleIdEntityLoader singleIdEntityLoader;
private final MultiIdEntityLoader multiIdEntityLoader;
private NaturalIdLoader naturalIdLoader;
private MultiNaturalIdLoader multiNaturalIdLoader;
private SqmMultiTableMutationStrategy sqmMultiTableMutationStrategy;
@ -1347,10 +1352,11 @@ public abstract class AbstractEntityPersister
String explicitSourceAlias,
boolean canUseInnerJoins,
LockMode lockMode,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAstCreationState creationState,
SqlAstCreationContext creationContext) {
final SqlExpressionResolver sqlExpressionResolver = creationState.getSqlExpressionResolver();
final SqlAliasBaseGenerator aliasBaseGenerator = creationState.getSqlAliasBaseGenerator();
final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() );
final TableReference primaryTableReference = createPrimaryTableReference(
@ -5688,6 +5694,12 @@ public abstract class AbstractEntityPersister
return entityMetamodel.getNaturalIdentifierProperties();
}
private void verifyHasNaturalId() {
if ( ! hasNaturalIdentifier() ) {
throw new HibernateException( "Entity does not define a natural id : " + getEntityName() );
}
}
public Object[] getNaturalIdentifierSnapshot(Object id, SharedSessionContractImplementor session) {
verifyHasNaturalId();
@ -5699,7 +5711,7 @@ public abstract class AbstractEntityPersister
);
}
final Object result = naturalIdMapping.getNaturalIdLoader().resolveIdToNaturalId( id, session );
final Object result = getNaturalIdLoader().resolveIdToNaturalId( id, session );
if ( result instanceof Object[] ) {
return (Object[]) result;
}
@ -5708,16 +5720,27 @@ public abstract class AbstractEntityPersister
}
}
private void verifyHasNaturalId() {
if ( ! hasNaturalIdentifier() ) {
throw new HibernateException( "Entity does not define a natural id : " + getEntityName() );
}
}
@Override
public NaturalIdLoader<?> getNaturalIdLoader() {
verifyHasNaturalId();
return naturalIdMapping.getNaturalIdLoader();
if ( naturalIdLoader == null ) {
naturalIdLoader = naturalIdMapping.makeLoader( this );
}
return naturalIdLoader;
}
@Override
public MultiNaturalIdLoader<?> getMultiNaturalIdLoader() {
verifyHasNaturalId();
if ( multiNaturalIdLoader == null ) {
multiNaturalIdLoader = naturalIdMapping.makeMultiLoader( this );
}
return multiNaturalIdLoader;
}
@Override
@ -5735,7 +5758,7 @@ public abstract class AbstractEntityPersister
);
}
return naturalIdMapping.getNaturalIdLoader().resolveNaturalIdToId( naturalIdValues, session );
return getNaturalIdLoader().resolveNaturalIdToId( naturalIdValues, session );
}
public boolean hasNaturalIdentifier() {
@ -5954,9 +5977,11 @@ public abstract class AbstractEntityPersister
else {
prepareMappingModel( creationProcess, bootEntityDescriptor );
}
rootEntityDescriptor = superMappingType.getRootEntityDescriptor();
}
else {
prepareMappingModel( creationProcess, bootEntityDescriptor );
rootEntityDescriptor = this;
}
final EntityMetamodel currentEntityMetamodel = this.getEntityMetamodel();
@ -6047,7 +6072,7 @@ public abstract class AbstractEntityPersister
else {
rowIdMapping = creationProcess.processSubPart(
rowIdName,
(role, process) -> new EntityRowIdMappingImpl( rowIdName, this.getTableName(), this)
(role, process) -> new EntityRowIdMappingImpl( rowIdName, this.getTableName(), this )
);
}
@ -6055,7 +6080,10 @@ public abstract class AbstractEntityPersister
}
private void postProcessAttributeMappings(MappingModelCreationProcess creationProcess, PersistentClass bootEntityDescriptor) {
if ( bootEntityDescriptor.hasNaturalId() ) {
if ( superMappingType != null ) {
naturalIdMapping = superMappingType.getNaturalIdMapping();
}
else if ( bootEntityDescriptor.hasNaturalId() ) {
naturalIdMapping = generateNaturalIdMapping( creationProcess, bootEntityDescriptor );
}
else {
@ -6066,43 +6094,47 @@ public abstract class AbstractEntityPersister
private NaturalIdMapping generateNaturalIdMapping(MappingModelCreationProcess creationProcess, PersistentClass bootEntityDescriptor) {
assert bootEntityDescriptor.hasNaturalId();
final List<SingularAttributeMapping> naturalIdAttributes = new ArrayList<>();
final Iterator<Property> iterator = bootEntityDescriptor.getPropertyIterator();
iterator.forEachRemaining(
property -> {
if ( property.isNaturalIdentifier() ) {
final AttributeMapping attributeMapping = findAttributeMapping( property.getName() );
if ( attributeMapping instanceof SingularAttributeMapping ) {
naturalIdAttributes.add( (SingularAttributeMapping) attributeMapping );
}
else {
throw new MappingException( "Natural-id only valid for singular attributes : " + property.getName() );
}
final int[] naturalIdAttributeIndexes = entityMetamodel.getNaturalIdentifierProperties();
assert naturalIdAttributeIndexes.length > 0;
if ( naturalIdAttributeIndexes.length == 1 ) {
final String propertyName = entityMetamodel.getPropertyNames()[ naturalIdAttributeIndexes[ 0 ] ];
final AttributeMapping attributeMapping = findAttributeMapping( propertyName );
return new SimpleNaturalIdMapping(
(SingularAttributeMapping) attributeMapping,
this,
creationProcess
);
}
// collect the names of the attributes making up the natural-id.
final Set<String> attributeNames = CollectionHelper.setOfSize( naturalIdAttributeIndexes.length );
for ( int naturalIdAttributeIndex : naturalIdAttributeIndexes ) {
attributeNames.add( this.getPropertyNames()[ naturalIdAttributeIndex ] );
}
// then iterate over the attribute mappings finding the ones having names
// in the collected names. iterate here because it is already alphabetical
final List<SingularAttributeMapping> collectedAttrMappings = new ArrayList<>();
this.attributeMappings.forEach(
(attributeMapping) -> {
if ( attributeNames.contains( attributeMapping.getAttributeName() ) ) {
collectedAttrMappings.add( (SingularAttributeMapping) attributeMapping );
}
}
);
if ( naturalIdAttributes.isEmpty() ) {
throw new MappingException( "Could not locate natural-id attribute(s)" );
}
if ( naturalIdAttributes.size() == 1 ) {
return new SimpleNaturalIdMapping(
naturalIdAttributes.get( 0 ),
this,
bootEntityDescriptor.getNaturalIdCacheRegionName(),
creationProcess
);
}
else {
return new CompoundNaturalIdMapping(
this,
naturalIdAttributes,
bootEntityDescriptor.getNaturalIdCacheRegionName(),
creationProcess
);
if ( collectedAttrMappings.size() <= 1 ) {
throw new MappingException( "Expected multiple natural-id attributes, but found only one: " + getEntityName() );
}
return new CompoundNaturalIdMapping(
this,
collectedAttrMappings,
creationProcess
);
}
protected static SqmMultiTableMutationStrategy interpretSqmMultiTableStrategy(

View File

@ -34,8 +34,10 @@ import org.hibernate.internal.FilterAliasGenerator;
import org.hibernate.internal.TableGroupFilterAliasGenerator;
import org.hibernate.loader.ast.spi.Loadable;
import org.hibernate.loader.ast.spi.MultiIdLoadOptions;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
import org.hibernate.loader.ast.spi.NaturalIdLoader;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.internal.InFlightEntityMappingType;
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
@ -404,6 +406,25 @@ public interface EntityPersister
*/
IdentifierGenerator getIdentifierGenerator();
@Override
default void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
final List<AttributeMapping> attributeMappings = getAttributeMappings();
if ( domainValue instanceof Object[] ) {
final Object[] values = (Object[]) domainValue;
for ( int i = 0; i < attributeMappings.size(); i++ ) {
final AttributeMapping attributeMapping = attributeMappings.get( i );
attributeMapping.breakDownJdbcValues( values[ i ], valueConsumer, session );
}
}
else {
for ( int i = 0; i < attributeMappings.size(); i++ ) {
final AttributeMapping attributeMapping = attributeMappings.get( i );
final Object attributeValue = attributeMapping.getPropertyAccess().getGetter().get( domainValue );
attributeMapping.breakDownJdbcValues( attributeValue, valueConsumer, session );
}
}
}
/**
* Determine whether this entity defines any lazy properties (ala
* bytecode instrumentation).
@ -414,7 +435,13 @@ public interface EntityPersister
default NaturalIdLoader getNaturalIdLoader() {
throw new UnsupportedOperationException(
"EntityPersister implementation `" + getClass().getName() + "` does not support `#getNaturalIdLoader`"
"EntityPersister implementation `" + getClass().getName() + "` does not support `NaturalIdLoader`"
);
}
default MultiNaturalIdLoader<?> getMultiNaturalIdLoader() {
throw new UnsupportedOperationException(
"EntityPersister implementation `" + getClass().getName() + "` does not support `MultiNaturalIdLoader`"
);
}

View File

@ -30,7 +30,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.DynamicFilterAliasGenerator;
import org.hibernate.internal.FilterAliasGenerator;
import org.hibernate.internal.util.MarkerObject;
import org.hibernate.internal.util.MutableInteger;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.mapping.Column;
@ -48,8 +47,8 @@ import org.hibernate.query.NavigablePath;
import org.hibernate.sql.InFragment;
import org.hibernate.sql.Insert;
import org.hibernate.sql.SelectFragment;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
@ -903,25 +902,22 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
String explicitSourceAlias,
boolean canUseInnerJoins,
LockMode lockMode,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAstCreationState creationState,
SqlAstCreationContext creationContext) {
final TableGroup tableGroup = super.createRootTableGroup(
navigablePath,
explicitSourceAlias,
canUseInnerJoins,
lockMode,
aliasBaseGenerator,
sqlExpressionResolver,
additionalPredicateCollectorAccess,
creationContext
creationState, creationContext
);
if ( needsDiscriminator() ) {
final Predicate discriminatorPredicate = createDiscriminatorPredicate(
tableGroup,
sqlExpressionResolver,
creationState.getSqlExpressionResolver(),
creationContext
);
additionalPredicateCollectorAccess.get().accept( discriminatorPredicate );

View File

@ -33,7 +33,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.id.IdentityGenerator;
import org.hibernate.internal.FilterAliasGenerator;
import org.hibernate.internal.StaticFilterAliasGenerator;
import org.hibernate.internal.util.MutableInteger;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.internal.util.collections.JoinedIterator;
import org.hibernate.internal.util.collections.SingletonIterator;
@ -46,9 +45,8 @@ import org.hibernate.persister.spi.PersisterCreationContext;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.SelectFragment;
import org.hibernate.sql.ast.spi.SqlAliasBase;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.UnionTableGroup;
@ -229,11 +227,10 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
String explicitSourceAlias,
boolean canUseInnerJoins,
LockMode lockMode,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAstCreationState creationState,
SqlAstCreationContext creationContext) {
final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() );
final SqlAliasBase sqlAliasBase = creationState.getSqlAliasBaseGenerator().createSqlAliasBase( getSqlAliasStem() );
final TableReference tableReference = resolvePrimaryTableReference(sqlAliasBase);

View File

@ -83,9 +83,8 @@ public class CompleteResultBuilderEntityJpa implements CompleteResultBuilderEnti
null,
false,
lockMode,
impl.getSqlAliasBaseManager(),
impl.getSqlAstCreationState().getSqlExpressionResolver(),
() -> predicate -> {},
impl.getSqlAstCreationState(),
impl.getSqlAstCreationState().getCreationContext()
)
);

View File

@ -6,9 +6,7 @@
*/
package org.hibernate.query.results.complete;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.function.BiFunction;
import org.hibernate.LockMode;
@ -16,13 +14,10 @@ import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.results.DomainResultCreationStateImpl;
import org.hibernate.query.results.FetchBuilder;
import org.hibernate.query.results.ResultBuilder;
import org.hibernate.query.results.ResultBuilderBasicValued;
import org.hibernate.query.results.ResultsHelper;
import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.sql.results.graph.entity.EntityResult;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
@ -76,10 +71,9 @@ public class CompleteResultBuilderEntityStandard implements CompleteResultBuilde
null,
false,
lockMode,
impl.getSqlAliasBaseManager(),
impl.getSqlAstCreationState().getSqlExpressionResolver(),
() -> predicate -> {},
impl.getSqlAstCreationState().getCreationContext()
impl,
impl.getCreationContext()
)
);

View File

@ -62,11 +62,10 @@ public class ImplicitModelPartResultBuilderEntity
null,
false,
LockMode.READ,
creationStateImpl.getSqlAliasBaseGenerator(),
creationStateImpl,
() -> predicate -> {
},
creationStateImpl.getSqlAstCreationState().getCreationContext()
creationStateImpl,
creationStateImpl.getCreationContext()
);
}
);

View File

@ -83,12 +83,10 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter<Sta
mutatingEntityExplicitAlias,
true,
LockMode.PESSIMISTIC_WRITE,
getSqlAliasBaseGenerator(),
getSqlExpressionResolver(),
() -> predicate -> {
},
creationContext.getSessionFactory()
);
this,
creationContext.getSessionFactory() );
getFromClauseAccess().registerTableGroup( navigablePath, mutatingTableGroup );
}

View File

@ -215,6 +215,7 @@ import org.hibernate.sql.ast.tree.expression.ExtractUnit;
import org.hibernate.sql.ast.tree.expression.Format;
import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.expression.NullnessLiteral;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression;
import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression;
@ -529,9 +530,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
sqmStatement.getRoot().getAlias(),
false,
LockMode.WRITE,
getSqlAliasBaseGenerator(),
getSqlExpressionResolver(),
() -> predicate -> additionalRestrictions = predicate,
this,
getCreationContext()
);
@ -725,9 +725,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
statement.getRoot().getAlias(),
false,
LockMode.WRITE,
stem -> getSqlAliasBaseGenerator().createSqlAliasBase( stem ),
getSqlExpressionResolver(),
() -> predicate -> additionalRestrictions = predicate,
this,
getCreationContext()
);
getFromClauseAccess().registerTableGroup( rootPath, rootTableGroup );
@ -804,9 +803,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
sqmStatement.getTarget().getExplicitAlias(),
false,
LockMode.WRITE,
stem -> getSqlAliasBaseGenerator().createSqlAliasBase( stem ),
getSqlExpressionResolver(),
() -> predicate -> additionalRestrictions = predicate,
this,
getCreationContext()
);
@ -867,9 +865,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
sqmStatement.getTarget().getExplicitAlias(),
false,
LockMode.WRITE,
stem -> getSqlAliasBaseGenerator().createSqlAliasBase( stem ),
getSqlExpressionResolver(),
() -> predicate -> additionalRestrictions = predicate,
this,
getCreationContext()
);
@ -1543,9 +1540,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
sqmRoot.getExplicitAlias(),
true,
LockMode.NONE,
sqlAliasBaseManager,
sqlExpressionResolver,
() -> predicate -> {},
this,
creationContext
);
final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping();
@ -1611,12 +1607,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
sqmRoot.getExplicitAlias(),
true,
LockMode.NONE,
sqlAliasBaseManager,
sqlExpressionResolver,
() -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
predicate
),
this,
creationContext
);
}
@ -1764,12 +1759,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
sqmJoin.getExplicitAlias(),
true,
determineLockMode( sqmJoin.getExplicitAlias() ),
sqlAliasBaseManager,
getSqlExpressionResolver(),
() -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
predicate
),
this,
getCreationContext()
);
@ -1795,12 +1789,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
sqmJoin.getExplicitAlias(),
true,
determineLockMode( sqmJoin.getExplicitAlias() ),
sqlAliasBaseManager,
getSqlExpressionResolver(),
() -> predicate -> additionalRestrictions = SqlAstTreeHelper.combinePredicates(
additionalRestrictions,
predicate
),
this,
getCreationContext()
);
getFromClauseIndex().register( sqmJoin, tableGroup );
@ -2028,7 +2021,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override
public Expression visitLiteral(SqmLiteral<?> literal) {
if ( literal instanceof SqmLiteralNull ) {
return new QueryLiteral<>( null, (BasicValuedMapping) inferableTypeAccessStack.getCurrent().get() );
return new NullnessLiteral( inferableTypeAccessStack.getCurrent().get() );
}
return new QueryLiteral<>(
@ -3349,9 +3342,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
null,
true,
LockOptions.NONE.getLockMode(),
sqlAliasBaseManager,
getSqlExpressionResolver(),
() -> querySpec::applyPredicate,
this,
creationContext
);

View File

@ -26,6 +26,7 @@ import org.hibernate.sql.ast.tree.expression.ExtractUnit;
import org.hibernate.sql.ast.tree.expression.Format;
import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.expression.NullnessLiteral;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression;
import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression;
@ -142,6 +143,8 @@ public interface SqlAstWalker {
void visitQueryLiteral(QueryLiteral queryLiteral);
void visitNullnessLiteral(NullnessLiteral nullnessLiteral);
void visitUnaryOperationExpression(UnaryOperation unaryOperationExpression);
void visitBetweenPredicate(BetweenPredicate betweenPredicate);

View File

@ -73,6 +73,7 @@ import org.hibernate.sql.ast.tree.expression.JdbcLiteral;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.LiteralAsParameter;
import org.hibernate.sql.ast.tree.expression.NullnessLiteral;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression;
import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression;
@ -2562,6 +2563,12 @@ public abstract class AbstractSqlAstWalker
visitLiteral( queryLiteral );
}
@Override
public void visitNullnessLiteral(NullnessLiteral nullnessLiteral) {
// todo (6.0) : account for composite nulls?
appendSql( "null" );
}
@SuppressWarnings("unchecked")
private void visitLiteral(Literal literal) {
if ( literal.getLiteralValue() == null ) {

View File

@ -0,0 +1,31 @@
/*
* 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.sql.ast.tree.expression;
import org.hibernate.metamodel.mapping.MappingModelExpressable;
import org.hibernate.sql.ast.SqlAstWalker;
/**
* Assertion that the SQL state of a "mapping model expressable" is null
*/
public class NullnessLiteral implements Expression {
private final MappingModelExpressable<?> valueMapping;
public NullnessLiteral(MappingModelExpressable<?> valueMapping) {
this.valueMapping = valueMapping;
}
@Override
public MappingModelExpressable<?> getExpressionType() {
return valueMapping;
}
@Override
public void accept(SqlAstWalker sqlTreeWalker) {
sqlTreeWalker.visitNullnessLiteral( this );
}
}

View File

@ -12,9 +12,8 @@ import java.util.function.Supplier;
import org.hibernate.LockMode;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.tree.predicate.Predicate;
/**
@ -32,8 +31,6 @@ public interface RootTableGroupProducer extends TableGroupProducer, ModelPartCon
String explicitSourceAlias,
boolean canUseInnerJoins,
LockMode lockMode,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAstCreationContext creationContext);
SqlAstCreationState creationState, SqlAstCreationContext creationContext);
}

View File

@ -16,6 +16,10 @@ public class NullnessPredicate implements Predicate {
private final Expression expression;
private final boolean negated;
public NullnessPredicate(Expression expression) {
this( expression, false );
}
public NullnessPredicate(Expression expression, boolean negated) {
this.expression = expression;
this.negated = negated;

View File

@ -149,6 +149,11 @@ public class CircularBiDirectionalFetchImpl implements BiDirectionalFetch, Assoc
return ( (Association) fetchParent ).getForeignKeyDescriptor();
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
fetchable.breakDownJdbcValues( domainValue, valueConsumer, session );
}
@Override
public Fetch generateFetch(
FetchParent fetchParent,

View File

@ -8,6 +8,7 @@ package org.hibernate.sql.results.internal.domain;
import org.hibernate.LockMode;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.mapping.Association;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
@ -180,6 +181,11 @@ public class CircularFetchImpl implements BiDirectionalFetch, Association {
return ( (Association) fetchParent ).getForeignKeyDescriptor();
}
@Override
public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) {
fetchable.breakDownJdbcValues( domainValue, valueConsumer, session );
}
@Override
public Fetch generateFetch(
FetchParent fetchParent,

View File

@ -11,7 +11,7 @@ import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.internal.NonAggregatedIdentifierMappingImpl;
import org.hibernate.metamodel.mapping.internal.EmbeddedIdentifierMappingImpl;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.orm.junit.ServiceRegistry;
@ -30,7 +30,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
@ServiceRegistry(
settings = @Setting( name = AvailableSettings.HBM2DDL_AUTO, value = "create-drop" )
)
public class DynamicCompositeIdBasicTests {
public class DynamicCompositeIdBasicBindingTests {
@Test
public void testBinding(ServiceRegistryScope scope) {
final SessionFactoryImplementor sessionFactory = (SessionFactoryImplementor) new MetadataSources( scope.getRegistry() )
@ -43,11 +43,11 @@ public class DynamicCompositeIdBasicTests {
.getMappingMetamodel()
.findEntityDescriptor( "DynamicCompositeIdBasic" );
assertThat( entityDescriptor.getNumberOfAttributeMappings(), is( 3 ) );
assertThat( entityDescriptor.getNumberOfAttributeMappings(), is( 1 ) );
final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping();
assertThat( identifierMapping, instanceOf( NonAggregatedIdentifierMappingImpl.class ) );
final NonAggregatedIdentifierMappingImpl cid = (NonAggregatedIdentifierMappingImpl) identifierMapping;
assertThat( identifierMapping, instanceOf( EmbeddedIdentifierMappingImpl.class ) );
final EmbeddedIdentifierMappingImpl cid = (EmbeddedIdentifierMappingImpl) identifierMapping;
assertThat( cid.getEmbeddableTypeDescriptor().getNumberOfAttributeMappings(), is( 2 ) );
final AttributeMapping key1 = cid.getEmbeddableTypeDescriptor().findAttributeMapping( "key1" );
@ -58,10 +58,6 @@ public class DynamicCompositeIdBasicTests {
final AttributeMapping attr1 = entityDescriptor.findAttributeMapping( "attr1" );
assertThat( attr1, notNullValue() );
sessionFactory.inTransaction(
session -> session.createQuery( "from DynamicCompositeIdBasic e where e.key1 = 1" ).list()
);
}
finally {
sessionFactory.close();

View File

@ -0,0 +1,54 @@
/*
* 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.bootstrap.binding.hbm.cid.nonaggregated.dynamic;
import org.hibernate.boot.MetadataSources;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.internal.EmbeddedIdentifierMappingImpl;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.ServiceRegistryScope;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author Steve Ebersole
*/
@ServiceRegistry(
settings = @Setting( name = AvailableSettings.HBM2DDL_AUTO, value = "create-drop" )
)
@DomainModel( xmlMappings = "org/hibernate/orm/test/bootstrap/binding/hbm/cid/nonaggregated/dynamic/DynamicCompositeIdBasic.hbm.xml" )
@SessionFactory
public class DynamicCompositeIdBasicUsageTests {
@Test
public void testFullQueryReference(SessionFactoryScope scope) {
scope.inTransaction(
session -> session.createQuery( "from DynamicCompositeIdBasic e where e.id.key1 = 1" ).list()
);
}
@Test
@FailureExpected( reason = "Do we want to allow this?" )
public void testEmbeddedQueryReference(SessionFactoryScope scope) {
scope.inTransaction(
session -> session.createQuery( "from DynamicCompositeIdBasic e where e.key1 = 1" ).list()
);
}
}

View File

@ -12,7 +12,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.internal.BasicValuedSingularAttributeMapping;
import org.hibernate.metamodel.mapping.internal.NonAggregatedIdentifierMappingImpl;
import org.hibernate.metamodel.mapping.internal.EmbeddedIdentifierMappingImpl;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.persister.entity.EntityPersister;
@ -25,7 +25,6 @@ import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hibernate.testing.transaction.TransactionUtil2.inTransaction;
/**
* Note that this test uses a composite-id with key-many-to-one as part of a
@ -36,7 +35,7 @@ import static org.hibernate.testing.transaction.TransactionUtil2.inTransaction;
@ServiceRegistry(
settings = @Setting( name = AvailableSettings.HBM2DDL_AUTO, value = "create-drop" )
)
public class DynamicCompositeIdManyToOneTests {
public class DynamicCompositeIdManyToOneBindingTests {
@Test
public void testBinding(ServiceRegistryScope scope) {
final SessionFactoryImplementor sessionFactory = (SessionFactoryImplementor) new MetadataSources( scope.getRegistry() )
@ -49,11 +48,11 @@ public class DynamicCompositeIdManyToOneTests {
.getMappingMetamodel()
.findEntityDescriptor( "DynamicCompositeIdManyToOne" );
assertThat( entityDescriptor.getNumberOfAttributeMappings(), is( 3 ) );
assertThat( entityDescriptor.getNumberOfAttributeMappings(), is( 1 ) );
final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping();
assertThat( identifierMapping, instanceOf( NonAggregatedIdentifierMappingImpl.class ) );
final NonAggregatedIdentifierMappingImpl cid = (NonAggregatedIdentifierMappingImpl) identifierMapping;
assertThat( identifierMapping, instanceOf( EmbeddedIdentifierMappingImpl.class ) );
final EmbeddedIdentifierMappingImpl cid = (EmbeddedIdentifierMappingImpl) identifierMapping;
assertThat( cid.getEmbeddableTypeDescriptor().getNumberOfAttributeMappings(), is( 2 ) );
final AttributeMapping key1 = cid.getEmbeddableTypeDescriptor().findAttributeMapping( "key1" );
@ -67,17 +66,6 @@ public class DynamicCompositeIdManyToOneTests {
final AttributeMapping attr1 = entityDescriptor.findAttributeMapping( "attr1" );
assertThat( attr1, notNullValue() );
assertThat( attr1, instanceOf( BasicValuedSingularAttributeMapping.class ) );
assertThat( entityDescriptor.getNumberOfAttributeMappings(), is( 3 ) );
inTransaction(
sessionFactory,
session -> {
session.createQuery( "select e__ from DynamicCompositeIdManyToOne e__" ).list();
session.createQuery( "select e__ from DynamicCompositeIdManyToOne e__ where e__.key1 = 1" ).list();
session.createQuery( "select e__ from DynamicCompositeIdManyToOne e__ where e__.key2.name = 'abc'" ).list();
}
);
}
finally {
sessionFactory.close();

View File

@ -0,0 +1,53 @@
/*
* 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.bootstrap.binding.hbm.cid.nonaggregated.dynamic;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.FailureExpected;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Test;
/**
* Note that this test uses a composite-id with key-many-to-one as part of a
* dynamic model, which is the main construct needed by hibernate-envers
*
* @author Steve Ebersole
*/
@ServiceRegistry(
settings = @Setting( name = AvailableSettings.HBM2DDL_AUTO, value = "create-drop" )
)
@DomainModel( xmlMappings = "org/hibernate/orm/test/bootstrap/binding/hbm/cid/nonaggregated/dynamic/DynamicCompositeIdManyToOne.hbm.xml" )
@SessionFactory
public class DynamicCompositeIdManyToOneUsageTests {
@Test
public void testFullQueryReference(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> {
session.createQuery( "select e__ from DynamicCompositeIdManyToOne e__" ).list();
session.createQuery( "select e__ from DynamicCompositeIdManyToOne e__ where e__.id.key1 = 1" ).list();
session.createQuery( "select e__ from DynamicCompositeIdManyToOne e__ where e__.id.key2.name = 'abc'" ).list();
}
);
}
@Test
@FailureExpected( reason = "Do we want to allow this?" )
public void testEmbeddedQueryReference(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> {
session.createQuery( "select e__ from DynamicCompositeIdManyToOne e__" ).list();
session.createQuery( "select e__ from DynamicCompositeIdManyToOne e__ where e__.key1 = 1" ).list();
session.createQuery( "select e__ from DynamicCompositeIdManyToOne e__ where e__.key2.name = 'abc'" ).list();
}
);
}
}

View File

@ -0,0 +1,90 @@
/*
* 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.mapping.naturalid.cid;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadata;
import org.hibernate.metamodel.mapping.internal.SimpleNaturalIdMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.TestForIssue;
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;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
/**
* @author Steve Ebersole
*/
public abstract class AbstractCompositeIdAndNaturalIdTest {
@Test
@TestForIssue( jiraKey = "HHH-10360")
public void testNaturalIdNullability(SessionFactoryScope scope) {
final EntityMappingType accountMapping = scope.getSessionFactory().getRuntimeMetamodels().getEntityMappingType( Account.class );
final SingularAttributeMapping shortCodeMapping = ((SimpleNaturalIdMapping) accountMapping.getNaturalIdMapping()).getAttribute();
final StateArrayContributorMetadata shortCodeMetadata = shortCodeMapping.getAttributeMetadataAccess().resolveAttributeMetadata( null );
assertThat( shortCodeMetadata.isNullable(), is( false ) );
final EntityPersister rootEntityPersister = accountMapping.getRootEntityDescriptor().getEntityPersister();
final int shortCodeLegacyPropertyIndex = rootEntityPersister.getEntityMetamodel().getPropertyIndex( "shortCode" );
assertThat( shortCodeLegacyPropertyIndex, is ( 0 ) );
assertThat( rootEntityPersister.getPropertyNullability()[ shortCodeLegacyPropertyIndex ], is( false ) );
}
public static final String NATURAL_ID_VALUE = "testAcct";
@BeforeEach
public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> {
// prepare some test data...
Account account = new Account( new AccountId( 1 ), NATURAL_ID_VALUE );
session.save( account );
}
);
}
@AfterEach
public void cleanUpTestData(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> {
session.createQuery( "delete Account" ).executeUpdate();
}
);
}
@Test
public void testNaturalIdCriteria(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> {
final Account account = session.bySimpleNaturalId( Account.class ).load( NATURAL_ID_VALUE );
assertThat( account, notNullValue() );
}
);
}
@Test
public void testNaturalIdApi(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> {
final Account account = session.bySimpleNaturalId( Account.class ).load( NATURAL_ID_VALUE );
assertThat( account, notNullValue() );
}
);
scope.inTransaction(
(session) -> {
final Account account = session.byNaturalId( Account.class ).using( "shortCode", NATURAL_ID_VALUE ).load();
assertThat( account, notNullValue() );
}
);
}
}

View File

@ -7,7 +7,7 @@
-->
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.hibernate.test.naturalid.cid" default-access="field">
<hibernate-mapping package="org.hibernate.orm.test.mapping.naturalid.cid" default-access="field">
<class name="Account" table="accounts" >
<composite-id name="accountId">
<key-property name="id"/>

View File

@ -4,13 +4,26 @@
* 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.test.naturalid.cid;
package org.hibernate.orm.test.mapping.naturalid.cid;
import javax.persistence.Basic;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.hibernate.annotations.NaturalId;
/**
* @author Donnchadh O Donnabhain
*/
@Entity
@Table( name = "t_acct" )
public class Account {
@EmbeddedId
private AccountId accountId;
@Basic( optional = false )
@NaturalId
private String shortCode;
protected Account() {

View File

@ -4,11 +4,14 @@
* 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.test.naturalid.cid;
package org.hibernate.orm.test.mapping.naturalid.cid;
import javax.persistence.Embeddable;
/**
* @author Donnchadh O Donnabhain
*/
@Embeddable
public class AccountId implements java.io.Serializable {
private final int id;

View File

@ -0,0 +1,31 @@
/*
* 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.mapping.naturalid.cid;
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.Setting;
import static org.hibernate.cfg.AvailableSettings.GENERATE_STATISTICS;
import static org.hibernate.cfg.AvailableSettings.USE_QUERY_CACHE;
import static org.hibernate.cfg.AvailableSettings.USE_SECOND_LEVEL_CACHE;
/**
* @author Donnchadh O Donnabhain
*/
@ServiceRegistry(
settings = {
@Setting( name = USE_SECOND_LEVEL_CACHE, value = "false" ),
@Setting( name = USE_QUERY_CACHE, value = "false" ),
@Setting( name = GENERATE_STATISTICS, value = "false" )
}
)
@DomainModel( annotatedClasses = { Account.class, AccountId.class } )
@SessionFactory
public class AnnotatedCompositeIdAndNaturalIdTest extends AbstractCompositeIdAndNaturalIdTest {
}

View File

@ -0,0 +1,31 @@
/*
* 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.mapping.naturalid.cid;
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.Setting;
import static org.hibernate.cfg.AvailableSettings.GENERATE_STATISTICS;
import static org.hibernate.cfg.AvailableSettings.USE_QUERY_CACHE;
import static org.hibernate.cfg.AvailableSettings.USE_SECOND_LEVEL_CACHE;
/**
* @author Donnchadh O Donnabhain
*/
@ServiceRegistry(
settings = {
@Setting( name = USE_SECOND_LEVEL_CACHE, value = "false" ),
@Setting( name = USE_QUERY_CACHE, value = "false" ),
@Setting( name = GENERATE_STATISTICS, value = "false" )
}
)
@DomainModel( xmlMappings = "org/hibernate/orm/test/mapping/naturalid/cid/Account.hbm.xml" )
@SessionFactory
public class HbmCompositeIdAndNaturalIdTest extends AbstractCompositeIdAndNaturalIdTest {
}

View File

@ -4,7 +4,7 @@
* 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.test.naturalid.composite;
package org.hibernate.orm.test.mapping.naturalid.composite;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
@ -13,14 +13,13 @@ import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.Test;
import org.junit.jupiter.api.Test;
/**
* @author Steve Ebersole
*/
@TestForIssue(jiraKey = "HHH-11255")
public class CompositeNaturalIdMappingTest extends BaseUnitTestCase {
public class CompositeNaturalIdMappingTest {
@Test
public void test() {

View File

@ -0,0 +1,69 @@
/*
* 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.mapping.naturalid.composite;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.DomainModel;
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;
import static org.junit.Assert.assertEquals;
/**
* @author Vlad Mihalcea
*/
@DomainModel( annotatedClasses = PostalCarrier.class )
@SessionFactory
@TestForIssue(jiraKey = "HHH-11255")
public class EmbeddedNaturalIdTest {
@BeforeEach
public void createTestData(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> {
PostalCarrier postalCarrier = new PostalCarrier();
postalCarrier.setId( 1L );
postalCarrier.setPostalCode( new PostalCode() );
postalCarrier.getPostalCode().setCode( "ABC123" );
postalCarrier.getPostalCode().setCountry( "US" );
session.persist( postalCarrier );
}
);
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> session.createQuery( "delete PostalCarrier" ).executeUpdate()
);
}
@Test
public void test(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> {
final PostalCarrier postalCarrier = session
.byNaturalId( PostalCarrier.class )
.using( "postalCode", new PostalCode( "ABC123", "US" ) )
.load();
assertEquals( Long.valueOf( 1L ), postalCarrier.getId() );
}
);
scope.inTransaction(
(session) -> {
final PostalCarrier postalCarrier = session
.bySimpleNaturalId( PostalCarrier.class )
.load(new PostalCode( "ABC123", "US" ) );
assertEquals( Long.valueOf( 1L ), postalCarrier.getId() );
}
);
}
}

View File

@ -4,7 +4,7 @@
* 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.test.naturalid.composite;
package org.hibernate.orm.test.mapping.naturalid.composite;
import javax.persistence.Embedded;
import javax.persistence.Entity;

View File

@ -4,7 +4,7 @@
* 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.test.naturalid.composite;
package org.hibernate.orm.test.mapping.naturalid.composite;
import javax.persistence.Embeddable;

View File

@ -0,0 +1,68 @@
/*
* 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.mapping.naturalid.compound;
import java.util.List;
import org.hibernate.SessionFactory;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.TestForIssue;
import org.junit.jupiter.api.Test;
import org.hamcrest.Matchers;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
/**
* @author Steve Ebersole
*/
@TestForIssue(jiraKey = "HHH-11255")
public class CompoundNaturalIdMappingTest {
@Test
public void test() {
final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder()
.build();
try {
Metadata meta = new MetadataSources( ssr )
.addAnnotatedClass( PostalCarrier.class )
.addAnnotatedClass( Country.class )
.buildMetadata();
( (MetadataImplementor) meta ).validate();
final SessionFactoryImplementor sessionFactory = (SessionFactoryImplementor) meta.buildSessionFactory();
try {
final EntityMappingType entityMappingType = sessionFactory.getRuntimeMetamodels().getEntityMappingType( PostalCarrier.class );
final NaturalIdMapping naturalIdMapping = entityMappingType.getNaturalIdMapping();
final List<SingularAttributeMapping> naturalIdAttributes = naturalIdMapping.getNaturalIdAttributes();
assertThat( naturalIdAttributes.size(), is( 2 ) );
assertThat( naturalIdAttributes.get( 0 ).getAttributeName(), is( "code" ) );
assertThat( naturalIdAttributes.get( 1 ).getAttributeName(), is( "country" ) );
}
finally {
sessionFactory.close();
}
}
finally {
StandardServiceRegistryBuilder.destroy( ssr );
}
}
}

View File

@ -0,0 +1,48 @@
package org.hibernate.orm.test.mapping.naturalid.compound;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* @author Steve Ebersole
*/
@Entity
public class Country {
@Id
private Integer id;
private String isoCode;
private String name;
public Country() {
}
public Country(Integer id, String isoCode, String name) {
this.id = id;
this.isoCode = isoCode;
this.name = name;
}
public Integer getId() {
return id;
}
private void setId(Integer id) {
this.id = id;
}
public String getIsoCode() {
return isoCode;
}
public void setIsoCode(String isoCode) {
this.isoCode = isoCode;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.mapping.naturalid.compound;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.hibernate.annotations.NaturalId;
/**
* @author Steve Ebersole
*/
@Entity(name = "PostalCarrier")
public class PostalCarrier {
@Id
private Long id;
@NaturalId
@ManyToOne
private Country country;
@NaturalId
private String code;
public PostalCarrier() {
}
public PostalCarrier(long id, String code, Country country) {
this.id = id;
this.code = code;
this.country = country;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Country getCountry() {
return country;
}
public void setCountry(Country country) {
this.country = country;
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.mapping.naturalid.immutable;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.hibernate.annotations.NaturalId;
/**
* @author Alex Burgel
*/
@Entity
public class Child {
@Id
private Integer id;
@NaturalId
private String name;
@NaturalId( )
@ManyToOne
private Parent parent;
Child() {}
public Child(Integer id, String name, Parent parent) {
this.id = id;
this.name = name;
this.parent = parent;
}
public Integer getId() {
return id;
}
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,123 @@
/*
* 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.mapping.naturalid.immutable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.NaturalId;
import org.hibernate.annotations.NaturalIdCache;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
import org.hibernate.testing.TestForIssue;
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.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.hamcrest.Matchers;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author Alex Burgel
*/
@TestForIssue( jiraKey = "HHH-11330" )
@RequiresDialectFeature( value = DialectChecks.SupportsIdentityColumns.class )
@ServiceRegistry(
settings = {
@Setting( name = AvailableSettings.GENERATE_STATISTICS, value = "true" ),
@Setting( name = AvailableSettings.USE_SECOND_LEVEL_CACHE, value = "true" )
}
)
@DomainModel( annotatedClasses = IdentityGeneratorWithNaturalIdCacheTest.Person.class )
@SessionFactory
public class IdentityGeneratorWithNaturalIdCacheTest {
@BeforeEach
public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> {
Person person = new Person();
person.setName( "John Doe" );
session.persist( person );
}
);
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> session.createQuery( "delete Person" ).executeUpdate()
);
}
@Test
@TestForIssue(jiraKey = "HHH-10659")
public void testNaturalIdCacheEntry(SessionFactoryScope scope) {
final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
statistics.clear();
assertThat( statistics.getSecondLevelCacheHitCount(), Matchers.is( 0L ) );
assertThat( statistics.getNaturalIdCacheHitCount(), Matchers.is( 0L ) );
scope.inTransaction(
(session) -> {
session.bySimpleNaturalId( Person.class ).load( "John Doe" );
assertThat( statistics.getSecondLevelCacheHitCount(), Matchers.is( 0L ) );
assertThat( statistics.getNaturalIdCacheHitCount(), Matchers.is( 1L ) );
}
);
scope.inTransaction(
(session) -> {
session.bySimpleNaturalId( Person.class ).load( "John Doe" );
assertThat( statistics.getSecondLevelCacheHitCount(), Matchers.is( 1L ) );
assertThat( statistics.getNaturalIdCacheHitCount(), Matchers.is( 2L ) );
}
);
}
@Entity(name = "Person")
@NaturalIdCache
@Cache( usage = CacheConcurrencyStrategy.READ_ONLY )
public static class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NaturalId
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@ -0,0 +1,73 @@
/*
* 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.mapping.naturalid.immutable;
import org.hibernate.metamodel.RuntimeMetamodels;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadata;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.tuple.entity.EntityMetamodel;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@DomainModel( annotatedClasses = { Child.class, Parent.class } )
@SessionFactory
public class ImmutableManyToOneNaturalIdAnnotationTest {
@Test
@TestForIssue( jiraKey = "HHH-10360")
public void testNaturalIdNullability(SessionFactoryScope scope) {
// nullability is not specified for either properties making up
// the natural ID, so they should be nullable by annotation-specific default
final RuntimeMetamodels runtimeMetamodels = scope.getSessionFactory().getRuntimeMetamodels();
final EntityMappingType childMapping = runtimeMetamodels.getEntityMappingType( Child.class.getName() );
final EntityPersister persister = childMapping.getEntityPersister();
final EntityMetamodel entityMetamodel = persister.getEntityMetamodel();
final int nameIndex = entityMetamodel.getPropertyIndex( "name" );
final int parentIndex = entityMetamodel.getPropertyIndex( "parent" );
// checking alphabetic sort in relation to EntityPersister/EntityMetamodel
assertThat( nameIndex, lessThan( parentIndex ) );
assertFalse( persister.getPropertyUpdateability()[ nameIndex ] );
assertFalse( persister.getPropertyUpdateability()[ parentIndex ] );
assertTrue( persister.getPropertyNullability()[ nameIndex ] );
assertTrue( persister.getPropertyNullability()[ parentIndex ] );
final NaturalIdMapping naturalIdMapping = childMapping.getNaturalIdMapping();
assertNotNull( naturalIdMapping );
assertThat( naturalIdMapping.getNaturalIdAttributes().size(), is( 2 ) );
// access by list-index should again be alphabetically sorted
final SingularAttributeMapping first = naturalIdMapping.getNaturalIdAttributes().get( 0 );
assertThat( first.getAttributeName(), is( "name" ) );
final StateArrayContributorMetadata firstMetadata = first.getAttributeMetadataAccess().resolveAttributeMetadata( null );
assertFalse( firstMetadata.getMutabilityPlan().isMutable() );
final SingularAttributeMapping second = naturalIdMapping.getNaturalIdAttributes().get( 1 );
assertThat( second.getAttributeName(), is( "parent" ) );
final StateArrayContributorMetadata secondMetadata = second.getAttributeMetadataAccess().resolveAttributeMetadata( null );
assertFalse( secondMetadata.getMutabilityPlan().isMutable() );
}
}

View File

@ -0,0 +1,157 @@
/*
* 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.mapping.naturalid.immutable;
import javax.persistence.PersistenceException;
import org.hibernate.cfg.Environment;
import org.hibernate.metamodel.RuntimeMetamodels;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadata;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.tuple.entity.EntityMetamodel;
import org.hibernate.testing.TestForIssue;
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.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
* @author Alex Burgel
*/
@ServiceRegistry(
settings = {
@Setting( name = Environment.USE_SECOND_LEVEL_CACHE, value = "true" ),
@Setting( name = Environment.USE_QUERY_CACHE, value = "true" ),
@Setting( name = Environment.GENERATE_STATISTICS, value = "true" )
}
)
@DomainModel( xmlMappings = "org/hibernate/orm/test/mapping/naturalid/immutable/ParentChildWithManyToOne.hbm.xml" )
@SessionFactory
public class ImmutableManyToOneNaturalIdHbmTest {
@BeforeEach
public void createTestData(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> {
Parent p = new Parent( 1, "alex" );
Child c = new Child( 1, "billy", p );
session.persist( p );
session.persist( c );
}
);
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> {
session.createQuery( "delete Child" ).executeUpdate();
session.createQuery( "delete Parent" ).executeUpdate();
}
);
}
@Test
@TestForIssue( jiraKey = "HHH-10360")
public void checkingMapping(SessionFactoryScope scope) {
final RuntimeMetamodels runtimeMetamodels = scope.getSessionFactory().getRuntimeMetamodels();
final EntityMappingType childMapping = runtimeMetamodels.getEntityMappingType( Child.class.getName() );
final EntityPersister persister = childMapping.getEntityPersister();
final EntityMetamodel entityMetamodel = persister.getEntityMetamodel();
final int nameIndex = entityMetamodel.getPropertyIndex( "name" );
final int parentIndex = entityMetamodel.getPropertyIndex( "parent" );
// checking alphabetic sort in relation to EntityPersister/EntityMetamodel
assertThat( nameIndex, lessThan( parentIndex ) );
assertFalse( persister.getPropertyUpdateability()[ nameIndex ] );
assertFalse( persister.getPropertyUpdateability()[ parentIndex ] );
// nullability is not specified for either properties making up
// the natural ID, so they should be non-nullable by hbm-specific default
assertFalse( persister.getPropertyNullability()[ nameIndex ] );
assertFalse( persister.getPropertyNullability()[ parentIndex ] );
final NaturalIdMapping naturalIdMapping = childMapping.getNaturalIdMapping();
assertNotNull( naturalIdMapping );
assertThat( naturalIdMapping.getNaturalIdAttributes().size(), is( 2 ) );
final SingularAttributeMapping first = naturalIdMapping.getNaturalIdAttributes().get( 0 );
assertThat( first.getAttributeName(), is( "name" ) );
final StateArrayContributorMetadata firstMetadata = first.getAttributeMetadataAccess().resolveAttributeMetadata( null );
assertFalse( firstMetadata.getMutabilityPlan().isMutable() );
final SingularAttributeMapping second = naturalIdMapping.getNaturalIdAttributes().get( 1 );
assertThat( second.getAttributeName(), is( "parent" ) );
final StateArrayContributorMetadata secondMetadata = second.getAttributeMetadataAccess().resolveAttributeMetadata( null );
assertFalse( secondMetadata.getMutabilityPlan().isMutable() );
}
@Test
public void testNaturalIdCheck(SessionFactoryScope scope) {
final Child child = scope.fromTransaction( (s) -> s.get( Child.class, 1 ) );
// child is detached...
// - change the name and attempt to reattach it, which should fail
// because name is defined as an immutable natural-id
child.setName( "phil" );
scope.inTransaction(
(s) -> {
try {
s.saveOrUpdate( child );
s.flush();
fail( "should have failed because immutable natural ID was altered");
}
catch (PersistenceException e) {
// expected
}
}
);
}
@Test
@SuppressWarnings( {"unchecked"})
public void testSaveParentWithDetachedChildren(SessionFactoryScope scope) {
final Parent p = scope.fromTransaction(
(session) -> session.createQuery( "from Parent p join fetch p.children where p.name = 'alex'", Parent.class )
.setCacheable( true )
.uniqueResult()
);
// parent and its child are detached...
// - create a new child and associate it with the parent and reattach
// NOTE : this fails if AbstractEntityPersister returns identifiers instead of entities from
// AbstractEntityPersister.getNaturalIdSnapshot()
// todo (6.0) : ^^ this test has nothing to do with (im)mutability...
Child c2 = new Child( 2, "joey", p );
p.getChildren().add( c2 );
scope.inTransaction( (session) -> session.update( p ) );
}
}

View File

@ -0,0 +1,178 @@
/*
* 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.mapping.naturalid.immutable;
import javax.persistence.PersistenceException;
import org.hibernate.Session;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.testing.TestForIssue;
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.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
/**
* @author Steve Ebersole
*/
@ServiceRegistry(
settings = {
@Setting( name = AvailableSettings.USE_SECOND_LEVEL_CACHE, value = "true" ),
@Setting( name = AvailableSettings.USE_QUERY_CACHE, value = "true" ),
@Setting( name = AvailableSettings.GENERATE_STATISTICS, value = "true" )
}
)
@DomainModel( xmlMappings = "org/hibernate/orm/test/mapping/naturalid/immutable/User.hbm.xml" )
@SessionFactory
public class ImmutableNaturalIdTest {
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> session.createQuery( "delete User" ).executeUpdate() );
}
@Test
@TestForIssue( jiraKey = "HHH-10360")
public void verifyMetamodel(SessionFactoryScope scope) {
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
final EntityMappingType entityMappingType = sessionFactory.getRuntimeMetamodels().getEntityMappingType( User.class );
final AttributeMapping userNameMapping = entityMappingType.findAttributeMapping( "userName" );
assertFalse( userNameMapping.getAttributeMetadataAccess().resolveAttributeMetadata( null ).isNullable() );
final EntityPersister persister = entityMappingType.getEntityPersister();
final int propertyIndex = persister.getEntityMetamodel().getPropertyIndex( "userName" );
// nullability is not specified, so it should be non-nullable by hbm-specific default
assertFalse( persister.getPropertyNullability()[propertyIndex] );
}
@Test
public void testNaturalIdCheck(SessionFactoryScope scope) {
final User detachedUser = scope.fromTransaction(
(session) -> {
final User user = new User( "steve", "superSecret" );
session.persist( user );
return user;
}
);
// try to change the user-name (natural-id) and re-attach... should error
detachedUser.setUserName( "Steve" );
try {
scope.inTransaction(
(session) -> {
session.merge( detachedUser );
}
);
fail();
}
catch (PersistenceException expected) {
// expected outcome
}
}
@Test
public void testNaturalIdCache(SessionFactoryScope scope) {
final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
scope.inTransaction(
(session) -> {
final User u = new User( "steve", "superSecret" );
session.persist( u );
}
);
statistics.clear();
scope.inTransaction(
(session) -> {
final User steve = session.bySimpleNaturalId( User.class ).load( "steve" );
assertNotNull( steve );
}
);
assertEquals( 0, statistics.getNaturalIdCacheHitCount() );
assertEquals( 0, statistics.getNaturalIdCachePutCount() );
scope.inTransaction(
(session) -> {
session.persist( new User( "gavin", "supsup" ) );
}
);
statistics.clear();
scope.inTransaction(
(session) -> {
final User steve = session.bySimpleNaturalId( User.class ).load( "steve" );
assertNotNull( steve );
assertEquals( 0, statistics.getNaturalIdCacheHitCount() );
final User steve2 = session.bySimpleNaturalId( User.class ).load( "steve" );
assertNotNull( steve2 );
assertEquals( 0, statistics.getNaturalIdCacheHitCount() );
}
);
}
@Test
public void testNaturalIdDeleteUsingCache(SessionFactoryScope scope) {
final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
scope.inTransaction(
(session) -> {
final User u = new User( "steve", "superSecret" );
session.persist( u );
}
);
statistics.clear();
scope.inTransaction(
(session) -> {
final User steve = session.bySimpleNaturalId( User.class ).load( "steve" );
assertNotNull( steve );
}
);
assertEquals( 0, statistics.getNaturalIdQueryExecutionCount() );
assertEquals( 0, statistics.getNaturalIdCacheHitCount() );
assertEquals( 0, statistics.getNaturalIdCachePutCount() );
statistics.clear();
scope.inTransaction(
(session) -> {
final User steve = session.bySimpleNaturalId( User.class ).load( "steve" );
session.delete( steve );
}
);
scope.inTransaction(
(session) -> {
final User steve = session.bySimpleNaturalId( User.class ).load( "steve" );
assertNull( steve );
}
);
}
}

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.orm.test.mapping.naturalid.immutable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
/**
* @author Alex Burgel
*/
@Entity
public class Parent {
@Id
private Integer id;
private String name;
@OneToMany( mappedBy = "parent" )
private List<Child> children = new ArrayList<>();
Parent() {}
public Parent(Integer id, String name) {
this.id = id;
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public List getChildren() {
return children;
}
public void setChildren(List children) {
this.children = children;
}
}

View File

@ -16,13 +16,11 @@
-->
<hibernate-mapping
package="org.hibernate.test.naturalid.immutable"
package="org.hibernate.orm.test.mapping.naturalid.immutable"
default-access="field">
<class name="Parent" table="Parent">
<id name="id">
<generator class="increment"/>
</id>
<id name="id" />
<property name="name"/>
@ -34,9 +32,7 @@
</class>
<class name="Child" table="Child">
<id name="id">
<generator class="increment"/>
</id>
<id name="id"/>
<natural-id mutable="false">
<many-to-one name="parent" class="Parent" />

View File

@ -14,7 +14,7 @@
This mapping illustrates use of <natural-id mutable="false"/>
-->
<hibernate-mapping package="org.hibernate.test.naturalid.immutable">
<hibernate-mapping package="org.hibernate.orm.test.mapping.naturalid.immutable">
<class name="User" table="IMM_NAT_ID_USER" lazy="true">
<comment>Users may bid for or sell auction items.</comment>

View File

@ -4,7 +4,7 @@
* 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.test.naturalid.immutable;
package org.hibernate.orm.test.mapping.naturalid.immutable;
/**

View File

@ -6,7 +6,7 @@
*/
//$Id$
package org.hibernate.test.naturalid.immutableentity;
package org.hibernate.orm.test.mapping.naturalid.immutableentity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

View File

@ -0,0 +1,239 @@
/*
* 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.mapping.naturalid.immutableentity;
import org.hibernate.NaturalIdLoadAccess;
import org.hibernate.annotations.Immutable;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.tuple.entity.EntityMetamodel;
import org.hibernate.testing.TestForIssue;
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.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Test case for NaturalId annotation on an {@link Immutable} entity
*
* @author Eric Dalquist
*/
@ServiceRegistry(
settings = {
@Setting( name = AvailableSettings.USE_QUERY_CACHE, value = "true" ),
@Setting( name = AvailableSettings.USE_SECOND_LEVEL_CACHE, value = "true" ),
@Setting( name = AvailableSettings.GENERATE_STATISTICS, value = "true" )
}
)
@DomainModel( annotatedClasses = Building.class )
@SessionFactory
@SuppressWarnings("unchecked")
@TestForIssue( jiraKey = "HHH-7085" )
public class ImmutableEntityNaturalIdTest {
@BeforeEach
public void createTestData(SessionFactoryScope scope) {
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
final StatisticsImplementor stats = sessionFactory.getStatistics();
sessionFactory.getCache().evictAllRegions();
stats.clear();
scope.inTransaction(
(session) -> {
Building b1 = new Building();
b1.setName( "Computer Science" );
b1.setAddress( "1210 W. Dayton St." );
b1.setCity( "Madison" );
b1.setState( "WI" );
session.persist( b1 );
}
);
assertEquals( "Cache hits should be empty", 0, stats.getNaturalIdCacheHitCount() );
assertEquals( "Cache misses should be empty", 0, stats.getNaturalIdCacheMissCount() );
assertEquals( "Cache put should be one after insert", 1, stats.getNaturalIdCachePutCount() );
}
@AfterEach
public void dropTestData(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> session.createQuery( "delete Building" ).executeUpdate()
);
}
@Test
public void testNaturalIdMapping(SessionFactoryScope scope) {
final EntityMappingType buildingMapping = scope.getSessionFactory()
.getRuntimeMetamodels()
.getEntityMappingType( Building.class );
final NaturalIdMapping naturalIdMapping = buildingMapping.getNaturalIdMapping();
assertThat( naturalIdMapping, notNullValue() );
assertThat( naturalIdMapping.getNaturalIdAttributes().size(), is( 3 ) );
// nullability is not specified, so they should be nullable by annotations-specific default
for ( SingularAttributeMapping attribute : naturalIdMapping.getNaturalIdAttributes() ) {
assertThat( attribute.getAttributeMetadataAccess().resolveAttributeMetadata( null ).isNullable(), is( true ) );
}
final EntityPersister entityPersister = buildingMapping.getEntityPersister();
assertThat(
"Class should have a natural key",
entityPersister.hasNaturalIdentifier(),
is( true )
);
final EntityMetamodel entityMetamodel = entityPersister.getEntityMetamodel();
assertThat(
"Wrong number of attributes",
entityMetamodel.getNaturalIdentifierProperties().length,
is( 3 )
);
// nullability is not specified, so they should be nullable by annotations-specific default
assertTrue( entityPersister.getPropertyNullability()[ entityMetamodel.getPropertyIndex( "address" )] );
assertTrue( entityPersister.getPropertyNullability()[ entityMetamodel.getPropertyIndex( "city" )] );
assertTrue( entityPersister.getPropertyNullability()[ entityMetamodel.getPropertyIndex( "state" )] );
}
@Test
public void testImmutableNaturalIdLifecycle(SessionFactoryScope scope) {
final SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
final StatisticsImplementor stats = sessionFactory.getStatistics();
// Clear caches and reset cache stats
sessionFactory.getCache().evictNaturalIdData();
stats.clear();
// load #1 - should result in:
// - cache miss
// - query
// - cache put
scope.inTransaction(
(session) -> {
final NaturalIdLoadAccess<Building> naturalIdLoader = session.byNaturalId( Building.class );
final Building building = naturalIdLoader
.using( "address", "1210 W. Dayton St." )
.using( "city", "Madison" )
.using( "state", "WI" )
.load();
assertThat( building, notNullValue() );
assertEquals( "Cache hits should be empty", 0, stats.getNaturalIdCacheHitCount() );
assertEquals( "Cache misses should be one", 1, stats.getNaturalIdCacheMissCount() );
assertEquals( "Cache put should be one after load", 1, stats.getNaturalIdCachePutCount() );
assertThat( stats.getPrepareStatementCount(), is( 1L ) );
}
);
// load #2 - should result in
// - cache hit
scope.inTransaction(
(session) -> {
final NaturalIdLoadAccess<Building> naturalIdLoader = session.byNaturalId( Building.class );
final Building building = naturalIdLoader
.using( "address", "1210 W. Dayton St." )
.using( "city", "Madison" )
.using( "state", "WI" )
.load();
assertThat( building, notNullValue() );
assertEquals( "Cache hits should be one after second query", 1, stats.getNaturalIdCacheHitCount() );
assertEquals( "Cache misses should be one after second query", 1, stats.getNaturalIdCacheMissCount() );
assertEquals( "Cache put should be one after second query", 1, stats.getNaturalIdCachePutCount() );
// Try Deleting
session.delete( building );
// third query
naturalIdLoader.load();
assertEquals( "Cache hits should be one after second query", 1, stats.getNaturalIdCacheHitCount() );
assertEquals( "Cache misses should be two after second query", 2, stats.getNaturalIdCacheMissCount() );
assertEquals( "Cache put should be one after second query", 1, stats.getNaturalIdCachePutCount() );
}
);
//Try three, should be db lookup and miss
scope.inTransaction(
(session) -> {
final Building building = session.byNaturalId( Building.class )
.using( "address", "1210 W. Dayton St." )
.using( "city", "Madison" )
.using( "state", "WI" )
.load();
// second query
assertNull( building );
assertEquals( "Cache hits should be one after third query", 1, stats.getNaturalIdCacheHitCount() );
assertEquals( "Cache misses should be one after third query", 3, stats.getNaturalIdCacheMissCount() );
assertEquals( "Cache put should be one after third query", 1, stats.getNaturalIdCachePutCount() );
// here, we should know that that natural-id does not exist as part of the Session...
session.byNaturalId( Building.class )
.using( "address", "1210 W. Dayton St." )
.using( "city", "Madison" )
.using( "state", "WI" )
.load();
assertEquals( "Cache hits should still be one", 1, stats.getNaturalIdCacheHitCount() );
assertEquals( "Cache misses should now be four", 4, stats.getNaturalIdCacheMissCount() );
assertEquals( "Cache put should still be one", 1, stats.getNaturalIdCachePutCount() );
}
);
}
@Test
@TestForIssue( jiraKey = "HHH-7371" )
public void testImmutableNaturalIdLifecycle2(SessionFactoryScope scope) {
scope.inTransaction(
(s) -> {
final NaturalIdLoadAccess<Building> naturalIdLoader = s.byNaturalId( Building.class );
naturalIdLoader
.using( "address", "1210 W. Dayton St." )
.using( "city", "Madison" )
.using( "state", "WI" );
Building building = naturalIdLoader.getReference();
assertNotNull( building );
s.delete( building );
building = naturalIdLoader.load();
//org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [org.hibernate.test.naturalid.immutableentity.Building#1]
// at org.hibernate.internal.SessionFactoryImpl$1$1.handleEntityNotFound(SessionFactoryImpl.java:247)
// at org.hibernate.event.internal.DefaultLoadEventListener.returnNarrowedProxy(DefaultLoadEventListener.java:282)
// at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:248)
// at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:148)
// at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1079)
// at org.hibernate.internal.SessionImpl.access$13(SessionImpl.java:1075)
// at org.hibernate.internal.SessionImpl$IdentifierLoadAccessImpl.load(SessionImpl.java:2425)
// at org.hibernate.internal.SessionImpl$NaturalIdLoadAccessImpl.load(SessionImpl.java:2586)
// at org.hibernate.test.naturalid.immutableentity.ImmutableEntityNaturalIdTest.testImmutableNaturalIdLifecycle2(ImmutableEntityNaturalIdTest.java:188)
assertNull( building );
}
);
}
}

Some files were not shown because too many files have changed in this diff Show More