HHH-14313 - NaturalId support

This commit is contained in:
Steve Ebersole 2020-11-06 11:09:46 -06:00
parent d4233ce6a8
commit be654c37b5
43 changed files with 2806 additions and 519 deletions

View File

@ -10,11 +10,16 @@ import java.util.Optional;
/**
* Loads an entity by its natural identifier.
*
*
* This is a generic form of load-by-natural-id covering both a single attribute
* and multiple attributes as the natural-id. For natural-ids defined by a single
* attribute, {@link SimpleNaturalIdLoadAccess} offers simplified access.
*
* @author Eric Dalquist
* @author Steve Ebersole
*
* @see org.hibernate.annotations.NaturalId
* @see Session#byNaturalId
*/
public interface NaturalIdLoadAccess<T> {
/**
@ -36,6 +41,15 @@ public interface NaturalIdLoadAccess<T> {
*/
NaturalIdLoadAccess<T> using(String attributeName, Object value);
/**
* Set multiple natural-id attribute values at once. The passed array is
* expected to have an even number of elements, with the attribute name followed
* by its value. E.g. `using( "system", "matrix", "username", "neo" )`
*
* @return {@code this}, for method chaining
*/
NaturalIdLoadAccess<T> using(Object... mappings);
/**
* For entities with mutable natural ids, should Hibernate perform "synchronization" prior to performing
* lookups? The default is to perform "synchronization" (for correctness).

View File

@ -0,0 +1,130 @@
/*
* 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;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.hibernate.graph.GraphSemantic;
import org.hibernate.graph.RootGraph;
import org.hibernate.internal.util.collections.CollectionHelper;
/**
* Defines the ability to load multiple entities by simple natural-id simultaneously.
*/
public interface NaturalIdMultiLoadAccess<T> {
/**
* Specify the {@link LockOptions} to use when retrieving the entity.
*
* @param lockOptions The lock options to use.
*
* @return {@code this}, for method chaining
*/
NaturalIdMultiLoadAccess<T> with(LockOptions lockOptions);
/**
* Specify the {@link CacheMode} to use when retrieving the entity.
*
* @param cacheMode The CacheMode to use.
*
* @return {@code this}, for method chaining
*/
NaturalIdMultiLoadAccess<T> with(CacheMode cacheMode);
/**
* Define a load graph to be used when retrieving the entity
*/
default NaturalIdMultiLoadAccess<T> with(RootGraph<T> graph) {
return with( graph, GraphSemantic.LOAD );
}
/**
* Define a load or fetch graph to be used when retrieving the entity
*/
NaturalIdMultiLoadAccess<T> with(RootGraph<T> graph, GraphSemantic semantic);
/**
* Specify a batch size for loading the entities (how many at a time). The default is
* to use a batch sizing strategy defined by the Dialect in use. Any greater-than-one
* value here will override that default behavior. If giving an explicit value here,
* care should be taken to not exceed the capabilities of the underlying database.
* <p/>
* Note that overall a batch-size is considered a hint.
*
* @param batchSize The batch size
*
* @return {@code this}, for method chaining
*/
NaturalIdMultiLoadAccess<T> withBatchSize(int batchSize);
/**
* Should the multi-load operation be allowed to return entities that are locally
* deleted? A locally deleted entity is one which has been passed to this
* Session's {@link Session#delete} / {@link Session#remove} method, but not
* yet flushed. The default behavior is to handle them as null in the return
* (see {@link #enableOrderedReturn}).
*
* @param enabled {@code true} enables returning the deleted entities;
* {@code false} (the default) disables it.
*
* @return {@code this}, for method chaining
*/
NaturalIdMultiLoadAccess<T> enableReturnOfDeletedEntities(boolean enabled);
/**
* Should the return List be ordered and positional in relation to the
* incoming ids? If enabled (the default), the return List is ordered and
* positional relative to the incoming ids. In other words, a request to
* {@code multiLoad([2,1,3])} will return {@code [Entity#2, Entity#1, Entity#3]}.
* <p/>
* An important distinction is made here in regards to the handling of
* unknown entities depending on this "ordered return" setting. If enabled
* a null is inserted into the List at the proper position(s). If disabled,
* the nulls are not put into the return List. In other words, consumers of
* the returned ordered List would need to be able to handle null elements.
*
* @param enabled {@code true} (the default) enables ordering;
* {@code false} disables it.
*
* @return {@code this}, for method chaining
*/
NaturalIdMultiLoadAccess<T> enableOrderedReturn(boolean enabled);
/**
* Perform a load of multiple entities by natural-id.
*
* See {@link #enableOrderedReturn} and {@link #enableReturnOfDeletedEntities}
* for options which effect the size and "shape" of the return list.
*
* @param ids The natural-id values to load
*
* @return The managed entities.
*/
List<T> multiLoad(Object... ids);
/**
* Perform a load of multiple entities by natural-id.
*
* See {@link #enableOrderedReturn} and {@link #enableReturnOfDeletedEntities}
* for options which effect the size and "shape" of the return list.
*
* @param ids The natural-id values to load
*
* @return The managed entities.
*/
List<T> multiLoad(List<?> ids);
/**
* Helper for creating a Map that represents the value of a compound natural-id
* for use in loading. The passed array is expected to have an even number of elements
* representing key, value pairs. E.g. `using( "system", "matrix", "username", "neo" )`
*/
static Map<String,?> compoundValue(Object... elements) {
return CollectionHelper.asMap( elements );
}
}

View File

@ -784,7 +784,7 @@ public interface Session extends SharedSessionContract, EntityManager, AutoClose
*
* @throws HibernateException If the specified entity name cannot be resolved as an entity name
*/
IdentifierLoadAccess byId(String entityName);
<T> IdentifierLoadAccess<T> byId(String entityName);
/**
* Create a {@link MultiIdentifierLoadAccess} instance to retrieve multiple entities at once
@ -808,7 +808,7 @@ public interface Session extends SharedSessionContract, EntityManager, AutoClose
*
* @throws HibernateException If the specified entity name cannot be resolved as an entity name
*/
MultiIdentifierLoadAccess byMultipleIds(String entityName);
<T> MultiIdentifierLoadAccess<T> byMultipleIds(String entityName);
/**
* Create an {@link IdentifierLoadAccess} instance to retrieve the specified entity by
@ -832,7 +832,7 @@ public interface Session extends SharedSessionContract, EntityManager, AutoClose
*
* @throws HibernateException If the specified entity name cannot be resolved as an entity name
*/
NaturalIdLoadAccess byNaturalId(String entityName);
<T> NaturalIdLoadAccess<T> byNaturalId(String entityName);
/**
* Create a {@link NaturalIdLoadAccess} instance to retrieve the specified entity by
@ -857,7 +857,7 @@ public interface Session extends SharedSessionContract, EntityManager, AutoClose
* @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.
*/
SimpleNaturalIdLoadAccess bySimpleNaturalId(String entityName);
<T> SimpleNaturalIdLoadAccess<T> bySimpleNaturalId(String entityName);
/**
* Create a {@link SimpleNaturalIdLoadAccess} instance to retrieve the specified entity by
@ -872,6 +872,16 @@ public interface Session extends SharedSessionContract, EntityManager, AutoClose
*/
<T> SimpleNaturalIdLoadAccess<T> bySimpleNaturalId(Class<T> entityClass);
/**
* Access to load multiple entities by natural-id
*/
<T> NaturalIdMultiLoadAccess<T> byMultipleNaturalId(Class<T> entityClass);
/**
* Access to load multiple entities by natural-id
*/
<T> NaturalIdMultiLoadAccess<T> byMultipleNaturalId(String entityName);
/**
* Enable the named filter for this current session.
*

View File

@ -39,6 +39,7 @@ import org.hibernate.Session;
import org.hibernate.SessionEventListener;
import org.hibernate.SharedSessionBuilder;
import org.hibernate.SimpleNaturalIdLoadAccess;
import org.hibernate.NaturalIdMultiLoadAccess;
import org.hibernate.Transaction;
import org.hibernate.UnknownProfileException;
import org.hibernate.cache.spi.CacheTransactionSynchronization;
@ -911,7 +912,7 @@ public class SessionDelegatorBaseImpl implements SessionImplementor {
}
@Override
public IdentifierLoadAccess byId(String entityName) {
public <T> IdentifierLoadAccess<T> byId(String entityName) {
return delegate.byId( entityName );
}
@ -921,7 +922,7 @@ public class SessionDelegatorBaseImpl implements SessionImplementor {
}
@Override
public MultiIdentifierLoadAccess byMultipleIds(String entityName) {
public <T> MultiIdentifierLoadAccess<T> byMultipleIds(String entityName) {
return delegate.byMultipleIds( entityName );
}
@ -931,7 +932,7 @@ public class SessionDelegatorBaseImpl implements SessionImplementor {
}
@Override
public NaturalIdLoadAccess byNaturalId(String entityName) {
public <T> NaturalIdLoadAccess<T> byNaturalId(String entityName) {
return delegate.byNaturalId( entityName );
}
@ -941,7 +942,7 @@ public class SessionDelegatorBaseImpl implements SessionImplementor {
}
@Override
public SimpleNaturalIdLoadAccess bySimpleNaturalId(String entityName) {
public <T> SimpleNaturalIdLoadAccess<T> bySimpleNaturalId(String entityName) {
return delegate.bySimpleNaturalId( entityName );
}
@ -950,6 +951,16 @@ public class SessionDelegatorBaseImpl implements SessionImplementor {
return delegate.bySimpleNaturalId( entityClass );
}
@Override
public <T> NaturalIdMultiLoadAccess<T> byMultipleNaturalId(Class<T> entityClass) {
return delegate.byMultipleNaturalId( entityClass );
}
@Override
public <T> NaturalIdMultiLoadAccess<T> byMultipleNaturalId(String entityName) {
return delegate.byMultipleNaturalId( entityName );
}
@Override
public Filter enableFilter(String filterName) {
return delegate.enableFilter( filterName );

View File

@ -6,17 +6,13 @@
*/
package org.hibernate.event.internal;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.ObjectDeletedException;
import org.hibernate.cache.spi.access.EntityDataAccess;
import org.hibernate.cache.spi.access.SoftLock;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.Status;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.internal.CoreLogging;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.loader.ast.internal.LoaderHelper;
import org.jboss.logging.Logger;
/**
@ -37,60 +33,6 @@ public abstract class AbstractLockUpgradeEventListener extends AbstractReassocia
* @param source The session which is the source of the event being processed.
*/
protected void upgradeLock(Object object, EntityEntry entry, LockOptions lockOptions, EventSource source) {
LockMode requestedLockMode = lockOptions.getLockMode();
if ( requestedLockMode.greaterThan( entry.getLockMode() ) ) {
// The user requested a "greater" (i.e. more restrictive) form of
// pessimistic lock
if ( entry.getStatus() != Status.MANAGED ) {
throw new ObjectDeletedException(
"attempted to lock a deleted instance",
entry.getId(),
entry.getPersister().getEntityName()
);
}
final EntityPersister persister = entry.getPersister();
if ( log.isTraceEnabled() ) {
log.tracev(
"Locking {0} in mode: {1}",
MessageHelper.infoString( persister, entry.getId(), source.getFactory() ),
requestedLockMode
);
}
final boolean cachingEnabled = persister.canWriteToCache();
SoftLock lock = null;
Object ck = null;
try {
if ( cachingEnabled ) {
EntityDataAccess cache = persister.getCacheAccessStrategy();
ck = cache.generateCacheKey( entry.getId(), persister, source.getFactory(), source.getTenantIdentifier() );
lock = cache.lockItem( source, ck, entry.getVersion() );
}
if ( persister.isVersioned() && requestedLockMode == LockMode.FORCE ) {
// todo : should we check the current isolation mode explicitly?
Object nextVersion = persister.forceVersionIncrement(
entry.getId(), entry.getVersion(), source
);
entry.forceLocked( object, nextVersion );
}
else {
persister.lock( entry.getId(), entry.getVersion(), object, lockOptions, source );
}
entry.setLockMode(requestedLockMode);
}
finally {
// the database now holds a lock + the object is flushed from the cache,
// so release the soft lock
if ( cachingEnabled ) {
persister.getCacheAccessStrategy().unlockItem( source, ck, lock );
}
}
}
LoaderHelper.upgradeLock( object, entry, lockOptions, (SessionImplementor) source );
}
}

View File

@ -0,0 +1,170 @@
/*
* 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.internal;
import java.util.List;
import java.util.function.Supplier;
import org.hibernate.CacheMode;
import org.hibernate.LockOptions;
import org.hibernate.MultiIdentifierLoadAccess;
import org.hibernate.graph.GraphSemantic;
import org.hibernate.graph.RootGraph;
import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.loader.ast.spi.MultiIdLoadOptions;
/**
* @author Steve Ebersole
*/
class MultiIdentifierLoadAccessImpl<T> implements MultiIdentifierLoadAccess<T>, MultiIdLoadOptions {
private final SessionImpl session;
private final EntityPersister entityPersister;
private LockOptions lockOptions;
private CacheMode cacheMode;
private RootGraphImplementor<T> rootGraph;
private GraphSemantic graphSemantic;
private Integer batchSize;
private boolean sessionCheckingEnabled;
private boolean returnOfDeletedEntitiesEnabled;
private boolean orderedReturnEnabled = true;
public MultiIdentifierLoadAccessImpl(SessionImpl session, EntityPersister entityPersister) {
this.session = session;
this.entityPersister = entityPersister;
}
@Override
public LockOptions getLockOptions() {
return lockOptions;
}
@Override
public final MultiIdentifierLoadAccess<T> with(LockOptions lockOptions) {
this.lockOptions = lockOptions;
return this;
}
@Override
public MultiIdentifierLoadAccess<T> with(CacheMode cacheMode) {
this.cacheMode = cacheMode;
return this;
}
@Override
public MultiIdentifierLoadAccess<T> with(RootGraph<T> graph, GraphSemantic semantic) {
this.rootGraph = (RootGraphImplementor<T>) graph;
this.graphSemantic = semantic;
return this;
}
@Override
public Integer getBatchSize() {
return batchSize;
}
@Override
public MultiIdentifierLoadAccess<T> withBatchSize(int batchSize) {
if ( batchSize < 1 ) {
this.batchSize = null;
}
else {
this.batchSize = batchSize;
}
return this;
}
@Override
public boolean isSessionCheckingEnabled() {
return sessionCheckingEnabled;
}
@Override
public boolean isSecondLevelCacheCheckingEnabled() {
return cacheMode == CacheMode.NORMAL || cacheMode == CacheMode.GET;
}
@Override
public MultiIdentifierLoadAccess<T> enableSessionCheck(boolean enabled) {
this.sessionCheckingEnabled = enabled;
return this;
}
@Override
public boolean isReturnOfDeletedEntitiesEnabled() {
return returnOfDeletedEntitiesEnabled;
}
@Override
public MultiIdentifierLoadAccess<T> enableReturnOfDeletedEntities(boolean enabled) {
this.returnOfDeletedEntitiesEnabled = enabled;
return this;
}
@Override
public boolean isOrderReturnEnabled() {
return orderedReturnEnabled;
}
@Override
public MultiIdentifierLoadAccess<T> enableOrderedReturn(boolean enabled) {
this.orderedReturnEnabled = enabled;
return this;
}
@Override
@SuppressWarnings( "unchecked" )
public <K> List<T> multiLoad(K... ids) {
return perform( () -> entityPersister.multiLoad( ids, session, this ) );
}
public List<T> perform(Supplier<List<T>> executor) {
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 );
}
}
}
@Override
@SuppressWarnings( "unchecked" )
public <K> List<T> multiLoad(List<K> ids) {
return perform( () -> entityPersister.multiLoad( ids.toArray( new Object[ 0 ] ), session, this ) );
}
}

View File

@ -0,0 +1,158 @@
/*
* 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.internal;
import java.util.List;
import org.hibernate.CacheMode;
import org.hibernate.LockOptions;
import org.hibernate.NaturalIdMultiLoadAccess;
import org.hibernate.engine.spi.EffectiveEntityGraph;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.graph.GraphSemantic;
import org.hibernate.graph.RootGraph;
import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoadOptions;
import org.hibernate.persister.entity.EntityPersister;
/**
* @author Steve Ebersole
*/
public class NaturalIdMultiLoadAccessStandard<T> implements NaturalIdMultiLoadAccess<T>, MultiNaturalIdLoadOptions {
private final EntityPersister entityDescriptor;
private final SessionImpl session;
private LockOptions lockOptions;
private CacheMode cacheMode;
private RootGraphImplementor<T> rootGraph;
private GraphSemantic graphSemantic;
private Integer batchSize;
private boolean returnOfDeletedEntitiesEnabled;
private boolean orderedReturnEnabled = true;
public NaturalIdMultiLoadAccessStandard(EntityPersister entityDescriptor, SessionImpl session) {
this.entityDescriptor = entityDescriptor;
this.session = session;
}
@Override
public NaturalIdMultiLoadAccess<T> with(LockOptions lockOptions) {
this.lockOptions = lockOptions;
return this;
}
@Override
public NaturalIdMultiLoadAccess<T> with(CacheMode cacheMode) {
this.cacheMode = cacheMode;
return this;
}
@Override
public NaturalIdMultiLoadAccess<T> with(RootGraph<T> graph, GraphSemantic semantic) {
this.rootGraph = (RootGraphImplementor<T>) graph;
this.graphSemantic = semantic;
return this;
}
@Override
public NaturalIdMultiLoadAccess<T> withBatchSize(int batchSize) {
this.batchSize = batchSize;
return this;
}
@Override
public NaturalIdMultiLoadAccess<T> enableReturnOfDeletedEntities(boolean enabled) {
returnOfDeletedEntitiesEnabled = enabled;
return this;
}
@Override
public NaturalIdMultiLoadAccess<T> enableOrderedReturn(boolean enabled) {
orderedReturnEnabled = enabled;
return this;
}
@Override
@SuppressWarnings( "unchecked" )
public List<T> multiLoad(Object... ids) {
final 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;
}
}
final LoadQueryInfluencers loadQueryInfluencers = session.getLoadQueryInfluencers();
try {
final EffectiveEntityGraph effectiveEntityGraph = loadQueryInfluencers.getEffectiveEntityGraph();
final GraphSemantic initialGraphSemantic = effectiveEntityGraph.getSemantic();
final RootGraphImplementor<?> initialGraph = effectiveEntityGraph.getGraph();
final boolean hadInitialGraph = initialGraphSemantic != null;
if ( graphSemantic != null ) {
if ( rootGraph == null ) {
throw new IllegalArgumentException( "Graph semantic specified, but no RootGraph was supplied" );
}
effectiveEntityGraph.applyGraph( rootGraph, graphSemantic );
}
try {
return entityDescriptor.getNaturalIdMapping().getMultiNaturalIdLoader().multiLoad( ids, this, session );
}
finally {
if ( graphSemantic != null ) {
if ( hadInitialGraph ) {
effectiveEntityGraph.applyGraph( initialGraph, initialGraphSemantic );
}
else {
effectiveEntityGraph.clear();
}
}
}
}
finally {
if ( cacheModeChanged ) {
// change it back
session.setCacheMode( sessionCacheMode );
}
}
}
@Override
public List<T> multiLoad(List<?> ids) {
return multiLoad( ids.toArray( new Object[ 0 ] ) );
}
@Override
public boolean isReturnOfDeletedEntitiesEnabled() {
return returnOfDeletedEntitiesEnabled;
}
@Override
public boolean isOrderReturnEnabled() {
return orderedReturnEnabled;
}
@Override
public LockOptions getLockOptions() {
return lockOptions;
}
@Override
public Integer getBatchSize() {
return batchSize;
}
}

View File

@ -56,6 +56,7 @@ import org.hibernate.SessionEventListener;
import org.hibernate.SessionException;
import org.hibernate.SharedSessionBuilder;
import org.hibernate.SimpleNaturalIdLoadAccess;
import org.hibernate.NaturalIdMultiLoadAccess;
import org.hibernate.Transaction;
import org.hibernate.TransientObjectException;
import org.hibernate.TypeMismatchException;
@ -114,6 +115,7 @@ 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;
@ -121,9 +123,9 @@ 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.metamodel.spi.MetamodelImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.MultiLoadOptions;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.procedure.ProcedureCall;
import org.hibernate.procedure.spi.NamedCallableQueryMemento;
@ -1105,8 +1107,8 @@ public class SessionImpl
}
@Override
public IdentifierLoadAccessImpl byId(String entityName) {
return new IdentifierLoadAccessImpl( entityName );
public <T> IdentifierLoadAccessImpl<T> byId(String entityName) {
return new IdentifierLoadAccessImpl<>( entityName );
}
@Override
@ -1116,17 +1118,17 @@ public class SessionImpl
@Override
public <T> MultiIdentifierLoadAccess<T> byMultipleIds(Class<T> entityClass) {
return new MultiIdentifierLoadAccessImpl<>( locateEntityPersister( entityClass ) );
return new MultiIdentifierLoadAccessImpl<>( this, requireEntityPersister( entityClass ) );
}
@Override
public MultiIdentifierLoadAccess byMultipleIds(String entityName) {
return new MultiIdentifierLoadAccessImpl( locateEntityPersister( entityName ) );
public <T> MultiIdentifierLoadAccess<T> byMultipleIds(String entityName) {
return new MultiIdentifierLoadAccessImpl<>( this, requireEntityPersister( entityName ) );
}
@Override
public NaturalIdLoadAccess byNaturalId(String entityName) {
return new NaturalIdLoadAccessImpl( entityName );
public <T> NaturalIdLoadAccess<T> byNaturalId(String entityName) {
return new NaturalIdLoadAccessImpl<>( entityName );
}
@Override
@ -1135,8 +1137,8 @@ public class SessionImpl
}
@Override
public SimpleNaturalIdLoadAccess bySimpleNaturalId(String entityName) {
return new SimpleNaturalIdLoadAccessImpl( entityName );
public <T> SimpleNaturalIdLoadAccess<T> bySimpleNaturalId(String entityName) {
return new SimpleNaturalIdLoadAccessImpl<>( entityName );
}
@Override
@ -1144,6 +1146,16 @@ public class SessionImpl
return new SimpleNaturalIdLoadAccessImpl<>( entityClass );
}
@Override
public <T> NaturalIdMultiLoadAccess<T> byMultipleNaturalId(Class<T> entityClass) {
return new NaturalIdMultiLoadAccessStandard<>( requireEntityPersister( entityClass ), this );
}
@Override
public <T> NaturalIdMultiLoadAccess<T> byMultipleNaturalId(String entityName) {
return new NaturalIdMultiLoadAccessStandard<>( requireEntityPersister( entityName ), this );
}
private void fireLoad(LoadEvent event, LoadType loadType) {
checkOpenOrWaitingForAutoClose();
fireLoadNoChecks( event, loadType );
@ -2109,11 +2121,11 @@ public class SessionImpl
}
private IdentifierLoadAccessImpl(String entityName) {
this( locateEntityPersister( entityName ) );
this( requireEntityPersister( entityName ) );
}
private IdentifierLoadAccessImpl(Class<T> entityClass) {
this( locateEntityPersister( entityClass ) );
this( requireEntityPersister( entityClass ) );
}
@Override
@ -2237,157 +2249,11 @@ public class SessionImpl
}
}
private class MultiIdentifierLoadAccessImpl<T> implements MultiIdentifierLoadAccess<T>, MultiLoadOptions {
private final EntityPersister entityPersister;
private LockOptions lockOptions;
private CacheMode cacheMode;
private RootGraphImplementor<T> rootGraph;
private GraphSemantic graphSemantic;
private Integer batchSize;
private boolean sessionCheckingEnabled;
private boolean returnOfDeletedEntitiesEnabled;
private boolean orderedReturnEnabled = true;
public MultiIdentifierLoadAccessImpl(EntityPersister entityPersister) {
this.entityPersister = entityPersister;
}
@Override
public LockOptions getLockOptions() {
return lockOptions;
}
@Override
public final MultiIdentifierLoadAccess<T> with(LockOptions lockOptions) {
this.lockOptions = lockOptions;
return this;
}
@Override
public MultiIdentifierLoadAccess<T> with(CacheMode cacheMode) {
this.cacheMode = cacheMode;
return this;
}
@Override
public MultiIdentifierLoadAccess<T> with(RootGraph<T> graph, GraphSemantic semantic) {
this.rootGraph = (RootGraphImplementor<T>) graph;
this.graphSemantic = semantic;
return this;
}
@Override
public Integer getBatchSize() {
return batchSize;
}
@Override
public MultiIdentifierLoadAccess<T> withBatchSize(int batchSize) {
if ( batchSize < 1 ) {
this.batchSize = null;
}
else {
this.batchSize = batchSize;
}
return this;
}
@Override
public boolean isSessionCheckingEnabled() {
return sessionCheckingEnabled;
}
@Override
public boolean isSecondLevelCacheCheckingEnabled() {
return cacheMode == CacheMode.NORMAL || cacheMode == CacheMode.GET;
}
@Override
public MultiIdentifierLoadAccess<T> enableSessionCheck(boolean enabled) {
this.sessionCheckingEnabled = enabled;
return this;
}
@Override
public boolean isReturnOfDeletedEntitiesEnabled() {
return returnOfDeletedEntitiesEnabled;
}
@Override
public MultiIdentifierLoadAccess<T> enableReturnOfDeletedEntities(boolean enabled) {
this.returnOfDeletedEntitiesEnabled = enabled;
return this;
}
@Override
public boolean isOrderReturnEnabled() {
return orderedReturnEnabled;
}
@Override
public MultiIdentifierLoadAccess<T> enableOrderedReturn(boolean enabled) {
this.orderedReturnEnabled = enabled;
return this;
}
@Override
@SuppressWarnings("unchecked")
public <K> List<T> multiLoad(K... ids) {
return perform( () -> entityPersister.multiLoad( ids, SessionImpl.this, this ) );
}
public List<T> perform(Supplier<List<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 );
}
}
}
@Override
@SuppressWarnings("unchecked")
public <K> List<T> multiLoad(List<K> ids) {
return perform( () -> entityPersister.multiLoad( ids.toArray( new Object[0] ), SessionImpl.this, this ) );
}
}
private EntityPersister locateEntityPersister(Class entityClass) {
private EntityPersister requireEntityPersister(Class entityClass) {
return getFactory().getMetamodel().locateEntityPersister( entityClass );
}
private EntityPersister locateEntityPersister(String entityName) {
private EntityPersister requireEntityPersister(String entityName) {
return getFactory().getMetamodel().locateEntityPersister( entityName );
}
@ -2406,6 +2272,14 @@ public class SessionImpl
}
}
public LockOptions getLockOptions() {
return lockOptions;
}
public boolean isSynchronizationEnabled() {
return synchronizationEnabled;
}
public BaseNaturalIdLoadAccessImpl<T> with(LockOptions lockOptions) {
this.lockOptions = lockOptions;
return this;
@ -2418,16 +2292,14 @@ public class SessionImpl
protected final Object resolveNaturalId(Map<String, Object> naturalIdParameters) {
performAnyNeededCrossReferenceSynchronizations();
final ResolveNaturalIdEvent event =
new ResolveNaturalIdEvent( naturalIdParameters, entityPersister, SessionImpl.this );
fireResolveNaturalId( event );
final Object resolvedId = entityPersister()
.getNaturalIdMapping()
.getNaturalIdLoader()
.resolveNaturalIdToId( naturalIdParameters, SessionImpl.this );
if ( event.getEntityId() == PersistenceContext.NaturalIdHelper.INVALID_NATURAL_ID_REFERENCE ) {
return null;
}
else {
return event.getEntityId();
}
return resolvedId == PersistenceContext.NaturalIdHelper.INVALID_NATURAL_ID_REFERENCE
? null
: resolvedId;
}
protected void performAnyNeededCrossReferenceSynchronizations() {
@ -2500,11 +2372,11 @@ public class SessionImpl
}
private NaturalIdLoadAccessImpl(String entityName) {
this( locateEntityPersister( entityName ) );
this( requireEntityPersister( entityName ) );
}
private NaturalIdLoadAccessImpl(Class entityClass) {
this( locateEntityPersister( entityClass ) );
this( requireEntityPersister( entityClass ) );
}
@Override
@ -2518,6 +2390,12 @@ public class SessionImpl
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 );
@ -2556,8 +2434,9 @@ public class SessionImpl
}
}
private class SimpleNaturalIdLoadAccessImpl<T> extends BaseNaturalIdLoadAccessImpl<T>
implements SimpleNaturalIdLoadAccess<T> {
private class SimpleNaturalIdLoadAccessImpl<T>
extends BaseNaturalIdLoadAccessImpl<T>
implements SimpleNaturalIdLoadAccess<T>, NaturalIdLoader.LoadOptions {
private final String naturalIdAttributeName;
private SimpleNaturalIdLoadAccessImpl(EntityPersister entityPersister) {
@ -2577,11 +2456,21 @@ public class SessionImpl
}
private SimpleNaturalIdLoadAccessImpl(String entityName) {
this( locateEntityPersister( entityName ) );
this( requireEntityPersister( entityName ) );
}
@Override
public LockOptions getLockOptions() {
return super.getLockOptions();
}
@Override
public boolean isSynchronizationEnabled() {
return super.isSynchronizationEnabled();
}
private SimpleNaturalIdLoadAccessImpl(Class entityClass) {
this( locateEntityPersister( entityClass ) );
this( requireEntityPersister( entityClass ) );
}
@Override
@ -2602,7 +2491,7 @@ public class SessionImpl
@Override
@SuppressWarnings("unchecked")
public T getReference(Object naturalIdValue) {
final Object entityId = resolveNaturalId( getNaturalIdParameters( naturalIdValue ) );
final Object entityId = entityPersister().getNaturalIdLoader().resolveNaturalIdToId( naturalIdValue, SessionImpl.this );
if ( entityId == null ) {
return null;
}
@ -2610,19 +2499,9 @@ public class SessionImpl
}
@Override
@SuppressWarnings("unchecked")
public T load(Object naturalIdValue) {
final Object entityId = resolveNaturalId( getNaturalIdParameters( naturalIdValue ) );
if ( entityId == null ) {
return null;
}
try {
return (T) this.getIdentifierLoadAccess().load( entityId );
}
catch (EntityNotFoundException | ObjectNotFoundException e) {
// OK
}
return null;
//noinspection unchecked
return (T) entityPersister().getNaturalIdLoader().load( naturalIdValue, this, SessionImpl.this );
}
@Override

View File

@ -48,4 +48,12 @@ public class MutableInteger {
public void increase() {
++value;
}
public void plus(int i) {
value += i;
}
public void minus(int i) {
value -= i;
}
}

View File

@ -18,6 +18,7 @@ import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
@ -361,4 +362,27 @@ public final class CollectionHelper {
}
}
@SuppressWarnings( "unchecked" )
public static <K,V> void collectMapEntries(BiConsumer<K, V> mapEntryConsumer, Object[] mappings) {
// even numbered
assert mappings.length %2 == 0;
for ( int i = 0; i < mappings.length; i += 2 ) {
mapEntryConsumer.accept( (K) mappings[i], (V) mappings[i+1] );
}
}
@SuppressWarnings( "unchecked" )
public static <K,S> Map<K, S> asMap(Object[] elements) {
assert elements != null;
assert elements.length % 2 == 0;
final HashMap<K,S> map = new HashMap<>();
collectMapEntries( map::put, elements );
for ( int i = 0; i < elements.length; i += 2 ) {
map.put( (K) elements[ i ], (S) elements[ i+1 ] );
}
return map;
}
}

View File

@ -0,0 +1,26 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.loader;
import org.hibernate.SharedSessionContract;
import org.hibernate.metamodel.mapping.EntityMappingType;
/**
* Listener for post load-by-natural-id events
*/
@FunctionalInterface
public interface NaturalIdPostLoadListener {
/**
* Singleton access for no listener
*/
NaturalIdPostLoadListener NO_OP = (loadingEntity, entity, session) -> {};
/**
* Callback for a load-by-natural-id pre event
*/
void completedLoadByNaturalId(EntityMappingType entityMappingType, Object entity, SharedSessionContract session);
}

View File

@ -0,0 +1,26 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.loader;
import org.hibernate.SharedSessionContract;
import org.hibernate.metamodel.mapping.EntityMappingType;
/**
* Listener for pre load-by-natural-id events
*/
@FunctionalInterface
public interface NaturalIdPreLoadListener {
/**
* Singleton access for no listener
*/
NaturalIdPreLoadListener NO_OP = (loadingEntity, naturalId, session) -> {};
/**
* Callback for a load-by-natural-id pre event
*/
void startingLoadByNaturalId(EntityMappingType loadingEntity, Object naturalId, SharedSessionContract session);
}

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.ast;
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

@ -17,11 +17,13 @@ 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.internal.util.StringHelper;
import org.hibernate.loader.NaturalIdPostLoadListener;
import org.hibernate.loader.NaturalIdPreLoadListener;
import org.hibernate.loader.ast.spi.Loadable;
import org.hibernate.loader.ast.spi.NaturalIdLoader;
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.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.sql.ast.Clause;
@ -36,71 +38,72 @@ import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.JdbcSelect;
/**
* @author Steve Ebersole
* Base support for NaturalIdLoader implementations
*/
public class NaturalIdLoaderStandardImpl<T> implements NaturalIdLoader<T> {
private final EntityPersister entityDescriptor;
private final NaturalIdMapping naturalIdMapping;
public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
public NaturalIdLoaderStandardImpl(EntityPersister entityDescriptor) {
// todo (6.0) : account for nullable attributes that are part of the natural-id (is-null-or-equals)
// todo (6.0) : cache the SQL AST and JdbcParameter list
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) {
this.naturalIdMapping = naturalIdMapping;
this.preLoadListener = preLoadListener;
this.postLoadListener = postLoadListener;
this.entityDescriptor = entityDescriptor;
this.naturalIdMapping = entityDescriptor.getNaturalIdMapping();
if ( ! entityDescriptor.hasNaturalIdentifier() ) {
throw new HibernateException( "Entity does not define natural-id : " + entityDescriptor.getEntityName() );
}
// todo (6.0) : account for nullable attributes that are part of the natural-id (is-null-or-equals)
// todo (6.0) : cache the SQL AST and JdbcParameter list
}
@Override
public EntityPersister getLoadable() {
protected EntityMappingType entityDescriptor() {
return entityDescriptor;
}
protected NaturalIdMapping naturalIdMapping() {
return naturalIdMapping;
}
@Override
public T load(Object naturalIdToLoad, LoadOptions options, SharedSessionContractImplementor session) {
public Loadable getLoadable() {
return entityDescriptor();
}
@Override
public T load(Object naturalIdValue, LoadOptions options, SharedSessionContractImplementor session) {
final Object bindValue = resolveNaturalIdBindValue( naturalIdValue, session );
preLoadListener.startingLoadByNaturalId( entityDescriptor, bindValue, session );
final SessionFactoryImplementor sessionFactory = session.getFactory();
final List<JdbcParameter> jdbcParameters = new ArrayList<>();
final SelectStatement sqlSelect = LoaderSelectBuilder.createSelect(
entityDescriptor,
Collections.emptyList(),
naturalIdMapping,
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 List<JdbcParameter> jdbcParameters = new ArrayList<>();
final SelectStatement sqlSelect = LoaderSelectBuilder.createSelect(
entityDescriptor(),
Collections.emptyList(),
naturalIdMapping(),
null,
1,
session.getLoadQueryInfluencers(),
options.getLockOptions(),
jdbcParameters::add,
sessionFactory
);
final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlSelect );
final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() );
final Iterator<JdbcParameter> jdbcParamItr = jdbcParameters.iterator();
for ( int i = 0; i < naturalIdMapping.getNaturalIdAttributes().size(); i++ ) {
final SingularAttributeMapping attrMapping = naturalIdMapping.getNaturalIdAttributes().get( i );
attrMapping.visitJdbcValues(
naturalIdToLoad,
Clause.WHERE,
(jdbcValue, jdbcMapping) -> {
assert jdbcParamItr.hasNext();
final JdbcParameter jdbcParam = jdbcParamItr.next();
jdbcParamBindings.addBinding(
jdbcParam,
new JdbcParameterBindingImpl( jdbcMapping, jdbcValue )
);
},
session
);
}
applyNaturalIdAsJdbcParameters( bindValue, jdbcParameters, jdbcParamBindings, session );
//noinspection unchecked
final List<T> results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
@ -141,18 +144,102 @@ public class NaturalIdLoaderStandardImpl<T> implements NaturalIdLoader<T> {
);
}
final T result = results.get( 0 );
postLoadListener.completedLoadByNaturalId( entityDescriptor, result, session );
return result;
}
protected abstract Object resolveNaturalIdBindValue(Object naturalIdToLoad, 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 JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlSelect );
final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() );
applyNaturalIdAsJdbcParameters(
bindValue,
jdbcParameters,
jdbcParamBindings,
session
);
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(
String.format(
"Resolving natural-id to id returned more that one row : %s [%s]",
entityDescriptor().getEntityName(),
bindValue
)
);
}
return results.get( 0 );
}
@Override
public Object[] resolveIdToNaturalId(Object id, SharedSessionContractImplementor session) {
public Object resolveIdToNaturalId(Object id, SharedSessionContractImplementor session) {
final SessionFactoryImplementor sessionFactory = session.getFactory();
final List<JdbcParameter> jdbcParameters = new ArrayList<>();
final SelectStatement sqlSelect = LoaderSelectBuilder.createSelect(
entityDescriptor,
naturalIdMapping.getNaturalIdAttributes(),
entityDescriptor.getIdentifierMapping(),
entityDescriptor(),
naturalIdMapping().getNaturalIdAttributes(),
entityDescriptor().getIdentifierMapping(),
null,
1,
session.getLoadQueryInfluencers(),
@ -170,7 +257,7 @@ public class NaturalIdLoaderStandardImpl<T> implements NaturalIdLoader<T> {
final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() );
final Iterator<JdbcParameter> jdbcParamItr = jdbcParameters.iterator();
entityDescriptor.getIdentifierMapping().visitJdbcValues(
entityDescriptor().getIdentifierMapping().visitJdbcValues(
id,
Clause.WHERE,
(value, type) -> {
@ -218,99 +305,19 @@ public class NaturalIdLoaderStandardImpl<T> implements NaturalIdLoader<T> {
throw new HibernateException(
String.format(
"Resolving id to natural-id returned more that one row : %s #%s",
entityDescriptor.getEntityName(),
entityDescriptor().getEntityName(),
id
)
);
}
return results.get( 0 );
}
@Override
public Object resolveNaturalIdToId(
Object[] naturalIdValues,
SharedSessionContractImplementor session) {
final SessionFactoryImplementor sessionFactory = session.getFactory();
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 JdbcServices jdbcServices = sessionFactory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlSelect );
final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() );
final Iterator<JdbcParameter> jdbcParamItr = jdbcParameters.iterator();
for ( int i = 0; i < naturalIdMapping.getNaturalIdAttributes().size(); i++ ) {
final SingularAttributeMapping attrMapping = naturalIdMapping.getNaturalIdAttributes().get( i );
attrMapping.visitJdbcValues(
naturalIdValues[i],
Clause.WHERE,
(jdbcValue, jdbcMapping) -> {
assert jdbcParamItr.hasNext();
jdbcParamBindings.addBinding(
jdbcParamItr.next(),
new JdbcParameterBindingImpl( jdbcMapping, jdbcValue )
);
},
session
);
}
assert !jdbcParamItr.hasNext();
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 natural-id to id returned more that one row : %s [%s]",
entityDescriptor.getEntityName(),
StringHelper.join( ", ", naturalIdValues )
)
);
final Object[] objects = results.get( 0 );
if ( isSimple() ) {
return objects[0];
}
return results.get( 0 );
return objects;
}
protected abstract boolean isSimple();
}

View File

@ -0,0 +1,98 @@
/*
* 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.ast.internal;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
/**
* NaturalIdLoader implementation for compound natural-ids
*/
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 );
}
@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,
SharedSessionContractImplementor session) {
assert naturalIdToLoad instanceof Object[];
final Object[] naturalIdValueArray = (Object[]) naturalIdToLoad;
final Iterator<JdbcParameter> jdbcParamItr = jdbcParameters.iterator();
for ( int i = 0; i < naturalIdMapping().getNaturalIdAttributes().size(); i++ ) {
final SingularAttributeMapping attrMapping = naturalIdMapping().getNaturalIdAttributes().get( i );
attrMapping.visitJdbcValues(
naturalIdValueArray[i],
Clause.WHERE,
(jdbcValue, jdbcMapping) -> {
assert jdbcParamItr.hasNext();
final JdbcParameter jdbcParam = jdbcParamItr.next();
jdbcParamBindings.addBinding(
jdbcParam,
new JdbcParameterBindingImpl( jdbcMapping, jdbcValue )
);
},
session
);
}
// make sure we've exhausted all JDBC parameters
assert ! jdbcParamItr.hasNext();
}
@Override
protected boolean isSimple() {
return false;
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.ast.internal;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.ObjectDeletedException;
import org.hibernate.cache.spi.access.EntityDataAccess;
import org.hibernate.cache.spi.access.SoftLock;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.loader.ast.LoaderLogging;
import org.hibernate.persister.entity.EntityPersister;
/**
* @author Steve Ebersole
*/
public class LoaderHelper {
public static void upgradeLock(Object object, EntityEntry entry, LockOptions lockOptions, SharedSessionContractImplementor session) {
LockMode requestedLockMode = lockOptions.getLockMode();
if ( requestedLockMode.greaterThan( entry.getLockMode() ) ) {
// The user requested a "greater" (i.e. more restrictive) form of
// pessimistic lock
if ( entry.getStatus() != Status.MANAGED ) {
throw new ObjectDeletedException(
"attempted to lock a deleted instance",
entry.getId(),
entry.getPersister().getEntityName()
);
}
final EntityPersister persister = entry.getPersister();
if ( LoaderLogging.TRACE_ENABLED ) {
LoaderLogging.LOADER_LOGGER.tracef(
"Locking `%s( %s )` in `%s` lock-mode",
persister.getEntityName(),
entry.getId(),
requestedLockMode
);
}
final boolean cachingEnabled = persister.canWriteToCache();
SoftLock lock = null;
Object ck = null;
try {
if ( cachingEnabled ) {
EntityDataAccess cache = persister.getCacheAccessStrategy();
ck = cache.generateCacheKey( entry.getId(), persister, session.getFactory(), session.getTenantIdentifier() );
lock = cache.lockItem( session, ck, entry.getVersion() );
}
if ( persister.isVersioned() && requestedLockMode == LockMode.FORCE ) {
// todo : should we check the current isolation mode explicitly?
Object nextVersion = persister.forceVersionIncrement(
entry.getId(), entry.getVersion(), session
);
entry.forceLocked( object, nextVersion );
}
else {
persister.lock( entry.getId(), entry.getVersion(), object, lockOptions, session );
}
entry.setLockMode(requestedLockMode);
}
finally {
// the database now holds a lock + the object is flushed from the cache,
// so release the soft lock
if ( cachingEnabled ) {
persister.getCacheAccessStrategy().unlockItem( session, ck, lock );
}
}
}
}
}

View File

@ -12,6 +12,7 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@ -39,6 +40,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping;
import org.hibernate.metamodel.mapping.internal.SimpleForeignKeyDescriptor;
@ -109,7 +111,7 @@ public class LoaderSelectBuilder {
Loadable loadable,
List<? extends ModelPart> partsToSelect,
ModelPart restrictedPart,
DomainResult cachedDomainResult,
DomainResult<?> cachedDomainResult,
int numberOfKeysToLoad,
LoadQueryInfluencers loadQueryInfluencers,
LockOptions lockOptions,
@ -135,7 +137,7 @@ public class LoaderSelectBuilder {
Loadable loadable,
List<? extends ModelPart> partsToSelect,
List<ModelPart> restrictedParts,
DomainResult cachedDomainResult,
DomainResult<?> cachedDomainResult,
int numberOfKeysToLoad,
LoadQueryInfluencers loadQueryInfluencers,
LockOptions lockOptions,
@ -216,6 +218,8 @@ public class LoaderSelectBuilder {
int numberOfKeysToLoad,
LoadQueryInfluencers loadQueryInfluencers,
LockOptions lockOptions,
EntityGraphTraversalState entityGraphTraversalState,
boolean forceIdentifierSelection,
Consumer<JdbcParameter> jdbcParameterConsumer) {
this.creationContext = creationContext;
this.loadable = loadable;
@ -224,32 +228,70 @@ public class LoaderSelectBuilder {
this.cachedDomainResult = cachedDomainResult;
this.numberOfKeysToLoad = numberOfKeysToLoad;
this.loadQueryInfluencers = loadQueryInfluencers;
this.lockOptions = lockOptions;
this.entityGraphTraversalState = entityGraphTraversalState;
this.forceIdentifierSelection = forceIdentifierSelection;
this.jdbcParameterConsumer = jdbcParameterConsumer;
}
private LoaderSelectBuilder(
SqlAstCreationContext creationContext,
Loadable loadable,
List<? extends ModelPart> partsToSelect,
List<ModelPart> restrictedParts,
DomainResult cachedDomainResult,
int numberOfKeysToLoad,
LoadQueryInfluencers loadQueryInfluencers,
LockOptions lockOptions,
Consumer<JdbcParameter> jdbcParameterConsumer) {
this.creationContext = creationContext;
this.loadable = loadable;
this.partsToSelect = partsToSelect;
this.restrictedParts = restrictedParts;
this.cachedDomainResult = cachedDomainResult;
this.numberOfKeysToLoad = numberOfKeysToLoad;
this.loadQueryInfluencers = loadQueryInfluencers;
this.forceIdentifierSelection = determineWhetherToForceIdSelection( numberOfKeysToLoad, restrictedParts );
this.entityGraphTraversalState = determineGraphTraversalState( loadQueryInfluencers );
this.lockOptions = lockOptions != null ? lockOptions : LockOptions.NONE;
this.jdbcParameterConsumer = jdbcParameterConsumer;
}
private static boolean determineWhetherToForceIdSelection(int numberOfKeysToLoad, List<ModelPart> restrictedParts) {
if ( numberOfKeysToLoad > 1 ) {
forceIdentifierSelection = true;
return true;
}
else {
for ( ModelPart restrictedPart : restrictedParts ) {
if ( restrictedPart instanceof ForeignKeyDescriptor ) {
forceIdentifierSelection = true;
}
if ( restrictedParts.size() == 1 ) {
final ModelPart restrictedPart = restrictedParts.get( 0 );
if ( Objects.equals( restrictedPart.getPartName(), NaturalIdMapping.PART_NAME ) ) {
return true;
}
}
EntityGraphTraversalState entityGraphTraversalState = null;
for ( ModelPart restrictedPart : restrictedParts ) {
if ( restrictedPart instanceof ForeignKeyDescriptor ) {
return true;
}
}
return false;
}
private static EntityGraphTraversalState determineGraphTraversalState(LoadQueryInfluencers loadQueryInfluencers) {
if ( loadQueryInfluencers != null ) {
final EffectiveEntityGraph effectiveEntityGraph = loadQueryInfluencers.getEffectiveEntityGraph();
if ( effectiveEntityGraph != null ) {
final GraphSemantic graphSemantic = effectiveEntityGraph.getSemantic();
final RootGraphImplementor rootGraphImplementor = effectiveEntityGraph.getGraph();
if ( graphSemantic != null && rootGraphImplementor != null ) {
entityGraphTraversalState = new StandardEntityGraphTraversalStateImpl( graphSemantic, rootGraphImplementor );
return new StandardEntityGraphTraversalStateImpl( graphSemantic, rootGraphImplementor );
}
}
}
this.entityGraphTraversalState = entityGraphTraversalState;
this.lockOptions = lockOptions != null ? lockOptions : LockOptions.NONE;
this.jdbcParameterConsumer = jdbcParameterConsumer;
return null;
}
private LoaderSelectBuilder(

View File

@ -0,0 +1,52 @@
/*
* 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.ast.internal;
import java.util.HashSet;
import java.util.List;
import org.hibernate.engine.spi.BatchFetchQueue;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.SubselectFetch;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
/**
* Given as the target for {@link ExecutionContext#registerLoadingEntityEntry} calls when
* performing multi-loads to apply sub-select fetching support.
*/
public class LoadingEntityCollector {
private final SubselectFetch subselectFetch;
private final BatchFetchQueue batchFetchQueue;
LoadingEntityCollector(
EntityValuedModelPart loadingEntityDescriptor,
QuerySpec loadingSqlAst,
List<JdbcParameter> jdbcParameters,
JdbcParameterBindings jdbcParameterBindings,
BatchFetchQueue batchFetchQueue) {
this.batchFetchQueue = batchFetchQueue;
this.subselectFetch = new SubselectFetch(
loadingEntityDescriptor,
loadingSqlAst,
loadingSqlAst.getFromClause().getRoots().get( 0 ),
jdbcParameters,
jdbcParameterBindings,
new HashSet<>()
);
}
public void collectLoadingEntityKey(EntityKey entityKey) {
subselectFetch.getResultingEntityKeys().add( entityKey );
batchFetchQueue.addSubselect( entityKey, subselectFetch );
}
}

View File

@ -9,7 +9,6 @@ package org.hibernate.loader.ast.internal;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@ -18,7 +17,6 @@ import org.hibernate.LockOptions;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.BatchFetchQueue;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.LoadQueryInfluencers;
@ -26,22 +24,19 @@ import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.engine.spi.SubselectFetch;
import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.LoadEvent;
import org.hibernate.event.spi.LoadEventListener;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.loader.ast.spi.MultiIdEntityLoader;
import org.hibernate.loader.ast.spi.MultiIdLoadOptions;
import org.hibernate.loader.entity.CacheEntityLoaderHelper;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.MultiLoadOptions;
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.JdbcParameter;
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;
@ -58,15 +53,15 @@ import org.jboss.logging.Logger;
/**
* @author Steve Ebersole
*/
public class MultiIdEntityLoaderStandardImpl<T> implements MultiIdEntityLoader<T> {
private static final Logger log = Logger.getLogger( MultiIdEntityLoaderStandardImpl.class );
public class MultiIdLoaderStandard<T> implements MultiIdEntityLoader<T> {
private static final Logger log = Logger.getLogger( MultiIdLoaderStandard.class );
private final EntityPersister entityDescriptor;
private final SessionFactoryImplementor sessionFactory;
private int idJdbcTypeCount = -1;
public MultiIdEntityLoaderStandardImpl(EntityPersister entityDescriptor, SessionFactoryImplementor sessionFactory) {
public MultiIdLoaderStandard(EntityPersister entityDescriptor, SessionFactoryImplementor sessionFactory) {
this.entityDescriptor = entityDescriptor;
this.sessionFactory = sessionFactory;
}
@ -77,9 +72,10 @@ public class MultiIdEntityLoaderStandardImpl<T> implements MultiIdEntityLoader<T
}
@Override
public List<T> load(Object[] ids, MultiLoadOptions loadOptions, SharedSessionContractImplementor session) {
// todo (6.0) : account for all of the `loadOptions`...
// for now just do a simple load
public List<T> load(Object[] ids, MultiIdLoadOptions loadOptions, SharedSessionContractImplementor session) {
// todo (6.0) : account for all of the `loadOptions` for now just do a simple load
// ^^ atm this is handled in `MultiIdentifierLoadAccess`. Need to decide on the design we want here...
// - see `SimpleNaturalIdMultiLoadAccessImpl` for example of alternative
assert ids != null;
@ -99,7 +95,7 @@ public class MultiIdEntityLoaderStandardImpl<T> implements MultiIdEntityLoader<T
private List<T> performOrderedMultiLoad(
Object[] ids,
SharedSessionContractImplementor session,
MultiLoadOptions loadOptions) {
MultiIdLoadOptions loadOptions) {
if ( log.isTraceEnabled() ) {
log.tracef( "#performOrderedMultiLoad(`%s`, ..)", entityDescriptor.getEntityName() );
}
@ -336,38 +332,10 @@ public class MultiIdEntityLoaderStandardImpl<T> implements MultiIdEntityLoader<T
);
}
private static class LoadingEntityCollector {
private final SubselectFetch subselectFetch;
private final BatchFetchQueue batchFetchQueue;
LoadingEntityCollector(
EntityValuedModelPart loadingEntityDescriptor,
QuerySpec loadingSqlAst,
List<JdbcParameter> jdbcParameters,
JdbcParameterBindings jdbcParameterBindings,
BatchFetchQueue batchFetchQueue) {
this.batchFetchQueue = batchFetchQueue;
this.subselectFetch = new SubselectFetch(
loadingEntityDescriptor,
loadingSqlAst,
loadingSqlAst.getFromClause().getRoots().get( 0 ),
jdbcParameters,
jdbcParameterBindings,
new HashSet<>()
);
}
void collectLoadingEntityKey(EntityKey entityKey) {
subselectFetch.getResultingEntityKeys().add( entityKey );
batchFetchQueue.addSubselect( entityKey, subselectFetch );
}
}
private List<T> performUnorderedMultiLoad(
Object[] ids,
SharedSessionContractImplementor session,
MultiLoadOptions loadOptions) {
MultiIdLoadOptions loadOptions) {
assert !loadOptions.isOrderReturnEnabled();
assert ids != null;

View File

@ -0,0 +1,93 @@
/*
* 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.ast.internal;
import java.util.Collections;
import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SessionFactoryImplementor;
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;
/**
* Standard MultiNaturalIdLoader implementation
*/
public class MultiNaturalIdLoaderStandard<E> implements MultiNaturalIdLoader<E> {
// todo (6.0) : much of the execution logic here is borrowed from `org.hibernate.loader.ast.internal.MultiIdEntityLoaderStandardImpl`
// - consider ways to consolidate/share logic
// - actually, org.hibernate.loader.ast.internal.MultiNaturalIdLoadingBatcher is pretty close
private final EntityMappingType entityDescriptor;
public MultiNaturalIdLoaderStandard(EntityMappingType entityDescriptor, MappingModelCreationProcess creationProcess) {
this.entityDescriptor = entityDescriptor;
}
@Override
public <K> List<E> multiLoad(K[] naturalIds, MultiNaturalIdLoadOptions options, SharedSessionContractImplementor session) {
if ( naturalIds == null ) {
throw new IllegalArgumentException( "`naturalIds` is null" );
}
if ( naturalIds.length == 0 ) {
return Collections.emptyList();
}
if ( LoadingLogger.LOGGER.isTraceEnabled() ) {
LoadingLogger.LOGGER.tracef( "Starting multi natural-id loading for `%s`", entityDescriptor.getEntityName() );
}
final SessionFactoryImplementor sessionFactory = session.getFactory();
final int maxBatchSize;
if ( options.getBatchSize() != null && options.getBatchSize() > 0 ) {
maxBatchSize = options.getBatchSize();
}
else {
maxBatchSize = session.getJdbcServices().getJdbcEnvironment().getDialect().getDefaultBatchLoadSizingStrategy().determineOptimalBatchLoadSize(
entityDescriptor.getNaturalIdMapping().getJdbcTypeCount( sessionFactory.getTypeConfiguration() ),
naturalIds.length
);
}
final int batchSize = Math.min( maxBatchSize, naturalIds.length );
final LockOptions lockOptions = (options.getLockOptions() == null)
? new LockOptions( LockMode.NONE )
: options.getLockOptions();
final MultiNaturalIdLoadingBatcher batcher = new MultiNaturalIdLoadingBatcher(
entityDescriptor,
entityDescriptor.getNaturalIdMapping(),
batchSize,
(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().normalizeValue( naturalId, session );
},
session.getLoadQueryInfluencers(),
lockOptions,
sessionFactory
);
final List<E> results = batcher.multiLoad( naturalIds, options, session );
if ( options.isOrderReturnEnabled() ) {
throw new NotYetImplementedFor6Exception( getClass() );
}
return results;
}
}

View File

@ -0,0 +1,206 @@
/*
* 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.ast.internal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.hibernate.LockOptions;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoadOptions;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstSelectTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
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.JdbcSelectExecutorStandardImpl;
import org.hibernate.sql.exec.spi.Callback;
import org.hibernate.sql.exec.spi.ExecutionContext;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.JdbcSelect;
import org.hibernate.sql.results.graph.entity.LoadingEntityEntry;
import org.hibernate.sql.results.internal.RowTransformerPassThruImpl;
import org.hibernate.type.SerializableType;
/**
* Batch support for natural-id multi loading
*/
public class MultiNaturalIdLoadingBatcher {
@FunctionalInterface
interface KeyValueResolver {
/**
* Resolve the supported forms of representing the natural-id value to
* 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#normalizeValue}
*/
Object resolveKeyToLoad(Object incoming, SharedSessionContractImplementor session);
}
private final EntityMappingType entityDescriptor;
private final SelectStatement sqlSelect;
private final List<JdbcParameter> jdbcParameters;
private final KeyValueResolver keyValueResolver;
private final JdbcSelect jdbcSelect;
public MultiNaturalIdLoadingBatcher(
EntityMappingType entityDescriptor,
ModelPart restrictedPart,
int batchSize,
KeyValueResolver keyValueResolver,
LoadQueryInfluencers loadQueryInfluencers,
LockOptions lockOptions,
SessionFactoryImplementor sessionFactory) {
this.entityDescriptor = entityDescriptor;
jdbcParameters = new ArrayList<>( batchSize );
sqlSelect = LoaderSelectBuilder.createSelect(
entityDescriptor,
// return the full entity rather than parts
null,
restrictedPart,
// no "cached" DomainResult
null,
batchSize,
loadQueryInfluencers,
lockOptions,
jdbcParameters::add,
sessionFactory
);
this.keyValueResolver = keyValueResolver;
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
final SqlAstSelectTranslator sqlAstTranslator = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory );
this.jdbcSelect = sqlAstTranslator.translate( sqlSelect );
}
public <E> List<E> multiLoad(Object[] naturalIdValues, MultiNaturalIdLoadOptions options, SharedSessionContractImplementor session) {
final ArrayList<E> multiLoadResults = CollectionHelper.arrayList( naturalIdValues.length );
JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() );
Iterator<JdbcParameter> jdbcParamItr = jdbcParameters.iterator();
boolean needsExecution = false;
for ( int i = 0; i < naturalIdValues.length; i++ ) {
final JdbcParameterBindings jdbcParamBindingsRef = jdbcParamBindings;
final Iterator<JdbcParameter> jdbcParamItrRef = jdbcParamItr;
final Object bindValue = keyValueResolver.resolveKeyToLoad( naturalIdValues[ i ], session );
if ( bindValue != null ) {
entityDescriptor.getNaturalIdMapping().visitJdbcValues(
bindValue,
Clause.IRRELEVANT,
(jdbcValue, jdbcMapping) -> jdbcParamBindingsRef.addBinding(
jdbcParamItrRef.next(),
new JdbcParameterBindingImpl( jdbcMapping, jdbcValue )
),
session
);
needsExecution = true;
}
if ( ! jdbcParamItr.hasNext() ) {
// we've hit the batch mark
final List<E> batchResults = performLoad( jdbcParamBindings, session );
multiLoadResults.addAll( batchResults );
jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() );
jdbcParamItr = jdbcParameters.iterator();
needsExecution = false;
}
}
if ( needsExecution ) {
while ( jdbcParamItr.hasNext() ) {
// pad the remaining parameters with null
jdbcParamBindings.addBinding(
jdbcParamItr.next(),
new JdbcParameterBindingImpl( SerializableType.INSTANCE, null )
);
}
final List<E> batchResults = performLoad( jdbcParamBindings, session );
multiLoadResults.addAll( batchResults );
}
return multiLoadResults;
}
private <E> List<E> performLoad(JdbcParameterBindings jdbcParamBindings, SharedSessionContractImplementor session) {
final LoadingEntityCollector loadingEntityCollector;
if ( entityDescriptor.getEntityPersister().hasSubselectLoadableCollections() ) {
loadingEntityCollector = new LoadingEntityCollector(
entityDescriptor,
sqlSelect.getQuerySpec(),
jdbcParameters,
jdbcParamBindings,
session.getPersistenceContext().getBatchFetchQueue()
);
}
else {
loadingEntityCollector = null;
}
return JdbcSelectExecutorStandardImpl.INSTANCE.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 null;
}
@Override
public void registerLoadingEntityEntry(EntityKey entityKey, LoadingEntityEntry entry) {
if ( loadingEntityCollector != null ) {
loadingEntityCollector.collectLoadingEntityKey( entityKey );
}
}
},
RowTransformerPassThruImpl.instance(),
true
);
}
}

View File

@ -0,0 +1,262 @@
/*
* 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.ast.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.hibernate.HibernateException;
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.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.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.JdbcParameter;
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.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.JdbcSelect;
/**
* NaturalIdLoader for simple natural-ids
*/
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 );
}
@Override
protected SimpleNaturalIdMapping naturalIdMapping() {
return (SimpleNaturalIdMapping) super.naturalIdMapping();
}
@Override
protected void applyNaturalIdAsJdbcParameters(
Object naturalIdToLoad,
List<JdbcParameter> jdbcParameters,
JdbcParameterBindings jdbcParamBindings,
SharedSessionContractImplementor session) {
assert jdbcParameters.size() == 1;
final Object bindableValue = naturalIdMapping().normalizeValue( naturalIdToLoad, session );
final SingularAttributeMapping attributeMapping = naturalIdMapping().getNaturalIdAttributes().get( 0 );
attributeMapping.visitJdbcValues(
bindableValue,
Clause.WHERE,
(jdbcValue, jdbcMapping) -> {
final JdbcParameter jdbcParam = jdbcParameters.get( 0 );
jdbcParamBindings.addBinding(
jdbcParam,
new JdbcParameterBindingImpl( jdbcMapping, jdbcValue )
);
},
session
);
}
@Override
protected Object resolveNaturalIdBindValue(Object naturalIdToLoad, SharedSessionContractImplementor session) {
return naturalIdMapping().normalizeValue( naturalIdToLoad, session );
}
@Override
public Object resolveIdToNaturalId(Object id, SharedSessionContractImplementor session) {
final SessionFactoryImplementor sessionFactory = session.getFactory();
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 JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlSelect );
final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() );
final Iterator<JdbcParameter> jdbcParamItr = jdbcParameters.iterator();
entityDescriptor().getIdentifierMapping().visitJdbcValues(
id,
Clause.WHERE,
(value, type) -> {
assert jdbcParamItr.hasNext();
final JdbcParameter jdbcParam = jdbcParamItr.next();
jdbcParamBindings.addBinding(
jdbcParam,
new JdbcParameterBindingImpl( type, value )
);
},
session
);
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;
}
@Override
public Object resolveNaturalIdToId(
Object naturalIdValue,
SharedSessionContractImplementor session) {
final Object bindValue = naturalIdMapping().normalizeValue( 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
);
assert jdbcParameters.size() == 1;
final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlSelect );
final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() );
final Iterator<JdbcParameter> jdbcParamItr = jdbcParameters.iterator();
final SingularAttributeMapping attributeMapping = naturalIdMapping().getAttribute();
attributeMapping.visitJdbcValues(
bindValue,
Clause.WHERE,
(jdbcValue, jdbcMapping) -> {
assert jdbcParamItr.hasNext();
jdbcParamBindings.addBinding(
jdbcParamItr.next(),
new JdbcParameterBindingImpl( jdbcMapping, jdbcValue )
);
},
session
);
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(
String.format(
"Resolving natural-id to id returned more that one row : %s [%s]",
entityDescriptor().getEntityName(),
bindValue
)
);
}
return results.get( 0 );
}
}

View File

@ -10,7 +10,6 @@ import java.util.List;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.MultiLoadOptions;
/**
* Loader subtype for loading multiple entities by multiple identifier values.
@ -21,5 +20,5 @@ public interface MultiIdEntityLoader<T> extends Loader {
@Override
EntityPersister getLoadable();
List<T> load(Object[] ids, MultiLoadOptions options, SharedSessionContractImplementor session);
List<T> load(Object[] ids, MultiIdLoadOptions options, SharedSessionContractImplementor session);
}

View File

@ -0,0 +1,28 @@
/*
* 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.ast.spi;
/**
* Encapsulation of the options for loading multiple entities by id
*/
public interface MultiIdLoadOptions extends MultiLoadOptions {
/**
* Check the first-level cache first, and only if the entity is not found in the cache
* should Hibernate hit the database.
*
* @return the session cache is checked first
*/
boolean isSessionCheckingEnabled();
/**
* Check the second-level cache first, and only if the entity is not found in the cache
* should Hibernate hit the database.
*
* @return the session factory cache is checked first
*/
boolean isSecondLevelCacheCheckingEnabled();
}

View File

@ -1,35 +1,17 @@
/*
* 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>.
* 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.persister.entity;
package org.hibernate.loader.ast.spi;
import org.hibernate.LockOptions;
/**
* Encapsulation of the options for performing a load by multiple identifiers.
*
* @author Steve Ebersole
* Base contract for options for multi-load operations
*/
public interface MultiLoadOptions {
/**
* Check the first-level cache first, and only if the entity is not found in the cache
* should Hibernate hit the database.
*
* @return the session cache is checked first
*/
boolean isSessionCheckingEnabled();
/**
* Check the second-level cache first, and only if the entity is not found in the cache
* should Hibernate hit the database.
*
* @return the session factory cache is checked first
*/
boolean isSecondLevelCacheCheckingEnabled();
/**
* Should we returned entities that are scheduled for deletion.
*

View File

@ -0,0 +1,13 @@
/*
* 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.ast.spi;
/**
* Encapsulation of the options for loading multiple entities by natural-id
*/
public interface MultiNaturalIdLoadOptions extends MultiLoadOptions {
}

View File

@ -0,0 +1,33 @@
/*
* 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.ast.spi;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
/**
* Loader for entities by multiple natural-ids
*
* @param <E> The entity Java type
*/
public interface MultiNaturalIdLoader<E> {
/**
* Load multiple entities by natural-id. The exact result depends on the passed options.
*
* @param naturalIds The natural-ids to load. The values of this array will depend on whether the
* natural-id is simple or complex.
*
* @param <K> The basic form for a natural-id is a Map of its attribute values, or an array of the
* values positioned according to "attribute ordering". Simple natural-ids can also be expressed
* by their simple (basic/embedded) type.
*/
<K> List<E> multiLoad(K[] naturalIds, MultiNaturalIdLoadOptions options, SharedSessionContractImplementor session);
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.loader.ast.spi;
import java.util.List;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
@ -15,7 +17,25 @@ 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
*/
@ -44,9 +64,12 @@ public interface NaturalIdLoader<T> extends Loader {
T load(Object naturalIdToLoad, LoadOptions options, SharedSessionContractImplementor session);
/**
* Resolve the natural-id value from an id
* Resolve the id from natural-id value
*/
Object[] resolveIdToNaturalId(Object id, SharedSessionContractImplementor session);
Object resolveNaturalIdToId(Object naturalIdValue, SharedSessionContractImplementor session);
Object resolveNaturalIdToId(Object[] naturalIdValues, SharedSessionContractImplementor session);
/**
* Resolve the natural-id value(s) from an id
*/
Object resolveIdToNaturalId(Object id, SharedSessionContractImplementor session);
}

View File

@ -7,7 +7,9 @@
package org.hibernate.loader.entity;
import org.hibernate.HibernateException;
import org.hibernate.Incubating;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.WrongClassException;
import org.hibernate.cache.spi.access.EntityDataAccess;
import org.hibernate.cache.spi.entry.CacheEntry;
@ -23,18 +25,14 @@ import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.event.internal.AbstractLockUpgradeEventListener;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.EventType;
import org.hibernate.event.spi.LoadEvent;
import org.hibernate.event.spi.LoadEventListener;
import org.hibernate.event.spi.PostLoadEvent;
import org.hibernate.event.spi.PostLoadEventListener;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.FastSessionServices;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.proxy.HibernateProxy;
@ -44,10 +42,12 @@ import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.type.Type;
import org.hibernate.type.TypeHelper;
import static org.hibernate.loader.ast.internal.LoaderHelper.upgradeLock;
/**
* @author Vlad Mihalcea
*/
public class CacheEntityLoaderHelper extends AbstractLockUpgradeEventListener {
public class CacheEntityLoaderHelper {
public static final CacheEntityLoaderHelper INSTANCE = new CacheEntityLoaderHelper();
@ -62,6 +62,46 @@ public class CacheEntityLoaderHelper extends AbstractLockUpgradeEventListener {
private CacheEntityLoaderHelper() {
}
@Incubating
public PersistenceContextEntry loadFromSessionCache(
EntityKey keyToLoad,
LoadEventListener.LoadType options,
LockOptions lockOptions,
SharedSessionContractImplementor session) {
final Object old = session.getEntityUsingInterceptor( keyToLoad );
if ( old != null ) {
// this object was already loaded
EntityEntry oldEntry = session.getPersistenceContext().getEntry( old );
if ( options.isCheckDeleted() ) {
Status status = oldEntry.getStatus();
if ( status == Status.DELETED || status == Status.GONE ) {
LoadingLogger.LOGGER.debug(
"Load request found matching entity in context, but it is scheduled for removal; returning null" );
return new PersistenceContextEntry( old, EntityStatus.REMOVED_ENTITY_MARKER );
}
}
if ( options.isAllowNulls() ) {
final EntityPersister persister = session.getFactory()
.getRuntimeMetamodels()
.getMappingMetamodel()
.getEntityDescriptor( keyToLoad.getEntityName() );
if ( ! persister.isInstance( old ) ) {
LOG.debugf(
"Load request found matching entity in context, but the matched entity was of an inconsistent return type. " +
"Setting status as `%s`",
EntityStatus.INCONSISTENT_RTN_CLASS_MARKER
);
return new PersistenceContextEntry( old, EntityStatus.INCONSISTENT_RTN_CLASS_MARKER );
}
}
upgradeLock( old, oldEntry, lockOptions, session );
}
return new PersistenceContextEntry( old, EntityStatus.MANAGED );
}
/**
* Attempts to locate the entity in the session-level cache.
* <p/>

View File

@ -8,9 +8,28 @@ package org.hibernate.metamodel.mapping;
import java.util.List;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.ast.spi.MultiNaturalIdLoader;
import org.hibernate.loader.ast.spi.NaturalIdLoader;
/**
* @author Steve Ebersole
* Mapping for an entity's natural-id, if one is defined
*/
public interface NaturalIdMapping extends VirtualModelPart {
String PART_NAME = "{natural-id}";
/**
* The attribute(s) making up the natural-id.
*/
List<SingularAttributeMapping> getNaturalIdAttributes();
@Override
default String getPartName() {
return PART_NAME;
}
NaturalIdLoader getNaturalIdLoader();
MultiNaturalIdLoader getMultiNaturalIdLoader();
Object normalizeValue(Object incoming, SharedSessionContractImplementor session);
}

View File

@ -0,0 +1,38 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.internal;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.model.domain.NavigableRole;
/**
* @author Steve Ebersole
*/
public abstract class AbstractNaturalIdMapping implements NaturalIdMapping {
private final EntityMappingType declaringType;
private final String cacheRegionName;
private final NavigableRole role;
public AbstractNaturalIdMapping(EntityMappingType declaringType, String cacheRegionName) {
this.declaringType = declaringType;
this.cacheRegionName = cacheRegionName;
this.role = declaringType.getNavigableRole().append( PART_NAME );
}
@Override
public NavigableRole getNavigableRole() {
return role;
}
@Override
public EntityMappingType findContainingEntityMapping() {
return declaringType;
}
}

View File

@ -0,0 +1,216 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.internal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.loader.NaturalIdPostLoadListener;
import org.hibernate.loader.NaturalIdPreLoadListener;
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.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
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.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Multi-attribute NaturalIdMapping implementation
*/
public class CompoundNaturalIdMapping extends AbstractNaturalIdMapping implements MappingType {
// todo (6.0) : create a composite MappingType for this descriptor's Object[]?
private final List<SingularAttributeMapping> attributes;
private final List<JdbcMapping> jdbcMappings;
private final NaturalIdLoader<?> loader;
private final MultiNaturalIdLoader<?> multiLoader;
public CompoundNaturalIdMapping(
EntityMappingType declaringType,
List<SingularAttributeMapping> attributes,
String cacheRegionName,
MappingModelCreationProcess creationProcess) {
super( declaringType, cacheRegionName );
this.attributes = attributes;
final List<JdbcMapping> jdbcMappings = new ArrayList<>();
final TypeConfiguration typeConfiguration = creationProcess.getCreationContext().getTypeConfiguration();
attributes.forEach(
(attribute) -> attribute.visitJdbcTypes( jdbcMappings::add, Clause.IRRELEVANT, typeConfiguration )
);
this.jdbcMappings = jdbcMappings;
loader = new CompoundNaturalIdLoader<>(
this,
NaturalIdPreLoadListener.NO_OP,
NaturalIdPostLoadListener.NO_OP,
declaringType,
creationProcess
);
multiLoader = new MultiNaturalIdLoaderStandard<>( declaringType, creationProcess );
}
@Override
@SuppressWarnings( "rawtypes" )
public Object normalizeValue(Object incoming, SharedSessionContractImplementor session) {
if ( incoming instanceof Object[] ) {
return incoming;
}
if ( incoming instanceof Map ) {
final Map valueMap = (Map) incoming;
final List<SingularAttributeMapping> attributes = getNaturalIdAttributes();
final Object[] values = new Object[ attributes.size() ];
for ( int i = 0; i < attributes.size(); i++ ) {
values[ i ] = valueMap.get( attributes.get( i ).getAttributeName() );
}
return values;
}
throw new UnsupportedOperationException( "Do not know how to normalize compound natural-id value : " + incoming );
}
@Override
public List<SingularAttributeMapping> getNaturalIdAttributes() {
return attributes;
}
@Override
public NaturalIdLoader<?> getNaturalIdLoader() {
return loader;
}
@Override
public MultiNaturalIdLoader<?> getMultiNaturalIdLoader() {
return multiLoader;
}
@Override
public MappingType getPartMappingType() {
return this;
}
@Override
public JavaTypeDescriptor<?> getJavaTypeDescriptor() {
throw new NotYetImplementedFor6Exception( getClass() );
}
@Override
public JavaTypeDescriptor<?> getMappedJavaTypeDescriptor() {
return getJavaTypeDescriptor();
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ModelPart
@Override
public <T> DomainResult<T> createDomainResult(NavigablePath navigablePath, TableGroup tableGroup, String resultVariable, DomainResultCreationState creationState) {
throw new NotYetImplementedFor6Exception( getClass() );
}
@Override
public void applySqlSelections(NavigablePath navigablePath, TableGroup tableGroup, DomainResultCreationState creationState) {
attributes.forEach(
(attribute) -> attribute.applySqlSelections( navigablePath, tableGroup, creationState )
);
}
@Override
public void applySqlSelections(NavigablePath navigablePath, TableGroup tableGroup, DomainResultCreationState creationState, BiConsumer<SqlSelection, JdbcMapping> selectionConsumer) {
attributes.forEach(
(attribute) -> attribute.applySqlSelections( navigablePath, tableGroup, creationState, selectionConsumer )
);
}
@Override
public void visitColumns(ColumnConsumer consumer) {
attributes.forEach(
(attribute) -> attribute.visitColumns( consumer )
);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Bindable
@Override
public int getJdbcTypeCount(TypeConfiguration typeConfiguration) {
return jdbcMappings.size();
}
@Override
public List<JdbcMapping> getJdbcMappings(TypeConfiguration typeConfiguration) {
return jdbcMappings;
}
@Override
public void visitJdbcTypes(Consumer<JdbcMapping> action, Clause clause, TypeConfiguration typeConfiguration) {
jdbcMappings.forEach( action );
}
@Override
public Object disassemble(Object value, SharedSessionContractImplementor session) {
assert value instanceof Object[];
final Object[] incoming = (Object[]) value;
assert incoming.length == attributes.size();
final Object[] outgoing = new Object[ incoming.length ];
for ( int i = 0; i < attributes.size(); i++ ) {
final SingularAttributeMapping attribute = attributes.get( i );
outgoing[ i ] = attribute.disassemble( incoming[ i ], session );
}
return outgoing;
}
@Override
public void visitDisassembledJdbcValues(Object value, Clause clause, JdbcValuesConsumer valuesConsumer, SharedSessionContractImplementor session) {
assert value instanceof Object[];
final Object[] incoming = (Object[]) value;
assert incoming.length == attributes.size();
for ( int i = 0; i < attributes.size(); i++ ) {
final SingularAttributeMapping attribute = attributes.get( i );
attribute.visitDisassembledJdbcValues( incoming[ i ], clause, valuesConsumer, session );
}
}
@Override
public void visitJdbcValues(Object value, Clause clause, JdbcValuesConsumer valuesConsumer, SharedSessionContractImplementor session) {
assert value instanceof Object[];
final Object[] incoming = (Object[]) value;
assert incoming.length == attributes.size();
for ( int i = 0; i < attributes.size(); i++ ) {
final SingularAttributeMapping attribute = attributes.get( i );
attribute.visitJdbcValues( incoming[ i ], clause, valuesConsumer, session );
}
}
}

View File

@ -0,0 +1,172 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.internal;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
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.metamodel.mapping.ColumnConsumer;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
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.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Single-attribute NaturalIdMapping implementation
*/
public class SimpleNaturalIdMapping extends AbstractNaturalIdMapping {
private final SingularAttributeMapping attribute;
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.loader = new SimpleNaturalIdLoader<>(
this,
NaturalIdPreLoadListener.NO_OP,
NaturalIdPostLoadListener.NO_OP,
declaringType,
creationProcess
);
this.multiLoader = new MultiNaturalIdLoaderStandard<>( declaringType, creationProcess );
}
@Override
public Object normalizeValue(Object incoming, SharedSessionContractImplementor session) {
return normalizeValue( incoming );
}
@SuppressWarnings( "rawtypes" )
public Object normalizeValue(Object naturalIdToLoad) {
if ( naturalIdToLoad instanceof Map ) {
final Map valueMap = (Map) naturalIdToLoad;
assert valueMap.size() == 1;
assert valueMap.containsKey( getAttribute().getAttributeName() );
return valueMap.get( getAttribute().getAttributeName() );
}
if ( naturalIdToLoad instanceof Object[] ) {
final Object[] values = (Object[]) naturalIdToLoad;
assert values.length == 1;
return values[0];
}
return naturalIdToLoad;
}
public SingularAttributeMapping getAttribute() {
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 MappingType getPartMappingType() {
return attribute.getPartMappingType();
}
@Override
public JavaTypeDescriptor getJavaTypeDescriptor() {
return attribute.getJavaTypeDescriptor();
}
@Override
public <T> DomainResult<T> createDomainResult(
NavigablePath navigablePath,
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
return attribute.createDomainResult( navigablePath, tableGroup, resultVariable, creationState );
}
@Override
public void applySqlSelections(NavigablePath navigablePath, TableGroup tableGroup, DomainResultCreationState creationState) {
attribute.applySqlSelections( navigablePath, tableGroup, creationState );
}
@Override
public void applySqlSelections(
NavigablePath navigablePath,
TableGroup tableGroup,
DomainResultCreationState creationState,
BiConsumer<SqlSelection, JdbcMapping> selectionConsumer) {
attribute.applySqlSelections( navigablePath, tableGroup, creationState, selectionConsumer );
}
@Override
public void visitColumns(ColumnConsumer consumer) {
attribute.visitColumns( consumer );
}
@Override
public int getJdbcTypeCount(TypeConfiguration typeConfiguration) {
return attribute.getJdbcTypeCount( typeConfiguration );
}
@Override
public List<JdbcMapping> getJdbcMappings(TypeConfiguration typeConfiguration) {
return attribute.getJdbcMappings( typeConfiguration );
}
@Override
public void visitJdbcTypes(Consumer<JdbcMapping> action, Clause clause, TypeConfiguration typeConfiguration) {
attribute.visitJdbcTypes( action, clause, typeConfiguration );
}
@Override
public Object disassemble(Object value, SharedSessionContractImplementor session) {
return attribute.disassemble( value, session );
}
@Override
public void visitDisassembledJdbcValues(Object value, Clause clause, JdbcValuesConsumer valuesConsumer, SharedSessionContractImplementor session) {
attribute.visitDisassembledJdbcValues( value, clause, valuesConsumer, session );
}
@Override
public void visitJdbcValues(Object value, Clause clause, JdbcValuesConsumer valuesConsumer, SharedSessionContractImplementor session) {
attribute.visitJdbcValues( value, clause, valuesConsumer, session );
}
}

View File

@ -78,8 +78,6 @@ import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.CascadingAction;
import org.hibernate.engine.spi.CascadingActions;
import org.hibernate.engine.spi.CollectionKey;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityEntryFactory;
@ -112,8 +110,7 @@ import org.hibernate.internal.util.collections.LockModeEnumMap;
import org.hibernate.jdbc.Expectation;
import org.hibernate.jdbc.Expectations;
import org.hibernate.jdbc.TooManyRowsAffectedException;
import org.hibernate.loader.ast.internal.MultiIdEntityLoaderStandardImpl;
import org.hibernate.loader.ast.internal.NaturalIdLoaderStandardImpl;
import org.hibernate.loader.ast.internal.MultiIdLoaderStandard;
import org.hibernate.loader.ast.internal.Preparable;
import org.hibernate.loader.ast.internal.SingleIdEntityLoaderDynamicBatch;
import org.hibernate.loader.ast.internal.SingleIdEntityLoaderProvidedQueryImpl;
@ -121,6 +118,7 @@ import org.hibernate.loader.ast.internal.SingleIdEntityLoaderStandardImpl;
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.NaturalIdLoader;
import org.hibernate.loader.ast.spi.SingleIdEntityLoader;
import org.hibernate.loader.ast.spi.SingleUniqueKeyEntityLoader;
@ -155,14 +153,16 @@ import org.hibernate.metamodel.mapping.Queryable;
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.EntityDiscriminatorMappingImpl;
import org.hibernate.metamodel.mapping.internal.EntityRowIdMappingImpl;
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.model.domain.NavigableRole;
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
@ -251,7 +251,6 @@ public abstract class AbstractEntityPersister
private final SingleIdEntityLoader singleIdEntityLoader;
private final MultiIdEntityLoader multiIdEntityLoader;
private final NaturalIdLoader naturalIdLoader;
private SqmMultiTableMutationStrategy sqmMultiTableMutationStrategy;
@ -749,11 +748,7 @@ public abstract class AbstractEntityPersister
// to load at once. i.e. it limits the number of the generated IN-list JDBC-parameters in a given
// PreparedStatement, opting to split the load into multiple JDBC operations to work around database
// limits on number of parameters, number of IN-list values, etc
multiIdEntityLoader = new MultiIdEntityLoaderStandardImpl( this, factory );
naturalIdLoader = bootDescriptor.hasNaturalId()
? new NaturalIdLoaderStandardImpl( this )
: null;
multiIdEntityLoader = new MultiIdLoaderStandard( this, factory );
Iterator iter = bootDescriptor.getIdentifier().getColumnIterator();
int i = 0;
@ -4443,7 +4438,6 @@ public abstract class AbstractEntityPersister
prepareLoader( singleIdEntityLoader );
prepareLoader( multiIdEntityLoader );
prepareLoader( naturalIdLoader );
doPostInstantiate();
}
@ -4563,7 +4557,7 @@ public abstract class AbstractEntityPersister
}
@Override
public List multiLoad(Object[] ids, SharedSessionContractImplementor session, MultiLoadOptions loadOptions) {
public List multiLoad(Object[] ids, SharedSessionContractImplementor session, MultiIdLoadOptions loadOptions) {
return multiIdEntityLoader.load( ids, loadOptions, session );
}
@ -5575,7 +5569,13 @@ public abstract class AbstractEntityPersister
);
}
return naturalIdLoader.resolveIdToNaturalId( id, session );
final Object result = naturalIdMapping.getNaturalIdLoader().resolveIdToNaturalId( id, session );
if ( result instanceof Object[] ) {
return (Object[]) result;
}
else {
return new Object[] { result };
}
}
private void verifyHasNaturalId() {
@ -5584,6 +5584,12 @@ public abstract class AbstractEntityPersister
}
}
@Override
public NaturalIdLoader<?> getNaturalIdLoader() {
verifyHasNaturalId();
return naturalIdMapping.getNaturalIdLoader();
}
@Override
public Object loadEntityIdByNaturalId(
Object[] naturalIdValues,
@ -5599,7 +5605,7 @@ public abstract class AbstractEntityPersister
);
}
return naturalIdLoader.resolveNaturalIdToId( naturalIdValues, session );
return naturalIdMapping.getNaturalIdLoader().resolveNaturalIdToId( naturalIdValues, session );
}
public boolean hasNaturalIdentifier() {
@ -5842,6 +5848,8 @@ public abstract class AbstractEntityPersister
getAttributeMappings();
postProcessAttributeMappings( creationProcess, bootEntityDescriptor );
final ReflectionOptimizer reflectionOptimizer = representationStrategy.getReflectionOptimizer();
if ( reflectionOptimizer != null ) {
@ -5893,12 +5901,61 @@ public abstract class AbstractEntityPersister
(role, process) -> new EntityRowIdMappingImpl( rowIdName, this.getTableName(), this)
);
}
// todo (6.0) : support for natural-id not yet implemented
naturalIdMapping = null;
discriminatorMapping = generateDiscriminatorMapping();
}
private void postProcessAttributeMappings(MappingModelCreationProcess creationProcess, PersistentClass bootEntityDescriptor) {
if ( bootEntityDescriptor.hasNaturalId() ) {
naturalIdMapping = generateNaturalIdMapping( creationProcess, bootEntityDescriptor );
}
else {
naturalIdMapping = null;
}
}
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() );
}
}
}
);
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
);
}
}
protected static SqmMultiTableMutationStrategy interpretSqmMultiTableStrategy(
AbstractEntityPersister entityMappingDescriptor,
MappingModelCreationProcess creationProcess) {

View File

@ -32,6 +32,8 @@ import org.hibernate.id.IdentifierGenerator;
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.NaturalIdLoader;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.internal.InFlightEntityMappingType;
@ -409,6 +411,12 @@ public interface EntityPersister
*/
boolean hasLazyProperties();
default NaturalIdLoader getNaturalIdLoader() {
throw new UnsupportedOperationException(
"EntityPersister implementation `" + getClass().getName() + "` does not support `#getNaturalIdLoader`"
);
}
/**
* Load the id for the entity based on the natural id.
* @return
@ -447,7 +455,7 @@ public interface EntityPersister
*
* @return The loaded, matching entities
*/
List multiLoad(Object[] ids, SharedSessionContractImplementor session, MultiLoadOptions loadOptions);
List multiLoad(Object[] ids, SharedSessionContractImplementor session, MultiIdLoadOptions loadOptions);
/**
* Do a version check (optional operation)

View File

@ -49,12 +49,11 @@ import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityRowIdMapping;
import org.hibernate.metamodel.mapping.EntityVersionMapping;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.MultiLoadOptions;
import org.hibernate.loader.ast.spi.MultiIdLoadOptions;
import org.hibernate.persister.internal.PersisterClassResolverInitiator;
import org.hibernate.persister.spi.PersisterClassResolver;
import org.hibernate.persister.spi.PersisterCreationContext;
@ -325,7 +324,7 @@ public class PersisterClassProviderTest {
}
@Override
public List multiLoad(Object[] ids, SharedSessionContractImplementor session, MultiLoadOptions loadOptions) {
public List multiLoad(Object[] ids, SharedSessionContractImplementor session, MultiIdLoadOptions loadOptions) {
return Collections.emptyList();
}

View File

@ -0,0 +1,266 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.metamodel.mapping.naturalid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.NaturalIdLoadAccess;
import org.hibernate.NaturalIdMultiLoadAccess;
import org.hibernate.annotations.NaturalId;
import org.hibernate.loader.ast.spi.NaturalIdLoader;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.DomainModelScope;
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.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
/**
* Tests for composite (multiple attributes) natural-ids
*/
@DomainModel( annotatedClasses = CompoundNaturalIdTests.Account.class )
@SessionFactory
public class CompoundNaturalIdTests {
public static final Object[] VALUE_ARRAY = new Object[] { "matrix", "neo" };
public static final Map<String,String> VALUE_NAP = toMap( "system", "matrix", "username", "neo" );
private static Map<String, String> toMap(String... values) {
assert values.length % 2 == 0;
final HashMap<String,String> valuesMap = new HashMap<>();
for ( int i = 0; i < values.length; i += 2 ) {
valuesMap.put( values[i], values[i+1] );
}
return valuesMap;
}
@BeforeEach
public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.persist( new Account( 1, "neo", "matrix", "neo@nebuchadnezzar.zion.net" ) );
session.persist( new Account( 2, "trinity", "matrix", "trin@nebuchadnezzar.zion.net" ) );
}
);
}
@AfterEach
public void releaseTestData(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "delete Account" ).executeUpdate();
}
);
}
@Test
public void testProcessing(DomainModelScope domainModelScope, SessionFactoryScope factoryScope) {
final PersistentClass accountBootMapping = domainModelScope.getDomainModel().getEntityBinding( Account.class.getName() );
assertThat( accountBootMapping.hasNaturalId(), is( true ) );
final Property username = accountBootMapping.getProperty( "username" );
assertThat( username.isNaturalIdentifier(), is( true ) );
final Property system = accountBootMapping.getProperty( "system" );
assertThat( system.isNaturalIdentifier(), is( true ) );
final MappingMetamodel mappingMetamodel = factoryScope.getSessionFactory().getRuntimeMetamodels().getMappingMetamodel();
final EntityPersister accountMapping = mappingMetamodel.findEntityDescriptor( Account.class );
assertThat( accountMapping.hasNaturalIdentifier(), is( true ) );
final NaturalIdMapping naturalIdMapping = accountMapping.getNaturalIdMapping();
assertThat( naturalIdMapping, notNullValue() );
final List<SingularAttributeMapping> attributes = naturalIdMapping.getNaturalIdAttributes();
assertThat( attributes.size(), is( 2 ) );
// alphabetical matching overall processing
final SingularAttributeMapping first = attributes.get( 0 );
assertThat( first, notNullValue() );
assertThat( first.getAttributeName(), is( "system" ) );
final SingularAttributeMapping second = attributes.get( 1 );
assertThat( second, notNullValue() );
assertThat( second.getAttributeName(), is( "username" ) );
}
@Test
public void testGetReference(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final NaturalIdLoadAccess<Account> loadAccess = session.byNaturalId( Account.class );
loadAccess.using( "system", "matrix" );
loadAccess.using( "username", "neo" );
verifyEntity( loadAccess.getReference() );
}
);
scope.inTransaction(
session -> {
final MappingMetamodel mappingMetamodel = session.getFactory().getRuntimeMetamodels().getMappingMetamodel();
final EntityPersister accountMapping = mappingMetamodel.findEntityDescriptor( Account.class );
final NaturalIdMapping naturalIdMapping = accountMapping.getNaturalIdMapping();
// test load by array
Object id = naturalIdMapping.getNaturalIdLoader().resolveNaturalIdToId( VALUE_ARRAY, session );
assertThat( id, is( 1 ) );
// and by Map
id = naturalIdMapping.getNaturalIdLoader().resolveNaturalIdToId( VALUE_NAP, session );
assertThat( id, is( 1 ) );
}
);
}
public void verifyEntity(Account accountRef) {
assertThat( accountRef, notNullValue() );
assertThat( accountRef.getId(), is( 1 ) );
assertThat( accountRef.getSystem(), is( "matrix" ) );
assertThat( accountRef.getUsername(), is( "neo" ) );
}
@Test
public void testLoad(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final Account account = session.byNaturalId( Account.class )
.using( "system", "matrix" )
.using( "username", "neo" )
.load();
verifyEntity( account );
}
);
scope.inTransaction(
session -> {
final MappingMetamodel mappingMetamodel = session.getFactory().getRuntimeMetamodels().getMappingMetamodel();
final EntityPersister accountMapping = mappingMetamodel.findEntityDescriptor( Account.class );
final NaturalIdMapping naturalIdMapping = accountMapping.getNaturalIdMapping();
// test load by array
naturalIdMapping.getNaturalIdLoader().load( VALUE_ARRAY, NaturalIdLoader.LoadOptions.NONE, session );
// and by Map
naturalIdMapping.getNaturalIdLoader().load( VALUE_NAP, NaturalIdLoader.LoadOptions.NONE, session );
}
);
}
@Test
public void testOptionalLoad(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final NaturalIdLoadAccess<Account> loadAccess = session.byNaturalId( Account.class );
final Optional<Account> optionalAccount = loadAccess
.using( "system", "matrix" )
.using( "username", "neo" )
.loadOptional();
assertThat( optionalAccount.isPresent(), is( true ) );
verifyEntity( optionalAccount.get() );
}
);
}
@Test
public void testMultiLoad(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final NaturalIdMultiLoadAccess<Account> loadAccess = session.byMultipleNaturalId( Account.class );
loadAccess.enableOrderedReturn( false );
final List<Account> accounts = loadAccess.multiLoad(
NaturalIdMultiLoadAccess.compoundValue( "system", "matrix", "username", "neo" ),
NaturalIdMultiLoadAccess.compoundValue( "system", "matrix", "username", "trinity" )
);
assertThat( accounts.size(), is( 2 ) );
final List<Account> byMap = loadAccess.multiLoad( VALUE_NAP );
assertThat( byMap.size(), is( 1 ) );
final List<Account> byArray = loadAccess.multiLoad( new Object[] { VALUE_ARRAY } );
assertThat( byArray.size(), is( 1 ) );
}
);
}
@Entity( name = "Account" )
@Table( name = "acct" )
public static class Account {
@Id
private Integer id;
@NaturalId
private String username;
@NaturalId
private String system;
private String emailAddress;
public Account() {
}
public Account(Integer id, String username, String system) {
this.id = id;
this.username = username;
this.system = system;
}
public Account(Integer id, String username, String system, String emailAddress) {
this.id = id;
this.username = username;
this.system = system;
this.emailAddress = emailAddress;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSystem() {
return system;
}
public void setSystem(String system) {
this.system = system;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
}
}

View File

@ -0,0 +1,138 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.orm.test.metamodel.mapping.naturalid;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.UUID;
import javax.money.Monetary;
import org.hibernate.SimpleNaturalIdLoadAccess;
import org.hibernate.NaturalIdMultiLoadAccess;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.metamodel.MappingMetamodel;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.testing.orm.domain.StandardDomainModel;
import org.hibernate.testing.orm.domain.retail.Product;
import org.hibernate.testing.orm.domain.retail.Vendor;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.DomainModelScope;
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.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
/**
* Tests for simple (single attribute) natural-ids
*/
@DomainModel( standardModels = StandardDomainModel.RETAIL )
@SessionFactory
public class SimpleNaturalIdTests {
private static final UUID uuid = UUID.randomUUID();
@BeforeEach
public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final Vendor vendor = new Vendor( 1, "Acme Brick", "Acme Global" );
session.persist( vendor );
final Product product = new Product(
1,
uuid,
vendor,
Monetary.getDefaultAmountFactory().setNumber( 1L ).setCurrency( Monetary.getCurrency( Locale.US ) ).create()
);
session.persist( product );
}
);
}
@AfterEach
public void releaseTestData(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "delete Product" ).executeUpdate();
session.createQuery( "delete Vendor" ).executeUpdate();
}
);
}
@Test
public void testProcessing(DomainModelScope domainModelScope, SessionFactoryScope factoryScope) {
final PersistentClass productBootMapping = domainModelScope.getDomainModel().getEntityBinding( Product.class.getName() );
assertThat( productBootMapping.hasNaturalId(), is( true ) );
final Property sku = productBootMapping.getProperty( "sku" );
assertThat( sku.isNaturalIdentifier(), is( true ) );
final MappingMetamodel mappingMetamodel = factoryScope.getSessionFactory().getRuntimeMetamodels().getMappingMetamodel();
final EntityPersister productMapping = mappingMetamodel.findEntityDescriptor( Product.class );
assertThat( productMapping.hasNaturalIdentifier(), is( true ) );
final NaturalIdMapping naturalIdMapping = productMapping.getNaturalIdMapping();
assertThat( naturalIdMapping, notNullValue() );
}
@Test
public void testGetReference(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final SimpleNaturalIdLoadAccess<Product> loadAccess = session.bySimpleNaturalId( Product.class );
verifyEntity( loadAccess.getReference( uuid ) );
}
);
}
public void verifyEntity(Product productRef) {
assertThat( productRef, notNullValue() );
assertThat( productRef.getId(), is( 1 ) );
assertThat( productRef.getSku(), is( uuid ) );
}
@Test
public void testLoad(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final SimpleNaturalIdLoadAccess<Product> loadAccess = session.bySimpleNaturalId( Product.class );
verifyEntity( loadAccess.load( uuid ) );
}
);
}
@Test
public void testOptionalLoad(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final SimpleNaturalIdLoadAccess<Product> loadAccess = session.bySimpleNaturalId( Product.class );
final Optional<Product> optionalProduct = loadAccess.loadOptional( uuid );
assertThat( optionalProduct.isPresent(), is( true ) );
verifyEntity( optionalProduct.get() );
}
);
}
@Test
public void testMultiLoad(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
final NaturalIdMultiLoadAccess<Product> loadAccess = session.byMultipleNaturalId( Product.class );
loadAccess.enableOrderedReturn( false );
final List<Product> products = loadAccess.multiLoad( uuid );
assertThat( products.size(), is( 1 ) );
verifyEntity( products.get( 0 ) );
}
);
}
}

View File

@ -49,12 +49,11 @@ import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityRowIdMapping;
import org.hibernate.metamodel.mapping.EntityVersionMapping;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.MultiLoadOptions;
import org.hibernate.loader.ast.spi.MultiIdLoadOptions;
import org.hibernate.persister.spi.PersisterClassResolver;
import org.hibernate.persister.spi.PersisterCreationContext;
import org.hibernate.persister.walking.spi.AttributeDefinition;
@ -290,7 +289,7 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver {
@Override
public List multiLoad(
Object[] ids, SharedSessionContractImplementor session, MultiLoadOptions loadOptions) {
Object[] ids, SharedSessionContractImplementor session, MultiIdLoadOptions loadOptions) {
return Collections.emptyList();
}

View File

@ -53,11 +53,10 @@ import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityRowIdMapping;
import org.hibernate.metamodel.mapping.EntityVersionMapping;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.MultiLoadOptions;
import org.hibernate.loader.ast.spi.MultiIdLoadOptions;
import org.hibernate.persister.spi.PersisterCreationContext;
import org.hibernate.persister.walking.spi.AttributeDefinition;
import org.hibernate.persister.walking.spi.EntityIdentifierDefinition;
@ -329,7 +328,7 @@ public class CustomPersister implements EntityPersister {
}
@Override
public List multiLoad(Object[] ids, SharedSessionContractImplementor session, MultiLoadOptions loadOptions) {
public List multiLoad(Object[] ids, SharedSessionContractImplementor session, MultiIdLoadOptions loadOptions) {
return Collections.emptyList();
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.testing.orm.domain;
import java.util.Locale;
import javax.money.CurrencyUnit;
import javax.money.Monetary;
import javax.money.MonetaryAmount;
import javax.persistence.AttributeConverter;
@ -27,6 +29,6 @@ public class MonetaryAmountConverter implements AttributeConverter<MonetaryAmoun
return null;
}
return Monetary.getDefaultAmountFactory().setNumber( dbData ).create();
return Monetary.getDefaultAmountFactory().setNumber( dbData ).setCurrency( Monetary.getCurrency( Locale.US ) ).create();
}
}

View File

@ -13,6 +13,8 @@ import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import org.hibernate.annotations.NaturalId;
/**
* @author Steve Ebersole
*/
@ -25,6 +27,22 @@ public class Product {
private MonetaryAmount currentSellPrice;
public Product() {
}
public Product(Integer id, UUID sku, Vendor vendor) {
this.id = id;
this.sku = sku;
this.vendor = vendor;
}
public Product(Integer id, UUID sku, Vendor vendor, MonetaryAmount currentSellPrice) {
this.id = id;
this.sku = sku;
this.vendor = vendor;
this.currentSellPrice = currentSellPrice;
}
@Id
public Integer getId() {
return id;
@ -44,6 +62,7 @@ public class Product {
this.vendor = vendor;
}
@NaturalId
public UUID getSku() {
return sku;
}

View File

@ -50,4 +50,12 @@ public class Vendor {
public void setName(String name) {
this.name = name;
}
public String getBillingEntity() {
return billingEntity;
}
public void setBillingEntity(String billingEntity) {
this.billingEntity = billingEntity;
}
}