HHH-14313 - NaturalId support
This commit is contained in:
parent
d4233ce6a8
commit
be654c37b5
|
@ -11,10 +11,15 @@ 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).
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 ) );
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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 NaturalIdLoaderStandardImpl(EntityPersister entityDescriptor) {
|
||||
this.entityDescriptor = entityDescriptor;
|
||||
this.naturalIdMapping = entityDescriptor.getNaturalIdMapping();
|
||||
|
||||
if ( ! entityDescriptor.hasNaturalIdentifier() ) {
|
||||
throw new HibernateException( "Entity does not define natural-id : " + entityDescriptor.getEntityName() );
|
||||
}
|
||||
public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@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 );
|
||||
final Object[] objects = results.get( 0 );
|
||||
if ( isSimple() ) {
|
||||
return objects[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;
|
||||
return objects;
|
||||
}
|
||||
|
||||
@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 )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return results.get( 0 );
|
||||
}
|
||||
protected abstract boolean isSimple();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
else {
|
||||
for ( ModelPart restrictedPart : restrictedParts ) {
|
||||
if ( restrictedPart instanceof ForeignKeyDescriptor ) {
|
||||
forceIdentifierSelection = true;
|
||||
return 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(
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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.
|
||||
*
|
|
@ -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 {
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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/>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue