HHH-18375 Reuse previous row state when result cardinality is duplicated
This commit is contained in:
parent
228bd7958f
commit
505092e4ea
|
@ -21,6 +21,10 @@ public interface AssemblerCreationState {
|
|||
return false;
|
||||
}
|
||||
|
||||
default boolean containsMultipleCollectionFetches() {
|
||||
return true;
|
||||
}
|
||||
|
||||
int acquireInitializerId();
|
||||
|
||||
Initializer<?> resolveInitializer(
|
||||
|
|
|
@ -79,4 +79,20 @@ public interface FetchList extends Iterable<Fetch> {
|
|||
return false;
|
||||
}
|
||||
|
||||
default int getCollectionFetchesCount() {
|
||||
int collectionFetchesCount = 0;
|
||||
for ( Fetch fetch : this ) {
|
||||
if ( fetch instanceof EagerCollectionFetch ) {
|
||||
collectionFetchesCount++;
|
||||
}
|
||||
else {
|
||||
final FetchParent fetchParent = fetch.asFetchParent();
|
||||
if ( fetchParent != null ) {
|
||||
collectionFetchesCount += fetchParent.getCollectionFetchesCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
return collectionFetchesCount;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -95,6 +95,10 @@ public interface FetchParent extends DomainResultGraphNode {
|
|||
|
||||
boolean containsCollectionFetches();
|
||||
|
||||
default int getCollectionFetchesCount() {
|
||||
return getFetches().getCollectionFetchesCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
default void collectValueIndexesToCache(BitSet valueIndexes) {
|
||||
for ( Fetch fetch : getFetches() ) {
|
||||
|
|
|
@ -86,7 +86,7 @@ public interface Initializer<Data extends InitializerData> {
|
|||
// by default - nothing to do
|
||||
|
||||
/**
|
||||
* Step 1 - Resolve the key value for this initializer for the current
|
||||
* Step 1.1 - Resolve the key value for this initializer for the current
|
||||
* row and then recurse to the sub-initializers.
|
||||
*
|
||||
* After this point, the initializer knows whether further processing is necessary
|
||||
|
@ -98,6 +98,20 @@ public interface Initializer<Data extends InitializerData> {
|
|||
resolveKey( getData( rowProcessingState ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 1.2 - Special variant of {@link #resolveKey(InitializerData)} that allows the reuse of key value
|
||||
* and instance value from the previous row.
|
||||
*
|
||||
* @implSpec Defaults to simply delegating to {@link #resolveKey(InitializerData)}.
|
||||
*/
|
||||
default void resolveFromPreviousRow(Data data) {
|
||||
resolveKey( data );
|
||||
}
|
||||
|
||||
default void resolveFromPreviousRow(RowProcessingState rowProcessingState) {
|
||||
resolveFromPreviousRow( getData( rowProcessingState ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 2.1 - Using the key resolved in {@link #resolveKey}, resolve the
|
||||
* instance (of the thing initialized) to use for the current row.
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
package org.hibernate.sql.results.graph.collection;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.collection.spi.PersistentCollection;
|
||||
|
@ -44,6 +45,13 @@ public interface LoadingCollectionEntry {
|
|||
*/
|
||||
void load(Consumer<List<Object>> loadingEntryConsumer);
|
||||
|
||||
/**
|
||||
* Callback for row loading. Allows delayed List creation
|
||||
*/
|
||||
default <T> void load(T arg1, BiConsumer<T, List<Object>> loadingEntryConsumer) {
|
||||
load( list -> loadingEntryConsumer.accept( arg1, list ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the load
|
||||
*/
|
||||
|
|
|
@ -117,6 +117,24 @@ public abstract class AbstractCollectionInitializer<Data extends AbstractCollect
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveFromPreviousRow(Data data) {
|
||||
if ( data.getState() == State.UNINITIALIZED ) {
|
||||
if ( data.collectionKey == null ) {
|
||||
setMissing( data );
|
||||
}
|
||||
else {
|
||||
if ( collectionKeyResultAssembler != null ) {
|
||||
final Initializer<?> initializer = collectionKeyResultAssembler.getInitializer();
|
||||
if ( initializer != null ) {
|
||||
initializer.resolveFromPreviousRow( data.getRowProcessingState() );
|
||||
}
|
||||
}
|
||||
data.setState( State.RESOLVED );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setMissing(Data data) {
|
||||
data.setState( State.MISSING );
|
||||
data.collectionKey = null;
|
||||
|
|
|
@ -12,7 +12,6 @@ import java.util.function.BiConsumer;
|
|||
import org.hibernate.LockMode;
|
||||
import org.hibernate.collection.spi.CollectionSemantics;
|
||||
import org.hibernate.collection.spi.PersistentCollection;
|
||||
import org.hibernate.engine.spi.CollectionKey;
|
||||
import org.hibernate.engine.spi.PersistenceContext;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.internal.log.LoggingHelper;
|
||||
|
@ -27,7 +26,6 @@ import org.hibernate.sql.results.graph.Initializer;
|
|||
import org.hibernate.sql.results.graph.InitializerData;
|
||||
import org.hibernate.sql.results.graph.InitializerParent;
|
||||
import org.hibernate.sql.results.graph.collection.LoadingCollectionEntry;
|
||||
import org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl;
|
||||
import org.hibernate.sql.results.internal.LoadingCollectionEntryImpl;
|
||||
import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
|
||||
|
||||
|
@ -42,12 +40,12 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
|||
* @implNote Mainly an intention contract wrt the immediacy of the fetch.
|
||||
*/
|
||||
public abstract class AbstractImmediateCollectionInitializer<Data extends AbstractImmediateCollectionInitializer.ImmediateCollectionInitializerData>
|
||||
extends AbstractCollectionInitializer<Data> {
|
||||
extends AbstractCollectionInitializer<Data> implements BiConsumer<Data, List<Object>> {
|
||||
|
||||
/**
|
||||
* refers to the rows entry in the collection. null indicates that the collection is empty
|
||||
*/
|
||||
private final @Nullable DomainResultAssembler<?> collectionValueKeyResultAssembler;
|
||||
protected final @Nullable DomainResultAssembler<?> collectionValueKeyResultAssembler;
|
||||
|
||||
public static class ImmediateCollectionInitializerData extends CollectionInitializerData {
|
||||
|
||||
|
@ -137,6 +135,14 @@ public abstract class AbstractImmediateCollectionInitializer<Data extends Abstra
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveFromPreviousRow(Data data) {
|
||||
super.resolveFromPreviousRow( data );
|
||||
if ( data.getState() == State.RESOLVED ) {
|
||||
resolveKeySubInitializers( data );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the collection value key is missing.
|
||||
*/
|
||||
|
@ -185,7 +191,12 @@ public abstract class AbstractImmediateCollectionInitializer<Data extends Abstra
|
|||
return;
|
||||
}
|
||||
|
||||
resolveCollectionKey( data, true );
|
||||
// Being a result initializer means that this collection initializer is for lazy loading,
|
||||
// which has a very high chance that a collection resolved of the previous row is the same for the current row,
|
||||
// so pass that flag as indicator whether to check previous row state.
|
||||
// Note that we don't need to check previous rows in other cases,
|
||||
// because the previous row checks are done by the owner of the collection initializer already.
|
||||
resolveCollectionKey( data, isResultInitializer );
|
||||
if ( data.getState() != State.KEY_RESOLVED ) {
|
||||
return;
|
||||
}
|
||||
|
@ -429,20 +440,17 @@ public abstract class AbstractImmediateCollectionInitializer<Data extends Abstra
|
|||
}
|
||||
data.setState( State.INITIALIZED );
|
||||
|
||||
final RowProcessingState initializerRowProcessingState = data.getRowProcessingState();
|
||||
if ( data.collectionValueKey == null && collectionValueKeyResultAssembler != null ) {
|
||||
final Initializer<?> initializer = collectionValueKeyResultAssembler.getInitializer();
|
||||
if ( initializer != null ) {
|
||||
data.collectionValueKey = collectionValueKeyResultAssembler.assemble( initializerRowProcessingState );
|
||||
data.collectionValueKey = collectionValueKeyResultAssembler.assemble( data.getRowProcessingState() );
|
||||
}
|
||||
}
|
||||
|
||||
// the RHS key value of the association - determines if the row contains an element of the initializing collection
|
||||
if ( collectionValueKeyResultAssembler == null || data.collectionValueKey != null ) {
|
||||
// the row contains an element in the collection...
|
||||
data.responsibility.load(
|
||||
loadingState -> readCollectionRow( data.collectionKey, loadingState, initializerRowProcessingState )
|
||||
);
|
||||
data.responsibility.load( data, this );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -453,10 +461,12 @@ public abstract class AbstractImmediateCollectionInitializer<Data extends Abstra
|
|||
initializeSubInstancesFromParent( data );
|
||||
}
|
||||
|
||||
protected abstract void readCollectionRow(
|
||||
CollectionKey collectionKey,
|
||||
List<Object> loadingState,
|
||||
RowProcessingState rowProcessingState);
|
||||
@Override
|
||||
public void accept(Data data, List<Object> objects) {
|
||||
readCollectionRow( data, objects );
|
||||
}
|
||||
|
||||
protected abstract void readCollectionRow(Data data, List<Object> loadingState);
|
||||
|
||||
protected abstract void initializeSubInstancesFromParent(Data data);
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ import java.util.function.BiConsumer;
|
|||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.collection.spi.PersistentArrayHolder;
|
||||
import org.hibernate.engine.spi.CollectionKey;
|
||||
import org.hibernate.internal.log.LoggingHelper;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
|
@ -86,10 +85,8 @@ public class ArrayInitializer extends AbstractImmediateCollectionInitializer<Abs
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void readCollectionRow(
|
||||
CollectionKey collectionKey,
|
||||
List<Object> loadingState,
|
||||
RowProcessingState rowProcessingState) {
|
||||
protected void readCollectionRow(ImmediateCollectionInitializerData data, List<Object> loadingState) {
|
||||
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
||||
final Integer indexValue = listIndexAssembler.assemble( rowProcessingState );
|
||||
if ( indexValue == null ) {
|
||||
throw new HibernateException( "Illegal null value for array index encountered while reading: "
|
||||
|
|
|
@ -13,7 +13,6 @@ import org.hibernate.LockMode;
|
|||
import org.hibernate.collection.spi.PersistentBag;
|
||||
import org.hibernate.collection.spi.PersistentCollection;
|
||||
import org.hibernate.collection.spi.PersistentIdentifierBag;
|
||||
import org.hibernate.engine.spi.CollectionKey;
|
||||
import org.hibernate.internal.log.LoggingHelper;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
|
@ -82,10 +81,8 @@ public class BagInitializer extends AbstractImmediateCollectionInitializer<Abstr
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void readCollectionRow(
|
||||
CollectionKey collectionKey,
|
||||
List<Object> loadingState,
|
||||
RowProcessingState rowProcessingState) {
|
||||
protected void readCollectionRow(ImmediateCollectionInitializerData data, List<Object> loadingState) {
|
||||
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
||||
if ( collectionIdAssembler != null ) {
|
||||
final Object collectionId = collectionIdAssembler.assemble( rowProcessingState );
|
||||
if ( collectionId == null ) {
|
||||
|
|
|
@ -209,6 +209,11 @@ public class EagerCollectionFetch extends CollectionFetch {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCollectionFetchesCount() {
|
||||
return 1 + super.getCollectionFetchesCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaType<?> getResultJavaType() {
|
||||
return getFetchedMapping().getJavaType();
|
||||
|
|
|
@ -12,7 +12,6 @@ import java.util.function.BiConsumer;
|
|||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.collection.spi.PersistentList;
|
||||
import org.hibernate.engine.spi.CollectionKey;
|
||||
import org.hibernate.internal.log.LoggingHelper;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
|
@ -87,10 +86,8 @@ public class ListInitializer extends AbstractImmediateCollectionInitializer<Abst
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void readCollectionRow(
|
||||
CollectionKey collectionKey,
|
||||
List<Object> loadingState,
|
||||
RowProcessingState rowProcessingState) {
|
||||
protected void readCollectionRow(ImmediateCollectionInitializerData data, List<Object> loadingState) {
|
||||
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
||||
final Integer indexValue = listIndexAssembler.assemble( rowProcessingState );
|
||||
if ( indexValue == null ) {
|
||||
throw new HibernateException( "Illegal null value for list index encountered while reading: "
|
||||
|
|
|
@ -12,7 +12,6 @@ import java.util.function.BiConsumer;
|
|||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.collection.spi.PersistentMap;
|
||||
import org.hibernate.engine.spi.CollectionKey;
|
||||
import org.hibernate.internal.log.LoggingHelper;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
|
@ -90,10 +89,8 @@ public class MapInitializer extends AbstractImmediateCollectionInitializer<Abstr
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void readCollectionRow(
|
||||
CollectionKey collectionKey,
|
||||
List<Object> loadingState,
|
||||
RowProcessingState rowProcessingState) {
|
||||
protected void readCollectionRow(ImmediateCollectionInitializerData data, List<Object> loadingState) {
|
||||
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
||||
final Object key = mapKeyAssembler.assemble( rowProcessingState );
|
||||
if ( key == null ) {
|
||||
// If element is null, then NotFoundAction must be IGNORE
|
||||
|
|
|
@ -11,7 +11,6 @@ import java.util.function.BiConsumer;
|
|||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.collection.spi.PersistentSet;
|
||||
import org.hibernate.engine.spi.CollectionKey;
|
||||
import org.hibernate.internal.log.LoggingHelper;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
|
@ -77,10 +76,8 @@ public class SetInitializer extends AbstractImmediateCollectionInitializer<Abstr
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void readCollectionRow(
|
||||
CollectionKey collectionKey,
|
||||
List<Object> loadingState,
|
||||
RowProcessingState rowProcessingState) {
|
||||
protected void readCollectionRow(ImmediateCollectionInitializerData data, List<Object> loadingState) {
|
||||
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
||||
final Object element = elementAssembler.assemble( rowProcessingState );
|
||||
if ( element == null ) {
|
||||
// If element is null, then NotFoundAction must be IGNORE
|
||||
|
|
|
@ -261,6 +261,22 @@ public class EmbeddableInitializerImpl extends AbstractInitializer<EmbeddableIni
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveFromPreviousRow(EmbeddableInitializerData data) {
|
||||
if ( data.getState() == State.UNINITIALIZED ) {
|
||||
if ( data.getInstance() == null ) {
|
||||
data.setState( State.MISSING );
|
||||
}
|
||||
else {
|
||||
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
||||
for ( Initializer<InitializerData> initializer : subInitializers[data.getSubclassId()] ) {
|
||||
initializer.resolveFromPreviousRow( rowProcessingState );
|
||||
}
|
||||
data.setState( State.INITIALIZED );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveInstance(EmbeddableInitializerData data) {
|
||||
if ( data.getState() != State.KEY_RESOLVED ) {
|
||||
|
|
|
@ -126,6 +126,23 @@ public class DiscriminatedEntityInitializer
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveFromPreviousRow(DiscriminatedEntityInitializerData data) {
|
||||
if ( data.getState() == State.UNINITIALIZED ) {
|
||||
if ( data.entityIdentifier == null ) {
|
||||
data.setState( State.MISSING );
|
||||
data.setInstance( null );
|
||||
}
|
||||
else {
|
||||
final Initializer<?> initializer = keyValueAssembler.getInitializer();
|
||||
if ( initializer != null ) {
|
||||
initializer.resolveFromPreviousRow( data.getRowProcessingState() );
|
||||
}
|
||||
data.setState( State.INITIALIZED );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveInstance(DiscriminatedEntityInitializerData data) {
|
||||
if ( data.getState() != State.KEY_RESOLVED ) {
|
||||
|
|
|
@ -102,6 +102,23 @@ public class EntityDelayedFetchInitializer
|
|||
return referencedModelPart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveFromPreviousRow(EntityDelayedFetchInitializerData data) {
|
||||
if ( data.getState() == State.UNINITIALIZED ) {
|
||||
if ( data.entityIdentifier == null ) {
|
||||
data.setState( State.MISSING );
|
||||
data.setInstance( null );
|
||||
}
|
||||
else {
|
||||
final Initializer<?> initializer = identifierAssembler.getInitializer();
|
||||
if ( initializer != null ) {
|
||||
initializer.resolveFromPreviousRow( data.getRowProcessingState() );
|
||||
}
|
||||
data.setState( State.RESOLVED );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveInstance(EntityDelayedFetchInitializerData data) {
|
||||
if ( data.getState() != State.KEY_RESOLVED ) {
|
||||
|
|
|
@ -63,6 +63,7 @@ import org.hibernate.sql.results.graph.Initializer;
|
|||
import org.hibernate.sql.results.graph.InitializerData;
|
||||
import org.hibernate.sql.results.graph.InitializerParent;
|
||||
import org.hibernate.sql.results.graph.basic.BasicResultAssembler;
|
||||
import org.hibernate.sql.results.graph.collection.internal.AbstractImmediateCollectionInitializer;
|
||||
import org.hibernate.sql.results.graph.entity.EntityInitializer;
|
||||
import org.hibernate.sql.results.graph.entity.EntityResultGraphNode;
|
||||
import org.hibernate.sql.results.graph.internal.AbstractInitializer;
|
||||
|
@ -106,6 +107,14 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
|
|||
private final boolean isPartOfKey;
|
||||
private final boolean isResultInitializer;
|
||||
private final boolean hasKeyManyToOne;
|
||||
/**
|
||||
* Indicates whether there is a high chance of the previous row to have the same entity key as the current row
|
||||
* and hence enable a check in the {@link #resolveKey(RowProcessingState)} phase which compare the previously read
|
||||
* identifier with the current row identifier. If it matches, the state from the previous row processing can be reused.
|
||||
* In addition to that, all direct sub-initializers can be informed about the reuse by calling {@link Initializer#resolveFromPreviousRow(RowProcessingState)},
|
||||
* so that these initializers can avoid unnecessary processing as well.
|
||||
*/
|
||||
private final boolean previousRowReuse;
|
||||
|
||||
private final @Nullable DomainResultAssembler<?> keyAssembler;
|
||||
private final @Nullable DomainResultAssembler<?> identifierAssembler;
|
||||
|
@ -163,6 +172,15 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
|
|||
this.parent = parent;
|
||||
this.isResultInitializer = isResultInitializer;
|
||||
this.isPartOfKey = Initializer.isPartOfKey( navigablePath, parent );
|
||||
// If the parent already has previous row reuse enabled, we can skip that here
|
||||
this.previousRowReuse = !isPreviousRowReuse( parent ) && (
|
||||
// If this entity domain result contains a collection join fetch, this usually means that the entity data is
|
||||
// duplicate in the result data for every collection element. Since collections usually have more than one element,
|
||||
// optimizing the resolving of the entity data is very beneficial.
|
||||
resultDescriptor.containsCollectionFetches()
|
||||
// Result duplication generally also happens if more than one collection is join fetched,
|
||||
|| creationState.containsMultipleCollectionFetches()
|
||||
);
|
||||
|
||||
assert identifierFetch != null || isResultInitializer : "Identifier must be fetched, unless this is a result initializer";
|
||||
if ( identifierFetch == null ) {
|
||||
|
@ -256,6 +274,21 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
|
|||
this.affectedByFilter = affectedByFilter;
|
||||
}
|
||||
|
||||
private static boolean isPreviousRowReuse(@Nullable InitializerParent<?> parent) {
|
||||
// Traverse up the parents to find out if one of our parents has row reuse enabled
|
||||
while ( parent != null ) {
|
||||
if ( parent instanceof EntityInitializerImpl ) {
|
||||
return ( (EntityInitializerImpl) parent ).isPreviousRowReuse();
|
||||
}
|
||||
// Immediate collections don't reuse previous rows for elements, so we can safely assume false
|
||||
if ( parent instanceof AbstractImmediateCollectionInitializer<?> ) {
|
||||
return false;
|
||||
}
|
||||
parent = parent.getParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EntityInitializerData createInitializerData(RowProcessingState rowProcessingState) {
|
||||
return new EntityInitializerData( rowProcessingState );
|
||||
|
@ -299,6 +332,10 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
|
|||
}
|
||||
data.setState( State.KEY_RESOLVED );
|
||||
|
||||
final EntityKey oldEntityKey = data.entityKey;
|
||||
final Object oldEntityInstance = data.getInstance();
|
||||
final Object oldEntityInstanceForNotify = data.entityInstanceForNotify;
|
||||
final EntityHolder oldEntityHolder = data.entityHolder;
|
||||
// reset row state
|
||||
data.concreteDescriptor = null;
|
||||
data.entityKey = null;
|
||||
|
@ -344,6 +381,20 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
|
|||
}
|
||||
}
|
||||
|
||||
if ( oldEntityKey != null && previousRowReuse && oldEntityInstance != null
|
||||
&& entityDescriptor.getIdentifierType().isEqual( oldEntityKey.getIdentifier(), id ) ) {
|
||||
// The row we read previously referred to this entity already, so we can safely assume it's initialized.
|
||||
// Unfortunately we can't set the state to INITIALIZED though, as that has other implications,
|
||||
// but RESOLVED is fine, since the EntityEntry is marked as initialized which skips instance initialization
|
||||
data.setState( State.RESOLVED );
|
||||
data.entityKey = oldEntityKey;
|
||||
data.setInstance( oldEntityInstance );
|
||||
data.entityInstanceForNotify = oldEntityInstanceForNotify;
|
||||
data.concreteDescriptor = oldEntityKey.getPersister();
|
||||
data.entityHolder = oldEntityHolder;
|
||||
notifySubInitializersToReusePreviousRowInstance( data );
|
||||
return;
|
||||
}
|
||||
resolveEntityKey( data, id );
|
||||
if ( !entityKeyOnly ) {
|
||||
// Resolve the entity instance early as we have no key many-to-one
|
||||
|
@ -413,6 +464,15 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
|
|||
}
|
||||
}
|
||||
|
||||
private void notifySubInitializersToReusePreviousRowInstance(EntityInitializerData data) {
|
||||
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
||||
for ( Initializer<?> initializer : subInitializers[data.concreteDescriptor.getSubclassId()] ) {
|
||||
if ( initializer != null ) {
|
||||
initializer.resolveFromPreviousRow( rowProcessingState );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveKeySubInitializers(EntityInitializerData data) {
|
||||
final RowProcessingState rowProcessingState = data.getRowProcessingState();
|
||||
for ( Initializer<?> initializer : subInitializers[data.concreteDescriptor.getSubclassId()] ) {
|
||||
|
@ -467,6 +527,20 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveFromPreviousRow(EntityInitializerData data) {
|
||||
if ( data.getState() == State.UNINITIALIZED ) {
|
||||
final EntityKey entityKey = data.entityKey;
|
||||
if ( entityKey == null ) {
|
||||
setMissing( data );
|
||||
}
|
||||
else {
|
||||
data.setState( State.RESOLVED );
|
||||
notifySubInitializersToReusePreviousRowInstance( data );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeInstanceFromParent(Object parentInstance, EntityInitializerData data) {
|
||||
final AttributeMapping attributeMapping = getInitializedPart().asAttributeMapping();
|
||||
|
@ -1384,6 +1458,10 @@ public class EntityInitializerImpl extends AbstractInitializer<EntityInitializer
|
|||
return isPartOfKey;
|
||||
}
|
||||
|
||||
public boolean isPreviousRowReuse() {
|
||||
return previousRowReuse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityPersister getConcreteDescriptor(EntityInitializerData data) {
|
||||
assert data.getState() != State.UNINITIALIZED;
|
||||
|
|
|
@ -104,6 +104,23 @@ public class EntitySelectFetchInitializer<Data extends EntitySelectFetchInitiali
|
|||
return navigablePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveFromPreviousRow(Data data) {
|
||||
if ( data.getState() == State.UNINITIALIZED ) {
|
||||
if ( data.entityIdentifier == null ) {
|
||||
data.setState( State.MISSING );
|
||||
data.setInstance( null );
|
||||
}
|
||||
else {
|
||||
final Initializer<?> initializer = keyAssembler.getInitializer();
|
||||
if ( initializer != null ) {
|
||||
initializer.resolveFromPreviousRow( data.getRowProcessingState() );
|
||||
}
|
||||
data.setState( State.INITIALIZED );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveInstance(Data data) {
|
||||
if ( data.getState() != State.KEY_RESOLVED ) {
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.sql.results.internal;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.collection.spi.PersistentCollection;
|
||||
|
@ -68,6 +69,11 @@ public class LoadingCollectionEntryImpl implements LoadingCollectionEntry {
|
|||
loadingEntryConsumer.accept( loadingState );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void load(T arg1, BiConsumer<T, List<Object>> loadingEntryConsumer) {
|
||||
loadingEntryConsumer.accept( arg1, loadingState );
|
||||
}
|
||||
|
||||
@Override public void finishLoading(ExecutionContext executionContext) {
|
||||
collectionInstance.injectLoadedState(
|
||||
getCollectionDescriptor().getAttributeMapping(),
|
||||
|
|
|
@ -109,6 +109,7 @@ public class StandardJdbcValuesMapping implements JdbcValuesMapping {
|
|||
private int initializerId;
|
||||
boolean hasCollectionInitializers;
|
||||
Boolean dynamicInstantiation;
|
||||
Boolean containsMultipleCollectionFetches;
|
||||
|
||||
public AssemblerCreationStateImpl(
|
||||
JdbcValuesMapping jdbcValuesMapping,
|
||||
|
@ -127,6 +128,20 @@ public class StandardJdbcValuesMapping implements JdbcValuesMapping {
|
|||
return dynamicInstantiation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsMultipleCollectionFetches() {
|
||||
if ( containsMultipleCollectionFetches == null ) {
|
||||
int collectionFetchesCount = 0;
|
||||
for ( DomainResult<?> domainResult : jdbcValuesMapping.getDomainResults() ) {
|
||||
if ( domainResult instanceof FetchParent ) {
|
||||
collectionFetchesCount += ( (FetchParent) domainResult ).getCollectionFetchesCount();
|
||||
}
|
||||
}
|
||||
containsMultipleCollectionFetches = collectionFetchesCount > 1;
|
||||
}
|
||||
return containsMultipleCollectionFetches;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int acquireInitializerId() {
|
||||
return initializerId++;
|
||||
|
|
|
@ -0,0 +1,328 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.mapping.collections;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.jpa.AvailableHints;
|
||||
|
||||
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.Jira;
|
||||
import org.hibernate.testing.orm.junit.Jpa;
|
||||
import org.hibernate.testing.orm.junit.Setting;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.Embedded;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@Jpa(
|
||||
annotatedClasses = {
|
||||
ElementCollectionCachePerfTest.Element.class,
|
||||
ElementCollectionCachePerfTest.KeyValue.class,
|
||||
ElementCollectionCachePerfTest.Association.class,
|
||||
},
|
||||
properties = {
|
||||
@Setting(name = AvailableSettings.USE_QUERY_CACHE, value = "true"),
|
||||
@Setting(name = AvailableSettings.USE_SECOND_LEVEL_CACHE, value = "true"),
|
||||
@Setting(name = AvailableSettings.QUERY_CACHE_LAYOUT, value = "auto")
|
||||
}
|
||||
)
|
||||
@Jira("https://hibernate.atlassian.net/browse/HHH-18375")
|
||||
public class ElementCollectionCachePerfTest {
|
||||
@BeforeAll
|
||||
public void setUp(EntityManagerFactoryScope scope) {
|
||||
scope.inTransaction( entityManager -> {
|
||||
for ( int i = 0; i < 100; i++ ) {
|
||||
final String id = UUID.randomUUID().toString();
|
||||
final Element element = new Element( id );
|
||||
element.setKeyValueEmbeddable( new KeyValue( "embeddable", "_" + id ) );
|
||||
element.setAssociation1( new Association( (long) i, "assoc_" + id ) );
|
||||
|
||||
final Set<KeyValue> key1Values = new HashSet<>();
|
||||
key1Values.add( new KeyValue( "key1_1", "_" + id ) );
|
||||
key1Values.add( new KeyValue( "key1_2", "_" + id ) );
|
||||
key1Values.add( new KeyValue( "key1_3", "_" + id ) );
|
||||
element.setKeyValues1( key1Values );
|
||||
|
||||
final Set<KeyValue> key2Values = new HashSet<>();
|
||||
key2Values.add( new KeyValue( "key2_1", "_" + id ) );
|
||||
key2Values.add( new KeyValue( "key2_2", "_" + id ) );
|
||||
element.setKeyValues2( key2Values );
|
||||
|
||||
final Map<String, KeyValue> map = new HashMap<>();
|
||||
map.put( "k1", new KeyValue( "k1", "_" + id ) );
|
||||
map.put( "k2", new KeyValue( "k2", "_" + id ) );
|
||||
element.setMap( map );
|
||||
|
||||
entityManager.persist( element );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelect1(EntityManagerFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
assertQuery(
|
||||
entityManager,
|
||||
"select e from Element e join fetch e.association1 join fetch e.keyValues1 join fetch e.keyValues2 join fetch e.map"
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelect2(EntityManagerFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
assertQuery(
|
||||
entityManager,
|
||||
"select e from Element e join fetch e.association1 join fetch e.map join fetch e.keyValues1 join fetch e.keyValues2"
|
||||
);
|
||||
} );
|
||||
}
|
||||
|
||||
private static void assertQuery(EntityManager entityManager, String query) {
|
||||
final List<Element> firstResult = entityManager.createQuery( query, Element.class )
|
||||
.setHint( AvailableHints.HINT_CACHEABLE, true )
|
||||
.getResultList();
|
||||
assertResults( firstResult );
|
||||
final List<Element> secondResult = entityManager.createQuery( query, Element.class )
|
||||
.setHint( AvailableHints.HINT_CACHEABLE, true )
|
||||
.getResultList();
|
||||
assertResults( secondResult );
|
||||
}
|
||||
|
||||
private static void assertResults(List<Element> result) {
|
||||
for ( Element element : result ) {
|
||||
final String id = element.getId();
|
||||
assertThat( element.getAssociation1().getName() ).isEqualTo( "assoc_" + id );
|
||||
assertThat( element.getKeyValueEmbeddable().getK() ).isEqualTo( "embeddable" );
|
||||
assertThat( element.getKeyValueEmbeddable().getV() ).isEqualTo( "_" + id );
|
||||
assertThat( element.getKeyValues1().size() ).isEqualTo( 3 );
|
||||
assertThat( element.getKeyValues2().size() ).isEqualTo( 2 );
|
||||
assertThat( element.getMap().size() ).isEqualTo( 2 );
|
||||
assertThat( element.getKeyValues1() ).containsExactlyInAnyOrder(
|
||||
new KeyValue( "key1_1", "_" + id ),
|
||||
new KeyValue( "key1_2", "_" + id ),
|
||||
new KeyValue( "key1_3", "_" + id )
|
||||
);
|
||||
assertThat( element.getKeyValues2() ).containsExactlyInAnyOrder(
|
||||
new KeyValue( "key2_1", "_" + id ),
|
||||
new KeyValue( "key2_2", "_" + id )
|
||||
);
|
||||
assertThat( element.getMap() ).containsExactly(
|
||||
new AbstractMap.SimpleEntry<>( "k1", new KeyValue( "k1", "_" + id ) ),
|
||||
new AbstractMap.SimpleEntry<>( "k2", new KeyValue( "k2", "_" + id ) )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Element")
|
||||
public static class Element {
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
private Association association1;
|
||||
|
||||
@Embedded
|
||||
KeyValue keyValueEmbeddable;
|
||||
|
||||
@ElementCollection
|
||||
private Set<KeyValue> keyValues1;
|
||||
|
||||
@ElementCollection
|
||||
private Set<KeyValue> keyValues2;
|
||||
|
||||
@ElementCollection
|
||||
private Map<String, KeyValue> map;
|
||||
|
||||
protected Element() {
|
||||
}
|
||||
|
||||
public Element(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Association getAssociation1() {
|
||||
return association1;
|
||||
}
|
||||
|
||||
public void setAssociation1(Association association1) {
|
||||
this.association1 = association1;
|
||||
}
|
||||
|
||||
public KeyValue getKeyValueEmbeddable() {
|
||||
return keyValueEmbeddable;
|
||||
}
|
||||
|
||||
public void setKeyValueEmbeddable(KeyValue keyValueEmbeddable) {
|
||||
this.keyValueEmbeddable = keyValueEmbeddable;
|
||||
}
|
||||
|
||||
public Set<KeyValue> getKeyValues1() {
|
||||
return keyValues1;
|
||||
}
|
||||
|
||||
public void setKeyValues1(Set<KeyValue> keyValues) {
|
||||
this.keyValues1 = keyValues;
|
||||
}
|
||||
|
||||
public Set<KeyValue> getKeyValues2() {
|
||||
return keyValues2;
|
||||
}
|
||||
|
||||
public void setKeyValues2(Set<KeyValue> keyValues2) {
|
||||
this.keyValues2 = keyValues2;
|
||||
}
|
||||
|
||||
public Map<String, KeyValue> getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
public void setMap(Map<String, KeyValue> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if ( this == o ) {
|
||||
return true;
|
||||
}
|
||||
if ( o == null || getClass() != o.getClass() ) {
|
||||
return false;
|
||||
}
|
||||
Element element = (Element) o;
|
||||
return Objects.equals( id, element.id );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash( id );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Element{" +
|
||||
"id=" + id +
|
||||
", keyValues=" + keyValues1 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@Embeddable
|
||||
public static class KeyValue {
|
||||
private String k;
|
||||
private String v;
|
||||
|
||||
public KeyValue() {
|
||||
}
|
||||
|
||||
public KeyValue(String k, String v) {
|
||||
this.k = k;
|
||||
this.v = v;
|
||||
}
|
||||
|
||||
public String getK() {
|
||||
return k;
|
||||
}
|
||||
|
||||
public void setK(String key) {
|
||||
this.k = key;
|
||||
}
|
||||
|
||||
public String getV() {
|
||||
return v;
|
||||
}
|
||||
|
||||
public void setV(String value) {
|
||||
this.v = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if ( this == o ) {
|
||||
return true;
|
||||
}
|
||||
if ( o == null || getClass() != o.getClass() ) {
|
||||
return false;
|
||||
}
|
||||
KeyValue keyValue = (KeyValue) o;
|
||||
return Objects.equals( k, keyValue.k ) && Objects.equals( v, keyValue.v );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash( k, v );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KeyValue{" +
|
||||
"key='" + k + '\'' +
|
||||
", value='" + v + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Association")
|
||||
public static class Association {
|
||||
@Id
|
||||
private Long id;
|
||||
private String name;
|
||||
|
||||
public Association() {
|
||||
}
|
||||
|
||||
public Association(Long id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,341 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.orm.test.mapping.collections;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.hibernate.testing.orm.junit.Jira;
|
||||
import org.hibernate.testing.orm.junit.Jpa;
|
||||
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.Embedded;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@Jpa(annotatedClasses = {
|
||||
ElementCollectionPerfTest.Element.class,
|
||||
ElementCollectionPerfTest.KeyValue.class,
|
||||
ElementCollectionPerfTest.Association.class,
|
||||
})
|
||||
@Jira("https://hibernate.atlassian.net/browse/HHH-18375")
|
||||
public class ElementCollectionPerfTest {
|
||||
@BeforeAll
|
||||
public void setUp(EntityManagerFactoryScope scope) {
|
||||
scope.inTransaction( entityManager -> {
|
||||
for ( int i = 0; i < 100; i++ ) {
|
||||
final String id = UUID.randomUUID().toString();
|
||||
final Element element = new Element( id );
|
||||
element.setKeyValueEmbeddable( new KeyValue( "embeddable", "_" + id ) );
|
||||
element.setAssociation1( new Association( (long) i, "assoc_" + id ) );
|
||||
|
||||
final Set<KeyValue> key1Values = new HashSet<>();
|
||||
key1Values.add( new KeyValue( "key1_1", "_" + id ) );
|
||||
key1Values.add( new KeyValue( "key1_2", "_" + id ) );
|
||||
key1Values.add( new KeyValue( "key1_3", "_" + id ) );
|
||||
element.setKeyValues1( key1Values );
|
||||
|
||||
element.association1.keyValues1 = new HashSet<>( key1Values);
|
||||
|
||||
final Set<KeyValue> key2Values = new HashSet<>();
|
||||
key2Values.add( new KeyValue( "key2_1", "_" + id ) );
|
||||
key2Values.add( new KeyValue( "key2_2", "_" + id ) );
|
||||
element.setKeyValues2( key2Values );
|
||||
|
||||
final Map<String, KeyValue> map = new HashMap<>();
|
||||
map.put( "k1", new KeyValue( "k1", "_" + id ) );
|
||||
map.put( "k2", new KeyValue( "k2", "_" + id ) );
|
||||
element.setMap( map );
|
||||
|
||||
entityManager.persist( element );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelect0(EntityManagerFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
List<Element> result = entityManager.createQuery(
|
||||
"select e from Element e join fetch e.association1 a join fetch a.keyValues1 join fetch e.keyValues1 join fetch e.keyValues2 join fetch e.map",
|
||||
Element.class
|
||||
).getResultList();
|
||||
|
||||
assertResults( result );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelect1(EntityManagerFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
List<Element> result = entityManager.createQuery(
|
||||
"select e from Element e join fetch e.association1 join fetch e.keyValues1 join fetch e.keyValues2 join fetch e.map",
|
||||
Element.class
|
||||
).getResultList();
|
||||
|
||||
assertResults( result );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelect2(EntityManagerFactoryScope scope) {
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
List<Element> result = entityManager.createQuery(
|
||||
"select e from Element e join fetch e.association1 join fetch e.map join fetch e.keyValues1 join fetch e.keyValues2",
|
||||
Element.class
|
||||
).getResultList();
|
||||
|
||||
assertResults( result );
|
||||
} );
|
||||
}
|
||||
|
||||
private static void assertResults(List<Element> result) {
|
||||
for ( Element element : result ) {
|
||||
final String id = element.getId();
|
||||
assertThat( element.getAssociation1().getName() ).isEqualTo( "assoc_" + id );
|
||||
assertThat( element.getAssociation1().getKeyValues1().size() ).isEqualTo( 3 );
|
||||
assertThat( element.getKeyValueEmbeddable().getK() ).isEqualTo( "embeddable" );
|
||||
assertThat( element.getKeyValueEmbeddable().getV() ).isEqualTo( "_" + id );
|
||||
assertThat( element.getKeyValues1().size() ).isEqualTo( 3 );
|
||||
assertThat( element.getKeyValues2().size() ).isEqualTo( 2 );
|
||||
assertThat( element.getMap().size() ).isEqualTo( 2 );
|
||||
assertThat( element.getAssociation1().getKeyValues1() ).containsExactlyInAnyOrder(
|
||||
new KeyValue( "key1_1", "_" + id ),
|
||||
new KeyValue( "key1_2", "_" + id ),
|
||||
new KeyValue( "key1_3", "_" + id )
|
||||
);
|
||||
assertThat( element.getKeyValues1() ).containsExactlyInAnyOrder(
|
||||
new KeyValue( "key1_1", "_" + id ),
|
||||
new KeyValue( "key1_2", "_" + id ),
|
||||
new KeyValue( "key1_3", "_" + id )
|
||||
);
|
||||
assertThat( element.getKeyValues2() ).containsExactlyInAnyOrder(
|
||||
new KeyValue( "key2_1", "_" + id ),
|
||||
new KeyValue( "key2_2", "_" + id )
|
||||
);
|
||||
assertThat( element.getMap() ).containsExactly(
|
||||
new AbstractMap.SimpleEntry<>( "k1", new KeyValue( "k1", "_" + id ) ),
|
||||
new AbstractMap.SimpleEntry<>( "k2", new KeyValue( "k2", "_" + id ) )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Element")
|
||||
public static class Element {
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
private Association association1;
|
||||
|
||||
@Embedded
|
||||
KeyValue keyValueEmbeddable;
|
||||
|
||||
@ElementCollection
|
||||
private Set<KeyValue> keyValues1;
|
||||
|
||||
@ElementCollection
|
||||
private Set<KeyValue> keyValues2;
|
||||
|
||||
@ElementCollection
|
||||
private Map<String, KeyValue> map;
|
||||
|
||||
protected Element() {
|
||||
}
|
||||
|
||||
public Element(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Association getAssociation1() {
|
||||
return association1;
|
||||
}
|
||||
|
||||
public void setAssociation1(Association association1) {
|
||||
this.association1 = association1;
|
||||
}
|
||||
|
||||
public KeyValue getKeyValueEmbeddable() {
|
||||
return keyValueEmbeddable;
|
||||
}
|
||||
|
||||
public void setKeyValueEmbeddable(KeyValue keyValueEmbeddable) {
|
||||
this.keyValueEmbeddable = keyValueEmbeddable;
|
||||
}
|
||||
|
||||
public Set<KeyValue> getKeyValues1() {
|
||||
return keyValues1;
|
||||
}
|
||||
|
||||
public void setKeyValues1(Set<KeyValue> keyValues) {
|
||||
this.keyValues1 = keyValues;
|
||||
}
|
||||
|
||||
public Set<KeyValue> getKeyValues2() {
|
||||
return keyValues2;
|
||||
}
|
||||
|
||||
public void setKeyValues2(Set<KeyValue> keyValues2) {
|
||||
this.keyValues2 = keyValues2;
|
||||
}
|
||||
|
||||
public Map<String, KeyValue> getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
public void setMap(Map<String, KeyValue> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if ( this == o ) {
|
||||
return true;
|
||||
}
|
||||
if ( o == null || getClass() != o.getClass() ) {
|
||||
return false;
|
||||
}
|
||||
Element element = (Element) o;
|
||||
return Objects.equals( id, element.id );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash( id );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Element{" +
|
||||
"id=" + id +
|
||||
", keyValues=" + keyValues1 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@Embeddable
|
||||
public static class KeyValue {
|
||||
private String k;
|
||||
private String v;
|
||||
|
||||
public KeyValue() {
|
||||
}
|
||||
|
||||
public KeyValue(String k, String v) {
|
||||
this.k = k;
|
||||
this.v = v;
|
||||
}
|
||||
|
||||
public String getK() {
|
||||
return k;
|
||||
}
|
||||
|
||||
public void setK(String key) {
|
||||
this.k = key;
|
||||
}
|
||||
|
||||
public String getV() {
|
||||
return v;
|
||||
}
|
||||
|
||||
public void setV(String value) {
|
||||
this.v = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if ( this == o ) {
|
||||
return true;
|
||||
}
|
||||
if ( o == null || getClass() != o.getClass() ) {
|
||||
return false;
|
||||
}
|
||||
KeyValue keyValue = (KeyValue) o;
|
||||
return Objects.equals( k, keyValue.k ) && Objects.equals( v, keyValue.v );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash( k, v );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KeyValue{" +
|
||||
"key='" + k + '\'' +
|
||||
", value='" + v + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Association")
|
||||
public static class Association {
|
||||
@Id
|
||||
private Long id;
|
||||
private String name;
|
||||
|
||||
@ElementCollection
|
||||
private Set<KeyValue> keyValues1;
|
||||
|
||||
public Association() {
|
||||
}
|
||||
|
||||
public Association(Long id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Set<KeyValue> getKeyValues1() {
|
||||
return keyValues1;
|
||||
}
|
||||
|
||||
public void setKeyValues1(Set<KeyValue> keyValues1) {
|
||||
this.keyValues1 = keyValues1;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue