From e7598f8b344603c78ad611d5b2cc46bd66c74583 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 25 Sep 2018 11:08:00 +0200 Subject: [PATCH] [#509] WIP --- CHANGELOG.md | 6 +- .../blaze-persistence/checkstyle-config.xml | 2 +- .../persistence/spi/ExtendedAttribute.java | 8 + .../persistence/spi/ExtendedManagedType.java | 17 + .../blazebit/persistence/spi/JoinTable.java | 30 +- .../blazebit/persistence/spi/JpaProvider.java | 65 +- .../impl/AbstractCTECriteriaBuilder.java | 6 +- .../impl/AbstractCommonQueryBuilder.java | 69 +- .../impl/AbstractFullQueryBuilder.java | 11 +- ...stractInsertCollectionCriteriaBuilder.java | 82 ++- .../AbstractModificationCriteriaBuilder.java | 6 +- ...stractUpdateCollectionCriteriaBuilder.java | 97 ++- ...AssociationFromIdParameterTransformer.java | 6 + .../AssociationToIdParameterTransformer.java | 7 + .../impl/BaseInsertCriteriaBuilderImpl.java | 13 +- .../impl/BaseUpdateCriteriaBuilderImpl.java | 58 +- .../persistence/impl/CachingJpaProvider.java | 35 +- .../persistence/impl/EntityMetamodelImpl.java | 317 ++++++--- .../persistence/impl/JoinManager.java | 172 +++-- .../blazebit/persistence/impl/JoinNode.java | 77 ++- .../blazebit/persistence/impl/JpaUtils.java | 210 ++++++ ...ssociationParameterTransformerFactory.java | 7 + .../impl/PaginatedCriteriaBuilderImpl.java | 27 +- .../impl/ParameterValueTransformer.java | 4 + .../persistence/impl/PredicateManager.java | 30 +- .../impl/ResolvingQueryGenerator.java | 6 +- .../persistence/impl/SelectManager.java | 20 +- .../persistence/impl/SimplePathReference.java | 2 +- .../impl/SplittingParameterTransformer.java | 148 ++++ .../impl/query/AbstractCustomQuery.java | 27 +- .../PathTargetResolvingExpressionVisitor.java | 18 +- .../testsuite/entity/DocumentInfoSimple.java | 19 + .../testsuite/CollectionRoleInsertTest.java | 38 +- .../testsuite/CollectionRoleUpdateTest.java | 20 +- .../testsuite/ValuesClauseTest.java | 59 +- dist/bom/pom.xml | 2 +- .../core/manual/en_US/04_from_clause.adoc | 4 +- .../view/impl/EntityViewManagerImpl.java | 44 +- .../view/impl/UpdatableExpressionVisitor.java | 151 +---- .../impl/collection/CollectionAction.java | 2 + .../collection/CollectionAddAllAction.java | 9 + .../collection/CollectionClearAction.java | 5 + .../collection/CollectionRemoveAllAction.java | 9 + .../view/impl/collection/ListAction.java | 13 +- .../view/impl/collection/ListAddAction.java | 42 +- .../impl/collection/ListAddAllAction.java | 59 +- .../impl/collection/ListRemoveAction.java | 45 +- .../view/impl/collection/ListSetAction.java | 61 +- .../impl/collection/RecordingCollection.java | 30 + .../view/impl/collection/RecordingList.java | 73 +- .../impl/entity/AbstractEntityLoader.java | 5 + .../entity/AbstractViewToEntityMapper.java | 9 +- .../entity/CreateOnlyViewToEntityMapper.java | 5 +- .../entity/DefaultEntityToEntityMapper.java | 3 +- ...eddableUpdaterBasedViewToEntityMapper.java | 5 +- .../view/impl/entity/EntityIdLoader.java | 5 + .../view/impl/entity/EntityLoader.java | 2 + .../entity/InverseViewToEntityMapper.java | 2 +- .../LoadOrPersistViewToEntityMapper.java | 5 +- .../UpdaterBasedViewToEntityMapper.java | 5 +- .../impl/metamodel/AbstractAttribute.java | 45 +- .../metamodel/AbstractMethodAttribute.java | 11 +- .../AbstractMethodPluralAttribute.java | 26 +- .../AbstractMethodSingularAttribute.java | 26 +- .../metamodel/AbstractParameterAttribute.java | 4 +- .../AbstractParameterPluralAttribute.java | 8 +- .../AbstractParameterSingularAttribute.java | 8 +- .../view/impl/metamodel/AttributeMapping.java | 183 +++-- .../impl/metamodel/ConvertedViewMapping.java | 4 +- .../view/impl/metamodel/EmbeddableOwner.java | 68 ++ .../view/impl/metamodel/FlatViewTypeImpl.java | 16 +- .../impl/metamodel/ManagedViewTypeImpl.java | 34 +- .../metamodel/MappingConstructorImpl.java | 4 +- .../metamodel/MethodAttributeMapping.java | 179 +++-- .../metamodel/ParameterAttributeMapping.java | 24 +- .../view/impl/metamodel/ViewMapping.java | 2 +- .../view/impl/metamodel/ViewMappingImpl.java | 23 +- .../impl/metamodel/ViewMetamodelImpl.java | 2 +- .../view/impl/metamodel/ViewTypeImpl.java | 6 +- .../AbstractMethodCollectionAttribute.java | 5 +- .../AbstractMethodListAttribute.java | 5 +- .../attribute/AbstractMethodMapAttribute.java | 9 +- .../attribute/AbstractMethodSetAttribute.java | 5 +- .../AbstractParameterCollectionAttribute.java | 5 +- .../AbstractParameterListAttribute.java | 5 +- .../AbstractParameterMapAttribute.java | 9 +- .../AbstractParameterSetAttribute.java | 5 +- .../CorrelatedMethodCollectionAttribute.java | 5 +- .../CorrelatedMethodListAttribute.java | 5 +- .../CorrelatedMethodSetAttribute.java | 5 +- .../CorrelatedMethodSingularAttribute.java | 5 +- ...orrelatedParameterCollectionAttribute.java | 5 +- .../CorrelatedParameterListAttribute.java | 5 +- .../CorrelatedParameterSetAttribute.java | 5 +- .../CorrelatedParameterSingularAttribute.java | 5 +- .../MappingMethodCollectionAttribute.java | 5 +- .../attribute/MappingMethodListAttribute.java | 5 +- .../attribute/MappingMethodMapAttribute.java | 5 +- .../attribute/MappingMethodSetAttribute.java | 5 +- .../MappingMethodSingularAttribute.java | 5 +- .../MappingParameterCollectionAttribute.java | 5 +- .../MappingParameterListAttribute.java | 5 +- .../MappingParameterMapAttribute.java | 5 +- .../MappingParameterSetAttribute.java | 5 +- .../MappingParameterSingularAttribute.java | 5 +- .../SubqueryMethodSingularAttribute.java | 2 +- .../SubqueryParameterSingularAttribute.java | 2 +- .../view/impl/proxy/ProxyFactory.java | 2 +- .../impl/update/EntityViewUpdaterImpl.java | 419 +++++++++--- .../flush/AbstractPluralAttributeFlusher.java | 173 +++-- .../update/flush/BasicAttributeFlusher.java | 13 +- .../flush/CollectionAttributeFlusher.java | 641 ++++++++++++++---- .../CollectionElementAttributeFlusher.java | 8 +- .../flush/CompositeAttributeFlusher.java | 76 ++- .../update/flush/DirtyAttributeFlusher.java | 4 +- .../flush/EmbeddableAttributeFlusher.java | 9 +- .../update/flush/FusedCollectionActions.java | 45 ++ .../flush/FusedCollectionElementActions.java | 99 +++ .../flush/FusedCollectionIndexActions.java | 348 ++++++++++ .../flush/IndexedListAttributeFlusher.java | 323 +++++++-- ...erseCollectionElementAttributeFlusher.java | 4 +- .../impl/update/flush/InverseFlusher.java | 23 +- .../update/flush/MapAttributeFlusher.java | 55 +- ...ergeCollectionElementAttributeFlusher.java | 27 +- ...ntCollectionReferenceAttributeFlusher.java | 2 +- .../ParentReferenceAttributeFlusher.java | 10 +- ...sistCollectionElementAttributeFlusher.java | 5 +- .../update/flush/SubviewAttributeFlusher.java | 24 +- .../impl/update/flush/TypeDescriptor.java | 53 +- ...dateCollectionElementAttributeFlusher.java | 6 +- .../update/flush/VersionAttributeFlusher.java | 3 +- .../FusedCollectionIndexActionsTest.java | 98 +++ .../AbstractEntityViewUpdateDocumentTest.java | 1 + .../update/AbstractEntityViewUpdateTest.java | 5 +- ...ViewUpdateMutableBasicCollectionsTest.java | 54 +- .../EntityViewUpdateMutableBasicMapsTest.java | 12 +- .../EntityViewUpdateMutableBasicTest.java | 2 +- .../converter/EntityViewUpdateBlobTest.java | 6 +- ...wUpdateCorrelatedCreatableSubviewTest.java | 12 +- ...elatedImmutableSubviewCollectionsTest.java | 6 +- ...wUpdateCorrelatedImmutableSubviewTest.java | 10 +- ...iewUpdateCorrelatedMutableSubviewTest.java | 4 +- ...atedMutableOnlySubviewCollectionsTest.java | 4 +- ...pdateCorrelatedMutableOnlySubviewTest.java | 4 +- ...edUpdatableOnlySubviewCollectionsTest.java | 12 +- ...ateCorrelatedUpdatableOnlySubviewTest.java | 10 +- ...utableNestedEmbeddableCollectionsTest.java | 162 +++-- ...UpdateMutableNestedEmbeddableMapsTest.java | 6 - ...ViewUpdateMutableNestedEmbeddableTest.java | 2 +- ...pdateMutableEmbeddableCollectionsTest.java | 112 +-- ...tyViewUpdateMutableEmbeddableMapsTest.java | 12 +- ...EntityViewUpdateMutableEmbeddableTest.java | 4 +- ...wUpdateCreatableEntityCollectionsTest.java | 128 +++- ...tityViewUpdateCreatableEntityMapsTest.java | 22 +- .../EntityViewUpdateCreatableEntityTest.java | 12 +- ...iewUpdateMutableEntityCollectionsTest.java | 299 +++++--- ...EntityViewUpdateMutableEntityMapsTest.java | 24 +- .../EntityViewUpdateMutableEntityTest.java | 24 +- ...pdateMutableOnlyEntityCollectionsTest.java | 24 +- ...tyViewUpdateMutableOnlyEntityMapsTest.java | 8 +- ...EntityViewUpdateMutableOnlyEntityTest.java | 4 +- ...ateUpdatableOnlyEntityCollectionsTest.java | 186 ++--- ...ViewUpdateUpdatableOnlyEntityMapsTest.java | 24 +- ...tityViewUpdateUpdatableOnlyEntityTest.java | 12 +- ...eNestedMutableFlatViewCollectionsTest.java | 33 +- ...ewUpdateNestedMutableFlatViewMapsTest.java | 2 +- ...tyViewUpdateNestedMutableFlatViewTest.java | 2 +- ...eSimpleMutableFlatViewCollectionsTest.java | 33 +- ...ewUpdateSimpleMutableFlatViewMapsTest.java | 2 +- ...tyViewUpdateSimpleMutableFlatViewTest.java | 4 +- ...iewRemoveNestedSubviewCollectionsTest.java | 4 +- ...EntityViewRemoveNestedSubviewMapsTest.java | 4 +- .../EntityViewRemoveNestedSubviewTest.java | 4 +- ...actEntityViewOrphanRemoveDocumentTest.java | 36 +- ...hanRemoveNestedSubviewCollectionsTest.java | 47 +- ...ViewOrphanRemoveNestedSubviewMapsTest.java | 5 +- ...tityViewOrphanRemoveNestedSubviewTest.java | 4 +- ...tityViewUpdateRollbackCollectionsTest.java | 48 +- .../EntityViewUpdateSubviewGraphTest.java | 104 ++- ...NestedCreatableSubviewCollectionsTest.java | 15 +- ...wUpdateNestedCreatableSubviewMapsTest.java | 2 +- ...yViewUpdateNestedCreatableSubviewTest.java | 12 +- ...NestedImmutableSubviewCollectionsTest.java | 83 +-- ...wUpdateNestedImmutableSubviewMapsTest.java | 16 +- ...yViewUpdateNestedImmutableSubviewTest.java | 10 +- ...teNestedMutableSubviewCollectionsTest.java | 101 +-- ...iewUpdateNestedMutableSubviewMapsTest.java | 16 +- ...ityViewUpdateNestedMutableSubviewTest.java | 10 +- ...stedMutableOnlySubviewCollectionsTest.java | 87 ++- ...pdateNestedMutableOnlySubviewMapsTest.java | 10 +- ...iewUpdateNestedMutableOnlySubviewTest.java | 4 +- ...edUpdatableOnlySubviewCollectionsTest.java | 86 +-- ...ateNestedUpdatableOnlySubviewMapsTest.java | 16 +- ...wUpdateNestedUpdatableOnlySubviewTest.java | 10 +- ...SimpleCreatableSubviewCollectionsTest.java | 36 +- ...wUpdateSimpleCreatableSubviewMapsTest.java | 8 +- ...yViewUpdateSimpleCreatableSubviewTest.java | 12 +- ...SimpleImmutableSubviewCollectionsTest.java | 58 +- ...wUpdateSimpleImmutableSubviewMapsTest.java | 12 +- ...yViewUpdateSimpleImmutableSubviewTest.java | 10 +- ...teSimpleMutableSubviewCollectionsTest.java | 67 +- ...iewUpdateSimpleMutableSubviewMapsTest.java | 10 +- ...ityViewUpdateSimpleMutableSubviewTest.java | 10 +- ...mpleMutableOnlySubviewCollectionsTest.java | 2 +- ...iewUpdateSimpleMutableOnlySubviewTest.java | 4 +- ...leUpdatableOnlySubviewCollectionsTest.java | 105 ++- ...ateSimpleUpdatableOnlySubviewMapsTest.java | 18 +- ...wUpdateSimpleUpdatableOnlySubviewTest.java | 10 +- .../datanucleus/DataNucleus51JpaProvider.java | 36 +- .../datanucleus/DataNucleusJpaProvider.java | 36 +- .../eclipselink/EclipseLinkJpaProvider.java | 47 +- .../base/HibernateJpa21Provider.java | 63 ++ .../hibernate/base/HibernateJpaProvider.java | 482 ++++++++++--- .../jpa/JpaMetamodelAccessorImpl.java | 3 +- .../openjpa/OpenJPAJpaProvider.java | 34 +- .../base/jpa/AbstractJpaPersistenceTest.java | 4 +- .../jpa/assertion/AssertInsertStatement.java | 2 +- .../jpa/assertion/AssertStatementBuilder.java | 24 + .../jpa/assertion/AssertUpdateStatement.java | 2 +- 219 files changed, 6271 insertions(+), 2440 deletions(-) create mode 100644 core/impl/src/main/java/com/blazebit/persistence/impl/SplittingParameterTransformer.java create mode 100644 entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/EmbeddableOwner.java create mode 100644 entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionActions.java create mode 100644 entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionElementActions.java create mode 100644 entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionIndexActions.java create mode 100644 entity-view/impl/src/test/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionIndexActionsTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 415b22f5bd..0b0f49c10c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,13 @@ Not yet released ### New features -* Support for binding Embeddables directly to embeddable path expressions in CTE's +* Support for binding Embeddables directly to embeddable path expressions in CTEs * Support for binding associations that are part of an entities identifier * Support for binding associations mapped by compound or foreign keys * Using a comparator with `List` and `Collection` types in entity views will sort the collection after load * Add option to force deduplication of elements in non-sets to `@CollectionMapping` +* Support `VALUES` clause with embeddable types +* Support for binding embeddable parameters in CTEs, insert and update queries * Properly implement dirty state transfer when converting one entity view to another * Added validation for `equals`/`hashCode` implementations of JPA managed types that are used within entity views which can be disabled with the property `com.blazebit.persistence.view.managed_type_validation_disabled` * Add support for DeltaSpike Data 1.9 @@ -23,6 +25,8 @@ Not yet released * Fix support for exists repository methods in Spring Data repositories * Fix problems with count queries that require parameters in Spring Data repositories * Properly set parent id on converted creatable subviews contained in inverse collections +* Fix type variable related issues with Spring Data Repositories +* Properly set parent id on converted creatable subviews contained in inverse collections * Fix entity view build time validation error with certain inheritance mappings * Fix problems with objects of wrong types being returned from standard repository methods with Spring Data 2.0+ * Fix various little issues related to embeddable handling in updatable entity views diff --git a/checkstyle-rules/src/main/resources/blaze-persistence/checkstyle-config.xml b/checkstyle-rules/src/main/resources/blaze-persistence/checkstyle-config.xml index 8c705ef1ee..bcfe97b5aa 100644 --- a/checkstyle-rules/src/main/resources/blaze-persistence/checkstyle-config.xml +++ b/checkstyle-rules/src/main/resources/blaze-persistence/checkstyle-config.xml @@ -115,7 +115,7 @@ - + diff --git a/core/api/src/main/java/com/blazebit/persistence/spi/ExtendedAttribute.java b/core/api/src/main/java/com/blazebit/persistence/spi/ExtendedAttribute.java index 45fe2f53ff..9937c87949 100644 --- a/core/api/src/main/java/com/blazebit/persistence/spi/ExtendedAttribute.java +++ b/core/api/src/main/java/com/blazebit/persistence/spi/ExtendedAttribute.java @@ -48,6 +48,14 @@ public interface ExtendedAttribute { */ public List> getAttributePath(); + /** + * Returns the path from the owning entity type to this attribute as string. + * + * @return The path to the attribute as string + * @since 1.3.0 + */ + public String getAttributePathString(); + /** * Returns the element type of the attribute. * diff --git a/core/api/src/main/java/com/blazebit/persistence/spi/ExtendedManagedType.java b/core/api/src/main/java/com/blazebit/persistence/spi/ExtendedManagedType.java index d0269acd45..433a2627a4 100644 --- a/core/api/src/main/java/com/blazebit/persistence/spi/ExtendedManagedType.java +++ b/core/api/src/main/java/com/blazebit/persistence/spi/ExtendedManagedType.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.spi; +import javax.persistence.metamodel.EntityType; import javax.persistence.metamodel.ManagedType; import javax.persistence.metamodel.SingularAttribute; import java.util.Map; @@ -37,6 +38,22 @@ public interface ExtendedManagedType { */ public ManagedType getType(); + /** + * Returns an entity type and path that owns this embeddable type via a singular attribute or null if there is none. + * + * @return An entity type and path that owns this embeddable type via a singular attribute or null if there is none + * @since 1.3.0 + */ + public Map.Entry, String> getEmbeddableSingularOwner(); + + /** + * Returns an entity type and path that owns this embeddable type via a plural attribute or null if there is none. + * + * @return An entity type and path that owns this embeddable type via a plural attribute or null if there is none + * @since 1.3.0 + */ + public Map.Entry, String> getEmbeddablePluralOwner(); + /** * Returns whether the type has a cascading delete cycle. * diff --git a/core/api/src/main/java/com/blazebit/persistence/spi/JoinTable.java b/core/api/src/main/java/com/blazebit/persistence/spi/JoinTable.java index 3a3a0280cc..df34a79fe8 100644 --- a/core/api/src/main/java/com/blazebit/persistence/spi/JoinTable.java +++ b/core/api/src/main/java/com/blazebit/persistence/spi/JoinTable.java @@ -17,6 +17,7 @@ package com.blazebit.persistence.spi; import java.util.Map; +import java.util.Set; /** * A structure for accessing join table information. @@ -27,22 +28,27 @@ public class JoinTable { private final String tableName; + private final Set idAttributeNames; + private final Set targetAttributeNames; private final Map idColumnMappings; private final Map keyColumnMappings; private final Map targetIdColumnMappings; /** * Constructs a JoinTable. - * * @param tableName The join table name + * @param idAttributeNames The attribute names of the owning entity * @param idColumnMappings The id column mappings * @param keyColumnMappings The key column mappings + * @param targetAttributeNames The attribute names of the target entity * @param targetIdColumnMappings The target id column mappings */ - public JoinTable(String tableName, Map idColumnMappings, Map keyColumnMappings, Map targetIdColumnMappings) { + public JoinTable(String tableName, Set idAttributeNames, Map idColumnMappings, Map keyColumnMappings, Set targetAttributeNames, Map targetIdColumnMappings) { this.tableName = tableName; + this.idAttributeNames = idAttributeNames; this.idColumnMappings = idColumnMappings; this.keyColumnMappings = keyColumnMappings; + this.targetAttributeNames = targetAttributeNames; this.targetIdColumnMappings = targetIdColumnMappings; } @@ -55,6 +61,16 @@ public String getTableName() { return tableName; } + /** + * Returns the id attribute names of the owning entity that map to the join table. + * + * @return The owner entity id attribute names + * @since 1.3.0 + */ + public Set getIdAttributeNames() { + return idAttributeNames; + } + /** * Returns the foreign key mappings of the join tables column names to the owner table column names. * The keys are column names of the join table. Values are the column names of the owner table. @@ -77,6 +93,16 @@ public Map getKeyColumnMappings() { return keyColumnMappings; } + /** + * Returns the id attribute names of the target entity that map to the join table. + * + * @return The target entity id attribute names + * @since 1.3.0 + */ + public Set getTargetAttributeNames() { + return targetAttributeNames; + } + /** * Returns the foreign key mappings of the join tables column names to the target table column names. * The keys are column names of the join table. Values are the column names of the target table. diff --git a/core/api/src/main/java/com/blazebit/persistence/spi/JpaProvider.java b/core/api/src/main/java/com/blazebit/persistence/spi/JpaProvider.java index f1dc4a52b3..1235014526 100644 --- a/core/api/src/main/java/com/blazebit/persistence/spi/JpaProvider.java +++ b/core/api/src/main/java/com/blazebit/persistence/spi/JpaProvider.java @@ -248,6 +248,17 @@ public interface JpaProvider { */ public String[] getColumnNames(EntityType ownerType, String attributeName); + /** + * Returns the column names of the attribute of the given entity type within the element collection. + * + * @param ownerType The owner of the attribute + * @param elementCollectionPath The path to the element collection within which the attribute is contained + * @param attributeName The attribute name + * @return The column names of the attribute + * @since 1.3.0 + */ + public String[] getColumnNames(EntityType ownerType, String elementCollectionPath, String attributeName); + /** * Returns the SQL column type names of the given attribute of the given entity type. * @@ -257,6 +268,17 @@ public interface JpaProvider { */ public String[] getColumnTypes(EntityType ownerType, String attributeName); + /** + * Returns the SQL column type names of the given attribute of the given entity type within the element collection. + * + * @param ownerType The owner of the attribute + * @param elementCollectionPath The path to the element collection within which the attribute is contained + * @param attributeName The attribute name + * @return The SQL column type names for the attribute + * @since 1.3.0 + */ + public String[] getColumnTypes(EntityType ownerType, String elementCollectionPath, String attributeName); + /** * Returns where to put treat filters for a treat joined association of this attribute. * This is for JPA providers that don't correctly filter the types. @@ -320,6 +342,17 @@ public interface JpaProvider { */ public boolean isOrphanRemoval(ManagedType ownerType, String attributeName); + /** + * Whether orphan removal is activated for the given attribute within the element collection. + * + * @param ownerType The declaring type of the attribute to check + * @param elementCollectionPath The path to the element collection within which the attribute is contained + * @param attributeName The name of the attribute to check + * @return True if orphan removal is activated, else false + * @since 1.3.0 + */ + public boolean isOrphanRemoval(ManagedType ownerType, String elementCollectionPath, String attributeName); + /** * Whether delete cascading is activated for the given attribute. * @@ -329,6 +362,17 @@ public interface JpaProvider { */ public boolean isDeleteCascaded(ManagedType ownerType, String attributeName); + /** + * Whether delete cascading is activated for the given attribute within the element collection. + * + * @param ownerType The declaring type of the attribute to check + * @param elementCollectionPath The path to the element collection within which the attribute is contained + * @param attributeName The name of the attribute to check + * @return True if delete cascading is activated, else false + * @since 1.3.0 + */ + public boolean isDeleteCascaded(ManagedType ownerType, String elementCollectionPath, String attributeName); + /** * Returns whether the entity with the id is contained in the entity managers persistence context. * @@ -426,7 +470,15 @@ public interface JpaProvider { * @return true if supported, else false * @since 1.3.0 */ - public boolean supportsJoinElementCollectionsOnCorrelatedInverseAssociations(); + public boolean needsCorrelationPredicateWhenCorrelatingWithWhereClause(); + + /** + * Indicates whether the provider supports optimized natural id access. + * + * @return true if supported, else false + * @since 1.3.0 + */ + public boolean supportsSingleValuedAssociationNaturalIdExpressions(); /** * Enables query result caching for the given query. @@ -446,6 +498,17 @@ public interface JpaProvider { */ public List getIdentifierOrUniqueKeyEmbeddedPropertyNames(EntityType owner, String attributeName); + /** + * Get the identifier or unique key inverse properties of an association attribute within an element collection. + * + * @param owner The owning entity type + * @param elementCollectionPath The path to the element collection within which the attribute is contained + * @param attributeName The association attribute + * @return the identifier or unique key inverse properties of the association attribute + * @since 1.3.0 + */ + public List getIdentifierOrUniqueKeyEmbeddedPropertyNames(EntityType owner, String elementCollectionPath, String attributeName); + /** * Returns the identifier of the entity object. * diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractCTECriteriaBuilder.java b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractCTECriteriaBuilder.java index 575bb3a2ef..e96988ccc0 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractCTECriteriaBuilder.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractCTECriteriaBuilder.java @@ -79,8 +79,8 @@ public AbstractCTECriteriaBuilder(MainQuery mainQuery, QueryContext queryContext this.cteType = mainQuery.metamodel.entity(clazz); this.attributeEntries = mainQuery.metamodel.getManagedType(ExtendedManagedType.class, clazz).getAttributes(); this.cteName = cteName; - this.bindingMap = new LinkedHashMap(attributeEntries.size()); - this.columnBindingMap = new LinkedHashMap(attributeEntries.size()); + this.bindingMap = new LinkedHashMap<>(attributeEntries.size()); + this.columnBindingMap = new LinkedHashMap<>(attributeEntries.size()); this.subListener = new CTEBuilderListenerImpl(); } @@ -214,7 +214,7 @@ protected List prepareAndGetAttributes() { final String attributeName = attributeQueue.remove(); Integer tupleIndex = bindingMap.get(attributeName); - final ExtendedAttribute attributeEntry = attributeEntries.get(attributeName); + final ExtendedAttribute attributeEntry = attributeEntries.get(attributeName); final List> attributePath = attributeEntry.getAttributePath(); final JpaMetamodelAccessor jpaMetamodelAccessor = mainQuery.jpaProvider.getJpaMetamodelAccessor(); final Attribute lastAttribute = attributePath.get(attributePath.size() - 1); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractCommonQueryBuilder.java b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractCommonQueryBuilder.java index 820664d20a..b400a30f38 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractCommonQueryBuilder.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractCommonQueryBuilder.java @@ -719,7 +719,7 @@ public BuilderType fromIdentifiableValues(Class valueClass, String alias, int throw new IllegalArgumentException("Only identifiable types allowed!"); } - joinManager.addRootValues(valuesClazz, valueClass, alias, valueCount, treatFunction, castedParameter, true); + joinManager.addRootValues(valuesClazz, valueClass, alias, valueCount, treatFunction, castedParameter, true, null); fromClassExplicitlySet = true; return (BuilderType) this; @@ -735,6 +735,7 @@ public BuilderType fromValues(Class valueClass, String alias, int valueCount) } Class valuesClazz = valueClass; + String valueClazzAttributeName = null; ManagedType type = mainQuery.metamodel.getManagedType(valueClass); String typeName = null; String castedParameter = null; @@ -748,9 +749,21 @@ public BuilderType fromValues(Class valueClass, String alias, int valueCount) castedParameter = mainQuery.dbmsDialect.cast("?", sqlType); valuesClazz = ValuesEntity.class; } else if (!(type instanceof EntityType)) { - throw new IllegalArgumentException("Unsupported use of embeddable type [" + valueClass + "] for values clause! Use the entity type and fromIdentifiableValues instead or introduce a CTE entity containing just the embeddable to be able to query it!"); + ExtendedManagedType extendedManagedType = mainQuery.metamodel.getManagedType(ExtendedManagedType.class, valueClass); + Map.Entry, String> entry = extendedManagedType.getEmbeddableSingularOwner(); + boolean singular = true; + if (entry == null) { +// singular = false; +// entry = extendedManagedType.getEmbeddablePluralOwner(); + throw new IllegalArgumentException("Unsupported plural-only use of embeddable type [" + valueClass + "] for values clause! Please introduce a CTE entity containing just the embeddable to be able to query it!"); + } + if (entry == null) { + throw new IllegalArgumentException("Unsupported use of embeddable type [" + valueClass + "] for values clause! Use the entity type and fromIdentifiableValues instead or introduce a CTE entity containing just the embeddable to be able to query it!"); + } + valuesClazz = entry.getKey().getJavaType(); + valueClazzAttributeName = entry.getValue(); } - joinManager.addRootValues(valuesClazz, valueClass, alias, valueCount, typeName, castedParameter, false); + joinManager.addRootValues(valuesClazz, valueClass, alias, valueCount, typeName, castedParameter, false, valueClazzAttributeName); fromClassExplicitlySet = true; return (BuilderType) this; @@ -1833,7 +1846,8 @@ protected List getEntityFunctionNodes(Query baseQuery) { String dummyTable = mainQuery.dbmsDialect.getDummyTable(); for (JoinNode node : joinManager.getEntityFunctionNodes()) { - Class clazz = node.getJavaType(); + Class clazz = node.getInternalEntityType().getJavaType(); + String valueClazzAttributeName = node.getValueClazzAttributeName(); int valueCount = node.getValueCount(); boolean identifiableReference = node.getValuesIdName() != null; String rootAlias = node.getAlias(); @@ -1843,7 +1857,7 @@ protected List getEntityFunctionNodes(Query baseQuery) { // We construct an example query representing the values clause with a SELECT clause that selects the fields in the right order which we need to construct SQL // that uses proper aliases and filters null values which are there in the first place to pad up parameters in case we don't reach the desired value count StringBuilder valuesSb = new StringBuilder(20 + valueCount * attributes.length * 3); - Query valuesExampleQuery = getValuesExampleQuery(clazz, valueCount, identifiableReference, rootAlias, castedParameter, attributes, valuesSb, strategy, dummyTable, node); + Query valuesExampleQuery = getValuesExampleQuery(clazz, valueCount, identifiableReference, valueClazzAttributeName, rootAlias, castedParameter, attributes, valuesSb, strategy, dummyTable, node); String exampleQuerySql = mainQuery.cbf.getExtendedQuerySupport().getSql(mainQuery.em, valuesExampleQuery); String exampleQuerySqlAlias = mainQuery.cbf.getExtendedQuerySupport().getSqlAlias(mainQuery.em, valuesExampleQuery, "e"); @@ -1949,7 +1963,7 @@ private String getValuesAliases(String tableAlias, int attributeCount, String ex return sb.toString(); } - private Query getValuesExampleQuery(Class clazz, int valueCount, boolean identifiableReference, String prefix, String castedParameter, String[] attributes, StringBuilder valuesSb, ValuesStrategy strategy, String dummyTable, JoinNode valuesNode) { + private Query getValuesExampleQuery(Class clazz, int valueCount, boolean identifiableReference, String valueClazzAttributeName, String prefix, String castedParameter, String[] attributes, StringBuilder valuesSb, ValuesStrategy strategy, String dummyTable, JoinNode valuesNode) { String[] attributeParameter = new String[attributes.length]; // This size estimation roughly assumes a maximum attribute name length of 15 StringBuilder sb = new StringBuilder(50 + valueCount * prefix.length() * attributes.length * 50); @@ -1970,7 +1984,12 @@ private Query getValuesExampleQuery(Class clazz, int valueCount, boolean iden Map mapping = mainQuery.metamodel.getManagedType(ExtendedManagedType.class, clazz).getAttributes(); StringBuilder paramBuilder = new StringBuilder(); for (int i = 0; i < attributes.length; i++) { - ExtendedAttribute entry = mapping.get(attributes[i]); + ExtendedAttribute entry; + if (valueClazzAttributeName == null) { + entry = mapping.get(attributes[i]); + } else { + entry = mapping.get(valueClazzAttributeName + "." + attributes[i]); + } Attribute attribute = entry.getAttribute(); String[] columnTypes = entry.getColumnTypes(); attributeParameter[i] = getCastedParameters(paramBuilder, mainQuery.dbmsDialect, columnTypes); @@ -1982,12 +2001,18 @@ private Query getValuesExampleQuery(Class clazz, int valueCount, boolean iden ManagedType managedAttributeType = mainQuery.metamodel.managedType(entry.getElementClass()); for (Attribute attributeTypeIdAttribute : JpaMetamodelUtils.getIdAttributes((IdentifiableType) managedAttributeType)) { sb.append("e."); + if (valueClazzAttributeName != null) { + sb.append(valueClazzAttributeName).append('.'); + } sb.append(attributes[i]); sb.append('.'); sb.append(attributeTypeIdAttribute.getName()); } } else { sb.append("e."); + if (valueClazzAttributeName != null) { + sb.append(valueClazzAttributeName).append('.'); + } sb.append(attributes[i]); } @@ -1999,7 +2024,7 @@ private Query getValuesExampleQuery(Class clazz, int valueCount, boolean iden sb.append("FROM "); sb.append(clazz.getName()); sb.append(" e WHERE "); - joinManager.renderValuesClausePredicate(sb, valuesNode, "e"); + joinManager.renderValuesClausePredicate(sb, valuesNode, "e", false); if (strategy == ValuesStrategy.SELECT_VALUES || strategy == ValuesStrategy.VALUES) { valuesSb.append("(VALUES "); @@ -2330,10 +2355,14 @@ protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean external boolean originalExternalRepresentation = queryGenerator.isExternalRepresentation(); queryGenerator.setExternalRepresentation(externalRepresentation); try { - appendSelectClause(sbSelectFrom); + appendSelectClause(sbSelectFrom, externalRepresentation); List whereClauseEndConjuncts = this instanceof Subquery ? new ArrayList() : null; - List whereClauseConjuncts = appendFromClause(sbSelectFrom, whereClauseEndConjuncts, externalRepresentation); - appendWhereClause(sbSelectFrom, whereClauseConjuncts, whereClauseEndConjuncts); + + List whereClauseConjuncts = new ArrayList<>(); + List optionalWhereClauseConjuncts = new ArrayList<>(); + joinManager.buildClause(sbSelectFrom, EnumSet.noneOf(ClauseType.class), null, false, externalRepresentation, false, optionalWhereClauseConjuncts, whereClauseConjuncts, whereClauseEndConjuncts, explicitVersionEntities, nodesToFetch, Collections.EMPTY_SET); + + appendWhereClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, whereClauseEndConjuncts); appendGroupByClause(sbSelectFrom); appendOrderByClause(sbSelectFrom); if (externalRepresentation && !isMainQuery) { @@ -2360,35 +2389,29 @@ protected void buildExternalQueryString(StringBuilder sbSelectFrom) { buildBaseQueryString(sbSelectFrom, true); } - protected void appendSelectClause(StringBuilder sbSelectFrom) { - selectManager.buildSelect(sbSelectFrom, false); - } - - protected List appendFromClause(StringBuilder sbSelectFrom, List whereClauseEndConjuncts, boolean externalRepresentation) { - List whereClauseConjuncts = new ArrayList<>(); - joinManager.buildClause(sbSelectFrom, EnumSet.noneOf(ClauseType.class), null, false, externalRepresentation, false, whereClauseConjuncts, whereClauseEndConjuncts, explicitVersionEntities, nodesToFetch, Collections.EMPTY_SET); - return whereClauseConjuncts; + protected void appendSelectClause(StringBuilder sbSelectFrom, boolean externalRepresentation) { + selectManager.buildSelect(sbSelectFrom, false, externalRepresentation); } protected void appendWhereClause(StringBuilder sbSelectFrom, boolean externalRepresentation) { boolean originalExternalRepresentation = queryGenerator.isExternalRepresentation(); queryGenerator.setExternalRepresentation(externalRepresentation); try { - appendWhereClause(sbSelectFrom, Collections.emptyList(), Collections.emptyList()); + appendWhereClause(sbSelectFrom, Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); } finally { queryGenerator.setExternalRepresentation(originalExternalRepresentation); } } - protected void appendWhereClause(StringBuilder sbSelectFrom, List whereClauseConjuncts, List whereClauseEndConjuncts) { + protected void appendWhereClause(StringBuilder sbSelectFrom, List whereClauseConjuncts, List optionalWhereClauseConjuncts, List whereClauseEndConjuncts) { KeysetLink keysetLink = keysetManager.getKeysetLink(); if (keysetLink == null || keysetLink.getKeysetMode() == KeysetMode.NONE) { - whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, whereClauseEndConjuncts); + whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, whereClauseEndConjuncts); } else { sbSelectFrom.append(" WHERE "); if (whereManager.hasPredicates()) { - whereManager.buildClausePredicate(sbSelectFrom, whereClauseConjuncts, whereClauseEndConjuncts); + whereManager.buildClausePredicate(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, whereClauseEndConjuncts); sbSelectFrom.append(" AND "); } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractFullQueryBuilder.java b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractFullQueryBuilder.java index 475ba369f1..5b07369a41 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractFullQueryBuilder.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractFullQueryBuilder.java @@ -176,12 +176,13 @@ protected final String buildPageCountQueryString(StringBuilder sbSelectFrom, boo } List whereClauseConjuncts = new ArrayList<>(); + List optionalWhereClauseConjuncts = new ArrayList<>(); // The count query does not have any fetch owners Set countNodesToFetch = Collections.emptySet(); if (countAll) { - joinManager.buildClause(sbSelectFrom, NO_CLAUSE_EXCLUSION, null, false, externalRepresentation, false, whereClauseConjuncts, null, explicitVersionEntities, countNodesToFetch, Collections.EMPTY_SET); - whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, null); + joinManager.buildClause(sbSelectFrom, NO_CLAUSE_EXCLUSION, null, false, externalRepresentation, false, optionalWhereClauseConjuncts, whereClauseConjuncts, null, explicitVersionEntities, countNodesToFetch, Collections.EMPTY_SET); + whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, null); } else { // Collect usage of collection join nodes to optimize away the count distinct // Note that we always exclude the nodes with group by dependency. We consider just the ones from the identifiers @@ -192,10 +193,10 @@ protected final String buildPageCountQueryString(StringBuilder sbSelectFrom, boo } else { identifierExpressionsToUseNonRootJoinNodes = Collections.EMPTY_SET; } - Set collectionJoinNodes = joinManager.buildClause(sbSelectFrom, COUNT_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, null, true, externalRepresentation, true, whereClauseConjuncts, null, explicitVersionEntities, countNodesToFetch, identifierExpressionsToUseNonRootJoinNodes); + Set collectionJoinNodes = joinManager.buildClause(sbSelectFrom, COUNT_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, null, true, externalRepresentation, true, optionalWhereClauseConjuncts, whereClauseConjuncts, null, explicitVersionEntities, countNodesToFetch, identifierExpressionsToUseNonRootJoinNodes); boolean hasCollectionJoinUsages = collectionJoinNodes.size() > 0; - whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, null); + whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, null); // Instead of a count distinct, we render a count(*) if we have no collection joins and the identifier expression is result unique // It is result unique when it contains the query root primary key or a unique key that of a uniqueness preserving association of that @@ -418,7 +419,7 @@ private void addAttributes(String prefix, Set> attribute addAttributes(attributeName + ".", subAttributes, resolvedExpressions, sb, rootNode); } else { sb.setLength(0); - rootNode.appendDeReference(sb, attributeName); + rootNode.appendDeReference(sb, attributeName, false); PathExpression expression = (PathExpression) rootNode.createExpression(attributeName); JpaMetamodelAccessor jpaMetamodelAccessor = mainQuery.jpaProvider.getJpaMetamodelAccessor(); expression.setPathReference(new SimplePathReference(rootNode, attributeName, getMetamodel().type(jpaMetamodelAccessor.getAttributePath(getMetamodel(), rootNode.getManagedType(), attributeName).getAttributeClass()))); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractInsertCollectionCriteriaBuilder.java b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractInsertCollectionCriteriaBuilder.java index 31872bcf05..b475492bc3 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractInsertCollectionCriteriaBuilder.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractInsertCollectionCriteriaBuilder.java @@ -28,22 +28,24 @@ import com.blazebit.persistence.impl.query.EntityFunctionNode; import com.blazebit.persistence.impl.query.QuerySpecification; import com.blazebit.persistence.impl.query.ReturningCollectionInsertModificationQuerySpecification; -import com.blazebit.persistence.spi.AttributePath; -import com.blazebit.persistence.parser.QualifiedAttribute; import com.blazebit.persistence.impl.util.SqlUtils; import com.blazebit.persistence.spi.DbmsModificationState; +import com.blazebit.persistence.spi.ExtendedAttribute; +import com.blazebit.persistence.spi.ExtendedManagedType; import com.blazebit.persistence.spi.ExtendedQuerySupport; import com.blazebit.persistence.spi.JoinTable; -import com.blazebit.persistence.spi.JpaMetamodelAccessor; import javax.persistence.Query; import javax.persistence.TypedQuery; -import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.ListAttribute; +import javax.persistence.metamodel.MapAttribute; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeSet; /** * @@ -55,16 +57,33 @@ public abstract class AbstractInsertCollectionCriteriaBuilder collectionAttributeEntries; + private final Map collectionColumnBindingMap; public AbstractInsertCollectionCriteriaBuilder(MainQuery mainQuery, QueryContext queryContext, boolean isMainQuery, Class clazz, String cteName, Class cteClass, Y result, CTEBuilderListener listener, String collectionName) { super(mainQuery, queryContext, isMainQuery, clazz, cteName, cteClass, result, listener); this.collectionName = collectionName; - // TODO: validate the collection name exists + ExtendedManagedType extendedManagedType = mainQuery.metamodel.getManagedType(ExtendedManagedType.class, entityType); + ExtendedAttribute extendedAttribute = extendedManagedType.getAttribute(collectionName); + Map collectionAttributeEntries = JpaUtils.getCollectionAttributeEntries(mainQuery.metamodel, entityType, extendedAttribute); + if (extendedAttribute.getAttribute() instanceof MapAttribute) { + keyFunctionExpression = "key(" + collectionName + ")"; + } else if (extendedAttribute.getAttribute() instanceof ListAttribute && !extendedAttribute.isBag()) { + keyFunctionExpression = "index(" + collectionName + ")"; + } else { + keyFunctionExpression = null; + } + this.collectionColumnBindingMap = new LinkedHashMap<>(collectionAttributeEntries.size()); + this.collectionAttributeEntries = collectionAttributeEntries; } public AbstractInsertCollectionCriteriaBuilder(AbstractInsertCollectionCriteriaBuilder builder, MainQuery mainQuery, QueryContext queryContext) { super(builder, mainQuery, queryContext); this.collectionName = builder.collectionName; + this.keyFunctionExpression = builder.keyFunctionExpression; + this.collectionColumnBindingMap = builder.collectionColumnBindingMap; + this.collectionAttributeEntries = builder.collectionAttributeEntries; } @Override @@ -86,29 +105,25 @@ protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean external @Override protected void addBind(String attributeName) { - JpaMetamodelAccessor jpaMetamodelAccessor = mainQuery.jpaProvider.getJpaMetamodelAccessor(); - AttributePath attributePath = jpaMetamodelAccessor.getJoinTableCollectionAttributePath(getMetamodel(), entityType, attributeName, collectionName); - StringBuilder sb = new StringBuilder(); - List> attributes = attributePath.getAttributes(); - Attribute attribute = attributes.get(0); - // Replace the collection name with the alias for easier processing - if (attribute instanceof QualifiedAttribute) { - sb.append(((QualifiedAttribute) attribute).getQualificationExpression()); - sb.append('('); - sb.append(COLLECTION_BASE_QUERY_ALIAS); - sb.append(')'); - } else if (collectionName.equals(attribute.getName())) { - sb.append(COLLECTION_BASE_QUERY_ALIAS); - } else { - sb.append(entityAlias).append('.'); - sb.append(attribute.getName()); + if (attributeName.equalsIgnoreCase(keyFunctionExpression)) { + Integer attributeBindIndex = bindingMap.get(attributeName); + + if (attributeBindIndex != null) { + throw new IllegalArgumentException("The attribute [" + attributeName + "] has already been bound!"); + } + + bindingMap.put(attributeName, selectManager.getSelectInfos().size()); + return; } - for (int i = 1; i < attributes.size(); i++) { - attribute = attributes.get(i); - sb.append('.'); - sb.append(attribute.getName()); + ExtendedAttribute attributeEntry = collectionAttributeEntries.get(attributeName); + if (attributeEntry == null) { + Set set = new TreeSet<>(collectionAttributeEntries.keySet()); + if (keyFunctionExpression != null) { + set.add(keyFunctionExpression); + } + throw new IllegalArgumentException("The attribute [" + attributeName + "] does not exist or can't be bound! Allowed attributes are: " + set); } - attributeName = sb.toString(); + Integer attributeBindIndex = bindingMap.get(attributeName); if (attributeBindIndex != null) { @@ -118,6 +133,11 @@ protected void addBind(String attributeName) { bindingMap.put(attributeName, selectManager.getSelectInfos().size()); } + @Override + protected void expandBindings() { + JpaUtils.expandBindings(entityType, bindingMap, collectionColumnBindingMap, collectionAttributeEntries, ClauseType.SELECT, this); + } + @Override protected Query getQuery(Map includedModificationStates) { Query baseQuery = em.createQuery(getBaseQueryStringWithCheck()); @@ -252,7 +272,15 @@ protected Query getInsertExampleQuery() { sb.append("SELECT "); for (Map.Entry entry : bindingMap.entrySet()) { - sb.append(entry.getKey()); + String expression = entry.getKey(); + int collectionIndex = expression.indexOf(collectionName); + if (collectionIndex == -1) { + sb.append(entityAlias).append('.').append(expression); + } else { + sb.append(expression, 0, collectionIndex); + sb.append(COLLECTION_BASE_QUERY_ALIAS); + sb.append(expression, collectionIndex + collectionName.length(), expression.length()); + } sb.append(','); } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractModificationCriteriaBuilder.java b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractModificationCriteriaBuilder.java index 532062cdde..16c0f1bee4 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractModificationCriteriaBuilder.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractModificationCriteriaBuilder.java @@ -98,7 +98,7 @@ public AbstractModificationCriteriaBuilder(MainQuery mainQuery, QueryContext que this.cteName = null; this.isReturningEntityAliasAllowed = false; this.returningAttributes = new LinkedHashMap<>(0); - this.returningAttributeBindingMap = new LinkedHashMap(0); + this.returningAttributeBindingMap = new LinkedHashMap<>(0); this.attributeEntries = null; this.columnBindingMap = null; } else { @@ -108,8 +108,8 @@ public AbstractModificationCriteriaBuilder(MainQuery mainQuery, QueryContext que this.isReturningEntityAliasAllowed = true; this.returningAttributes = null; this.attributeEntries = mainQuery.metamodel.getManagedType(ExtendedManagedType.class, cteClass).getAttributes(); - this.returningAttributeBindingMap = new LinkedHashMap(attributeEntries.size()); - this.columnBindingMap = new LinkedHashMap(attributeEntries.size()); + this.returningAttributeBindingMap = new LinkedHashMap<>(attributeEntries.size()); + this.columnBindingMap = new LinkedHashMap<>(attributeEntries.size()); } } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractUpdateCollectionCriteriaBuilder.java b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractUpdateCollectionCriteriaBuilder.java index 0f91d4df7f..311cc2174d 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractUpdateCollectionCriteriaBuilder.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/AbstractUpdateCollectionCriteriaBuilder.java @@ -21,8 +21,6 @@ import com.blazebit.persistence.ReturningBuilder; import com.blazebit.persistence.ReturningObjectBuilder; import com.blazebit.persistence.ReturningResult; -import com.blazebit.persistence.spi.AttributePath; -import com.blazebit.persistence.parser.QualifiedAttribute; import com.blazebit.persistence.parser.SimpleQueryGenerator; import com.blazebit.persistence.parser.expression.Expression; import com.blazebit.persistence.impl.function.entity.ValuesEntity; @@ -34,19 +32,23 @@ import com.blazebit.persistence.impl.query.ReturningCollectionUpdateModificationQuerySpecification; import com.blazebit.persistence.impl.util.SqlUtils; import com.blazebit.persistence.spi.DbmsModificationState; +import com.blazebit.persistence.spi.ExtendedAttribute; +import com.blazebit.persistence.spi.ExtendedManagedType; import com.blazebit.persistence.spi.ExtendedQuerySupport; import com.blazebit.persistence.spi.JoinTable; -import com.blazebit.persistence.spi.JpaMetamodelAccessor; import javax.persistence.Query; import javax.persistence.TypedQuery; -import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.ListAttribute; +import javax.persistence.metamodel.MapAttribute; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeSet; /** * @@ -57,6 +59,9 @@ public abstract class AbstractUpdateCollectionCriteriaBuilder, Y> extends BaseUpdateCriteriaBuilderImpl { private final String collectionName; + private final String keyFunctionExpression; + private final Map collectionAttributeEntries; + private final Map collectionColumnBindingMap; private List cachedBaseQueryStrings; @@ -66,40 +71,56 @@ public AbstractUpdateCollectionCriteriaBuilder(MainQuery mainQuery, QueryContext // Add the join here so that references in the where clause go the the expected join node // Also, this validates the collection actually exists joinManager.join(entityAlias + "." + collectionName, CollectionUpdateModificationQuerySpecification.COLLECTION_BASE_QUERY_ALIAS, JoinType.LEFT, false, true); + ExtendedManagedType extendedManagedType = mainQuery.metamodel.getManagedType(ExtendedManagedType.class, entityType); + ExtendedAttribute extendedAttribute = extendedManagedType.getAttribute(collectionName); + Map collectionAttributeEntries = JpaUtils.getCollectionAttributeEntries(mainQuery.metamodel, entityType, extendedAttribute); + if (extendedAttribute.getAttribute() instanceof MapAttribute) { + keyFunctionExpression = "key(" + collectionName + ")"; + } else if (extendedAttribute.getAttribute() instanceof ListAttribute && !mainQuery.jpaProvider.isBag(entityType, collectionName)) { + keyFunctionExpression = "index(" + collectionName + ")"; + } else { + keyFunctionExpression = null; + } + this.collectionColumnBindingMap = new LinkedHashMap<>(collectionAttributeEntries.size()); + this.collectionAttributeEntries = collectionAttributeEntries; } public AbstractUpdateCollectionCriteriaBuilder(AbstractUpdateCollectionCriteriaBuilder builder, MainQuery mainQuery, QueryContext queryContext) { super(builder, mainQuery, queryContext); this.collectionName = builder.collectionName; + this.keyFunctionExpression = builder.keyFunctionExpression; + this.collectionColumnBindingMap = builder.collectionColumnBindingMap; + this.collectionAttributeEntries = builder.collectionAttributeEntries; } @Override - protected String checkAttribute(String attributeName) { - // Assert the attribute exists and "clean" the attribute path - JpaMetamodelAccessor jpaMetamodelAccessor = mainQuery.jpaProvider.getJpaMetamodelAccessor(); - AttributePath attributePath = jpaMetamodelAccessor.getJoinTableCollectionAttributePath(getMetamodel(), entityType, attributeName, collectionName); - StringBuilder sb = new StringBuilder(); - for (Attribute attribute : attributePath.getAttributes()) { - // Replace the collection name with the alias for easier processing - if (attribute instanceof QualifiedAttribute) { - sb.append(((QualifiedAttribute) attribute).getQualificationExpression()); - sb.append('('); - sb.append(CollectionUpdateModificationQuerySpecification.COLLECTION_BASE_QUERY_ALIAS); - sb.append(')'); - } else if (collectionName.equals(attribute.getName())) { - sb.append(CollectionUpdateModificationQuerySpecification.COLLECTION_BASE_QUERY_ALIAS); - } else { - sb.append(attribute.getName()); + protected void addAttribute(String attributeName) { + if (attributeName.equalsIgnoreCase(keyFunctionExpression)) { + Integer attributeBindIndex = setAttributeBindingMap.get(attributeName); + + if (attributeBindIndex != null) { + throw new IllegalArgumentException("The attribute [" + attributeName + "] has already been bound!"); } - sb.append('.'); + + setAttributeBindingMap.put(attributeName, selectManager.getSelectInfos().size()); + return; } - attributeName = sb.substring(0, sb.length() - 1); - Expression attributeExpression = setAttributes.get(attributeName); + ExtendedAttribute attributeEntry = collectionAttributeEntries.get(attributeName); + if (attributeEntry == null) { + Set set = new TreeSet<>(collectionAttributeEntries.keySet()); + if (keyFunctionExpression != null) { + set.add(keyFunctionExpression); + } + throw new IllegalArgumentException("The attribute [" + attributeName + "] does not exist or can't be bound! Allowed attributes are: " + set); + } + + Integer attributeBindIndex = setAttributeBindingMap.get(attributeName); - if (attributeExpression != null) { + if (attributeBindIndex != null) { throw new IllegalArgumentException("The attribute [" + attributeName + "] has already been bound!"); } - return attributeName; + + setAttributeBindingMap.put(attributeName, selectManager.getSelectInfos().size()); } @Override @@ -135,8 +156,20 @@ protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean external cachedBaseQueryStrings = new ArrayList<>(); StringBuilder sbSetExpressionQuery = new StringBuilder(); - for (Map.Entry attributeEntry : setAttributes.entrySet()) { - fillCachedBaseQueryStrings(sbSetExpressionQuery, attributeEntry.getKey(), attributeEntry.getValue()); + List selectInfos = selectManager.getSelectInfos(); + for (Map.Entry attributeEntry : setAttributeBindingMap.entrySet()) { + String expression = attributeEntry.getKey(); + int collectionIndex = expression.indexOf(collectionName); + if (collectionIndex == -1) { + expression = entityAlias + '.' + expression; + } else { + StringBuilder sb = new StringBuilder(); + sb.append(expression, 0, collectionIndex); + sb.append(CollectionUpdateModificationQuerySpecification.COLLECTION_BASE_QUERY_ALIAS); + sb.append(expression, collectionIndex + collectionName.length(), expression.length()); + expression = sb.toString(); + } + fillCachedBaseQueryStrings(sbSetExpressionQuery, expression, selectInfos.get(attributeEntry.getValue()).getExpression()); } } } @@ -171,6 +204,16 @@ protected boolean appendSetElementEntityPrefix(String trimmedPath) { return !trimmedPath.startsWith(CollectionUpdateModificationQuerySpecification.COLLECTION_BASE_QUERY_ALIAS) && super.appendSetElementEntityPrefix(trimmedPath); } + @Override + protected void prepareAndCheck() { + if (!needsCheck) { + return; + } + + JpaUtils.expandBindings(entityType, setAttributeBindingMap, collectionColumnBindingMap, collectionAttributeEntries, ClauseType.SET, this); + super.prepareAndCheck(); + } + @Override protected Query getQuery(Map includedModificationStates) { Query baseQuery = em.createQuery(getBaseQueryStringWithCheck()); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/AssociationFromIdParameterTransformer.java b/core/impl/src/main/java/com/blazebit/persistence/impl/AssociationFromIdParameterTransformer.java index d185703115..082ac2f2b6 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/AssociationFromIdParameterTransformer.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/AssociationFromIdParameterTransformer.java @@ -18,6 +18,7 @@ import com.blazebit.reflection.ReflectionUtils; +import javax.persistence.Query; import javax.persistence.metamodel.Attribute; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -78,6 +79,11 @@ public static AssociationFromIdParameterTransformer getInstance(Class associa return transformer; } + @Override + public ParameterValueTransformer forQuery(Query query) { + return this; + } + @Override public Object transform(Object originalValue) { try { diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/AssociationToIdParameterTransformer.java b/core/impl/src/main/java/com/blazebit/persistence/impl/AssociationToIdParameterTransformer.java index d76b02d7c1..c76b98d439 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/AssociationToIdParameterTransformer.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/AssociationToIdParameterTransformer.java @@ -18,6 +18,8 @@ import com.blazebit.persistence.spi.JpaProvider; +import javax.persistence.Query; + /** * @author Christian Beikov * @since 1.2.0 @@ -30,6 +32,11 @@ public AssociationToIdParameterTransformer(JpaProvider jpaProvider) { this.jpaProvider = jpaProvider; } + @Override + public ParameterValueTransformer forQuery(Query query) { + return this; + } + @Override public Object transform(Object originalValue) { return jpaProvider.getIdentifier(originalValue); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/BaseInsertCriteriaBuilderImpl.java b/core/impl/src/main/java/com/blazebit/persistence/impl/BaseInsertCriteriaBuilderImpl.java index bebbce062b..e516d09ca9 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/BaseInsertCriteriaBuilderImpl.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/BaseInsertCriteriaBuilderImpl.java @@ -44,7 +44,7 @@ */ public abstract class BaseInsertCriteriaBuilderImpl, Y> extends AbstractModificationCriteriaBuilder implements BaseInsertCriteriaBuilder, SelectBuilder { - protected final Map bindingMap = new TreeMap(); + protected final Map bindingMap = new TreeMap<>(); public BaseInsertCriteriaBuilderImpl(MainQuery mainQuery, QueryContext queryContext, boolean isMainQuery, Class clazz, String cteName, Class cteClass, Y result, CTEBuilderListener listener) { super(mainQuery, queryContext, isMainQuery, DbmsStatementType.INSERT, clazz, null, cteName, cteClass, result, listener); @@ -60,8 +60,8 @@ public BaseInsertCriteriaBuilderImpl(BaseInsertCriteriaBuilderImpl buil } @Override - protected void appendSelectClause(StringBuilder sbSelectFrom) { - selectManager.buildSelect(sbSelectFrom, true); + protected void appendSelectClause(StringBuilder sbSelectFrom, boolean externalRepresentation) { + selectManager.buildSelect(sbSelectFrom, true, externalRepresentation); } @Override @@ -97,6 +97,9 @@ protected void addBind(String attributeName) { @Override protected void prepareAndCheck() { + if (!needsCheck) { + return; + } List attributes = new ArrayList(bindingMap.size()); List originalSelectInfos = new ArrayList(selectManager.getSelectInfos()); List newSelectInfos = selectManager.getSelectInfos(); @@ -111,9 +114,13 @@ protected void prepareAndCheck() { newSelectInfos.add(selectInfo); attributeEntry.setValue(newPosition); } + expandBindings(); super.prepareAndCheck(); } + protected void expandBindings() { + } + @Override protected boolean isJoinRequiredForSelect() { // NOTE: since we aren't actually selecting properties but passing them through to the insert, we don't require joins diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/BaseUpdateCriteriaBuilderImpl.java b/core/impl/src/main/java/com/blazebit/persistence/impl/BaseUpdateCriteriaBuilderImpl.java index 521c14bcec..920e3dd618 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/BaseUpdateCriteriaBuilderImpl.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/BaseUpdateCriteriaBuilderImpl.java @@ -26,12 +26,12 @@ import com.blazebit.persistence.parser.SimpleQueryGenerator; import com.blazebit.persistence.parser.expression.Expression; import com.blazebit.persistence.parser.expression.SubqueryExpression; -import com.blazebit.persistence.parser.expression.VisitorAdapter; import com.blazebit.persistence.spi.DbmsStatementType; import com.blazebit.persistence.spi.JpaMetamodelAccessor; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -43,7 +43,7 @@ */ public abstract class BaseUpdateCriteriaBuilderImpl, Y> extends AbstractModificationCriteriaBuilder implements BaseUpdateCriteriaBuilder, SubqueryBuilderListener, ExpressionBuilderEndedListener { - protected final Map setAttributes = new LinkedHashMap<>(); + protected final Map setAttributeBindingMap = new LinkedHashMap<>(); private SubqueryInternalBuilder currentSubqueryBuilder; private String currentAttribute; @@ -54,8 +54,8 @@ public BaseUpdateCriteriaBuilderImpl(MainQuery mainQuery, QueryContext queryCont public BaseUpdateCriteriaBuilderImpl(BaseUpdateCriteriaBuilderImpl builder, MainQuery mainQuery, QueryContext queryContext) { super(builder, mainQuery, queryContext); builder.verifyBuilderEnded(); - for (Entry entry : builder.setAttributes.entrySet()) { - this.setAttributes.put(entityType.getName(), entry.getValue().clone(false)); + for (Entry entry : builder.setAttributeBindingMap.entrySet()) { + this.setAttributeBindingMap.put(entityType.getName(), entry.getValue()); } } @@ -67,9 +67,7 @@ protected void collectParameters() { try { parameterRegistrationVisitor.setQueryBuilder(this); parameterRegistrationVisitor.setClauseType(ClauseType.SET); - for (Expression value : setAttributes.values()) { - value.accept(parameterRegistrationVisitor); - } + selectManager.acceptVisitor(parameterRegistrationVisitor); parameterRegistrationVisitor.setClauseType(ClauseType.WHERE); whereManager.acceptVisitor(parameterRegistrationVisitor); } finally { @@ -78,21 +76,13 @@ protected void collectParameters() { } } - @Override - protected void applyVisitor(VisitorAdapter expressionVisitor) { - for (Expression value : setAttributes.values()) { - value.accept(expressionVisitor); - } - super.applyVisitor(expressionVisitor); - } - @Override @SuppressWarnings("unchecked") public X set(String attributeName, Object value) { verifyBuilderEnded(); - attributeName = checkAttribute(attributeName); + addAttribute(attributeName); Expression attributeExpression = parameterManager.addParameterExpression(value, ClauseType.SET, this); - setAttributes.put(attributeName, attributeExpression); + selectManager.select(attributeExpression, null); return (X) this; } @@ -100,10 +90,10 @@ public X set(String attributeName, Object value) { @SuppressWarnings("unchecked") public X setExpression(String attributeName, String expression) { verifyBuilderEnded(); - attributeName = checkAttribute(attributeName); + addAttribute(attributeName); Expression attributeExpression = expressionFactory.createScalarExpression(expression); parameterManager.collectParameterRegistrations(attributeExpression, ClauseType.SET, this); - setAttributes.put(attributeName, attributeExpression); + selectManager.select(attributeExpression, null); return (X) this; } @@ -111,7 +101,7 @@ public X setExpression(String attributeName, String expression) { @Override public SubqueryInitiator set(String attribute) { verifySubqueryBuilderEnded(); - attribute = checkAttribute(attribute); + addAttribute(attribute); this.currentAttribute = attribute; return subqueryInitFactory.createSubqueryInitiator((X) this, this, false, ClauseType.SET); } @@ -120,7 +110,7 @@ public SubqueryInitiator set(String attribute) { @Override public MultipleSubqueryInitiator setSubqueries(String attribute, String expression) { verifySubqueryBuilderEnded(); - attribute = checkAttribute(attribute); + addAttribute(attribute); this.currentAttribute = attribute; Expression expr = expressionFactory.createSimpleExpression(expression, true); MultipleSubqueryInitiator initiator = new MultipleSubqueryInitiatorImpl((X) this, expr, this, subqueryInitFactory, ClauseType.SET); @@ -130,7 +120,7 @@ public MultipleSubqueryInitiator setSubqueries(String attribute, String expre @Override public SubqueryBuilder set(String attribute, FullQueryBuilder criteriaBuilder) { verifySubqueryBuilderEnded(); - attribute = checkAttribute(attribute); + addAttribute(attribute); this.currentAttribute = attribute; return subqueryInitFactory.createSubqueryBuilder((X) this, this, false, criteriaBuilder, ClauseType.SET); } @@ -152,7 +142,7 @@ public void onBuilderEnded(ExpressionBuilder builder) { Expression attributeExpression = builder.getExpression(); parameterManager.collectParameterRegistrations(attributeExpression, ClauseType.SET, this); - setAttributes.put(currentAttribute, attributeExpression); + selectManager.select(attributeExpression, null); } @Override @@ -165,7 +155,7 @@ public void onBuilderEnded(SubqueryInternalBuilder builder) { } Expression attributeExpression = new SubqueryExpression(builder); parameterManager.collectParameterRegistrations(attributeExpression, ClauseType.SET, this); - setAttributes.put(currentAttribute, attributeExpression); + selectManager.select(attributeExpression, null); currentAttribute = null; currentSubqueryBuilder = null; } @@ -191,16 +181,17 @@ public void onInitiatorStarted(SubqueryInitiator initiator) { throw new IllegalArgumentException("Initiator started not valid!"); } - protected String checkAttribute(String attributeName) { + protected void addAttribute(String attributeName) { // Just do that to assert the attribute exists JpaMetamodelAccessor jpaMetamodelAccessor = mainQuery.jpaProvider.getJpaMetamodelAccessor(); jpaMetamodelAccessor.getBasicAttributePath(getMetamodel(), entityType, attributeName); - Expression attributeExpression = setAttributes.get(attributeName); - - if (attributeExpression != null) { + Integer attributeBindIndex = setAttributeBindingMap.get(attributeName); + + if (attributeBindIndex != null) { throw new IllegalArgumentException("The attribute [" + attributeName + "] has already been bound!"); } - return attributeName; + + setAttributeBindingMap.put(attributeName, selectManager.getSelectInfos().size()); } @Override @@ -219,14 +210,15 @@ protected void appendSetClause(StringBuilder sbSelectFrom) { queryGenerator.setQueryBuffer(sbSelectFrom); SimpleQueryGenerator.BooleanLiteralRenderingContext oldBooleanLiteralRenderingContext = queryGenerator.setBooleanLiteralRenderingContext(SimpleQueryGenerator.BooleanLiteralRenderingContext.CASE_WHEN); - Iterator> setAttributeIter = setAttributes.entrySet().iterator(); + List selectInfos = selectManager.getSelectInfos(); + Iterator> setAttributeIter = setAttributeBindingMap.entrySet().iterator(); if (setAttributeIter.hasNext()) { - Map.Entry attributeEntry = setAttributeIter.next(); - appendSetElement(sbSelectFrom, attributeEntry.getKey(), attributeEntry.getValue()); + Map.Entry attributeEntry = setAttributeIter.next(); + appendSetElement(sbSelectFrom, attributeEntry.getKey(), selectInfos.get(attributeEntry.getValue()).getExpression()); while (setAttributeIter.hasNext()) { attributeEntry = setAttributeIter.next(); sbSelectFrom.append(','); - appendSetElement(sbSelectFrom, attributeEntry.getKey(), attributeEntry.getValue()); + appendSetElement(sbSelectFrom, attributeEntry.getKey(), selectInfos.get(attributeEntry.getValue()).getExpression()); } } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/CachingJpaProvider.java b/core/impl/src/main/java/com/blazebit/persistence/impl/CachingJpaProvider.java index 40201edba2..2130a89f5d 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/CachingJpaProvider.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/CachingJpaProvider.java @@ -77,11 +77,21 @@ public String[] getColumnNames(EntityType ownerType, String attributeName) { return entityMetamodel.getManagedType(ExtendedManagedType.class, ownerType.getName()).getAttribute(attributeName).getColumnNames(); } + @Override + public String[] getColumnNames(EntityType ownerType, String elementCollectionPath, String attributeName) { + return getColumnNames(ownerType, attributeName); + } + @Override public String[] getColumnTypes(EntityType ownerType, String attributeName) { return entityMetamodel.getManagedType(ExtendedManagedType.class, ownerType.getName()).getAttribute(attributeName).getColumnTypes(); } + @Override + public String[] getColumnTypes(EntityType ownerType, String elementCollectionPath, String attributeName) { + return getColumnTypes(ownerType, attributeName); + } + @Override public Map getWritableMappedByMappings(EntityType inverseType, EntityType ownerType, String attributeName) { return entityMetamodel.getManagedType(ExtendedManagedType.class, ownerType.getName()).getAttribute(attributeName).getWritableMappedByMappings(inverseType); @@ -110,6 +120,11 @@ public boolean isOrphanRemoval(ManagedType ownerType, String attributeName) { return attribute != null && attribute.isOrphanRemoval(); } + @Override + public boolean isOrphanRemoval(ManagedType ownerType, String elementCollectionPath, String attributeName) { + return isOrphanRemoval(ownerType, attributeName); + } + @Override public boolean isDeleteCascaded(ManagedType ownerType, String attributeName) { ExtendedManagedType managedType; @@ -122,6 +137,10 @@ public boolean isDeleteCascaded(ManagedType ownerType, String attributeName) return attribute != null && attribute.isDeleteCascaded(); } + @Override + public boolean isDeleteCascaded(ManagedType ownerType, String elementCollectionPath, String attributeName) { + return isDeleteCascaded(ownerType, attributeName); + } // Simple delegates @Override @@ -295,8 +314,13 @@ public boolean supportsJoinTableCleanupOnDelete() { } @Override - public boolean supportsJoinElementCollectionsOnCorrelatedInverseAssociations() { - return jpaProvider.supportsJoinElementCollectionsOnCorrelatedInverseAssociations(); + public boolean needsCorrelationPredicateWhenCorrelatingWithWhereClause() { + return jpaProvider.needsCorrelationPredicateWhenCorrelatingWithWhereClause(); + } + + @Override + public boolean supportsSingleValuedAssociationNaturalIdExpressions() { + return jpaProvider.supportsSingleValuedAssociationNaturalIdExpressions(); } @Override @@ -306,9 +330,16 @@ public void setCacheable(Query query) { @Override public List getIdentifierOrUniqueKeyEmbeddedPropertyNames(EntityType owner, String attributeName) { + // TODO: cache this via extended metamodel return jpaProvider.getIdentifierOrUniqueKeyEmbeddedPropertyNames(owner, attributeName); } + @Override + public List getIdentifierOrUniqueKeyEmbeddedPropertyNames(EntityType owner, String elementCollectionPath, String attributeName) { + // TODO: cache this via extended metamodel + return jpaProvider.getIdentifierOrUniqueKeyEmbeddedPropertyNames(owner, elementCollectionPath, attributeName); + } + @Override public Object getIdentifier(Object entity) { return jpaProvider.getIdentifier(entity); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/EntityMetamodelImpl.java b/core/impl/src/main/java/com/blazebit/persistence/impl/EntityMetamodelImpl.java index ed544194ac..afe3757a50 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/EntityMetamodelImpl.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/EntityMetamodelImpl.java @@ -40,6 +40,7 @@ import javax.persistence.metamodel.PluralAttribute; import javax.persistence.metamodel.SingularAttribute; import javax.persistence.metamodel.Type; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -110,42 +111,16 @@ public EntityMetamodelImpl(EntityManagerFactory emf, JpaProviderFactory jpaProvi Map> attributeMap = new HashMap<>(attributes.size()); TemporaryExtendedManagedType extendedManagedType = new TemporaryExtendedManagedType(e, attributeMap); temporaryExtendedManagedTypes.put(e.getName(), extendedManagedType); - - for (Attribute attribute : attributes) { - List> parents = Collections.>singletonList(attribute); - Class fieldType = JpaMetamodelUtils.resolveFieldClass(e.getJavaType(), attribute); - if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED) { - EmbeddableType embeddableType; - // Hibernate Envers reports java.util.Map as type for the embedded id of an audited entity which we have to handle specially - if (fieldType == Map.class) { - embeddableType = (EmbeddableType) ((SingularAttribute) attribute).getType(); - } else { - embeddableType = delegate.embeddable(fieldType); - } - collectColumnNames(e, attributeMap, attribute.getName(), parents, embeddableType, temporaryExtendedManagedTypes); - } else if (isAssociation(attribute) && !attribute.isCollection() && !jpaProvider.isForeignJoinColumn(e, attribute.getName())) { - collectIdColumns(attribute.getName(), e, e, attributeMap, attribute, fieldType); - } - - attributeMap.put(attribute.getName(), new AttributeEntry(jpaProvider, e, attribute, attribute.getName(), fieldType, parents)); - discoverEnumTypes(seenTypesForEnumResolving, enumTypes, e.getJavaType(), attribute); + if (e.getJavaType() != null) { + temporaryExtendedManagedTypes.put(e.getJavaType().getName(), extendedManagedType); } + collectColumnNames(e, attributeMap, null, null, null, e, temporaryExtendedManagedTypes, seenTypesForEnumResolving, enumTypes); } for (ManagedType t : managedTypes) { // we already checked all entity types, so skip these if (!(t instanceof EntityType)) { - TemporaryExtendedManagedType extendedManagedType = temporaryExtendedManagedTypes.get(t.getJavaType().getName()); - if (extendedManagedType == null) { - Set> attributes = (Set>) (Set) t.getAttributes(); - Map> attributeMap = new HashMap<>(attributes.size()); - for (Attribute attribute : attributes) { - Class attributeType = JpaMetamodelUtils.resolveFieldClass(t.getJavaType(), attribute); - attributeMap.put(attribute.getName(), new AttributeEntry(jpaProvider, t, attribute, attribute.getName(), attributeType, Collections.singletonList(attribute))); - } - extendedManagedType = new TemporaryExtendedManagedType(t, attributeMap); - temporaryExtendedManagedTypes.put(t.getJavaType().getName(), extendedManagedType); - } + collectColumnNames(null, null, null, null, null, t, temporaryExtendedManagedTypes, seenTypesForEnumResolving, enumTypes); if (t.getJavaType() != null) { classToType.put(t.getJavaType(), t); } @@ -173,7 +148,7 @@ public EntityMetamodelImpl(EntityManagerFactory emf, JpaProviderFactory jpaProvi Map> extendedManagedTypes = new HashMap<>(temporaryExtendedManagedTypes.size()); for (Map.Entry entry : temporaryExtendedManagedTypes.entrySet()) { TemporaryExtendedManagedType value = entry.getValue(); - ExtendedManagedTypeImpl extendedManagedType = new ExtendedManagedTypeImpl(value.managedType, value.cascadingDeleteCycle, initAttributes(value.attributes)); + ExtendedManagedTypeImpl extendedManagedType = new ExtendedManagedTypeImpl(value.managedType, value.singularOwnerType, value.pluralOwnerType, value.cascadingDeleteCycle, initAttributes(value.attributes)); extendedManagedTypes.put(entry.getKey(), extendedManagedType); if (value.managedType.getJavaType() != null) { extendedManagedTypes.put(value.managedType.getJavaType(), extendedManagedType); @@ -196,22 +171,6 @@ private Map> initAttributes(Map ownerType, EntityType currentType, Map> attributeMap, Attribute attribute, Class fieldType) { - List identifierOrUniqueKeyEmbeddedPropertyNames = jpaProvider.getIdentifierOrUniqueKeyEmbeddedPropertyNames(currentType, attribute.getName()); - - for (String name : identifierOrUniqueKeyEmbeddedPropertyNames) { - EntityType fieldEntityType = delegate.entity(fieldType); - Attribute idAttribute = JpaMetamodelUtils.getAttribute(fieldEntityType, name); - Class idType = JpaMetamodelUtils.resolveFieldClass(fieldType, idAttribute); - String idPath = prefix + "." + name; - attributeMap.put(idPath, new AttributeEntry(jpaProvider, ownerType, idAttribute, idPath, idType, Arrays.asList(attribute, idAttribute))); - - if (isAssociation(idAttribute) && !idAttribute.isCollection() && !jpaProvider.isForeignJoinColumn(ownerType, idAttribute.getName())) { - collectIdColumns(idPath, ownerType, fieldEntityType, attributeMap, idAttribute, idType); - } - } - } - private void detectCascadingDeleteCycles(TemporaryExtendedManagedType ownerManagedType, Map extendedManagedTypes, Set> cascadingDeleteCycleSet, Class ownerClass, AttributeEntry attributeEntry, Map, Type> classToType) { Class targetClass = attributeEntry.getElementClass(); Type type = classToType.get(targetClass); @@ -303,7 +262,7 @@ private void discoverEnumTypes(Set> seenTypesForEnumResolving, Map e, Map> attributeMap, String parent, List> parents, EmbeddableType type, Map temporaryExtendedManagedTypes) { + private TemporaryExtendedManagedType collectColumnNames(EntityType e, Map> attributeMap, String parent, List> parents, String elementCollectionPath, ManagedType type, Map temporaryExtendedManagedTypes, Set> seenTypesForEnumResolving, Map>> enumTypes) { Set> attributes = (Set>) (Set) type.getAttributes(); TemporaryExtendedManagedType extendedManagedType = temporaryExtendedManagedTypes.get(type.getJavaType().getName()); if (extendedManagedType == null) { @@ -311,33 +270,183 @@ private void collectColumnNames(EntityType e, Map parentAttribute = parents.get(parents.size() - 1); + if (parentAttribute.isCollection()) { + if (extendedManagedType.pluralOwnerType == null) { + extendedManagedType.pluralOwnerType = new AbstractMap.SimpleEntry(e, parent); + } + } else if (elementCollectionPath == null && (extendedManagedType.singularOwnerType == null || shouldReplaceOwner(parent, extendedManagedType.singularOwnerType.getValue()))) { + extendedManagedType.singularOwnerType = new AbstractMap.SimpleEntry(e, parent); + } + } + + final Map> managedTypeAttributes = extendedManagedType.attributes; + for (Attribute attribute : attributes) { - ArrayList> newParents = new ArrayList<>(parents.size() + 1); - newParents.addAll(parents); - newParents.add(attribute); - String attributeName = parent + "." + attribute.getName(); + List> newParents; + String attributeName; Class fieldType = JpaMetamodelUtils.resolveFieldClass(type.getJavaType(), attribute); - if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED) { - collectColumnNames(e, attributeMap, attributeName, newParents, delegate.embeddable(fieldType), temporaryExtendedManagedTypes); - } else if (isAssociation(attribute) && !attribute.isCollection() && !jpaProvider.isForeignJoinColumn(e, attributeName)) { - // We create an attribute entry for the id attribute of *ToOne relations if the columns reside on the Many side - List identifierOrUniqueKeyEmbeddedPropertyNames = jpaProvider.getIdentifierOrUniqueKeyEmbeddedPropertyNames(e, attributeName); - - for (String name : identifierOrUniqueKeyEmbeddedPropertyNames) { + if (e == null) { + attributeName = attribute.getName(); + newParents = Collections.>singletonList(attribute); + } else { + if (parent == null) { + attributeName = attribute.getName(); + newParents = Collections.>singletonList(attribute); + } else { + attributeName = parent + "." + attribute.getName(); + newParents = new ArrayList<>(parents.size() + 1); + newParents.addAll(parents); + newParents.add(attribute); + } + if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED) { + EmbeddableType embeddableType; + // Hibernate Envers reports java.util.Map as type for the embedded id of an audited entity which we have to handle specially + if (fieldType == Map.class) { + embeddableType = (EmbeddableType) ((SingularAttribute) attribute).getType(); + } else { + embeddableType = delegate.embeddable(fieldType); + } + TemporaryExtendedManagedType extendedEmbeddableType = collectColumnNames(e, attributeMap, attributeName, newParents, elementCollectionPath, embeddableType, temporaryExtendedManagedTypes, seenTypesForEnumResolving, enumTypes); + if (elementCollectionPath == null && (extendedEmbeddableType.singularOwnerType == null || shouldReplaceOwner(attributeName, extendedEmbeddableType.singularOwnerType.getValue()))) { + extendedEmbeddableType.singularOwnerType = new AbstractMap.SimpleEntry(e, attributeName); + } + } else if (attribute.isCollection()) { + if (((PluralAttribute) attribute).getElementType() instanceof EmbeddableType) { + EmbeddableType embeddableType; + // Hibernate Envers reports java.util.Map as type for the embedded id of an audited entity which we have to handle specially + if (fieldType == Map.class) { + embeddableType = (EmbeddableType) ((SingularAttribute) attribute).getType(); + } else { + embeddableType = delegate.embeddable(fieldType); + } + TemporaryExtendedManagedType extendedEmbeddableType = collectColumnNames(e, attributeMap, attributeName, newParents, attributeName, embeddableType, temporaryExtendedManagedTypes, seenTypesForEnumResolving, enumTypes); + if (extendedEmbeddableType.pluralOwnerType == null) { + extendedEmbeddableType.pluralOwnerType = new AbstractMap.SimpleEntry(e, attributeName); + } + } + // If this attribute is part of an element collection, we assume there are no inverse one-to-ones + } else if (isAssociation(attribute) && (elementCollectionPath != null || !jpaProvider.isForeignJoinColumn(e, attributeName))) { + // We create an attribute entry for the id attribute of *ToOne relations if the columns reside on the Many side + List identifierOrUniqueKeyEmbeddedPropertyNames; + if (elementCollectionPath == null) { + identifierOrUniqueKeyEmbeddedPropertyNames = jpaProvider.getIdentifierOrUniqueKeyEmbeddedPropertyNames(e, attributeName); + } else { + identifierOrUniqueKeyEmbeddedPropertyNames = jpaProvider.getIdentifierOrUniqueKeyEmbeddedPropertyNames(e, elementCollectionPath, attributeName); + } EntityType fieldEntityType = delegate.entity(fieldType); - Attribute idAttribute = JpaMetamodelUtils.getAttribute(fieldEntityType, name); - Class idType = JpaMetamodelUtils.resolveFieldClass(fieldType, idAttribute); - String idPath = attributeName + "." + name; - ArrayList> idParents = new ArrayList<>(newParents.size() + 1); - idParents.addAll(newParents); - idParents.add(idAttribute); - attributeMap.put(idPath, new AttributeEntry(jpaProvider, e, idAttribute, idPath, idType, idParents)); + + if (jpaProvider.supportsSingleValuedAssociationNaturalIdExpressions() || isSame(jpaProvider, fieldEntityType, JpaMetamodelUtils.getIdAttributes(fieldEntityType), identifierOrUniqueKeyEmbeddedPropertyNames)) { + for (String name : identifierOrUniqueKeyEmbeddedPropertyNames) { + Attribute idAttribute = JpaMetamodelUtils.getAttribute(fieldEntityType, name); + Class idType = JpaMetamodelUtils.resolveFieldClass(fieldType, idAttribute); + String idPath = attributeName + "." + name; + ArrayList> idParents = new ArrayList<>(newParents.size() + 1); + idParents.addAll(newParents); + idParents.add(idAttribute); + AttributeEntry attributeEntry = new AttributeEntry(jpaProvider, e, idAttribute, idPath, idType, idParents, elementCollectionPath); + attributeMap.put(idPath, attributeEntry); + managedTypeAttributes.put(attribute.getName() + "." + name, attributeEntry); + } + } } } - AttributeEntry attributeEntry = new AttributeEntry(jpaProvider, e, attribute, attributeName, fieldType, newParents); - attributeMap.put(attributeName, attributeEntry); - extendedManagedType.attributes.put(attribute.getName(), attributeEntry); + discoverEnumTypes(seenTypesForEnumResolving, enumTypes, type.getJavaType(), attribute); + AttributeEntry attributeEntry = null; + if (e == null) { + // Never overwrite an existing attribute with one that has no owner + if (!managedTypeAttributes.containsKey(attribute.getName())) { + attributeEntry = new AttributeEntry(jpaProvider, type, attribute, attributeName, fieldType, newParents, elementCollectionPath); + managedTypeAttributes.put(attribute.getName(), attributeEntry); + } + } else { + attributeEntry = new AttributeEntry(jpaProvider, e, attribute, attributeName, fieldType, newParents, elementCollectionPath); + attributeMap.put(attributeName, attributeEntry); + managedTypeAttributes.put(attribute.getName(), attributeEntry); + } + + if (attributeEntry != null && attributeEntry.joinTable != null && attributeEntry.joinTable.getTargetAttributeNames() != null) { + // Initialize the extended attributes for join table attributes as well upon which the collection DML implementation builds + for (String targetAttributeName : attributeEntry.joinTable.getTargetAttributeNames()) { + String subAttributeName = attributeName; + ManagedType subType = delegate.managedType(attributeEntry.getElementClass()); + + for (String attributePart : targetAttributeName.split("\\.")) { + subAttributeName += "." + attributePart; + Attribute subAttribute = JpaMetamodelUtils.getAttribute(subType, attributePart); + fieldType = JpaMetamodelUtils.resolveFieldClass(subType.getJavaType(), subAttribute); + List> subParents = new ArrayList<>(newParents.size() + 1); + subParents.addAll(newParents); + subParents.add(subAttribute); + + AttributeEntry subAttributeEntry = new AttributeEntry(jpaProvider, e, subAttribute, subAttributeName, fieldType, subParents, attributeName); + if (e != null) { + attributeMap.put(subAttributeName, subAttributeEntry); + } + managedTypeAttributes.put(attribute.getName() + "." + targetAttributeName, subAttributeEntry); + + if (subAttribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.BASIC) { + subType = null; + } else { + subType = delegate.managedType(fieldType); + } + } + + } + } + } + + return extendedManagedType; + } + + private boolean shouldReplaceOwner(String attributeName, String existingAttributeName) { + // We prefer less nested attributes, after that we prefer attributes with smaller name lengths + int dotCount = 0; + for (int i = 0; i < attributeName.length(); i++) { + if (attributeName.charAt(i) == '.') { + dotCount++; + } + } + int existingDotCount = 0; + for (int i = 0; i < existingAttributeName.length(); i++) { + if (existingAttributeName.charAt(i) == '.') { + existingDotCount++; + } + } + if (dotCount == existingDotCount) { + return attributeName.length() < existingAttributeName.length(); + } + return dotCount < existingDotCount; + } + + private static boolean isSame(JpaProvider jpaProvider, ManagedType type, Set> idAttributes, List identifierOrUniqueKeyEmbeddedPropertyNames) { + List expectedIdAttributeNames = new ArrayList<>(identifierOrUniqueKeyEmbeddedPropertyNames.size()); + for (SingularAttribute idAttribute : idAttributes) { + collectIdAttributePaths(jpaProvider, expectedIdAttributeNames, type, null, idAttribute); + } + + return expectedIdAttributeNames.size() == identifierOrUniqueKeyEmbeddedPropertyNames.size() && expectedIdAttributeNames.containsAll(identifierOrUniqueKeyEmbeddedPropertyNames); + } + + public static void collectIdAttributePaths(JpaProvider jpaProvider, List collectionIdAttributes, ManagedType type, String prefix, Attribute attribute) { + if (attribute instanceof SingularAttribute) { + Type attributeType = ((SingularAttribute) attribute).getType(); + if (attributeType instanceof EmbeddableType) { + for (Attribute subAttribute : ((EmbeddableType) attributeType).getAttributes()) { + collectIdAttributePaths(jpaProvider, collectionIdAttributes, type, attribute.getName() + ".", subAttribute); + } + } else if (attributeType instanceof IdentifiableType) { + String path = prefix == null ? attribute.getName() : prefix + attribute.getName(); + List idAttributeNames = jpaProvider.getIdentifierOrUniqueKeyEmbeddedPropertyNames((EntityType) type, path); + for (String idAttributeName : idAttributeNames) { + collectionIdAttributes.add(path + "." + idAttributeName); + } + } else { + String path = prefix == null ? attribute.getName() : prefix + attribute.getName(); + collectionIdAttributes.add(path); + } } } @@ -506,6 +615,8 @@ private ExtendedManagedType getEntry(String managedTypeName) { private static final class TemporaryExtendedManagedType { private final ManagedType managedType; private final Map> attributes; + private Map.Entry, String> singularOwnerType; + private Map.Entry, String> pluralOwnerType; private boolean done; private boolean cascadingDeleteCycle; @@ -521,13 +632,17 @@ private TemporaryExtendedManagedType(ManagedType managedType, Map implements ExtendedManagedType { private final ManagedType managedType; + private final Map.Entry, String> singularOwnerType; + private final Map.Entry, String> pluralOwnerType; private final boolean hasCascadingDeleteCycle; private final Set> idAttributes; private final Map> attributes; @SuppressWarnings("unchecked") - private ExtendedManagedTypeImpl(ManagedType managedType, boolean hasCascadingDeleteCycle, Map> attributes) { + private ExtendedManagedTypeImpl(ManagedType managedType, Map.Entry, String> singularOwnerType, Map.Entry, String> pluralOwnerType, boolean hasCascadingDeleteCycle, Map> attributes) { this.managedType = managedType; + this.singularOwnerType = singularOwnerType; + this.pluralOwnerType = pluralOwnerType; if (JpaMetamodelUtils.isIdentifiable(managedType)) { this.idAttributes = (Set>) (Set) JpaMetamodelUtils.getIdAttributes((IdentifiableType) managedType); } else { @@ -542,6 +657,16 @@ public ManagedType getType() { return managedType; } + @Override + public Map.Entry, String> getEmbeddableSingularOwner() { + return singularOwnerType; + } + + @Override + public Map.Entry, String> getEmbeddablePluralOwner() { + return pluralOwnerType; + } + @Override public boolean hasCascadingDeleteCycle() { return hasCascadingDeleteCycle; @@ -594,6 +719,7 @@ private static final class AttributeEntry implements ExtendedAttribute ownerType; private final Attribute attribute; private final List> attributePath; + private final String attributePathString; private final Class elementClass; private final boolean hasCascadeDeleteCycle; private final boolean isForeignJoinColumn; @@ -609,29 +735,48 @@ private static final class AttributeEntry implements ExtendedAttribute, Map> inverseAttributeCache = new ConcurrentHashMap<>(); private final Set> columnEquivalentAttributes = new HashSet<>(); - public AttributeEntry(JpaProvider jpaProvider, ManagedType ownerType, Attribute attribute, String attributeName, Class fieldType, List> parents) { + public AttributeEntry(JpaProvider jpaProvider, ManagedType ownerType, Attribute attribute, String attributeName, Class fieldType, List> parents, String elementCollectionPath) { this.jpaProvider = jpaProvider; this.ownerType = ownerType; this.attribute = attribute; this.attributePath = Collections.unmodifiableList(parents); + this.attributePathString = attributeName; this.elementClass = fieldType; - this.isOrphanRemoval = jpaProvider.isOrphanRemoval(ownerType, attributeName); - this.isDeleteCascaded = jpaProvider.isDeleteCascaded(ownerType, attributeName); + if (elementCollectionPath == null) { + this.isOrphanRemoval = jpaProvider.isOrphanRemoval(ownerType, attributeName); + this.isDeleteCascaded = jpaProvider.isDeleteCascaded(ownerType, attributeName); + } else { + this.isOrphanRemoval = jpaProvider.isOrphanRemoval(ownerType, elementCollectionPath, attributeName); + this.isDeleteCascaded = jpaProvider.isDeleteCascaded(ownerType, elementCollectionPath, attributeName); + } this.hasCascadeDeleteCycle = false; JoinType[] joinTypes = JoinType.values(); JpaProvider.ConstraintType[] requiresTreatFilter = new JpaProvider.ConstraintType[joinTypes.length]; if (ownerType instanceof EntityType) { EntityType entityType = (EntityType) ownerType; - this.isForeignJoinColumn = jpaProvider.isForeignJoinColumn(entityType, attributeName); - this.isColumnShared = jpaProvider.isColumnShared(entityType, attributeName); - this.isBag = jpaProvider.isBag(entityType, attributeName); - this.mappedBy = jpaProvider.getMappedBy(entityType, attributeName); - this.joinTable = jpaProvider.getJoinTable(entityType, attributeName); - this.columnNames = jpaProvider.getColumnNames(entityType, attributeName); - this.columnTypes = jpaProvider.getColumnTypes(entityType, attributeName); - for (JoinType joinType : joinTypes) { - requiresTreatFilter[joinType.ordinal()] = jpaProvider.requiresTreatFilter(entityType, attributeName, joinType); + if (elementCollectionPath == null) { + this.isForeignJoinColumn = jpaProvider.isForeignJoinColumn(entityType, attributeName); + this.isColumnShared = jpaProvider.isColumnShared(entityType, attributeName); + this.isBag = jpaProvider.isBag(entityType, attributeName); + this.mappedBy = jpaProvider.getMappedBy(entityType, attributeName); + this.joinTable = jpaProvider.getJoinTable(entityType, attributeName); + this.columnNames = jpaProvider.getColumnNames(entityType, attributeName); + this.columnTypes = jpaProvider.getColumnTypes(entityType, attributeName); + for (JoinType joinType : joinTypes) { + requiresTreatFilter[joinType.ordinal()] = jpaProvider.requiresTreatFilter(entityType, attributeName, joinType); + } + } else { + this.isForeignJoinColumn = false; + this.isColumnShared = false; + this.isBag = false; + this.mappedBy = null; + this.joinTable = null; + this.columnNames = jpaProvider.getColumnNames(entityType, elementCollectionPath, attributeName); + this.columnTypes = jpaProvider.getColumnTypes(entityType, elementCollectionPath, attributeName); + for (JoinType joinType : joinTypes) { + requiresTreatFilter[joinType.ordinal()] = JpaProvider.ConstraintType.NONE; + } } } else { this.isForeignJoinColumn = false; @@ -650,6 +795,7 @@ private AttributeEntry(AttributeEntry original, boolean hasCascadeDeleteCy this.ownerType = original.ownerType; this.attribute = original.attribute; this.attributePath = original.attributePath; + this.attributePathString = original.attributePathString; this.elementClass = original.elementClass; this.hasCascadeDeleteCycle = hasCascadeDeleteCycle; this.isForeignJoinColumn = original.isForeignJoinColumn; @@ -668,7 +814,7 @@ private AttributeEntry(AttributeEntry original, boolean hasCascadeDeleteCy public Map getWritableMappedByMappings(EntityType inverseType) { Map mappings = inverseAttributeCache.get(inverseType); if (mappings == null) { - mappings = jpaProvider.getWritableMappedByMappings(inverseType, (EntityType) ownerType, attribute.getName()); + mappings = jpaProvider.getWritableMappedByMappings(inverseType, (EntityType) ownerType, attributePathString); if (mappings == null) { inverseAttributeCache.putIfAbsent(inverseType, NO_MAPPINGS); } else { @@ -692,6 +838,11 @@ public Attribute getAttribute() { return attributePath; } + @Override + public String getAttributePathString() { + return attributePathString; + } + @Override public Class getElementClass() { return elementClass; diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java index 10be82f8df..03bbbd38aa 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinManager.java @@ -340,21 +340,27 @@ private void visitKeyOrIndexExpression(PathExpression pathExpression) { } } - String addRootValues(Class clazz, Class valueClazz, String rootAlias, int valueCount, String typeName, String castedParameter, boolean identifiableReference) { + String addRootValues(Class clazz, Class valueClazz, String rootAlias, int valueCount, String typeName, String castedParameter, boolean identifiableReference, String valueClazzAttributeName) { if (rootAlias == null) { throw new IllegalArgumentException("Illegal empty alias for the VALUES clause: " + clazz.getName()); } // TODO: we should pad the value count to avoid filling query caches - ManagedType managedType = mainQuery.metamodel.getManagedType(clazz); + EntityType entityType = mainQuery.metamodel.getEntity(clazz); + Type type = mainQuery.metamodel.type(valueClazz); String idAttributeName = null; Set> attributeSet; if (identifiableReference) { - SingularAttribute idAttribute = JpaMetamodelUtils.getSingleIdAttribute((EntityType) managedType); + SingularAttribute idAttribute = JpaMetamodelUtils.getSingleIdAttribute(entityType); idAttributeName = idAttribute.getName(); attributeSet = (Set>) (Set) Collections.singleton(idAttribute); } else { - Set> originalAttributeSet = (Set>) (Set) managedType.getAttributes(); + Set> originalAttributeSet; + if (valueClazzAttributeName == null) { + originalAttributeSet = (Set>) (Set) entityType.getAttributes(); + } else { + originalAttributeSet = (Set>) (Set) mainQuery.metamodel.getManagedType(valueClazz).getAttributes(); + } attributeSet = new TreeSet<>(JpaMetamodelUtils.ATTRIBUTE_NAME_COMPARATOR); for (Attribute attr : originalAttributeSet) { // Filter out collection attributes @@ -367,11 +373,11 @@ String addRootValues(Class clazz, Class valueClazz, String rootAlias, int String[][] parameterNames = new String[valueCount][attributeSet.size()]; ValueRetriever[] pathExpressions = new ValueRetriever[attributeSet.size()]; - String[] attributes = initializeValuesParameter(clazz, identifiableReference, rootAlias, attributeSet, parameterNames, pathExpressions); + String[] attributes = initializeValuesParameter(clazz, valueClazz, identifiableReference, rootAlias, attributeSet, parameterNames, pathExpressions); parameterManager.registerValuesParameter(rootAlias, valueClazz, parameterNames, pathExpressions, queryBuilder); JoinAliasInfo rootAliasInfo = new JoinAliasInfo(rootAlias, rootAlias, true, true, aliasManager); - JoinNode rootNode = JoinNode.createValuesRootNode(managedType, typeName, valueCount, idAttributeName, castedParameter, attributes, rootAliasInfo); + JoinNode rootNode = JoinNode.createValuesRootNode(type, entityType, typeName, valueCount, idAttributeName, valueClazzAttributeName, castedParameter, attributes, rootAliasInfo); rootAliasInfo.setJoinNode(rootNode); rootNodes.add(rootNode); // register root alias in aliasManager @@ -391,7 +397,7 @@ public Object getValue(Object target) { } } - private String[] initializeValuesParameter(Class clazz, boolean identifiableReference, String prefix, Set> attributeSet, String[][] parameterNames, ValueRetriever[] pathExpressions) { + private String[] initializeValuesParameter(Class clazz, Class valueClazz, boolean identifiableReference, String prefix, Set> attributeSet, String[][] parameterNames, ValueRetriever[] pathExpressions) { int valueCount = parameterNames.length; String[] attributes = new String[attributeSet.size()]; @@ -416,7 +422,7 @@ private String[] initializeValuesParameter(Class clazz, boolean identifiableR Attribute attribute = iter.next(); String attributeName = attribute.getName(); attributes[i] = attributeName; - pathExpressions[i] = com.blazebit.reflection.ExpressionUtils.getExpression(clazz, attributeName); + pathExpressions[i] = com.blazebit.reflection.ExpressionUtils.getExpression(valueClazz, attributeName); for (int j = 0; j < valueCount; j++) { parameterNames[j][i] = prefix + '_' + attributeName + '_' + j; } @@ -506,7 +512,7 @@ String addRoot(String correlationPath, String rootAlias) { } if (correlationParent == null) { - correlationParent = getRootNodeOrFail("Could not join correlation path [", correlationPath, "] because it did not use an absolute path but multiple root nodes are available!"); + correlationParent = parent.getRootNodeOrFail("Could not join correlation path [", correlationPath, "] because it did not use an absolute path but multiple root nodes are available!"); } if (correlationParent.getAliasInfo().getAliasOwner() == aliasManager) { @@ -682,7 +688,7 @@ private static boolean isNoJoinValuesNode(JoinNode rootNode) { return rootNode.getValueCount() > 0 && rootNode.getNodes().isEmpty() && rootNode.getTreatedJoinNodes().isEmpty() && rootNode.getEntityJoinNodes().isEmpty(); } - Set buildClause(StringBuilder sb, Set clauseExclusions, String aliasPrefix, boolean collectCollectionJoinNodes, boolean externalRepresenation, boolean ignoreCardinality, + Set buildClause(StringBuilder sb, Set clauseExclusions, String aliasPrefix, boolean collectCollectionJoinNodes, boolean externalRepresentation, boolean ignoreCardinality, List optionalWhereConjuncts, List whereConjuncts, List syntheticSubqueryValuesWhereClauseConjuncts, Map, Map> explicitVersionEntities, Set nodesToFetch, Set alwaysIncludedNodes) { final boolean renderFetches = !clauseExclusions.contains(ClauseType.SELECT); StringBuilder tempSb = null; @@ -702,24 +708,26 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St JoinNode rootNode = nodes.get(i); JoinNode correlationParent = rootNode.getCorrelationParent(); - if (externalRepresenation && rootNode.getValueCount() > 0) { - ManagedType type = rootNode.getManagedType(); - if (type.getJavaType() == ValuesEntity.class) { + if (externalRepresentation && rootNode.getValueCount() > 0) { + EntityType valueType = rootNode.getValueType(); + Type nodeType = rootNode.getNodeType(); + if (valueType.getJavaType() == ValuesEntity.class) { sb.append(rootNode.getValuesTypeName()); } else { - if (type instanceof EntityType) { - sb.append(((EntityType) type).getName()); + if (nodeType instanceof EntityType) { + sb.append(((EntityType) nodeType).getName()); if (rootNode.getValuesIdName() != null) { sb.append('.').append(rootNode.getValuesIdName()); } } else { - sb.append(rootNode.getValuesTypeName()); + // Not sure how safe that is regarding ambiguity + sb.append(rootNode.getNodeType().getJavaType().getSimpleName()); } } sb.append("("); sb.append(rootNode.getValueCount()); sb.append(" VALUES)"); - } else if (externalRepresenation && explicitVersionEntities.get(rootNode.getJavaType()) != null) { + } else if (externalRepresentation && explicitVersionEntities.get(rootNode.getJavaType()) != null) { DbmsModificationState state = explicitVersionEntities.get(rootNode.getJavaType()).get(rootNode.getAlias()); EntityType type = rootNode.getEntityType(); if (state == DbmsModificationState.NEW) { @@ -731,9 +739,9 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St sb.append(')'); } else { if (correlationParent != null) { - renderCorrelationJoinPath(sb, correlationParent.getAliasInfo(), rootNode, whereConjuncts); + renderCorrelationJoinPath(sb, correlationParent.getAliasInfo(), rootNode, whereConjuncts, optionalWhereConjuncts, externalRepresentation); } else { - EntityType type = rootNode.getEntityType(); + EntityType type = rootNode.getInternalEntityType(); sb.append(type.getName()); } } @@ -766,18 +774,18 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St } } if (!rootNode.getNodes().isEmpty()) { - valuesNode = applyJoins(sb, rootNode.getAliasInfo(), rootNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, ignoreCardinality, nodesToFetch, whereConjuncts, valuesNode, alwaysIncludedNodes); + valuesNode = applyJoins(sb, rootNode.getAliasInfo(), rootNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, ignoreCardinality, nodesToFetch, whereConjuncts, valuesNode, alwaysIncludedNodes, externalRepresentation); } for (JoinNode treatedNode : rootNode.getTreatedJoinNodes().values()) { if (!treatedNode.getNodes().isEmpty()) { - valuesNode = applyJoins(sb, treatedNode.getAliasInfo(), treatedNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, ignoreCardinality, nodesToFetch, whereConjuncts, valuesNode, alwaysIncludedNodes); + valuesNode = applyJoins(sb, treatedNode.getAliasInfo(), treatedNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, ignoreCardinality, nodesToFetch, whereConjuncts, valuesNode, alwaysIncludedNodes, externalRepresentation); } } if (!rootNode.getEntityJoinNodes().isEmpty()) { // TODO: Fix this with #216 boolean isCollection = true; if (mainQuery.jpaProvider.supportsEntityJoin() && !emulateJoins) { - valuesNode = applyJoins(sb, rootNode.getAliasInfo(), new ArrayList<>(rootNode.getEntityJoinNodes()), isCollection, clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, ignoreCardinality, nodesToFetch, whereConjuncts, valuesNode, alwaysIncludedNodes); + valuesNode = applyJoins(sb, rootNode.getAliasInfo(), new ArrayList<>(rootNode.getEntityJoinNodes()), isCollection, clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, ignoreCardinality, nodesToFetch, whereConjuncts, valuesNode, alwaysIncludedNodes, externalRepresentation); } else { Set entityNodes = rootNode.getEntityJoinNodes(); for (JoinNode entityNode : entityNodes) { @@ -815,7 +823,7 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St } if (valuesNode != null) { - renderValuesClausePredicate(tempSb, valuesNode, valuesNode.getAlias()); + renderValuesClausePredicate(tempSb, valuesNode, valuesNode.getAlias(), externalRepresentation); whereConjuncts.add(tempSb.toString()); tempSb.setLength(0); valuesNode = null; @@ -831,10 +839,10 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St renderedJoins.add(entityNode); if (!entityNode.getNodes().isEmpty()) { - valuesNode = applyJoins(sb, entityNode.getAliasInfo(), entityNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, ignoreCardinality, nodesToFetch, whereConjuncts, valuesNode, alwaysIncludedNodes); + valuesNode = applyJoins(sb, entityNode.getAliasInfo(), entityNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, ignoreCardinality, nodesToFetch, whereConjuncts, valuesNode, alwaysIncludedNodes, externalRepresentation); } for (JoinNode treatedNode : entityNode.getTreatedJoinNodes().values()) { - valuesNode = applyJoins(sb, treatedNode.getAliasInfo(), treatedNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, ignoreCardinality, nodesToFetch, whereConjuncts, valuesNode, alwaysIncludedNodes); + valuesNode = applyJoins(sb, treatedNode.getAliasInfo(), treatedNode.getNodes(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, ignoreCardinality, nodesToFetch, whereConjuncts, valuesNode, alwaysIncludedNodes, externalRepresentation); } } } @@ -844,7 +852,7 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St if (noJoinValuesNodesSb.length() != 0) { noJoinValuesNodesSb.append(" AND "); } - renderValuesClausePredicate(noJoinValuesNodesSb, valuesNode, valuesNode.getAlias()); + renderValuesClausePredicate(noJoinValuesNodesSb, valuesNode, valuesNode.getAlias(), externalRepresentation); } } @@ -855,12 +863,13 @@ Set buildClause(StringBuilder sb, Set clauseExclusions, St return collectionJoinNodes; } - void renderValuesClausePredicate(StringBuilder sb, JoinNode rootNode, String alias) { + void renderValuesClausePredicate(StringBuilder sb, JoinNode rootNode, String alias, boolean externalRepresentation) { // The rendering strategy is to render the VALUES clause predicate into JPQL with the values parameters // in the correct order. The whole SQL part of that will be replaced later by the correct SQL int valueCount = rootNode.getValueCount(); if (valueCount > 0) { String typeName = rootNode.getValuesTypeName() == null ? null : rootNode.getValuesTypeName().toUpperCase(); + String valueClazzAttributeName = externalRepresentation ? null : rootNode.getValueClazzAttributeName(); String[] attributes = rootNode.getValuesAttributes(); String prefix = rootNode.getAlias(); @@ -877,6 +886,9 @@ void renderValuesClausePredicate(StringBuilder sb, JoinNode rootNode, String ali } else { sb.append(alias); sb.append('.'); + if (valueClazzAttributeName != null) { + sb.append(valueClazzAttributeName).append('.'); + } sb.append(attributes[j]); } @@ -934,7 +946,7 @@ public void apply(ExpressionModifierVisitor visitor) } } - private JoinNode renderJoinNode(StringBuilder sb, JoinAliasInfo joinBase, JoinNode node, String aliasPrefix, boolean renderFetches, Set nodesToFetch, List whereConjuncts, JoinNode valuesNode) { + private JoinNode renderJoinNode(StringBuilder sb, JoinAliasInfo joinBase, JoinNode node, String aliasPrefix, boolean renderFetches, Set nodesToFetch, List whereConjuncts, JoinNode valuesNode, boolean externalRepresentation) { if (!renderedJoins.contains(node)) { // We determine the nodes that should be fetched by analyzing the fetch owners during implicit joining final boolean fetch = nodesToFetch.contains(node) && renderFetches; @@ -970,7 +982,7 @@ private JoinNode renderJoinNode(StringBuilder sb, JoinAliasInfo joinBase, JoinNo sb.append(aliasPrefix); } - String onCondition = renderJoinPath(sb, joinBase, node, whereConjuncts); + String onCondition = renderJoinPath(sb, joinBase, node, whereConjuncts, externalRepresentation); sb.append(' '); if (aliasPrefix != null) { @@ -991,7 +1003,7 @@ private JoinNode renderJoinNode(StringBuilder sb, JoinAliasInfo joinBase, JoinNo // This condition will be removed in the final SQL, so no worries about it // It is just there to have parameters at the right position in the final SQL if (valuesNode != null) { - renderValuesClausePredicate(sb, valuesNode, valuesNode.getAlias()); + renderValuesClausePredicate(sb, valuesNode, valuesNode.getAlias(), externalRepresentation); sb.append(" AND "); valuesNode = null; } @@ -1020,23 +1032,48 @@ private JoinNode renderJoinNode(StringBuilder sb, JoinAliasInfo joinBase, JoinNo return valuesNode; } - private void renderCorrelationJoinPath(StringBuilder sb, JoinAliasInfo joinBase, JoinNode node, List whereConjuncts) { + private void renderCorrelationJoinPath(StringBuilder sb, JoinAliasInfo joinBase, JoinNode node, List whereConjuncts, List optionalWhereConjuncts, boolean externalRepresentation) { final boolean renderTreat = mainQuery.jpaProvider.supportsTreatJoin() && (!mainQuery.jpaProvider.supportsSubtypeRelationResolving() || node.getJoinType() == JoinType.INNER); - if (!mainQuery.jpaProvider.supportsJoinElementCollectionsOnCorrelatedInverseAssociations() && node.hasElementCollectionJoins() || node.getTreatType() != null && !renderTreat && !mainQuery.jpaProvider.supportsSubtypeRelationResolving()) { - ExtendedManagedType extendedManagedType = metamodel.getManagedType(ExtendedManagedType.class, node.getCorrelationParent().getManagedType()); + if (mainQuery.jpaProvider.needsCorrelationPredicateWhenCorrelatingWithWhereClause() || node.getTreatType() != null && !renderTreat && !mainQuery.jpaProvider.supportsSubtypeRelationResolving()) { + ExtendedManagedType extendedManagedType = metamodel.getManagedType(ExtendedManagedType.class, node.getCorrelationParent().getManagedType()); ExtendedAttribute attribute = extendedManagedType.getAttribute(node.getCorrelationPath()); - if (attribute.getMappedBy() != null) { + if (attribute.getMappedBy() == null) { + if (attribute.getAttribute() instanceof ListAttribute && !attribute.isBag()) { + // What the hell Hibernate? Why just for indexed lists? + sb.append(node.getCorrelationParent().getEntityType().getName()); + sb.append(" _synthetic_"); + sb.append(node.getAlias()); + sb.append(" JOIN _synthetic_"); + sb.append(node.getAlias()); + sb.append('.').append(node.getCorrelationPath()); + + StringBuilder whereSb = new StringBuilder(); + whereSb.append("_synthetic_").append(node.getAlias()); + boolean singleValuedAssociationId = mainQuery.jpaProvider.supportsSingleValuedAssociationIdExpressions() && extendedManagedType.getIdAttributes().size() == 1; + if (singleValuedAssociationId) { + whereSb.append('.').append(extendedManagedType.getIdAttribute().getName()); + } + + whereSb.append(" = "); + node.getCorrelationParent().appendAlias(whereSb, false, externalRepresentation); + if (singleValuedAssociationId) { + whereSb.append('.').append(extendedManagedType.getIdAttribute().getName()); + } + whereConjuncts.add(whereSb.toString()); + return; + } + } else { sb.append(node.getEntityType().getName()); StringBuilder whereSb = new StringBuilder(); - node.appendAlias(whereSb, false); + node.appendAlias(whereSb, false, externalRepresentation); whereSb.append('.').append(attribute.getMappedBy()); boolean singleValuedAssociationId = mainQuery.jpaProvider.supportsSingleValuedAssociationIdExpressions() && extendedManagedType.getIdAttributes().size() == 1; if (singleValuedAssociationId) { whereSb.append('.').append(extendedManagedType.getIdAttribute().getName()); } whereSb.append(" = "); - node.getCorrelationParent().appendAlias(whereSb, false); + node.getCorrelationParent().appendAlias(whereSb, false, externalRepresentation); if (singleValuedAssociationId) { whereSb.append('.').append(extendedManagedType.getIdAttribute().getName()); } @@ -1047,7 +1084,7 @@ private void renderCorrelationJoinPath(StringBuilder sb, JoinAliasInfo joinBase, if (node.getTreatType() != null) { if (renderTreat) { sb.append("TREAT("); - renderAlias(sb, joinBase.getJoinNode(), mainQuery.jpaProvider.supportsRootTreat()); + renderAlias(sb, joinBase.getJoinNode(), mainQuery.jpaProvider.supportsRootTreat(), externalRepresentation); sb.append('.'); sb.append(node.getCorrelationPath()); sb.append(" AS "); @@ -1062,21 +1099,21 @@ private void renderCorrelationJoinPath(StringBuilder sb, JoinAliasInfo joinBase, JoinNode baseNode = joinBase.getJoinNode(); if (baseNode.getTreatType() != null) { if (mainQuery.jpaProvider.supportsRootTreatJoin()) { - baseNode.appendAlias(sb, true); + baseNode.appendAlias(sb, true, externalRepresentation); } else if (mainQuery.jpaProvider.supportsSubtypeRelationResolving()) { - baseNode.appendAlias(sb, false); + baseNode.appendAlias(sb, false, externalRepresentation); } else { throw new IllegalArgumentException("Treat should not be used as the JPA provider does not support subtype property access!"); } } else { - baseNode.appendAlias(sb, false); + baseNode.appendAlias(sb, false, externalRepresentation); } sb.append('.').append(node.getCorrelationPath()); } } - private String renderJoinPath(StringBuilder sb, JoinAliasInfo joinBase, JoinNode node, List whereConjuncts) { + private String renderJoinPath(StringBuilder sb, JoinAliasInfo joinBase, JoinNode node, List whereConjuncts, boolean externalRepresentation) { if (node.getTreatType() != null) { // We render the treat join only if it makes sense. If we have e.g. a left join and the provider supports // implicit relation resolving then there is no point in rendering the treat join. On the contrary, that might lead to wrong results @@ -1100,7 +1137,7 @@ private String renderJoinPath(StringBuilder sb, JoinAliasInfo joinBase, JoinNode } if (renderTreat) { sb.append("TREAT("); - renderAlias(sb, baseNode, mainQuery.jpaProvider.supportsRootTreatTreatJoin()); + renderAlias(sb, baseNode, mainQuery.jpaProvider.supportsRootTreatTreatJoin(), externalRepresentation); sb.append('.'); sb.append(relationName); sb.append(" AS "); @@ -1120,31 +1157,31 @@ private String renderJoinPath(StringBuilder sb, JoinAliasInfo joinBase, JoinNode sb.append(joinBase.getJoinNode().getAlias()); sb.append(')'); } else { - renderAlias(sb, joinBase.getJoinNode(), mainQuery.jpaProvider.supportsRootTreatJoin()); + renderAlias(sb, joinBase.getJoinNode(), mainQuery.jpaProvider.supportsRootTreatJoin(), externalRepresentation); sb.append('.').append(node.getParentTreeNode().getRelationName()); } return null; } - private void renderAlias(StringBuilder sb, JoinNode baseNode, boolean supportsTreat) { + private void renderAlias(StringBuilder sb, JoinNode baseNode, boolean supportsTreat, boolean externalRepresentation) { if (baseNode.getTreatType() != null) { if (supportsTreat) { - baseNode.appendAlias(sb, true); + baseNode.appendAlias(sb, true, externalRepresentation); } else if (mainQuery.jpaProvider.supportsSubtypeRelationResolving()) { - baseNode.appendAlias(sb, false); + baseNode.appendAlias(sb, false, externalRepresentation); } else { throw new IllegalArgumentException("Treat should not be used as the JPA provider does not support subtype property access!"); } } else { - baseNode.appendAlias(sb, false); + baseNode.appendAlias(sb, false, externalRepresentation); } } - private JoinNode renderReverseDependency(StringBuilder sb, JoinNode dependency, String aliasPrefix, boolean renderFetches, Set nodesToFetch, List whereConjuncts, JoinNode valuesNode) { + private JoinNode renderReverseDependency(StringBuilder sb, JoinNode dependency, String aliasPrefix, boolean renderFetches, Set nodesToFetch, List whereConjuncts, JoinNode valuesNode, boolean externalRepresentation) { if (dependency.getParent() != null) { if (dependency.getParent() != valuesNode) { - valuesNode = renderReverseDependency(sb, dependency.getParent(), aliasPrefix, renderFetches, nodesToFetch, whereConjuncts, valuesNode); + valuesNode = renderReverseDependency(sb, dependency.getParent(), aliasPrefix, renderFetches, nodesToFetch, whereConjuncts, valuesNode, externalRepresentation); } if (!dependency.getDependencies().isEmpty()) { markedJoinNodes.add(dependency); @@ -1166,30 +1203,31 @@ private JoinNode renderReverseDependency(StringBuilder sb, JoinNode dependency, throw new IllegalStateException(errorSb.toString()); } // render reverse dependencies - valuesNode = renderReverseDependency(sb, dep, aliasPrefix, renderFetches, nodesToFetch, whereConjuncts, valuesNode); + valuesNode = renderReverseDependency(sb, dep, aliasPrefix, renderFetches, nodesToFetch, whereConjuncts, valuesNode, externalRepresentation); } } finally { markedJoinNodes.remove(dependency); } } - valuesNode = renderJoinNode(sb, dependency.getParent().getAliasInfo(), dependency, aliasPrefix, renderFetches, nodesToFetch, whereConjuncts, valuesNode); + valuesNode = renderJoinNode(sb, dependency.getParent().getAliasInfo(), dependency, aliasPrefix, renderFetches, nodesToFetch, whereConjuncts, valuesNode, externalRepresentation); } return valuesNode; } - private JoinNode applyJoins(StringBuilder sb, JoinAliasInfo joinBase, Map nodes, Set clauseExclusions, String aliasPrefix, boolean collectCollectionJoinNodes, boolean renderFetches, boolean ignoreCardinality, Set nodesToFetch, List whereConjuncts, JoinNode valuesNode, Set alwaysIncludedNodes) { + private JoinNode applyJoins(StringBuilder sb, JoinAliasInfo joinBase, Map nodes, Set clauseExclusions, String aliasPrefix, boolean collectCollectionJoinNodes, boolean renderFetches, boolean ignoreCardinality, Set nodesToFetch, List whereConjuncts, JoinNode valuesNode, Set alwaysIncludedNodes, boolean externalRepresentation) { for (Map.Entry nodeEntry : nodes.entrySet()) { JoinTreeNode treeNode = nodeEntry.getValue(); - List stack = new ArrayList(); + List stack = new ArrayList<>(); stack.addAll(treeNode.getJoinNodes().descendingMap().values()); - valuesNode = applyJoins(sb, joinBase, stack, treeNode.isCollection(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, ignoreCardinality, nodesToFetch, whereConjuncts, valuesNode, alwaysIncludedNodes); + valuesNode = applyJoins(sb, joinBase, stack, treeNode.isCollection(), clauseExclusions, aliasPrefix, collectCollectionJoinNodes, renderFetches, ignoreCardinality, nodesToFetch, whereConjuncts, valuesNode, alwaysIncludedNodes, externalRepresentation); } return valuesNode; } - private JoinNode applyJoins(StringBuilder sb, JoinAliasInfo joinBase, List stack, boolean isCollection, Set clauseExclusions, String aliasPrefix, boolean collectCollectionJoinNodes, boolean renderFetches, boolean ignoreCardinality, Set nodesToFetch, List whereConjuncts, JoinNode valuesNode, Set alwaysIncludedNodes) { + private JoinNode applyJoins(StringBuilder sb, JoinAliasInfo joinBase, List stack, boolean isCollection, Set clauseExclusions, String aliasPrefix, boolean collectCollectionJoinNodes, boolean renderFetches, boolean ignoreCardinality, Set nodesToFetch, List whereConjuncts, JoinNode valuesNode, Set alwaysIncludedNodes, + boolean externalRepresentation) { while (!stack.isEmpty()) { JoinNode node = stack.remove(stack.size() - 1); // If the clauses in which a join node occurs are all excluded or the join node is not mandatory for the cardinality, we skip it @@ -1202,7 +1240,7 @@ private JoinNode applyJoins(StringBuilder sb, JoinAliasInfo joinBase, List) { // Skip the foreign join column check for map keys // They aren't allowed as join sources in the JPA providers yet so we can only render them directly } else if (baseType instanceof EmbeddableType) { // Get the base type. This is important if the path is "deeper" i.e. when having embeddables - String attributePath = joinResult.joinFields(maybeSingularAssociationName); JoinNode node = parent; baseType = node.getNodeType(); while (baseType instanceof EmbeddableType) { if (node.getParentTreeNode() == null) { + attributePath = node.getValueClazzAttributeName() + "." + attributePath; + baseType = node.getValueType(); break; } attributePath = node.getParentTreeNode().getRelationName() + "." + attributePath; @@ -2029,9 +2062,8 @@ private boolean isSingleValuedAssociationId(JoinResult joinResult, List managedType = metamodel.getManagedType(ExtendedManagedType.class, JpaMetamodelUtils.getTypeName(parent.getManagedType())); - String field = maybeSingularAssociationName + "." + maybeSingularAssociationIdExpression; - return managedType.getAttributes().containsKey(joinResult.joinFields(field)); + ExtendedManagedType managedType = metamodel.getManagedType(ExtendedManagedType.class, JpaMetamodelUtils.getTypeName(baseType)); + return managedType.getAttributes().containsKey(attributePath + "." + maybeSingularAssociationIdExpression); } private boolean isId(Type type, Expression idExpression) { diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinNode.java b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinNode.java index caa1a4bf5c..76daccd8a1 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/JoinNode.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/JoinNode.java @@ -74,7 +74,9 @@ public class JoinNode implements From, ExpressionModifier, BaseNode { private final EntityType treatType; private final String valuesTypeName; private final int valueCount; + private final EntityType valueType; private final String valuesIdName; + private final String valueClazzAttributeName; private final String valuesCastedParameter; private final String[] valuesAttributes; private final String qualificationExpression; @@ -106,7 +108,9 @@ private JoinNode(TreatedJoinAliasInfo treatedJoinAliasInfo) { this.qualificationExpression = treatedJoinNode.qualificationExpression; this.valuesTypeName = treatedJoinNode.valuesTypeName; this.valueCount = treatedJoinNode.valueCount; + this.valueType = treatedJoinNode.valueType; this.valuesIdName = treatedJoinNode.valuesIdName; + this.valueClazzAttributeName = treatedJoinNode.valueClazzAttributeName; this.valuesCastedParameter = treatedJoinNode.valuesCastedParameter; this.valuesAttributes = treatedJoinNode.valuesAttributes; this.aliasInfo = treatedJoinAliasInfo; @@ -126,7 +130,9 @@ private JoinNode(JoinNode parent, JoinTreeNode parentTreeNode, JoinType joinType this.treatType = treatType; this.valuesTypeName = null; this.valueCount = 0; + this.valueType = null; this.valuesIdName = null; + this.valueClazzAttributeName = null; this.valuesCastedParameter = null; this.valuesAttributes = null; this.qualificationExpression = qualificationExpression; @@ -150,7 +156,7 @@ private JoinNode(JoinNode parent, JoinTreeNode parentTreeNode, JoinType joinType onUpdate(null); } - private JoinNode(ManagedType nodeType, String valuesTypeName, int valueCount, String valuesIdName, String valuesCastedParameter, String[] valuesAttributes, JoinAliasInfo aliasInfo) { + private JoinNode(Type nodeType, EntityType valueType, String valuesTypeName, int valueCount, String valuesIdName, String valueClazzAttributeName, String valuesCastedParameter, String[] valuesAttributes, JoinAliasInfo aliasInfo) { this.parent = null; this.parentTreeNode = null; this.joinType = null; @@ -160,7 +166,9 @@ private JoinNode(ManagedType nodeType, String valuesTypeName, int valueCount, this.treatType = null; this.valuesTypeName = valuesTypeName; this.valueCount = valueCount; + this.valueType = valueType; this.valuesIdName = valuesIdName; + this.valueClazzAttributeName = valueClazzAttributeName; this.valuesCastedParameter = valuesCastedParameter; this.valuesAttributes = valuesAttributes; this.qualificationExpression = null; @@ -173,8 +181,8 @@ public static JoinNode createRootNode(EntityType nodeType, JoinAliasInfo alia return new JoinNode(null, null, null, null, null, nodeType, null, null, aliasInfo); } - public static JoinNode createValuesRootNode(ManagedType nodeType, String valuesTypeName, int valueCount, String valuesIdName, String valuesCastedParameter, String[] valuesAttributes, JoinAliasInfo aliasInfo) { - return new JoinNode(nodeType, valuesTypeName, valueCount, valuesIdName, valuesCastedParameter, valuesAttributes, aliasInfo); + public static JoinNode createValuesRootNode(Type nodeType, EntityType valueType, String valuesTypeName, int valueCount, String valuesIdName, String valueClazzAttributeName, String valuesCastedParameter, String[] valuesAttributes, JoinAliasInfo aliasInfo) { + return new JoinNode(nodeType, valueType, valuesTypeName, valueCount, valuesIdName, valueClazzAttributeName, valuesCastedParameter, valuesAttributes, aliasInfo); } public static JoinNode createCorrelationRootNode(JoinNode correlationParent, String correlationPath, Attribute correlatedAttribute, Type nodeType, EntityType treatType, JoinAliasInfo aliasInfo) { @@ -193,7 +201,7 @@ public JoinNode cloneRootNode(JoinAliasInfo aliasInfo) { // NOTE: no cloning of treatedJoinNodes and entityJoinNodes is intentional JoinNode newNode; if (valueCount > 0) { - newNode = createValuesRootNode((ManagedType) nodeType, valuesTypeName, valueCount, valuesIdName, valuesCastedParameter, valuesAttributes, aliasInfo); + newNode = createValuesRootNode(nodeType, valueType, valuesTypeName, valueCount, valuesIdName, valueClazzAttributeName, valuesCastedParameter, valuesAttributes, aliasInfo); } else if (joinType == null) { newNode = createRootNode((EntityType) nodeType, aliasInfo); } else { @@ -556,6 +564,13 @@ public EntityType getEntityType() { throw new IllegalArgumentException("Expected type of join node to be an entity but isn't: " + JpaMetamodelUtils.getTypeName(nodeType)); } + public EntityType getInternalEntityType() { + if (valueType != null) { + return valueType; + } + return getEntityType(); + } + public ManagedType getManagedType() { if (treatType != null) { return treatType; @@ -587,6 +602,14 @@ public int getValueCount() { return valueCount; } + public EntityType getValueType() { + return valueType; + } + + public String getValueClazzAttributeName() { + return valueClazzAttributeName; + } + public String getValuesIdName() { return valuesIdName; } @@ -722,16 +745,6 @@ public boolean isQualifiedJoin() { return qualificationExpression != null; } - public boolean hasElementCollectionJoins() { - for (JoinTreeNode treeNode : nodes.values()) { - if (treeNode.getAttribute().getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION) { - return true; - } - } - - return false; - } - /** * @author Christian Beikov * @since 1.2.0 @@ -773,30 +786,40 @@ public Expression createExpression(String field) { } } - public void appendDeReference(StringBuilder sb, String property) { - appendDeReference(sb, property, false); + public void appendDeReference(StringBuilder sb, String property, boolean externalRepresentation) { + appendDeReference(sb, property, false, externalRepresentation); } - public void appendDeReference(StringBuilder sb, String property, boolean renderTreat) { - appendAlias(sb, renderTreat); + public void appendDeReference(StringBuilder sb, String property, boolean renderTreat, boolean externalRepresentation) { + appendAlias(sb, renderTreat, externalRepresentation); // If we have a valuesTypeName, the property can only be "value" which is already handled in appendAlias if (property != null && valuesTypeName == null) { sb.append('.').append(property); } } - public void appendAlias(StringBuilder sb) { - appendAlias(sb, false); + public void appendAlias(StringBuilder sb, boolean externalRepresentation) { + appendAlias(sb, false, externalRepresentation); } - public void appendAlias(StringBuilder sb, boolean renderTreat) { + public void appendAlias(StringBuilder sb, boolean renderTreat, boolean externalRepresentation) { if (valuesTypeName != null) { - // NOTE: property should always be null - sb.append("TREAT_"); - sb.append(valuesTypeName.toUpperCase()).append('('); - sb.append(aliasInfo.getAlias()); - sb.append(".value"); - sb.append(')'); + if (externalRepresentation) { + sb.append(aliasInfo.getAlias()); + } else { + // NOTE: property should always be null + sb.append("TREAT_"); + sb.append(valuesTypeName.toUpperCase()).append('('); + sb.append(aliasInfo.getAlias()); + sb.append(".value"); + sb.append(')'); + } + } else if (valueClazzAttributeName != null) { + if (externalRepresentation) { + sb.append(aliasInfo.getAlias()); + } else { + sb.append(aliasInfo.getAlias()).append('.').append(valueClazzAttributeName); + } } else { if (qualificationExpression != null) { boolean hasTreat = renderTreat && treatType != null; diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/JpaUtils.java b/core/impl/src/main/java/com/blazebit/persistence/impl/JpaUtils.java index a4a47fe372..7a6d1e9606 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/JpaUtils.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/JpaUtils.java @@ -19,11 +19,30 @@ import com.blazebit.persistence.parser.EntityMetamodel; import com.blazebit.persistence.parser.PathTargetResolvingExpressionVisitor; import com.blazebit.persistence.parser.expression.Expression; +import com.blazebit.persistence.parser.expression.NullExpression; +import com.blazebit.persistence.parser.expression.ParameterExpression; import com.blazebit.persistence.parser.expression.PathExpression; +import com.blazebit.persistence.parser.expression.PropertyExpression; +import com.blazebit.persistence.spi.ExtendedAttribute; +import com.blazebit.persistence.spi.ExtendedManagedType; +import com.blazebit.persistence.spi.JoinTable; +import com.blazebit.persistence.spi.JpaMetamodelAccessor; +import com.blazebit.persistence.spi.JpaProvider; import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.EntityType; +import javax.persistence.metamodel.ManagedType; +import javax.persistence.metamodel.PluralAttribute; import javax.persistence.metamodel.Type; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.NavigableSet; +import java.util.Queue; +import java.util.TreeSet; /** * @@ -35,6 +54,197 @@ public final class JpaUtils { private JpaUtils() { } + public static void expandBindings(EntityType bindType, Map bindingMap, Map columnBindingMap, Map attributeEntries, ClauseType clause, AbstractCommonQueryBuilder queryBuilder) { + SelectManager selectManager = queryBuilder.selectManager; + JoinManager joinManager = queryBuilder.joinManager; + ParameterManager parameterManager = queryBuilder.parameterManager; + JpaProvider jpaProvider = queryBuilder.mainQuery.jpaProvider; + EntityMetamodel metamodel = queryBuilder.mainQuery.metamodel; + JpaMetamodelAccessor jpaMetamodelAccessor = jpaProvider.getJpaMetamodelAccessor(); + final Queue attributeQueue = new ArrayDeque<>(bindingMap.keySet()); + while (!attributeQueue.isEmpty()) { + final String attributeName = attributeQueue.remove(); + Integer tupleIndex = bindingMap.get(attributeName); + + final ExtendedAttribute attributeEntry = attributeEntries.get(attributeName); + if (attributeEntry != null) { + final List> attributePath = attributeEntry.getAttributePath(); + final Attribute lastAttribute = attributePath.get(attributePath.size() - 1); + + if (jpaMetamodelAccessor.isJoinable(lastAttribute) || lastAttribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED) { + // We have to map *-to-one relationships to their id or unique props + // NOTE: Since we are talking about *-to-ones, the expression can only be a path to an object + // so it is safe to just append the id to the path + Expression selectExpression = selectManager.getSelectInfos().get(tupleIndex).getExpression(); + + // TODO: Maybe also allow Treat, Case-When, Array? + if (selectExpression instanceof NullExpression) { + // When binding null, we don't have to adapt anything + } else if (selectExpression instanceof PathExpression) { + boolean firstBinding = true; + final Collection embeddedPropertyNames = getEmbeddedPropertyPaths(attributeEntries, attributeName); + + PathExpression baseExpression = embeddedPropertyNames.size() > 1 ? + ((PathExpression) selectExpression).clone(false) : ((PathExpression) selectExpression); + + joinManager.implicitJoin(baseExpression, true, null, ClauseType.SELECT, null, false, false, false, false); + + if (attributeEntry.getElementClass() != baseExpression.getPathReference().getType().getJavaType()) { + throw new IllegalStateException("An association should be bound to its association type and not its identifier type"); + } + + if (embeddedPropertyNames.size() > 0) { + bindingMap.remove(attributeName); + // We are going to insert the expanded attributes as new select items and shift existing ones + int delta = embeddedPropertyNames.size() - 1; + if (delta > 0) { + for (Map.Entry entry : bindingMap.entrySet()) { + if (entry.getValue() > tupleIndex) { + entry.setValue(entry.getValue() + delta); + } + } + } + + int offset = 0; + for (String embeddedPropertyName : embeddedPropertyNames) { + PathExpression pathExpression = firstBinding ? + ((PathExpression) selectExpression) : baseExpression.clone(false); + + for (String propertyNamePart : embeddedPropertyName.split("\\.")) { + pathExpression.getExpressions().add(new PropertyExpression(propertyNamePart)); + } + + String nestedAttributePath = attributeName + "." + embeddedPropertyName; + ExtendedAttribute nestedAttributeEntry = attributeEntries.get(nestedAttributePath); + + // Process the nested attribute path recursively + attributeQueue.add(nestedAttributePath); + + // Replace this binding in the binding map, additional selects need an updated index + bindingMap.put(nestedAttributePath, firstBinding ? tupleIndex : tupleIndex + offset); + + if (!firstBinding) { + selectManager.select(pathExpression, null, tupleIndex + offset); + } else { + firstBinding = false; + } + + for (String column : nestedAttributeEntry.getColumnNames()) { + columnBindingMap.put(column, nestedAttributePath); + } + offset++; + } + } + } else if (selectExpression instanceof ParameterExpression) { + final Collection embeddedPropertyNames = getEmbeddedPropertyPaths(attributeEntries, attributeName); + selectManager.getSelectInfos().remove(tupleIndex.intValue()); + ParameterExpression parameterExpression = (ParameterExpression) selectExpression; + String parameterName = parameterExpression.getName(); + + if (embeddedPropertyNames.size() > 0) { + Map> parameterAccessPaths = new HashMap<>(embeddedPropertyNames.size()); + ParameterValueTransformer tranformer = parameterManager.getParameter(parameterName).getTranformer(); + if (tranformer instanceof SplittingParameterTransformer) { + for (String name : ((SplittingParameterTransformer) tranformer).getParameterNames()) { + parameterManager.unregisterParameterName(name, clause, queryBuilder); + } + } + + bindingMap.remove(attributeName); + // We are going to insert the expanded attributes as new select items and shift existing ones + int delta = embeddedPropertyNames.size() - 1; + if (delta > 0) { + for (Map.Entry entry : bindingMap.entrySet()) { + if (entry.getValue() > tupleIndex) { + entry.setValue(entry.getValue() + delta); + } + } + } + + int offset = 0; + for (String embeddedPropertyName : embeddedPropertyNames) { + String subParamName = "_" + parameterName + "_" + embeddedPropertyName.replace('.', '_'); + parameterManager.registerParameterName(subParamName, false, clause, queryBuilder); + parameterAccessPaths.put(subParamName, Arrays.asList(embeddedPropertyName.split("\\."))); + + String nestedAttributePath = attributeName + "." + embeddedPropertyName; + ExtendedAttribute nestedAttributeEntry = attributeEntries.get(nestedAttributePath); + + // Process the nested attribute path recursively + attributeQueue.add(nestedAttributePath); + + // Replace this binding in the binding map, additional selects need an updated index + bindingMap.put(nestedAttributePath, tupleIndex + offset); + selectManager.select(new ParameterExpression(subParamName), null, tupleIndex + offset); + + for (String column : nestedAttributeEntry.getColumnNames()) { + columnBindingMap.put(column, nestedAttributePath); + } + offset++; + } + + parameterManager.getParameter(parameterName).setTranformer(new SplittingParameterTransformer(parameterManager, metamodel, attributeEntry.getElementClass(), parameterAccessPaths)); + } + } else { + throw new IllegalArgumentException("Illegal expression '" + selectExpression.toString() + "' for binding relation '" + attributeName + "'!"); + } + } + } + } + } + + private static Collection getEmbeddedPropertyPaths(Map attributeEntries, String attributeName) { + final NavigableSet embeddedPropertyNames = new TreeSet<>(); + String prefix = attributeName + "."; + for (Map.Entry entry : attributeEntries.entrySet()) { + if (entry.getKey().startsWith(prefix)) { + String subAttribute = entry.getKey().substring(prefix.length()); + String lower = embeddedPropertyNames.lower(subAttribute); + if (lower == null) { + String higher = embeddedPropertyNames.higher(subAttribute); + if (higher == null || !higher.startsWith(subAttribute + ".")) { + embeddedPropertyNames.add(subAttribute); + } + } else { + if (subAttribute.startsWith(lower + ".")) { + embeddedPropertyNames.remove(lower); + } + if (!lower.startsWith(subAttribute + ".")) { + String higher = embeddedPropertyNames.higher(subAttribute); + if (higher == null || !higher.startsWith(subAttribute + ".")) { + embeddedPropertyNames.add(subAttribute); + } + } + } + } + } + return embeddedPropertyNames; + } + + public static Map getCollectionAttributeEntries(EntityMetamodel metamodel, EntityType entityType, ExtendedAttribute attribute) { + Map collectionAttributeEntries = new HashMap<>(); + JoinTable joinTable = attribute.getJoinTable(); + if (joinTable == null) { + throw new IllegalArgumentException("No support for inserting into inverse collection via DML API yet!"); + } + + ExtendedManagedType extendedManagedType = metamodel.getManagedType(ExtendedManagedType.class, entityType); + for (String idAttributeName : joinTable.getIdAttributeNames()) { + collectionAttributeEntries.put(idAttributeName, extendedManagedType.getAttribute(idAttributeName)); + } + if (((PluralAttribute) attribute.getAttribute()).getElementType() instanceof ManagedType) { + String prefix = attribute.getAttributePathString() + "."; + for (Map.Entry> entry : extendedManagedType.getAttributes().entrySet()) { + if (entry.getKey().startsWith(prefix)) { + collectionAttributeEntries.put(entry.getKey(), entry.getValue()); + } + } + } + + collectionAttributeEntries.put(attribute.getAttributePathString(), attribute); + return collectionAttributeEntries; + } + public static AttributeHolder getAttributeForJoining(EntityMetamodel metamodel, PathExpression expression) { JoinNode expressionBaseNode = ((JoinNode) expression.getPathReference().getBaseNode()); String firstElementString = expression.getExpressions().get(0).toString(); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/ManagedEntityAssociationParameterTransformerFactory.java b/core/impl/src/main/java/com/blazebit/persistence/impl/ManagedEntityAssociationParameterTransformerFactory.java index 07ca683139..7f83c5c11f 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/ManagedEntityAssociationParameterTransformerFactory.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/ManagedEntityAssociationParameterTransformerFactory.java @@ -17,6 +17,7 @@ package com.blazebit.persistence.impl; import javax.persistence.EntityManager; +import javax.persistence.Query; /** * @author Christian Beikov @@ -35,6 +36,12 @@ public ManagedEntityAssociationParameterTransformerFactory(EntityManager em, Par @Override public ParameterValueTransformer getToEntityTranformer(final Class entityType) { return new ParameterValueTransformer() { + + @Override + public ParameterValueTransformer forQuery(Query query) { + return this; + } + @Override public Object transform(Object originalValue) { return em.getReference(entityType, originalValue); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/PaginatedCriteriaBuilderImpl.java b/core/impl/src/main/java/com/blazebit/persistence/impl/PaginatedCriteriaBuilderImpl.java index ff81655d0c..f3786af3f5 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/PaginatedCriteriaBuilderImpl.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/PaginatedCriteriaBuilderImpl.java @@ -707,12 +707,13 @@ private String appendSimplePageIdQueryString(StringBuilder sbSelectFrom) { // TODO: if we do so, the page position function has to omit select items other than the first List whereClauseConjuncts = new ArrayList<>(); + List optionalWhereClauseConjuncts = new ArrayList<>(); // The id query does not have any fetch owners // Note that we always exclude the nodes with group by dependency. We consider just the ones from the identifiers Set idNodesToFetch = Collections.emptySet(); Set identifierExpressionsToUseNonRootJoinNodes = getIdentifierExpressionsToUseNonRootJoinNodes(); - joinManager.buildClause(sbSelectFrom, ID_QUERY_CLAUSE_EXCLUSIONS, PAGE_POSITION_ID_QUERY_ALIAS_PREFIX, false, false, true, whereClauseConjuncts, null, explicitVersionEntities, idNodesToFetch, identifierExpressionsToUseNonRootJoinNodes); - whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, null); + joinManager.buildClause(sbSelectFrom, ID_QUERY_CLAUSE_EXCLUSIONS, PAGE_POSITION_ID_QUERY_ALIAS_PREFIX, false, false, true, optionalWhereClauseConjuncts, whereClauseConjuncts, null, explicitVersionEntities, idNodesToFetch, identifierExpressionsToUseNonRootJoinNodes); + whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, null); boolean inverseOrder = false; @@ -756,14 +757,15 @@ private String buildPageIdQueryString(StringBuilder sbSelectFrom, boolean extern } List whereClauseConjuncts = new ArrayList<>(); + List optionalWhereClauseConjuncts = new ArrayList<>(); // The id query does not have any fetch owners // Note that we always exclude the nodes with group by dependency. We consider just the ones from the identifiers Set idNodesToFetch = Collections.emptySet(); Set identifierExpressionsToUseNonRootJoinNodes = getIdentifierExpressionsToUseNonRootJoinNodes(); - joinManager.buildClause(sbSelectFrom, ID_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, null, false, externalRepresentation, true, whereClauseConjuncts, null, explicitVersionEntities, idNodesToFetch, identifierExpressionsToUseNonRootJoinNodes); + joinManager.buildClause(sbSelectFrom, ID_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, null, false, externalRepresentation, true, optionalWhereClauseConjuncts, whereClauseConjuncts, null, explicitVersionEntities, idNodesToFetch, identifierExpressionsToUseNonRootJoinNodes); if (keysetMode == KeysetMode.NONE) { - whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, null); + whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, null); } else { sbSelectFrom.append(" WHERE "); @@ -776,7 +778,7 @@ private String buildPageIdQueryString(StringBuilder sbSelectFrom, boolean extern if (whereManager.hasPredicates() || !whereClauseConjuncts.isEmpty()) { sbSelectFrom.append(" AND "); - whereManager.buildClausePredicate(sbSelectFrom, whereClauseConjuncts, null); + whereManager.buildClausePredicate(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, null); } } @@ -810,7 +812,7 @@ protected String buildBaseQueryString(boolean externalRepresentation) { @Override protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean externalRepresentation) { - selectManager.buildSelect(sbSelectFrom, false); + selectManager.buildSelect(sbSelectFrom, false, externalRepresentation); /** * we have already selected the IDs so now we only need so select the @@ -819,7 +821,9 @@ protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean external * ORDER_BY clause do not depend on */ List whereClauseConjuncts = new ArrayList<>(); - joinManager.buildClause(sbSelectFrom, OBJECT_QUERY_CLAUSE_EXCLUSIONS, null, false, externalRepresentation, false, whereClauseConjuncts, null, explicitVersionEntities, nodesToFetch, Collections.EMPTY_SET); + // We always have a where clause, so no need for an separate collection + List optionalWhereClauseConjuncts = whereClauseConjuncts; + joinManager.buildClause(sbSelectFrom, OBJECT_QUERY_CLAUSE_EXCLUSIONS, null, false, externalRepresentation, false, optionalWhereClauseConjuncts, whereClauseConjuncts, null, explicitVersionEntities, nodesToFetch, Collections.EMPTY_SET); sbSelectFrom.append(" WHERE "); ResolvedExpression[] identifierExpressions = getIdentifierExpressions(); @@ -872,17 +876,18 @@ private String buildObjectQueryString(boolean externalRepresentation) { } private String buildObjectQueryString(StringBuilder sbSelectFrom, boolean externalRepresentation) { - selectManager.buildSelect(sbSelectFrom, false); + selectManager.buildSelect(sbSelectFrom, false, externalRepresentation); if (keysetExtraction) { orderByManager.buildSelectClauses(sbSelectFrom, true, keysetToSelectIndexMapping); } List whereClauseConjuncts = new ArrayList<>(); - joinManager.buildClause(sbSelectFrom, NO_CLAUSE_EXCLUSION, null, false, externalRepresentation, false, whereClauseConjuncts, null, explicitVersionEntities, nodesToFetch, Collections.EMPTY_SET); + List optionalWhereClauseConjuncts = new ArrayList<>(); + joinManager.buildClause(sbSelectFrom, NO_CLAUSE_EXCLUSION, null, false, externalRepresentation, false, optionalWhereClauseConjuncts, whereClauseConjuncts, null, explicitVersionEntities, nodesToFetch, Collections.EMPTY_SET); if (keysetMode == KeysetMode.NONE) { - whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, null); + whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, null); } else { sbSelectFrom.append(" WHERE "); @@ -895,7 +900,7 @@ private String buildObjectQueryString(StringBuilder sbSelectFrom, boolean extern if (whereManager.hasPredicates() || !whereClauseConjuncts.isEmpty()) { sbSelectFrom.append(" AND "); - whereManager.buildClausePredicate(sbSelectFrom, whereClauseConjuncts, null); + whereManager.buildClausePredicate(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, null); } } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/ParameterValueTransformer.java b/core/impl/src/main/java/com/blazebit/persistence/impl/ParameterValueTransformer.java index 2b93810bac..d7ab41aded 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/ParameterValueTransformer.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/ParameterValueTransformer.java @@ -16,12 +16,16 @@ package com.blazebit.persistence.impl; +import javax.persistence.Query; + /** * @author Christian Beikov * @since 1.2.0 */ public interface ParameterValueTransformer { + public ParameterValueTransformer forQuery(Query query); + public Object transform(Object originalValue); } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/PredicateManager.java b/core/impl/src/main/java/com/blazebit/persistence/impl/PredicateManager.java index 1cdab5e5d5..f0c30bee24 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/PredicateManager.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/PredicateManager.java @@ -211,10 +211,10 @@ boolean hasPredicates() { } void buildClause(StringBuilder sb) { - buildClause(sb, Collections.emptyList(), null); + buildClause(sb, Collections.emptyList(), Collections.emptyList(), null); } - void buildClause(StringBuilder sb, List additionalConjuncts, List endConjuncts) { + void buildClause(StringBuilder sb, List additionalConjuncts, List optionalConjuncts, List endConjuncts) { if (!hasPredicates() && additionalConjuncts.isEmpty()) { return; } @@ -222,14 +222,15 @@ void buildClause(StringBuilder sb, List additionalConjuncts, List additionalConjuncts, List endConjuncts) { + void buildClausePredicate(StringBuilder sb, List additionalConjuncts, List optionalConjuncts, List endConjuncts) { int size = additionalConjuncts.size(); + boolean hasPredicates = size > 0; for (int i = 0; i < size; i++) { sb.append(additionalConjuncts.get(i)); sb.append(" AND "); @@ -240,14 +241,27 @@ void buildClausePredicate(StringBuilder sb, List additionalConjuncts, Li int oldLength = sb.length(); applyPredicate(queryGenerator); queryGenerator.setClauseType(null); - if (sb.length() == oldLength && size > 0) { - sb.setLength(sb.length() - " AND ".length()); + if (sb.length() == oldLength) { + if (size > 0) { + sb.setLength(sb.length() - " AND ".length()); + } + } else { + hasPredicates = true; } if (endConjuncts != null && !endConjuncts.isEmpty()) { - for (String endConjunct : endConjuncts) { + for (int i = 0; i < optionalConjuncts.size(); i++) { + sb.append(" AND "); + sb.append(optionalConjuncts.get(i)); + } + for (int i = 0; i < endConjuncts.size(); i++) { + sb.append(" AND "); + sb.append(endConjuncts.get(i)); + } + } else if (hasPredicates) { + for (int i = 0; i < optionalConjuncts.size(); i++) { sb.append(" AND "); - sb.append(endConjunct); + sb.append(optionalConjuncts.get(i)); } } } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/ResolvingQueryGenerator.java b/core/impl/src/main/java/com/blazebit/persistence/impl/ResolvingQueryGenerator.java index fc9a88f24c..bc02d29982 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/ResolvingQueryGenerator.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/ResolvingQueryGenerator.java @@ -398,7 +398,7 @@ public void visit(PathExpression expression) { sb.append(aliasPrefix); } - baseNode.appendAlias(sb); + baseNode.appendAlias(sb, externalRepresentation); if (valueFunction) { sb.append(')'); @@ -458,7 +458,7 @@ public void visit(PathExpression expression) { sb.append(aliasPrefix); } - baseNode.appendAlias(sb, renderTreat); + baseNode.appendAlias(sb, renderTreat, externalRepresentation); sb.append(')'); sb.append(".").append(field); } else { @@ -466,7 +466,7 @@ public void visit(PathExpression expression) { sb.append(aliasPrefix); } - baseNode.appendDeReference(sb, field, renderTreat); + baseNode.appendDeReference(sb, field, renderTreat, externalRepresentation); } if (addTypeCaseWhen) { diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/SelectManager.java b/core/impl/src/main/java/com/blazebit/persistence/impl/SelectManager.java index ee2a54b18c..236acd5aa7 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/SelectManager.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/SelectManager.java @@ -260,7 +260,7 @@ void buildGroupByClauses(final EntityMetamodel m, GroupByManager groupByManager, groupByExpressionGatheringVisitor.clear(); } - void buildSelect(StringBuilder sb, boolean isInsertInto) { + void buildSelect(StringBuilder sb, boolean isInsertInto, boolean externalRepresentation) { sb.append("SELECT "); if (distinct) { @@ -271,7 +271,7 @@ void buildSelect(StringBuilder sb, boolean isInsertInto) { int size = selectInfos.size(); if (size == 0) { JoinNode rootNode = joinManager.getRootNodeOrFail("Empty select not allowed when having multiple roots!"); - rootNode.appendAlias(sb); + rootNode.appendAlias(sb, externalRepresentation); } else { // we must not replace select alias since we would loose the original expressions queryGenerator.setClauseType(ClauseType.SELECT); @@ -395,18 +395,30 @@ Class getExpectedQueryResultType() { } void select(Expression expr, String selectAlias) { + select(expr, selectAlias, -1); + } + + void select(Expression expr, String selectAlias, int index) { verifyBuilderEnded(); clearDefaultSelects(); - selectInternal(expr, selectAlias); + selectInternal(expr, selectAlias, index); } private void selectInternal(Expression expr, String selectAlias) { + selectInternal(expr, selectAlias, -1); + } + + private void selectInternal(Expression expr, String selectAlias, int index) { SelectInfo selectInfo = new SelectInfo(expr, selectAlias, aliasManager); if (selectAlias != null) { aliasManager.registerAliasInfo(selectInfo); selectAliasToPositionMap.put(selectAlias, selectAliasToPositionMap.size()); } - selectInfos.add(selectInfo); + if (index == -1) { + selectInfos.add(selectInfo); + } else { + selectInfos.add(index, selectInfo); + } hasSizeSelect = hasSizeSelect || ExpressionUtils.containsSizeExpression(selectInfo.getExpression()); registerParameterExpressions(expr); diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/SimplePathReference.java b/core/impl/src/main/java/com/blazebit/persistence/impl/SimplePathReference.java index 0cd967c023..4982cf64a0 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/SimplePathReference.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/SimplePathReference.java @@ -63,7 +63,7 @@ public From getFrom() { @Override public String getPath() { StringBuilder sb = new StringBuilder(); - baseNode.appendDeReference(sb, field); + baseNode.appendDeReference(sb, field, false); return sb.toString(); } diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/SplittingParameterTransformer.java b/core/impl/src/main/java/com/blazebit/persistence/impl/SplittingParameterTransformer.java new file mode 100644 index 0000000000..df7adaf36d --- /dev/null +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/SplittingParameterTransformer.java @@ -0,0 +1,148 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.impl; + +import com.blazebit.persistence.parser.EntityMetamodel; +import com.blazebit.persistence.parser.util.JpaMetamodelUtils; +import com.blazebit.reflection.ReflectionUtils; + +import javax.persistence.Query; +import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.ManagedType; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +/** + * @author Christian Beikov + * @since 1.3.0 + */ +public class SplittingParameterTransformer implements ParameterValueTransformer { + + private final ParameterManager parameterManager; + private final String[] parameterNames; + private final Field[][] fields; + private final Method[][] getters; + + public SplittingParameterTransformer(ParameterManager parameterManager, EntityMetamodel metamodel, Class parameterType, Map> parameterAccessPaths) { + try { + this.parameterManager = parameterManager; + this.parameterNames = parameterAccessPaths.keySet().toArray(new String[parameterAccessPaths.size()]); + Field[][] fields = new Field[parameterAccessPaths.size()][]; + Method[][] getters = new Method[parameterAccessPaths.size()][]; + + int i = 0; + for (List accessPath : parameterAccessPaths.values()) { + ManagedType t = metamodel.getManagedType(parameterType); + fields[i] = new Field[accessPath.size()]; + getters[i] = new Method[accessPath.size()]; + int j = 0; + for (String property : accessPath) { + Attribute attribute = t.getAttribute(property); + Member member = attribute.getJavaMember(); + + if (member instanceof Method) { + Method getter = ReflectionUtils.getGetter(t.getJavaType(), attribute.getName()); + getter.setAccessible(true); + getters[i][j++] = getter; + } else if (member instanceof Field) { + Field field = (Field) member; + field.setAccessible(true); + fields[i][j++] = field; + } else { + throw new IllegalArgumentException("Unsupported attribute member type [" + member + "] for attribute [" + attribute.getName() + "] of class [" + t.getJavaType().getName() + "]"); + } + t = metamodel.getManagedType(JpaMetamodelUtils.resolveFieldClass(t.getJavaType(), attribute)); + } + i++; + } + + this.fields = fields; + this.getters = getters; + } catch (Exception e) { + throw new IllegalArgumentException("The parameter splitter for the managed type [" + parameterType.getName() + "] could not be initialized!", e); + } + } + + @Override + public ParameterValueTransformer forQuery(final Query query) { + return new ParameterValueTransformer() { + @Override + public ParameterValueTransformer forQuery(Query query) { + return SplittingParameterTransformer.this.forQuery(query); + } + + @Override + public Object transform(Object originalValue) { + try { + for (int i = 0; i < parameterNames.length; i++) { + Object o = originalValue; + if (o != null) { + Field[] fieldAccess = fields[i]; + Method[] methodAccess = getters[i]; + for (int j = 0; j < fieldAccess.length; j++) { + if (fieldAccess[j] != null) { + o = fieldAccess[j].get(o); + } else { + o = methodAccess[j].invoke(o); + } + if (o == null) { + break; + } + } + } + query.setParameter(parameterNames[i], o); + } + return originalValue; + } catch (Exception ex) { + throw new IllegalArgumentException("Could not split parameter value [" + originalValue + "]", ex); + } + } + }; + } + + public String[] getParameterNames() { + return parameterNames; + } + + @Override + public Object transform(Object originalValue) { + try { + for (int i = 0; i < parameterNames.length; i++) { + Field[] fieldAccess = fields[i]; + Method[] methodAccess = getters[i]; + Object o = originalValue; + for (int j = 0; j < fieldAccess.length; j++) { + if (fieldAccess[j] != null) { + o = fieldAccess[j].get(o); + } else { + o = methodAccess[j].invoke(o); + } + if (o == null) { + break; + } + } + parameterManager.satisfyParameter(parameterNames[i], o); + } + return originalValue; + } catch (Exception ex) { + throw new IllegalArgumentException("Could not split parameter value [" + originalValue + "]", ex); + } + } +} diff --git a/core/impl/src/main/java/com/blazebit/persistence/impl/query/AbstractCustomQuery.java b/core/impl/src/main/java/com/blazebit/persistence/impl/query/AbstractCustomQuery.java index 521a7e78d0..c23108c78a 100644 --- a/core/impl/src/main/java/com/blazebit/persistence/impl/query/AbstractCustomQuery.java +++ b/core/impl/src/main/java/com/blazebit/persistence/impl/query/AbstractCustomQuery.java @@ -30,6 +30,7 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -68,7 +69,12 @@ public AbstractCustomQuery(QuerySpecification querySpecification, Map newTransformers = new HashMap<>(transformers.size()); + for (Map.Entry entry : transformers.entrySet()) { + newTransformers.put(entry.getKey(), entry.getValue().forQuery(this)); + } + + this.transformers = Collections.unmodifiableMap(newTransformers); this.valuesParameters = Collections.unmodifiableMap(valuesParameterMap); this.valuesElementParameters = Collections.unmodifiableMap(valuesParameters); this.parameters = Collections.unmodifiableMap(parameters); @@ -139,7 +145,24 @@ protected void bindParameters() { } } if (missingParameters != null && !missingParameters.isEmpty()) { - throw new IllegalArgumentException("The following parameters have not been set: " + missingParameters); + // Re-Check since a transformer could spread values + Iterator iterator = missingParameters.iterator(); + while (iterator.hasNext()) { + String missingParamName = iterator.next(); + String valuesName = valuesElementParameters.get(missingParamName); + if (valuesName == null) { + if (valueBinders.get(missingParamName) != null) { + iterator.remove(); + } + } else { + if (valuesParameters.get(valuesName).getValue() != null) { + iterator.remove(); + } + } + } + if (!missingParameters.isEmpty()) { + throw new IllegalArgumentException("The following parameters have not been set: " + missingParameters); + } } } diff --git a/core/parser/src/main/java/com/blazebit/persistence/parser/PathTargetResolvingExpressionVisitor.java b/core/parser/src/main/java/com/blazebit/persistence/parser/PathTargetResolvingExpressionVisitor.java index eeab3236bf..2fa353228d 100644 --- a/core/parser/src/main/java/com/blazebit/persistence/parser/PathTargetResolvingExpressionVisitor.java +++ b/core/parser/src/main/java/com/blazebit/persistence/parser/PathTargetResolvingExpressionVisitor.java @@ -89,19 +89,19 @@ public class PathTargetResolvingExpressionVisitor implements Expression.Visitor private final EntityMetamodel metamodel; private final String skipBaseNodeAlias; - private PathPosition currentPosition; - private List pathPositions; + protected PathPosition currentPosition; + protected List pathPositions; /** * @author Christian Beikov * @since 1.0.0 */ - private static class PathPosition { + protected static class PathPosition { - private Type currentClass; - private Type valueClass; - private Type keyClass; - private Attribute attribute; + public Type currentClass; + public Type valueClass; + public Type keyClass; + public Attribute attribute; PathPosition(Type currentClass, Attribute attribute) { this.currentClass = currentClass; @@ -516,11 +516,11 @@ public void visit(ExistsPredicate predicate) { invalid(predicate); } - private void invalid(Object o) { + protected final void invalid(Object o) { throw new IllegalArgumentException("Illegal occurence of [" + o + "] in path chain resolver!"); } - private void invalid(Object o, String reason) { + protected final void invalid(Object o, String reason) { throw new IllegalArgumentException("Illegal occurence of [" + o + "] in path chain resolver! " + reason); } diff --git a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/DocumentInfoSimple.java b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/DocumentInfoSimple.java index 57b90c63b7..7b71540a4e 100644 --- a/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/DocumentInfoSimple.java +++ b/core/testsuite/src/main/java/com/blazebit/persistence/testsuite/entity/DocumentInfoSimple.java @@ -72,4 +72,23 @@ public String getSomeInfo() { public void setSomeInfo(String someInfo) { this.someInfo = someInfo; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof DocumentInfoSimple)) { + return false; + } + + DocumentInfoSimple that = (DocumentInfoSimple) o; + + return getId() != null ? getId().equals(that.getId()) : that.getId() == null; + } + + @Override + public int hashCode() { + return getId() != null ? getId().hashCode() : 0; + } } diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/CollectionRoleInsertTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/CollectionRoleInsertTest.java index b20a5a7c8d..980f5ff3a1 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/CollectionRoleInsertTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/CollectionRoleInsertTest.java @@ -114,7 +114,7 @@ public void insertIndexedAccessOtherAttributes() { try { criteria.bind("name"); } catch (IllegalArgumentException ex) { - assertTrue(ex.getMessage().contains("Only access to the owner type's id attribute")); + assertTrue(ex.getMessage().contains("The attribute [name] does not exist or can't be bound")); } } @@ -129,8 +129,8 @@ public void work(EntityManager em) { criteria.bind("INDEX(indexedNodes)").select("1"); criteria.bind("indexedNodes.id").select("4"); - assertEquals("INSERT INTO Root.indexedNodes(INDEX(_collection), _collection.id, root.id)\n" - + "SELECT 1, 4, 1" + assertEquals("INSERT INTO Root.indexedNodes(INDEX(indexedNodes), id, indexedNodes.id)\n" + + "SELECT 1, 1, 4" + " FROM Integer(1 VALUES) valuesAlias" + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); int updated = criteria.executeUpdate(); @@ -162,8 +162,8 @@ public void work(EntityManager em) { criteria.bind("INDEX(indexedNodes)").select("1"); criteria.bind("indexedNodes.id").select("4"); - assertEquals("INSERT INTO Root.indexedNodes(INDEX(_collection), _collection.id, root.id)\n" - + "SELECT 1, 4, 1" + assertEquals("INSERT INTO Root.indexedNodes(INDEX(indexedNodes), id, indexedNodes.id)\n" + + "SELECT 1, 1, 4" + " FROM Integer(1 VALUES) valuesAlias" + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); ReturningResult returningResult = criteria.executeWithReturning("indexedNodes.id"); @@ -192,8 +192,8 @@ public void work(EntityManager em) { criteria.bind("INDEX(indexedNodesMany)").select("1"); criteria.bind("indexedNodesMany.id").select("4"); - assertEquals("INSERT INTO Root.indexedNodesMany(INDEX(_collection), _collection.id, root.id)\n" - + "SELECT 1, 4, 1" + assertEquals("INSERT INTO Root.indexedNodesMany(INDEX(indexedNodesMany), id, indexedNodesMany.id)\n" + + "SELECT 1, 1, 4" + " FROM Integer(1 VALUES) valuesAlias" + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); int updated = criteria.executeUpdate(); @@ -221,8 +221,8 @@ public void work(EntityManager em) { criteria.bind("INDEX(indexedNodesManyDuplicate)").select("1"); criteria.bind("indexedNodesManyDuplicate.id").select("4"); - assertEquals("INSERT INTO Root.indexedNodesManyDuplicate(INDEX(_collection), _collection.id, root.id)\n" - + "SELECT 1, 4, 1" + assertEquals("INSERT INTO Root.indexedNodesManyDuplicate(INDEX(indexedNodesManyDuplicate), id, indexedNodesManyDuplicate.id)\n" + + "SELECT 1, 1, 4" + " FROM Integer(1 VALUES) valuesAlias" + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); int updated = criteria.executeUpdate(); @@ -251,8 +251,8 @@ public void work(EntityManager em) { criteria.bind("indexedNodesElementCollection.value").select("'B'"); criteria.bind("indexedNodesElementCollection.value2").select("'P'"); - assertEquals("INSERT INTO Root.indexedNodesElementCollection(INDEX(_collection), _collection.value, _collection.value2, root.id)\n" - + "SELECT 1, 'B', 'P', 1" + assertEquals("INSERT INTO Root.indexedNodesElementCollection(INDEX(indexedNodesElementCollection), id, indexedNodesElementCollection.value, indexedNodesElementCollection.value2)\n" + + "SELECT 1, 1, 'B', 'P'" + " FROM Integer(1 VALUES) valuesAlias" + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); int updated = criteria.executeUpdate(); @@ -281,8 +281,8 @@ public void work(EntityManager em) { criteria.bind("KEY(keyedNodes)").select("'b'"); criteria.bind("keyedNodes.id").select("5"); - assertEquals("INSERT INTO Root.keyedNodes(KEY(_collection), _collection.id, root.id)\n" - + "SELECT 'b', 5, 1" + assertEquals("INSERT INTO Root.keyedNodes(KEY(keyedNodes), id, keyedNodes.id)\n" + + "SELECT 'b', 1, 5" + " FROM Integer(1 VALUES) valuesAlias" + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); int updated = criteria.executeUpdate(); @@ -310,8 +310,8 @@ public void work(EntityManager em) { criteria.bind("KEY(keyedNodesMany)").select("'b'"); criteria.bind("keyedNodesMany.id").select("5"); - assertEquals("INSERT INTO Root.keyedNodesMany(KEY(_collection), _collection.id, root.id)\n" - + "SELECT 'b', 5, 1" + assertEquals("INSERT INTO Root.keyedNodesMany(KEY(keyedNodesMany), id, keyedNodesMany.id)\n" + + "SELECT 'b', 1, 5" + " FROM Integer(1 VALUES) valuesAlias" + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); int updated = criteria.executeUpdate(); @@ -339,8 +339,8 @@ public void work(EntityManager em) { criteria.bind("KEY(keyedNodesManyDuplicate)").select("'b'"); criteria.bind("keyedNodesManyDuplicate.id").select("5"); - assertEquals("INSERT INTO Root.keyedNodesManyDuplicate(KEY(_collection), _collection.id, root.id)\n" - + "SELECT 'b', 5, 1" + assertEquals("INSERT INTO Root.keyedNodesManyDuplicate(KEY(keyedNodesManyDuplicate), id, keyedNodesManyDuplicate.id)\n" + + "SELECT 'b', 1, 5" + " FROM Integer(1 VALUES) valuesAlias" + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); int updated = criteria.executeUpdate(); @@ -369,8 +369,8 @@ public void work(EntityManager em) { criteria.bind("keyedNodesElementCollection.value").select("'B'"); criteria.bind("keyedNodesElementCollection.value2").select("'P'"); - assertEquals("INSERT INTO Root.keyedNodesElementCollection(KEY(_collection), _collection.value, _collection.value2, root.id)\n" - + "SELECT 'b', 'B', 'P', 1" + assertEquals("INSERT INTO Root.keyedNodesElementCollection(KEY(keyedNodesElementCollection), id, keyedNodesElementCollection.value, keyedNodesElementCollection.value2)\n" + + "SELECT 'b', 1, 'B', 'P'" + " FROM Integer(1 VALUES) valuesAlias" + " WHERE TREAT_INTEGER(valuesAlias.value) = :valuesAlias_value_0", criteria.getQueryString()); int updated = criteria.executeUpdate(); diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/CollectionRoleUpdateTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/CollectionRoleUpdateTest.java index f8105b2e76..6b0e37b5fc 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/CollectionRoleUpdateTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/CollectionRoleUpdateTest.java @@ -111,7 +111,7 @@ public void updateIndexedAccessOtherAttributes() { try { criteria.set("name", "BLA"); } catch (IllegalArgumentException ex) { - assertTrue(ex.getMessage().contains("Only access to the owner type's id attribute")); + assertTrue(ex.getMessage().contains("The attribute [name] does not exist or can't be bound")); } } @@ -128,7 +128,7 @@ public void work(EntityManager em) { criteria.where("r.indexedNodes.id").eq(2); assertEquals("UPDATE Root(indexedNodes) r" - + " SET INDEX(_collection) = :param_0,_collection.id = :param_1" + + " SET INDEX(indexedNodes) = :param_0,r.indexedNodes.id = :param_1" + " WHERE INDEX(_collection) = :param_2 AND r.id = :param_3 AND _collection.id = :param_4", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -158,7 +158,7 @@ public void work(EntityManager em) { criteria.where("r.id").eq(1); assertEquals("UPDATE Root(indexedNodes) r" - + " SET INDEX(_collection) = :param_0,_collection.id = :param_1" + + " SET INDEX(indexedNodes) = :param_0,r.indexedNodes.id = :param_1" + " WHERE INDEX(_collection) = :param_2 AND r.id = :param_3", criteria.getQueryString()); ReturningResult returningResult = criteria.executeWithReturning("indexedNodes.id"); Root r = getRoot(em); @@ -188,7 +188,7 @@ public void work(EntityManager em) { criteria.where("r.indexedNodesMany.id").eq(2); assertEquals("UPDATE Root(indexedNodesMany) r" - + " SET INDEX(_collection) = :param_0,_collection.id = :param_1" + + " SET INDEX(indexedNodesMany) = :param_0,r.indexedNodesMany.id = :param_1" + " WHERE INDEX(_collection) = :param_2 AND r.id = :param_3 AND _collection.id = :param_4", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -217,7 +217,7 @@ public void work(EntityManager em) { criteria.where("r.indexedNodesManyDuplicate.id").eq(2); assertEquals("UPDATE Root(indexedNodesManyDuplicate) r" - + " SET INDEX(_collection) = :param_0,_collection.id = :param_1" + + " SET INDEX(indexedNodesManyDuplicate) = :param_0,r.indexedNodesManyDuplicate.id = :param_1" + " WHERE INDEX(_collection) = :param_2 AND r.id = :param_3 AND _collection.id = :param_4", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -248,7 +248,7 @@ public void work(EntityManager em) { criteria.where("r.indexedNodesElementCollection.value2").eq("b"); assertEquals("UPDATE Root(indexedNodesElementCollection) r" - + " SET INDEX(_collection) = :param_0,_collection.value = :param_1,_collection.value2 = :param_2" + + " SET INDEX(indexedNodesElementCollection) = :param_0,r.indexedNodesElementCollection.value = :param_1,r.indexedNodesElementCollection.value2 = :param_2" + " WHERE INDEX(_collection) = :param_3 AND r.id = :param_4 AND _collection.value = :param_5 AND _collection.value2 = :param_6", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -278,7 +278,7 @@ public void work(EntityManager em) { criteria.where("r.keyedNodes.id").eq(3); assertEquals("UPDATE Root(keyedNodes) r" - + " SET KEY(_collection) = :param_0,_collection.id = :param_1" + + " SET KEY(keyedNodes) = :param_0,r.keyedNodes.id = :param_1" + " WHERE KEY(_collection) = :param_2 AND r.id = :param_3 AND _collection.id = :param_4", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -307,7 +307,7 @@ public void work(EntityManager em) { criteria.where("r.keyedNodesMany.id").eq(3); assertEquals("UPDATE Root(keyedNodesMany) r" - + " SET KEY(_collection) = :param_0,_collection.id = :param_1" + + " SET KEY(keyedNodesMany) = :param_0,r.keyedNodesMany.id = :param_1" + " WHERE KEY(_collection) = :param_2 AND r.id = :param_3 AND _collection.id = :param_4", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -336,7 +336,7 @@ public void work(EntityManager em) { criteria.where("r.keyedNodesManyDuplicate.id").eq(3); assertEquals("UPDATE Root(keyedNodesManyDuplicate) r" - + " SET KEY(_collection) = :param_0,_collection.id = :param_1" + + " SET KEY(keyedNodesManyDuplicate) = :param_0,r.keyedNodesManyDuplicate.id = :param_1" + " WHERE KEY(_collection) = :param_2 AND r.id = :param_3 AND _collection.id = :param_4", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); @@ -367,7 +367,7 @@ public void work(EntityManager em) { criteria.where("r.keyedNodesElementCollection.value2").eq("b"); assertEquals("UPDATE Root(keyedNodesElementCollection) r" - + " SET KEY(_collection) = :param_0,_collection.value = :param_1,_collection.value2 = :param_2" + + " SET KEY(keyedNodesElementCollection) = :param_0,r.keyedNodesElementCollection.value = :param_1,r.keyedNodesElementCollection.value2 = :param_2" + " WHERE KEY(_collection) = :param_3 AND r.id = :param_4 AND _collection.value = :param_5 AND _collection.value2 = :param_6", criteria.getQueryString()); int updated = criteria.executeUpdate(); Root r = getRoot(em); diff --git a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/ValuesClauseTest.java b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/ValuesClauseTest.java index 43da78b0ae..87b16f38dd 100644 --- a/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/ValuesClauseTest.java +++ b/core/testsuite/src/test/java/com/blazebit/persistence/testsuite/ValuesClauseTest.java @@ -74,7 +74,7 @@ public void setUpOnce() { public void work(EntityManager em) { p1 = new Person("p1"); d1 = new Document("doc1", 1); - + d1.setNameObject(new NameObject("123", "abc")); d1.setOwner(p1); em.persist(p1); @@ -95,12 +95,12 @@ public void testValuesEntityFunction() { CriteriaBuilder cb = cbf.create(em, Tuple.class); cb.fromValues(Long.class, "allowedAge", Collections.singleton(1L)); cb.from(Document.class, "doc"); - cb.where("doc.age").eqExpression("allowedAge.value"); + cb.where("doc.age").eqExpression("allowedAge"); cb.select("doc.name"); - cb.select("allowedAge.value"); + cb.select("allowedAge"); String expected = "" - + "SELECT doc.name, TREAT_LONG(allowedAge.value) FROM Document doc, Long(1 VALUES) allowedAge WHERE TREAT_LONG(allowedAge.value) = :allowedAge_value_0 AND doc.age = TREAT_LONG(allowedAge.value)"; + + "SELECT doc.name, allowedAge FROM Document doc, Long(1 VALUES) allowedAge WHERE TREAT_LONG(allowedAge.value) = :allowedAge_value_0 AND doc.age = allowedAge"; assertEquals(expected, cb.getQueryString()); List resultList = cb.getResultList(); @@ -109,6 +109,29 @@ public void testValuesEntityFunction() { assertEquals(1L, resultList.get(0).get(1)); } + @Test + @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) + public void testValuesEntityFunctionWithEmbeddable() { + CriteriaBuilder cb = cbf.create(em, Tuple.class); + cb.fromValues(NameObject.class, "embeddable", Collections.singleton(new NameObject("abc", "123"))); + cb.from(Document.class, "doc"); + cb.where("doc.nameObject.primaryName").eqExpression("embeddable.secondaryName"); + cb.where("doc.nameObject.secondaryName").eqExpression("embeddable.primaryName"); + cb.select("doc.name"); + cb.select("embeddable"); + + String expected = "" + + "SELECT doc.name, embeddable FROM Document doc, NameObject(1 VALUES) embeddable" + + " WHERE embeddable.intIdEntity = :embeddable_intIdEntity_0 OR embeddable.primaryName = :embeddable_primaryName_0 OR embeddable.secondaryName = :embeddable_secondaryName_0" + + " AND doc.nameObject.primaryName = embeddable.secondaryName AND doc.nameObject.secondaryName = embeddable.primaryName"; + + assertEquals(expected, cb.getQueryString()); + List resultList = cb.getResultList(); + assertEquals(1, resultList.size()); + assertEquals("doc1", resultList.get(0).get(0)); + assertEquals(new NameObject("abc", "123"), resultList.get(0).get(1)); + } + // Test for #305 @Test @Category({ NoDatanucleus.class, NoEclipselink.class, NoOpenJPA.class }) @@ -116,13 +139,13 @@ public void testValuesEntityFunctionWithParameterInSelect() { CriteriaBuilder cb = cbf.create(em, Tuple.class); cb.fromValues(Long.class, "allowedAge", Arrays.asList(1L, 2L)); cb.from(Document.class, "doc"); - cb.where("doc.age").eqExpression("allowedAge.value"); + cb.where("doc.age").eqExpression("allowedAge"); cb.select("CASE WHEN doc.name = :param THEN doc.name ELSE '' END"); - cb.select("allowedAge.value"); + cb.select("allowedAge"); String expected = "" - + "SELECT CASE WHEN doc.name = :param THEN doc.name ELSE '' END, TREAT_LONG(allowedAge.value) FROM Document doc, Long(2 VALUES) allowedAge " + - "WHERE TREAT_LONG(allowedAge.value) = :allowedAge_value_0 OR TREAT_LONG(allowedAge.value) = :allowedAge_value_1 AND doc.age = TREAT_LONG(allowedAge.value)"; + + "SELECT CASE WHEN doc.name = :param THEN doc.name ELSE '' END, allowedAge FROM Document doc, Long(2 VALUES) allowedAge " + + "WHERE TREAT_LONG(allowedAge.value) = :allowedAge_value_0 OR TREAT_LONG(allowedAge.value) = :allowedAge_value_1 AND doc.age = allowedAge"; assertEquals(expected, cb.getQueryString()); cb.setParameter("param", "doc1"); @@ -141,8 +164,8 @@ public void testValuesEntityFunctionWithParameterInSelectSubquery() { cb.select("CASE WHEN doc.name = :param THEN doc.name ELSE '' END"); cb.selectSubquery() .fromValues(Long.class, "allowedAge", Arrays.asList(1L, 2L)) - .select("CASE WHEN doc.name = :param THEN allowedAge.value ELSE 2L END") - .where("doc.age").eqExpression("allowedAge.value") + .select("CASE WHEN doc.name = :param THEN allowedAge ELSE 2L END") + .where("doc.age").eqExpression("allowedAge") .end(); // We can't check the JPQL here because it contains SQL as literal text :| @@ -159,9 +182,9 @@ public void testValuesEntityFunctionParameters() { CriteriaBuilder cb = cbf.create(em, Tuple.class); cb.fromValues(Long.class, "allowedAge", Arrays.asList(1L, 2L)); cb.from(Document.class, "doc"); - cb.where("doc.age").eqExpression("allowedAge.value"); + cb.where("doc.age").eqExpression("allowedAge"); cb.select("doc.name"); - cb.select("allowedAge.value"); + cb.select("allowedAge"); TypedQuery query = cb.getQuery(); assertEquals(1, query.getParameters().size()); @@ -185,16 +208,16 @@ public void testValuesEntityFunctionLeftJoin() { CriteriaBuilder cb = cbf.create(em, Tuple.class); cb.fromValues(Long.class, "allowedAge", Arrays.asList(1L, 2L, 3L)); cb.leftJoinOn(Document.class, "doc") - .on("doc.age").eqExpression("allowedAge.value") + .on("doc.age").eqExpression("allowedAge") .end(); - cb.select("allowedAge.value"); + cb.select("allowedAge"); cb.select("doc.name"); - cb.orderByAsc("allowedAge.value"); + cb.orderByAsc("allowedAge"); String expected = "" - + "SELECT TREAT_LONG(allowedAge.value), doc.name FROM Long(3 VALUES) allowedAge LEFT JOIN Document doc" + - onClause("TREAT_LONG(allowedAge.value) = :allowedAge_value_0 OR TREAT_LONG(allowedAge.value) = :allowedAge_value_1 OR TREAT_LONG(allowedAge.value) = :allowedAge_value_2 AND doc.age = TREAT_LONG(allowedAge.value)") + - " ORDER BY TREAT_LONG(allowedAge.value) ASC"; + + "SELECT allowedAge, doc.name FROM Long(3 VALUES) allowedAge LEFT JOIN Document doc" + + onClause("TREAT_LONG(allowedAge.value) = :allowedAge_value_0 OR TREAT_LONG(allowedAge.value) = :allowedAge_value_1 OR TREAT_LONG(allowedAge.value) = :allowedAge_value_2 AND doc.age = allowedAge") + + " ORDER BY allowedAge ASC"; assertEquals(expected, cb.getQueryString()); List resultList = cb.getResultList(); diff --git a/dist/bom/pom.xml b/dist/bom/pom.xml index c771265771..be8f31e8ce 100644 --- a/dist/bom/pom.xml +++ b/dist/bom/pom.xml @@ -22,7 +22,7 @@ com.blazebit blaze-persistence-parent 1.3.0-SNAPSHOT - ../../parent/pom.xml + ../../pom.xml blaze-persistence-bom diff --git a/documentation/src/main/asciidoc/core/manual/en_US/04_from_clause.adoc b/documentation/src/main/asciidoc/core/manual/en_US/04_from_clause.adoc index c2da4e0f73..cf402f245e 100644 --- a/documentation/src/main/asciidoc/core/manual/en_US/04_from_clause.adoc +++ b/documentation/src/main/asciidoc/core/manual/en_US/04_from_clause.adoc @@ -434,7 +434,7 @@ The join alias that must be defined for a `VALUES` clause is reused as alias for ---- CriteriaBuilder cb = cbf.create(em, String.class) .fromValues(String.class, "myValue", 10) - .select("myValue.value") + .select("myValue") .setParameter("myValue", valueCollection); ---- @@ -468,7 +468,7 @@ In order to access the actual value in an expression, the `value` property of th Collection valueCollection = Arrays.asList("value1", "value2"); CriteriaBuilder cb = cbf.create(em, String.class) .fromValues(String.class, "myValue", valueCollection) - .select("myValue.value"); + .select("myValue"); ---- The resulting logical JPQL doesn't include individual parameters, but specifies the count of the values. The alias of the values clause from item also represents the parameter name. diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/EntityViewManagerImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/EntityViewManagerImpl.java index 875d68f770..bdbf87b575 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/EntityViewManagerImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/EntityViewManagerImpl.java @@ -206,8 +206,8 @@ public EntityViewManagerImpl(EntityViewConfigurationImpl config, CriteriaBuilder } if (Boolean.valueOf(String.valueOf(config.getProperty(ConfigurationProperties.UPDATER_EAGER_LOADING)))) { - for (ManagedViewType view : metamodel.getManagedViews()) { - getUpdater((ManagedViewTypeImplementor) view, null); + for (ManagedViewType view : metamodel.getViews()) { + getUpdater((ManagedViewTypeImplementor) view, null, null, null); } } } @@ -345,7 +345,7 @@ public SingularChangeModel getChangeModel(T entityView) { DirtyStateTrackable updatableProxy = (DirtyStateTrackable) entityView; Class entityViewClass = updatableProxy.$$_getEntityViewClass(); ManagedViewTypeImplementor viewType = (ManagedViewTypeImplementor) metamodel.managedView(entityViewClass); - EntityViewUpdater updater = getUpdater(viewType, null); + EntityViewUpdater updater = getUpdater(viewType, null, null, null); return (SingularChangeModel) new ViewChangeModel<>(viewType, updatableProxy, updater.getDirtyChecker()); } @@ -368,7 +368,7 @@ public void remove(EntityManager entityManager, Object view) { EntityViewProxy proxy = (EntityViewProxy) view; Class entityViewClass = proxy.$$_getEntityViewClass(); ManagedViewTypeImplementor viewType = metamodel.managedView(entityViewClass); - EntityViewUpdater updater = getUpdater(viewType, null); + EntityViewUpdater updater = getUpdater(viewType, null, null, null); try { if (proxy.$$_isNew()) { MutableStateTrackable updatableProxy = (MutableStateTrackable) proxy; @@ -392,7 +392,7 @@ public void remove(EntityManager entityManager, Class entityViewClass, Object if (viewType == null) { throw new IllegalArgumentException("Can't remove non entity view object: " + entityViewClass.getName()); } - EntityViewUpdater updater = getUpdater(viewType, null); + EntityViewUpdater updater = getUpdater(viewType, null, null, null); try { updater.remove(context, viewId); } catch (Throwable t) { @@ -413,7 +413,7 @@ public void update(UpdateContext context, Object view) { MutableStateTrackable updatableProxy = (MutableStateTrackable) view; Class entityViewClass = updatableProxy.$$_getEntityViewClass(); ManagedViewTypeImplementor viewType = metamodel.managedView(entityViewClass); - EntityViewUpdater updater = getUpdater(viewType, null); + EntityViewUpdater updater = getUpdater(viewType, null, null, null); try { if (updatableProxy.$$_isNew()) { updater.executePersist(context, updatableProxy); @@ -434,7 +434,7 @@ public Object persist(UpdateContext context, Object view) { MutableStateTrackable updatableProxy = (MutableStateTrackable) view; Class entityViewClass = updatableProxy.$$_getEntityViewClass(); ManagedViewTypeImplementor viewType = metamodel.managedView(entityViewClass); - EntityViewUpdater updater = getUpdater(viewType, null); + EntityViewUpdater updater = getUpdater(viewType, null, null, null); return updater.executePersist(context, updatableProxy); } @@ -617,13 +617,13 @@ public ViewTypeObjectBuilderTemplate getTemplate(ExpressionFactory ef, Manage return value; } - public EntityViewUpdater getUpdater(ManagedViewTypeImplementor viewType, ManagedViewTypeImplementor declaredViewType) { - if (declaredViewType != null && declaredViewType != viewType) { - ContextAwareUpdaterKey key = new ContextAwareUpdaterKey(viewType, declaredViewType); + public EntityViewUpdater getUpdater(ManagedViewTypeImplementor viewType, ManagedViewTypeImplementor declaredViewType, EntityViewUpdaterImpl owner, String ownerMapping) { + if (declaredViewType != null && declaredViewType != viewType || owner != null) { + ContextAwareUpdaterKey key = new ContextAwareUpdaterKey(viewType, declaredViewType, owner, ownerMapping); EntityViewUpdaterImpl value = contextAwareEntityViewUpdaterCache.get(key); if (value == null) { - value = new EntityViewUpdaterImpl(this, viewType, declaredViewType); + value = new EntityViewUpdaterImpl(this, viewType, declaredViewType, owner, ownerMapping); EntityViewUpdaterImpl oldValue = contextAwareEntityViewUpdaterCache.putIfAbsent(key, value); if (oldValue != null) { @@ -636,7 +636,7 @@ public EntityViewUpdater getUpdater(ManagedViewTypeImplementor viewType, Mana EntityViewUpdaterImpl value = entityViewUpdaterCache.get(viewType); if (value == null) { - value = new EntityViewUpdaterImpl(this, viewType, null); + value = new EntityViewUpdaterImpl(this, viewType, null, null, null); EntityViewUpdaterImpl oldValue = entityViewUpdaterCache.putIfAbsent(viewType, value); if (oldValue != null) { @@ -670,10 +670,14 @@ private void registerFilterMappings() { private static class ContextAwareUpdaterKey { private final ManagedViewTypeImplementor viewType; private final ManagedViewTypeImplementor declaredViewType; + private final EntityViewUpdaterImpl owner; + private final String ownerMapping; - public ContextAwareUpdaterKey(ManagedViewTypeImplementor viewType, ManagedViewTypeImplementor declaredViewType) { + public ContextAwareUpdaterKey(ManagedViewTypeImplementor viewType, ManagedViewTypeImplementor declaredViewType, EntityViewUpdaterImpl owner, String ownerMapping) { this.viewType = viewType; this.declaredViewType = declaredViewType; + this.owner = owner; + this.ownerMapping = ownerMapping; } @Override @@ -681,7 +685,7 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!(o instanceof ContextAwareUpdaterKey)) { return false; } @@ -690,13 +694,21 @@ public boolean equals(Object o) { if (!viewType.equals(that.viewType)) { return false; } - return declaredViewType.equals(that.declaredViewType); + if (declaredViewType != null ? !declaredViewType.equals(that.declaredViewType) : that.declaredViewType != null) { + return false; + } + if (owner != that.owner) { + return false; + } + return ownerMapping != null ? ownerMapping.equals(that.ownerMapping) : that.ownerMapping == null; } @Override public int hashCode() { int result = viewType.hashCode(); - result = 31 * result + declaredViewType.hashCode(); + result = 31 * result + (declaredViewType != null ? declaredViewType.hashCode() : 0); + result = 31 * result + (owner != null ? owner.hashCode() : 0); + result = 31 * result + (ownerMapping != null ? ownerMapping.hashCode() : 0); return result; } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/UpdatableExpressionVisitor.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/UpdatableExpressionVisitor.java index 5353cc430c..ad92b9568d 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/UpdatableExpressionVisitor.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/UpdatableExpressionVisitor.java @@ -16,20 +16,14 @@ package com.blazebit.persistence.view.impl; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import com.blazebit.persistence.parser.EntityMetamodel; +import com.blazebit.persistence.parser.PathTargetResolvingExpressionVisitor; import com.blazebit.persistence.parser.expression.ArithmeticExpression; import com.blazebit.persistence.parser.expression.ArithmeticFactor; import com.blazebit.persistence.parser.expression.ArrayExpression; import com.blazebit.persistence.parser.expression.DateLiteral; import com.blazebit.persistence.parser.expression.EntityLiteral; import com.blazebit.persistence.parser.expression.EnumLiteral; -import com.blazebit.persistence.parser.expression.Expression; import com.blazebit.persistence.parser.expression.FunctionExpression; import com.blazebit.persistence.parser.expression.GeneralCaseExpression; import com.blazebit.persistence.parser.expression.ListIndexExpression; @@ -39,8 +33,6 @@ import com.blazebit.persistence.parser.expression.NullExpression; import com.blazebit.persistence.parser.expression.NumericLiteral; import com.blazebit.persistence.parser.expression.ParameterExpression; -import com.blazebit.persistence.parser.expression.PathElementExpression; -import com.blazebit.persistence.parser.expression.PathExpression; import com.blazebit.persistence.parser.expression.PropertyExpression; import com.blazebit.persistence.parser.expression.SimpleCaseExpression; import com.blazebit.persistence.parser.expression.StringLiteral; @@ -65,167 +57,46 @@ import com.blazebit.persistence.parser.predicate.LikePredicate; import com.blazebit.persistence.parser.predicate.LtPredicate; import com.blazebit.persistence.parser.predicate.MemberOfPredicate; -import com.blazebit.reflection.ReflectionUtils; + +import javax.persistence.metamodel.Type; /** * * @author Christian Beikov * @since 1.0.0 */ -public class UpdatableExpressionVisitor implements Expression.Visitor { - - private PathPosition currentPosition; - private List pathPositions; - - /** - * @author Christian Beikov - * @since 1.0.0 - */ - private static class PathPosition { - - private Class currentClass; - private Class valueClass; - private Class keyClass; - private Method method; - - PathPosition(Class currentClass, Method method) { - this.currentClass = currentClass; - this.method = method; - } - - Class getValueClass() { - return valueClass; - } +public class UpdatableExpressionVisitor extends PathTargetResolvingExpressionVisitor { - public Class getKeyClass() { - return keyClass; - } - - Class getRealCurrentClass() { - return currentClass; - } - - Class getCurrentClass() { - if (valueClass != null) { - return valueClass; - } - if (keyClass != null) { - return keyClass; - } - - return currentClass; - } - - void setCurrentClass(Class currentClass) { - this.currentClass = currentClass; - this.valueClass = null; - this.keyClass = null; - } - - Method getMethod() { - return method; - } - - void setMethod(Method method) { - this.method = method; - } - - void setValueClass(Class valueClass) { - this.valueClass = valueClass; - } - - public void setKeyClass(Class keyClass) { - this.keyClass = keyClass; - } - } - - public UpdatableExpressionVisitor(Class startClass) { - this.pathPositions = new ArrayList(); - this.currentPosition = new PathPosition(startClass, null); - this.pathPositions.add(currentPosition); - } - - private Method resolve(Class currentClass, String property) { - return ReflectionUtils.getGetter(currentClass, property); - } - - private Class getType(Class baseClass, Method element) { - return ReflectionUtils.getResolvedMethodReturnType(baseClass, element); - } - - public Map[]> getPossibleTargets() { - Map[]> possibleTargets = new HashMap[]>(); - - List positions = pathPositions; - int size = positions.size(); - for (int i = 0; i < size; i++) { - PathPosition position = positions.get(i); - possibleTargets.put(position.getMethod(), new Class[]{ position.getRealCurrentClass(), position.getCurrentClass() }); - } - - return possibleTargets; + public UpdatableExpressionVisitor(EntityMetamodel metamodel, Class startClass) { + super(metamodel, metamodel.type(startClass), null); } @Override public void visit(PropertyExpression expression) { - if (currentPosition.getValueClass() != null || currentPosition.getKeyClass() != null) { + if (currentPosition.valueClass != null || currentPosition.keyClass != null) { throw new IllegalArgumentException("Invalid dereferencing of collection property '" + expression.getProperty() + "' in updatable expression!"); } - - currentPosition.setMethod(resolve(currentPosition.getRealCurrentClass(), expression.getProperty())); - if (currentPosition.getMethod() == null) { - currentPosition.setCurrentClass(null); - } else { - Class type = getType(currentPosition.getRealCurrentClass(), currentPosition.getMethod()); - Class valueType = null; - Class keyType = null; - - if (Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)) { - Class[] typeArguments = ReflectionUtils.getResolvedMethodReturnTypeArguments(currentPosition.getRealCurrentClass(), currentPosition.getMethod()); - valueType = typeArguments[typeArguments.length - 1]; - if (typeArguments.length > 1) { - keyType = typeArguments[0]; - } - } else { - valueType = type; - } - - currentPosition.setCurrentClass(type); - currentPosition.setValueClass(valueType); - currentPosition.setKeyClass(keyType); - } - } - @Override - public void visit(PathExpression expression) { - List expressions = expression.getExpressions(); - int size = expressions.size(); - for (int i = 0; i < size; i++) { - expressions.get(i).accept(this); - } + super.visit(expression); } @Override public void visit(ListIndexExpression expression) { - // NOTE: JPQL does not support treat in the SET clause invalid(expression); } @Override public void visit(MapEntryExpression expression) { - // NOTE: JPQL does not support treat in the SET clause invalid(expression); } @Override public void visit(MapKeyExpression expression) { - // NOTE: JPQL does not support treat in the SET clause invalid(expression); } @Override public void visit(MapValueExpression expression) { - // NOTE: JPQL does not support treat in the SET clause invalid(expression); } @@ -403,8 +274,4 @@ public void visit(ExistsPredicate predicate) { invalid(predicate); } - private void invalid(Object o) { - throw new IllegalArgumentException("Illegal occurence of [" + o + "] in path chain resolver!"); - } - } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionAction.java index 1d3589fe25..cdbf267fdd 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionAction.java @@ -32,6 +32,8 @@ public interface CollectionAction> { public void doAction(T collection, UpdateContext context, ViewToEntityMapper mapper, CollectionRemoveListener removeListener); + public void undo(T collection, Collection removedObjects, Collection addedObjects); + public boolean containsObject(T collection, Object o); public Collection getAddedObjects(); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionAddAllAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionAddAllAction.java index c0d220a2f3..74a18720a6 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionAddAllAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionAddAllAction.java @@ -86,6 +86,15 @@ public void doAction(C collection, UpdateContext context, ViewToEntityMapper map } } + @Override + public void undo(C collection, Collection removedObjects, Collection addedObjects) { + for (E element : elements) { + if (addedObjects.contains(element)) { + collection.remove(element); + } + } + } + @Override public boolean containsObject(C collection, Object o) { for (Object element : elements) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionClearAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionClearAction.java index ff3d619ddd..50f4c1824d 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionClearAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionClearAction.java @@ -41,6 +41,11 @@ public void doAction(C collection, UpdateContext context, ViewToEntityMapper map collection.clear(); } + @Override + public void undo(C collection, Collection removedObjects, Collection addedObjects) { + throw new UnsupportedOperationException("Can't undo clear!"); + } + @Override public boolean containsObject(C collection, Object o) { // Trivially, a clear action contains all objects diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionRemoveAllAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionRemoveAllAction.java index e388bdfa3b..56f9369b9c 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionRemoveAllAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/CollectionRemoveAllAction.java @@ -108,6 +108,15 @@ public void doAction(C collection, UpdateContext context, ViewToEntityMapper map } } + @Override + public void undo(C collection, Collection removedObjects, Collection addedObjects) { + for (Object element : elements) { + if (removedObjects.contains(element)) { + collection.add((E) element); + } + } + } + @Override public boolean containsObject(C collection, Object o) { for (Object element : elements) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAction.java index a01e618762..7050cc8609 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAction.java @@ -16,10 +16,11 @@ package com.blazebit.persistence.view.impl.collection; -import com.blazebit.persistence.view.impl.update.UpdateContext; import com.blazebit.persistence.view.impl.entity.ViewToEntityMapper; +import com.blazebit.persistence.view.impl.update.UpdateContext; import java.util.List; +import java.util.Map; /** * @@ -30,5 +31,13 @@ public interface ListAction> extends CollectionAction { @Override public void doAction(T list, UpdateContext context, ViewToEntityMapper mapper, CollectionRemoveListener removeListener); - + + public List> getInsertedObjectEntries(); + + public List> getAppendedObjectEntries(); + + public List> getRemovedObjectEntries(); + + public List> getTrimmedObjectEntries(); + } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAddAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAddAction.java index fa1ab1eb11..f47e72bc18 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAddAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAddAction.java @@ -19,6 +19,7 @@ import com.blazebit.persistence.view.impl.update.UpdateContext; import com.blazebit.persistence.view.impl.entity.ViewToEntityMapper; +import java.util.AbstractMap; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -32,10 +33,12 @@ public class ListAddAction, E> implements ListAction { private final int index; + private final boolean append; private final E element; - public ListAddAction(int index, E element) { + public ListAddAction(int index, boolean append, E element) { this.index = index; + this.append = append; this.element = element; } @@ -49,6 +52,11 @@ public void doAction(C list, UpdateContext context, ViewToEntityMapper mapper, C } } + @Override + public void undo(C collection, Collection removedObjects, Collection addedObjects) { + collection.remove(index); + } + @Override public boolean containsObject(C collection, Object o) { return o == element; @@ -77,13 +85,41 @@ public Collection getRemovedObjects(C collection) { return Collections.emptyList(); } + @Override + public List> getInsertedObjectEntries() { + if (append) { + return Collections.emptyList(); + } else { + return Collections.>singletonList(new AbstractMap.SimpleEntry(element, index)); + } + } + + @Override + public List> getAppendedObjectEntries() { + if (append) { + return Collections.>singletonList(new AbstractMap.SimpleEntry(element, index)); + } else { + return Collections.emptyList(); + } + } + + @Override + public List> getRemovedObjectEntries() { + return Collections.emptyList(); + } + + @Override + public List> getTrimmedObjectEntries() { + return Collections.emptyList(); + } + @Override @SuppressWarnings("unchecked") public CollectionAction replaceObject(Object oldElem, Object elem) { if (element != oldElem) { return null; } - return new ListAddAction(index, elem); + return new ListAddAction(index, append, elem); } @Override @@ -97,7 +133,7 @@ public CollectionAction replaceObjects(Map objectMapping) { if (newElement == null) { return this; } - return new ListAddAction(index, newElement); + return new ListAddAction(index, append, newElement); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAddAllAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAddAllAction.java index e0acdd2e85..8a8e5f855f 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAddAllAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListAddAllAction.java @@ -16,9 +16,10 @@ package com.blazebit.persistence.view.impl.collection; -import com.blazebit.persistence.view.impl.update.UpdateContext; import com.blazebit.persistence.view.impl.entity.ViewToEntityMapper; +import com.blazebit.persistence.view.impl.update.UpdateContext; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -33,16 +34,19 @@ public class ListAddAllAction, E> implements ListAction { private final int index; + private final boolean append; private final List elements; - public ListAddAllAction(int index, Collection collection) { + public ListAddAllAction(int index, boolean append, Collection collection) { this.index = index; + this.append = append; this.elements = new ArrayList(collection); } - private ListAddAllAction(List collection, int index) { + private ListAddAllAction(List collection, int index, boolean append) { this.index = index; this.elements = collection; + this.append = append; } @Override @@ -61,6 +65,13 @@ public void doAction(C list, UpdateContext context, ViewToEntityMapper mapper, C } } + @Override + public void undo(C collection, Collection removedObjects, Collection addedObjects) { + for (int i = index + elements.size() - 1; i >= index; i--) { + collection.remove(i); + } + } + @Override public boolean containsObject(C collection, Object o) { for (Object element : elements) { @@ -97,6 +108,42 @@ public Collection getRemovedObjects(C collection) { return Collections.emptyList(); } + @Override + public List> getInsertedObjectEntries() { + if (append) { + return Collections.emptyList(); + } else { + List> list = new ArrayList<>(elements.size()); + for (int i = 0; i < elements.size(); i++) { + list.add(new AbstractMap.SimpleEntry(elements.get(i), index + i)); + } + return list; + } + } + + @Override + public List> getAppendedObjectEntries() { + if (append) { + List> list = new ArrayList<>(elements.size()); + for (int i = 0; i < elements.size(); i++) { + list.add(new AbstractMap.SimpleEntry(elements.get(i), index + i)); + } + return list; + } else { + return Collections.emptyList(); + } + } + + @Override + public List> getRemovedObjectEntries() { + return Collections.emptyList(); + } + + @Override + public List> getTrimmedObjectEntries() { + return Collections.emptyList(); + } + @Override @SuppressWarnings("unchecked") public CollectionAction replaceObject(Object oldElem, Object elem) { @@ -105,7 +152,7 @@ public CollectionAction replaceObject(Object oldElem, Object elem) { if (newElements == null) { return null; } - return new ListAddAllAction(newElements, index); + return new ListAddAllAction(index, append, newElements); } @Override @@ -114,9 +161,9 @@ public CollectionAction replaceObjects(Map objectMapping) { List newElements = RecordingUtils.replaceElements(elements, objectMapping); if (newElements == null) { - return new ListAddAllAction<>(index, elements); + return new ListAddAllAction<>(index, append, elements); } - return new ListAddAllAction(newElements, index); + return new ListAddAllAction(newElements, index, append); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListRemoveAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListRemoveAction.java index 1746fdf0c1..712b97baac 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListRemoveAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListRemoveAction.java @@ -19,6 +19,7 @@ import com.blazebit.persistence.view.impl.update.UpdateContext; import com.blazebit.persistence.view.impl.entity.ViewToEntityMapper; +import java.util.AbstractMap; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -32,15 +33,18 @@ public class ListRemoveAction, E> implements ListAction { private final int index; + private final boolean last; private final Object removedElementInView; - public ListRemoveAction(int index, List delegate) { + public ListRemoveAction(int index, boolean last, List delegate) { this.index = index; + this.last = last; this.removedElementInView = delegate.get(index); } - private ListRemoveAction(int index, Object removedElementInView) { + private ListRemoveAction(int index, boolean last, Object removedElementInView) { this.index = index; + this.last = last; this.removedElementInView = removedElementInView; } @@ -52,6 +56,11 @@ public void doAction(C list, UpdateContext context, ViewToEntityMapper mapper, C } } + @Override + public void undo(C collection, Collection removedObjects, Collection addedObjects) { + collection.add(index, (E) removedElementInView); + } + @Override public boolean containsObject(C collection, Object o) { // For completeness we implemented this, but actually this is never needed @@ -78,13 +87,41 @@ public Collection getRemovedObjects(C collection) { return (Collection) Collections.singleton(collection.get(index)); } + @Override + public List> getInsertedObjectEntries() { + return Collections.emptyList(); + } + + @Override + public List> getAppendedObjectEntries() { + return Collections.emptyList(); + } + + @Override + public List> getRemovedObjectEntries() { + if (last) { + return Collections.emptyList(); + } else { + return Collections.>singletonList(new AbstractMap.SimpleEntry(removedElementInView, index)); + } + } + + @Override + public List> getTrimmedObjectEntries() { + if (last) { + return Collections.>singletonList(new AbstractMap.SimpleEntry(removedElementInView, index)); + } else { + return Collections.emptyList(); + } + } + @Override public CollectionAction replaceObject(Object oldElem, Object elem) { if (oldElem != removedElementInView) { return this; } - return new ListRemoveAction(index, elem); + return new ListRemoveAction(index, last, elem); } @Override @@ -97,7 +134,7 @@ public CollectionAction replaceObjects(Map objectMapping) { return this; } - return new ListRemoveAction(index, newElement); + return new ListRemoveAction(index, last, newElement); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListSetAction.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListSetAction.java index d378f8adfe..6f421e0464 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListSetAction.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/ListSetAction.java @@ -19,6 +19,7 @@ import com.blazebit.persistence.view.impl.update.UpdateContext; import com.blazebit.persistence.view.impl.entity.ViewToEntityMapper; +import java.util.AbstractMap; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -32,17 +33,20 @@ public class ListSetAction, E> implements ListAction { final int index; + final boolean last; final E element; final E removedElementInView; - public ListSetAction(int index, E element, List delegate) { + public ListSetAction(int index, boolean last, E element, List delegate) { this.index = index; + this.last = last; this.element = element; this.removedElementInView = (E) delegate.get(index); } - private ListSetAction(int index, E element, E removedElementInView) { + private ListSetAction(int index, boolean last, E element, E removedElementInView) { this.index = index; + this.last = last; this.element = element; this.removedElementInView = removedElementInView; } @@ -62,6 +66,11 @@ public void doAction(C list, UpdateContext context, ViewToEntityMapper mapper, C } } + @Override + public void undo(C collection, Collection removedObjects, Collection addedObjects) { + collection.set(index, removedElementInView); + } + @Override public boolean containsObject(C collection, Object o) { return element == o; @@ -87,17 +96,53 @@ public Collection getRemovedObjects(C collection) { return Collections.singleton(collection.get(index)); } + @Override + public List> getInsertedObjectEntries() { + if (last) { + return Collections.emptyList(); + } else { + return Collections.>singletonList(new AbstractMap.SimpleEntry(element, index)); + } + } + + @Override + public List> getAppendedObjectEntries() { + if (last) { + return Collections.>singletonList(new AbstractMap.SimpleEntry(element, index)); + } else { + return Collections.emptyList(); + } + } + + @Override + public List> getRemovedObjectEntries() { + if (last) { + return Collections.emptyList(); + } else { + return Collections.>singletonList(new AbstractMap.SimpleEntry(removedElementInView, index)); + } + } + + @Override + public List> getTrimmedObjectEntries() { + if (last) { + return Collections.>singletonList(new AbstractMap.SimpleEntry(removedElementInView, index)); + } else { + return Collections.emptyList(); + } + } + @Override @SuppressWarnings("unchecked") public CollectionAction replaceObject(Object oldElem, Object elem) { if (element == oldElem) { if (removedElementInView == oldElem) { - return new ListSetAction(index, elem, elem); + return new ListSetAction(index, last, elem, elem); } else { - return new ListSetAction(index, elem, removedElementInView); + return new ListSetAction(index, last, elem, removedElementInView); } } else if (removedElementInView == oldElem) { - return new ListSetAction(index, element, elem); + return new ListSetAction(index, last, element, elem); } else { return null; } @@ -120,15 +165,17 @@ public CollectionAction replaceObjects(Map objectMapping) { newRemovedElement = removedElementInView; } - return new ListSetAction(index, newElement, newRemovedElement); + return new ListSetAction(index, last, newElement, newRemovedElement); } @Override public void addAction(List> actions, Collection addedElements, Collection removedElements) { CollectionAction lastAction; + // Multiple set operations are coalesced into a single one if (!actions.isEmpty() && (lastAction = actions.get(actions.size() - 1)) instanceof ListSetAction) { if (index == ((ListSetAction) lastAction).index) { - actions.set(actions.size() - 1, this); + // Don't forget to retain the original removed element + actions.set(actions.size() - 1, (CollectionAction) new ListSetAction<>(index, last, element, ((ListSetAction) lastAction).removedElementInView)); return; } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingCollection.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingCollection.java index 02c9b97cc1..354467e2a4 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingCollection.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingCollection.java @@ -25,8 +25,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.ListIterator; import java.util.Map; @@ -309,6 +311,34 @@ public void setActions(List> actions, Map addedElement $$_markDirty(-1); } + protected C copyDelegate() { + if (ordered) { + if (hashBased) { + return (C) new LinkedHashSet<>(delegate); + } else { + return (C) new ArrayList<>(delegate); + } + } else { + if (hashBased) { + return (C) new HashSet<>(delegate); + } else { + return (C) new ArrayList<>(delegate); + } + } + } + + public C getInitialVersion() { + if (actions == null || actions.isEmpty()) { + return (C) this; + } + C collection = copyDelegate(); + for (int i = actions.size() - 1; i >= 0; i--) { + CollectionAction action = actions.get(i); + action.undo(collection, removedElements.keySet(), addedElements.keySet()); + } + return collection; + } + public List> resetActions(UpdateContext context) { List> oldActions = this.actions; if (oldActions == null) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingList.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingList.java index 88bf32b850..f934656c6b 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingList.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/collection/RecordingList.java @@ -18,6 +18,7 @@ import java.util.AbstractList; import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Set; @@ -33,9 +34,71 @@ public RecordingList(List delegate, boolean indexed, Set> allowedSub super(delegate, indexed, indexed, allowedSubtypes, updatable, optimize); } + @Override + void addAddAction(E e) { + addAddAction(size(), e); + } + + @Override + void addRemoveAction(Object o) { + if (indexed) { + addRemoveAction(indexOf(o)); + } else { + super.addRemoveAction(o); + } + } + + @Override + void addAddAllAction(Collection c) { + addAddAllAction(size(), c); + } + + @Override + public boolean removeAll(Collection c) { + if (indexed) { + checkType(c, "Removing"); + boolean modified = false; + for (Object o : c) { + if (remove(indexOf(o)) != null) { + modified = true; + } + } + return modified; + } else { + return super.removeAll(c); + } + } + + @Override + public void clear() { + int size = delegate.size(); + // Always remove the last element to benefit from tail removals + for (int i = size - 1; i > 0; i--) { + addRemoveAction(i); + } + delegate.clear(); + } + + @Override + public boolean retainAll(Collection c) { + if (indexed) { + boolean modified = false; + Iterator it = listIterator(); + while (it.hasNext()) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } else { + return super.retainAll(c); + } + } + void addAddAllAction(int index, Collection c) { if (indexed) { - addAction(new ListAddAllAction, E>(index, c)); + addAction(new ListAddAllAction, E>(index, delegate.size() == index, c)); } else { addAddAllAction(c); } @@ -50,7 +113,7 @@ public boolean addAll(int index, Collection c) { void addSetAction(int index, E element) { if (indexed) { - addAction(new ListSetAction, E>(index, element, delegate)); + addAction(new ListSetAction, E>(index, delegate.size() == index - 1, element, delegate)); } else { addRemoveAction(index); addAddAction(element); @@ -66,9 +129,9 @@ public E set(int index, E element) { void addAddAction(int index, E element) { if (indexed) { - addAction(new ListAddAction, E>(index, element)); + addAction(new ListAddAction, E>(index, delegate.size() == index, element)); } else { - addAddAction(element); + super.addAddAction(element); } } @@ -81,7 +144,7 @@ public void add(int index, E element) { void addRemoveAction(int index) { if (indexed) { - addAction(new ListRemoveAction, E>(index, delegate)); + addAction(new ListRemoveAction, E>(index, index == delegate.size() - 1, delegate)); } else { addRemoveAction(delegate.get(index)); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/AbstractEntityLoader.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/AbstractEntityLoader.java index 96ca848f62..a1ac95185c 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/AbstractEntityLoader.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/AbstractEntityLoader.java @@ -64,6 +64,11 @@ protected static javax.persistence.metamodel.SingularAttribute jpaIdOf(EntityVie return null; } + @Override + public Class getEntityClass() { + return entityClass; + } + @Override public Object getEntityId(UpdateContext context, Object entity) { if (entityIdAccessor == null) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/AbstractViewToEntityMapper.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/AbstractViewToEntityMapper.java index 9a4eefc8c2..eaae4b799f 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/AbstractViewToEntityMapper.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/AbstractViewToEntityMapper.java @@ -21,6 +21,7 @@ import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.proxy.MutableStateTrackable; import com.blazebit.persistence.view.impl.update.EntityViewUpdater; +import com.blazebit.persistence.view.impl.update.EntityViewUpdaterImpl; import com.blazebit.persistence.view.impl.update.UpdateContext; import com.blazebit.persistence.view.impl.update.flush.DirtyAttributeFlusher; import com.blazebit.persistence.view.impl.update.flush.FetchGraphNode; @@ -55,7 +56,7 @@ public abstract class AbstractViewToEntityMapper implements ViewToEntityMapper { protected final AttributeAccessor entityIdAccessor; protected final boolean persistAllowed; - public AbstractViewToEntityMapper(String attributeLocation, EntityViewManagerImpl evm, Class viewTypeClass, Set> persistAllowedSubtypes, Set> updateAllowedSubtypes, EntityLoader entityLoader, AttributeAccessor viewIdAccessor, boolean persistAllowed) { + public AbstractViewToEntityMapper(String attributeLocation, EntityViewManagerImpl evm, Class viewTypeClass, Set> persistAllowedSubtypes, Set> updateAllowedSubtypes, EntityLoader entityLoader, AttributeAccessor viewIdAccessor, boolean persistAllowed, EntityViewUpdaterImpl owner, String ownerMapping) { this.attributeLocation = attributeLocation; this.viewTypeClass = viewTypeClass; ManagedViewTypeImplementor managedViewTypeImplementor = evm.getMetamodel().managedView(viewTypeClass); @@ -66,17 +67,17 @@ public AbstractViewToEntityMapper(String attributeLocation, EntityViewManagerImp Map, EntityViewUpdater> removeUpdater = new HashMap<>(); for (Type t : persistAllowedSubtypes) { - EntityViewUpdater updater = evm.getUpdater((ManagedViewTypeImplementor) t, managedViewTypeImplementor); + EntityViewUpdater updater = evm.getUpdater((ManagedViewTypeImplementor) t, managedViewTypeImplementor, owner, ownerMapping); persistUpdater.put(t.getJavaType(), updater); removeUpdater.put(t.getJavaType(), updater); } for (Type t : updateAllowedSubtypes) { - EntityViewUpdater updater = evm.getUpdater((ManagedViewTypeImplementor) t, null); + EntityViewUpdater updater = evm.getUpdater((ManagedViewTypeImplementor) t, null, owner, ownerMapping); updateUpdater.put(t.getJavaType(), updater); removeUpdater.put(t.getJavaType(), updater); } - this.defaultUpdater = evm.getUpdater(managedViewTypeImplementor, null); + this.defaultUpdater = evm.getUpdater(managedViewTypeImplementor, null, owner, ownerMapping); removeUpdater.put(viewTypeClass, defaultUpdater); this.persistUpdater = Collections.unmodifiableMap(persistUpdater); this.updateUpdater = Collections.unmodifiableMap(updateUpdater); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/CreateOnlyViewToEntityMapper.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/CreateOnlyViewToEntityMapper.java index 0ccfe2bea3..0b9a15b157 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/CreateOnlyViewToEntityMapper.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/CreateOnlyViewToEntityMapper.java @@ -18,6 +18,7 @@ import com.blazebit.persistence.view.impl.EntityViewManagerImpl; import com.blazebit.persistence.view.impl.accessor.AttributeAccessor; +import com.blazebit.persistence.view.impl.update.EntityViewUpdaterImpl; import com.blazebit.persistence.view.impl.update.UpdateContext; import com.blazebit.persistence.view.metamodel.Type; @@ -30,8 +31,8 @@ */ public class CreateOnlyViewToEntityMapper extends AbstractViewToEntityMapper { - public CreateOnlyViewToEntityMapper(String attributeLocation, EntityViewManagerImpl evm, Class viewTypeClass, Set> persistAllowedSubtypes, Set> updateAllowedSubtypes, EntityLoader entityLoader, AttributeAccessor viewIdAccessor, boolean persistAllowed) { - super(attributeLocation, evm, viewTypeClass, persistAllowedSubtypes, updateAllowedSubtypes, entityLoader, viewIdAccessor, persistAllowed); + public CreateOnlyViewToEntityMapper(String attributeLocation, EntityViewManagerImpl evm, Class viewTypeClass, Set> persistAllowedSubtypes, Set> updateAllowedSubtypes, EntityLoader entityLoader, AttributeAccessor viewIdAccessor, boolean persistAllowed, EntityViewUpdaterImpl owner, String ownerMapping) { + super(attributeLocation, evm, viewTypeClass, persistAllowedSubtypes, updateAllowedSubtypes, entityLoader, viewIdAccessor, persistAllowed, owner, ownerMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/DefaultEntityToEntityMapper.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/DefaultEntityToEntityMapper.java index 95e099a528..ae3c53ed3c 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/DefaultEntityToEntityMapper.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/DefaultEntityToEntityMapper.java @@ -35,7 +35,7 @@ public class DefaultEntityToEntityMapper extends AbstractEntityToEntityMapper { private final BasicUserType basicUserType; private final BasicDirtyChecker dirtyChecker; - public DefaultEntityToEntityMapper(boolean shouldPersist, boolean shouldMerge, BasicUserType basicUserType, EntityLoaderFetchGraphNode entityLoaderFetchGraphNode, UnmappedAttributeCascadeDeleter deleter) { + public DefaultEntityToEntityMapper(boolean shouldPersist, boolean shouldMerge, Class jpaType, BasicUserType basicUserType, EntityLoaderFetchGraphNode entityLoaderFetchGraphNode, UnmappedAttributeCascadeDeleter deleter) { super(entityLoaderFetchGraphNode, deleter); this.shouldPersist = shouldPersist; this.shouldMerge = shouldMerge; @@ -50,6 +50,7 @@ public DefaultEntityToEntityMapper(boolean shouldPersist, boolean shouldMerge, B shouldPersist, shouldMerge, null, + jpaType, null, (BasicUserType) basicUserType, null, diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/EmbeddableUpdaterBasedViewToEntityMapper.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/EmbeddableUpdaterBasedViewToEntityMapper.java index 2ec3fa4973..31a1954f71 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/EmbeddableUpdaterBasedViewToEntityMapper.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/EmbeddableUpdaterBasedViewToEntityMapper.java @@ -21,6 +21,7 @@ import com.blazebit.persistence.view.impl.mapper.Mapper; import com.blazebit.persistence.view.impl.proxy.MutableStateTrackable; import com.blazebit.persistence.view.impl.update.EntityViewUpdater; +import com.blazebit.persistence.view.impl.update.EntityViewUpdaterImpl; import com.blazebit.persistence.view.impl.update.UpdateContext; import com.blazebit.persistence.view.impl.update.flush.DirtyAttributeFlusher; import com.blazebit.persistence.view.metamodel.Type; @@ -36,8 +37,8 @@ public class EmbeddableUpdaterBasedViewToEntityMapper extends AbstractViewToEnti private final Mapper idViewToEntityMapper; - public EmbeddableUpdaterBasedViewToEntityMapper(String attributeLocation, EntityViewManagerImpl evm, Class viewTypeClass, Set> persistAllowedSubtypes, Set> updateAllowedSubtypes, EntityLoader entityLoader, boolean persistAllowed, Mapper idViewToEntityMapper) { - super(attributeLocation, evm, viewTypeClass, persistAllowedSubtypes, updateAllowedSubtypes, entityLoader, null, persistAllowed); + public EmbeddableUpdaterBasedViewToEntityMapper(String attributeLocation, EntityViewManagerImpl evm, Class viewTypeClass, Set> persistAllowedSubtypes, Set> updateAllowedSubtypes, EntityLoader entityLoader, boolean persistAllowed, Mapper idViewToEntityMapper, EntityViewUpdaterImpl owner, String ownerMapping) { + super(attributeLocation, evm, viewTypeClass, persistAllowedSubtypes, updateAllowedSubtypes, entityLoader, null, persistAllowed, owner, ownerMapping); this.idViewToEntityMapper = idViewToEntityMapper; } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/EntityIdLoader.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/EntityIdLoader.java index 5d244fb2a8..25362d8aa6 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/EntityIdLoader.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/EntityIdLoader.java @@ -39,6 +39,11 @@ public EntityIdLoader(Class entityClass) { } } + @Override + public Class getEntityClass() { + return entityIdConstructor.getDeclaringClass(); + } + @Override public Object getEntityId(UpdateContext context, Object entity) { return entity; diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/EntityLoader.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/EntityLoader.java index 1cf3b3a401..577752fbd7 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/EntityLoader.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/EntityLoader.java @@ -25,6 +25,8 @@ */ public interface EntityLoader { + public Class getEntityClass(); + public Object toEntity(UpdateContext context, Object id); public Object getEntityId(UpdateContext context, Object entity); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/InverseViewToEntityMapper.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/InverseViewToEntityMapper.java index b584a11de8..2d6c255acf 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/InverseViewToEntityMapper.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/InverseViewToEntityMapper.java @@ -174,7 +174,7 @@ public Query createInverseUpdateQuery(UpdateContext context, Object view, DirtyA if (idAttributeFlusher == null) { query.setParameter(ID_PARAM_NAME, viewIdAccessor.getValue(view)); } else { - idAttributeFlusher.flushQuery(context, "_", query, view, viewIdAccessor.getValue(view), null); + idAttributeFlusher.flushQuery(context, "_", query, view, view, viewIdAccessor.getValue(view), null); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/LoadOrPersistViewToEntityMapper.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/LoadOrPersistViewToEntityMapper.java index db1e49e94f..2654ad9f4c 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/LoadOrPersistViewToEntityMapper.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/LoadOrPersistViewToEntityMapper.java @@ -18,6 +18,7 @@ import com.blazebit.persistence.view.impl.EntityViewManagerImpl; import com.blazebit.persistence.view.impl.accessor.AttributeAccessor; +import com.blazebit.persistence.view.impl.update.EntityViewUpdaterImpl; import com.blazebit.persistence.view.spi.type.EntityViewProxy; import com.blazebit.persistence.view.impl.update.UpdateContext; import com.blazebit.persistence.view.metamodel.Type; @@ -31,8 +32,8 @@ */ public class LoadOrPersistViewToEntityMapper extends AbstractViewToEntityMapper { - public LoadOrPersistViewToEntityMapper(String attributeLocation, EntityViewManagerImpl evm, Class viewTypeClass, Set> persistAllowedSubtypes, Set> updateAllowedSubtypes, EntityLoader entityLoader, AttributeAccessor viewIdAccessor, boolean persistAllowed) { - super(attributeLocation, evm, viewTypeClass, persistAllowedSubtypes, updateAllowedSubtypes, entityLoader, viewIdAccessor, persistAllowed); + public LoadOrPersistViewToEntityMapper(String attributeLocation, EntityViewManagerImpl evm, Class viewTypeClass, Set> persistAllowedSubtypes, Set> updateAllowedSubtypes, EntityLoader entityLoader, AttributeAccessor viewIdAccessor, boolean persistAllowed, EntityViewUpdaterImpl owner, String ownerMapping) { + super(attributeLocation, evm, viewTypeClass, persistAllowedSubtypes, updateAllowedSubtypes, entityLoader, viewIdAccessor, persistAllowed, owner, ownerMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/UpdaterBasedViewToEntityMapper.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/UpdaterBasedViewToEntityMapper.java index 231a643aee..5a46b744a6 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/UpdaterBasedViewToEntityMapper.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/entity/UpdaterBasedViewToEntityMapper.java @@ -18,6 +18,7 @@ import com.blazebit.persistence.view.impl.EntityViewManagerImpl; import com.blazebit.persistence.view.impl.accessor.AttributeAccessor; +import com.blazebit.persistence.view.impl.update.EntityViewUpdaterImpl; import com.blazebit.persistence.view.spi.type.EntityViewProxy; import com.blazebit.persistence.view.impl.proxy.MutableStateTrackable; import com.blazebit.persistence.view.impl.update.EntityViewUpdater; @@ -35,8 +36,8 @@ */ public class UpdaterBasedViewToEntityMapper extends AbstractViewToEntityMapper { - public UpdaterBasedViewToEntityMapper(String attributeLocation, EntityViewManagerImpl evm, Class viewTypeClass, Set> persistAllowedSubtypes, Set> updateAllowedSubtypes, EntityLoader entityLoader, AttributeAccessor viewIdAccessor, boolean persistAllowed) { - super(attributeLocation, evm, viewTypeClass, persistAllowedSubtypes, updateAllowedSubtypes, entityLoader, viewIdAccessor, persistAllowed); + public UpdaterBasedViewToEntityMapper(String attributeLocation, EntityViewManagerImpl evm, Class viewTypeClass, Set> persistAllowedSubtypes, Set> updateAllowedSubtypes, EntityLoader entityLoader, AttributeAccessor viewIdAccessor, boolean persistAllowed, EntityViewUpdaterImpl owner, String ownerMapping) { + super(attributeLocation, evm, viewTypeClass, persistAllowedSubtypes, updateAllowedSubtypes, entityLoader, viewIdAccessor, persistAllowed, owner, ownerMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractAttribute.java index 1a26ab68e8..c8bc0d083d 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractAttribute.java @@ -53,13 +53,13 @@ import javax.persistence.metamodel.ManagedType; import java.lang.annotation.Annotation; -import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -94,14 +94,14 @@ public abstract class AbstractAttribute implements Attribute { protected final String correlationExpression; protected final MappingType mappingType; protected final boolean id; - private final boolean updateMappable; + protected final javax.persistence.metamodel.Attribute updateMappableAttribute; private final List possibleTargetTypes; @SuppressWarnings("unchecked") - public AbstractAttribute(ManagedViewTypeImplementor declaringType, AttributeMapping mapping, MetamodelBuildingContext context) { + public AbstractAttribute(ManagedViewTypeImplementor declaringType, AttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { Class javaType = null; try { - javaType = (Class) mapping.getJavaType(context); + javaType = (Class) mapping.getJavaType(context, embeddableMapping); if (javaType == null) { context.addError("The attribute type is not resolvable at the " + mapping.getErrorLocation()); } @@ -123,7 +123,7 @@ public AbstractAttribute(ManagedViewTypeImplementor declaringType, AttributeM this.declaringType = declaringType; this.javaType = javaType; - this.convertedJavaType = getConvertedType(declaringType.getJavaType(), mapping.getType(context).getConvertedType(), javaType); + this.convertedJavaType = getConvertedType(declaringType.getJavaType(), mapping.getType(context, embeddableMapping).getConvertedType(), javaType); Annotation mappingAnnotation = mapping.getMapping(); if (mappingAnnotation instanceof IdMapping) { @@ -133,7 +133,7 @@ public AbstractAttribute(ManagedViewTypeImplementor declaringType, AttributeM this.batchSize = -1; this.subqueryProvider = null; this.id = true; - this.updateMappable = checkUpdatableMapping(this.mapping, context); + this.updateMappableAttribute = getUpdateMappableAttribute(this.mapping, context); this.mappingType = MappingType.BASIC; this.subqueryExpression = null; this.subqueryAlias = null; @@ -151,7 +151,7 @@ public AbstractAttribute(ManagedViewTypeImplementor declaringType, AttributeM this.batchSize = batchSize; this.subqueryProvider = null; this.id = false; - this.updateMappable = checkUpdatableMapping(this.mapping, context); + this.updateMappableAttribute = getUpdateMappableAttribute(this.mapping, context); this.mappingType = MappingType.BASIC; this.subqueryExpression = null; this.subqueryAlias = null; @@ -194,7 +194,7 @@ public AbstractAttribute(ManagedViewTypeImplementor declaringType, AttributeM this.subqueryProvider = null; this.id = false; // Parameters are never update mappable - this.updateMappable = false; + this.updateMappableAttribute = null; this.mappingType = MappingType.PARAMETER; this.subqueryExpression = null; this.subqueryAlias = null; @@ -213,7 +213,7 @@ public AbstractAttribute(ManagedViewTypeImplementor declaringType, AttributeM this.batchSize = -1; this.id = false; // Subqueries are never update mappable - this.updateMappable = false; + this.updateMappableAttribute = null; this.mappingType = MappingType.SUBQUERY; this.subqueryExpression = mappingSubquery.expression(); this.subqueryAlias = mappingSubquery.subqueryAlias(); @@ -244,8 +244,7 @@ public AbstractAttribute(ManagedViewTypeImplementor declaringType, AttributeM this.subqueryProvider = null; this.id = false; - // Since we can cascade correlated views, we consider them update mappable - this.updateMappable = true; + this.updateMappableAttribute = null; this.mappingType = MappingType.CORRELATED; this.subqueryExpression = null; this.subqueryAlias = null; @@ -273,8 +272,7 @@ public AbstractAttribute(ManagedViewTypeImplementor declaringType, AttributeM this.subqueryProvider = null; this.id = false; - // Since we can cascade correlated views, we consider them update mappable - this.updateMappable = true; + this.updateMappableAttribute = null; this.mappingType = MappingType.CORRELATED; this.subqueryExpression = null; this.subqueryAlias = null; @@ -296,7 +294,7 @@ public AbstractAttribute(ManagedViewTypeImplementor declaringType, AttributeM this.batchSize = Integer.MIN_VALUE; this.subqueryProvider = null; this.id = false; - this.updateMappable = false; + this.updateMappableAttribute = null; this.mappingType = null; this.subqueryExpression = null; this.subqueryAlias = null; @@ -316,15 +314,19 @@ private static Class getConvertedType(Class declaringClass, java.lang.refl return ReflectionUtils.resolveType(declaringClass, convertedType); } - private boolean checkUpdatableMapping(String mapping, MetamodelBuildingContext context) { + private javax.persistence.metamodel.Attribute getUpdateMappableAttribute(String mapping, MetamodelBuildingContext context) { try { - UpdatableExpressionVisitor visitor = new UpdatableExpressionVisitor(declaringType.getEntityClass()); + UpdatableExpressionVisitor visitor = new UpdatableExpressionVisitor(context.getEntityMetamodel(), declaringType.getEntityClass()); context.getExpressionFactory().createPathExpression(mapping).accept(visitor); - return true; + Iterator> iterator = visitor.getPossibleTargets().keySet().iterator(); + if (iterator.hasNext()) { + return iterator.next(); + } } catch (Exception ex) { // Don't care about the actual exception as that will be thrown anyway when validating the expressions later - return false; } + + return null; } public static String stripThisFromMapping(String mapping) { @@ -418,7 +420,8 @@ protected boolean determineForcedUnique(MetamodelBuildingContext context) { } public boolean isUpdateMappable() { - return updateMappable; + // Since we can cascade correlated views, we consider them update mappable + return isCorrelated() || updateMappableAttribute != null; } public Class getCorrelated() { @@ -753,7 +756,7 @@ public void checkAttribute(ManagedType managedType, MetamodelBuildingContext } if (isUpdatable() && (declaringType.isUpdatable() || declaringType.isCreatable())) { - UpdatableExpressionVisitor visitor = new UpdatableExpressionVisitor(managedType.getJavaType()); + UpdatableExpressionVisitor visitor = new UpdatableExpressionVisitor(context.getEntityMetamodel(), managedType.getJavaType()); try { // NOTE: Not supporting "this" here because it doesn't make sense to have an updatable mapping that refers to this // The only thing that might be interesting is supporting "this" when we support cascading as properties could be nested @@ -761,7 +764,7 @@ public void checkAttribute(ManagedType managedType, MetamodelBuildingContext // I guess these attributes are not "updatable" but that probably depends on the decision regarding collections as they have a similar problem // A collection itself might not be "updatable" but it's elements could be. This is roughly the same problem context.getExpressionFactory().createPathExpression(mapping).accept(visitor); - Map[]> possibleTargets = visitor.getPossibleTargets(); + Map, javax.persistence.metamodel.Type> possibleTargets = visitor.getPossibleTargets(); if (possibleTargets.size() > 1) { context.addError("Multiple possible target type for the mapping in the " + getLocation() + ": " + possibleTargets); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodAttribute.java index 39b0f1017f..99114e3307 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodAttribute.java @@ -41,6 +41,7 @@ import com.blazebit.persistence.view.metamodel.Type; import com.blazebit.reflection.ReflectionUtils; +import javax.persistence.metamodel.Attribute; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Collection; @@ -65,8 +66,8 @@ public abstract class AbstractMethodAttribute extends AbstractAttribute filterMappings; - protected AbstractMethodAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, int attributeIndex, MetamodelBuildingContext context) { - super(viewType, mapping, context); + protected AbstractMethodAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, int attributeIndex, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(viewType, mapping, context, embeddableMapping); this.attributeIndex = attributeIndex; this.name = mapping.getName(); this.javaMethod = mapping.getMethod(); @@ -220,15 +221,15 @@ private Class getPluralContainerType(MetamodelBuildingContext context) { return null; } } - UpdatableExpressionVisitor visitor = new UpdatableExpressionVisitor(getDeclaringType().getEntityClass()); + UpdatableExpressionVisitor visitor = new UpdatableExpressionVisitor(context.getEntityMetamodel(), getDeclaringType().getEntityClass()); try { context.getExpressionFactory().createPathExpression(mapping).accept(visitor); - Map[]> possibleTargets = visitor.getPossibleTargets(); + Map, javax.persistence.metamodel.Type> possibleTargets = visitor.getPossibleTargets(); if (possibleTargets.size() > 1) { context.addError("Multiple possible target type for the mapping in the " + getLocation() + ": " + possibleTargets); } - return possibleTargets.values().iterator().next()[0]; + return possibleTargets.values().iterator().next().getJavaType(); } catch (SyntaxErrorException ex) { try { context.getExpressionFactory().createSimpleExpression(mapping, false); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodPluralAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodPluralAttribute.java index 526ef90857..6165320e0f 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodPluralAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodPluralAttribute.java @@ -64,8 +64,8 @@ public abstract class AbstractMethodPluralAttribute extends AbstractMet private final Comparator comparator; @SuppressWarnings("unchecked") - public AbstractMethodPluralAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex) { - super(viewType, mapping, attributeIndex, context); + public AbstractMethodPluralAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex, EmbeddableOwner embeddableMapping) { + super(viewType, mapping, attributeIndex, context, embeddableMapping); // Id and version can't be plural attributes if (mapping.isId()) { context.addError("Attribute annotated with @IdMapping must use a singular type. Plural type found at attribute on the " + mapping.getErrorLocation() + "!"); @@ -73,7 +73,7 @@ public AbstractMethodPluralAttribute(ManagedViewTypeImplementor viewType, Met if (mapping.isVersion()) { context.addError("Attribute annotated with @Version must use a singular type. Plural type found at attribute on the " + mapping.getErrorLocation() + "!"); } - this.elementType = (Type) mapping.getElementType(context); + this.elementType = (Type) mapping.getElementType(context, embeddableMapping); // The declaring type must be mutable, otherwise attributes can't be considered updatable if (mapping.getUpdatable() == null) { @@ -92,10 +92,10 @@ public AbstractMethodPluralAttribute(ManagedViewTypeImplementor viewType, Met boolean definesDeleteCascading = mapping.getCascadeTypes().contains(CascadeType.DELETE); boolean allowsDeleteCascading = updatable || mapping.getCascadeTypes().contains(CascadeType.AUTO); - this.readOnlySubtypes = (Set>) (Set) mapping.getReadOnlySubtypes(context); + this.readOnlySubtypes = (Set>) (Set) mapping.getReadOnlySubtypes(context, embeddableMapping); if (updatable) { - this.persistSubtypes = determinePersistSubtypeSet(elementType, mapping.getCascadeSubtypes(context), mapping.getCascadePersistSubtypes(context), context); + this.persistSubtypes = determinePersistSubtypeSet(elementType, mapping.getCascadeSubtypes(context, embeddableMapping), mapping.getCascadePersistSubtypes(context, embeddableMapping), context); this.persistCascaded = mapping.getCascadeTypes().contains(CascadeType.PERSIST) || mapping.getCascadeTypes().contains(CascadeType.AUTO) && !persistSubtypes.isEmpty(); } else { @@ -109,7 +109,7 @@ public AbstractMethodPluralAttribute(ManagedViewTypeImplementor viewType, Met this.updateSubtypes = Collections.emptySet(); } else { // TODO: maybe allow to override mutability? - Set> updateCascadeAllowedSubtypes = determineUpdateSubtypeSet(elementType, mapping.getCascadeSubtypes(context), mapping.getCascadeUpdateSubtypes(context), context); + Set> updateCascadeAllowedSubtypes = determineUpdateSubtypeSet(elementType, mapping.getCascadeSubtypes(context, embeddableMapping), mapping.getCascadeUpdateSubtypes(context, embeddableMapping), context); boolean updateCascaded = mapping.getCascadeTypes().contains(CascadeType.UPDATE) || mapping.getCascadeTypes().contains(CascadeType.AUTO) && !updateCascadeAllowedSubtypes.isEmpty(); if (updateCascaded) { @@ -135,15 +135,23 @@ public AbstractMethodPluralAttribute(ManagedViewTypeImplementor viewType, Met this.allowedSubtypes = createAllowedSubtypesSet(); this.optimisticLockProtected = determineOptimisticLockProtected(mapping, context, mutable); - this.elementInheritanceSubtypes = (Map, String>) (Map) mapping.getElementInheritanceSubtypes(context); + this.elementInheritanceSubtypes = (Map, String>) (Map) mapping.getElementInheritanceSubtypes(context, embeddableMapping); this.dirtyStateIndex = determineDirtyStateIndex(dirtyStateIndex); if (this.dirtyStateIndex == -1) { this.mappedBy = null; this.inverseRemoveStrategy = null; this.writableMappedByMapping = null; } else { - ManagedType managedType = context.getEntityMetamodel().getManagedType(declaringType.getEntityClass()); - this.mappedBy = mapping.determineMappedBy(managedType, this.mapping, context); + ManagedType managedType; + String mappingPath; + if (embeddableMapping == null) { + mappingPath = this.mapping; + managedType = context.getEntityMetamodel().getManagedType(declaringType.getEntityClass()); + } else { + mappingPath = embeddableMapping.getEmbeddableMapping() + "." + this.mapping; + managedType = context.getEntityMetamodel().getManagedType(embeddableMapping.getEntityClass()); + } + this.mappedBy = mapping.determineMappedBy(managedType, mappingPath, context, embeddableMapping); if (this.mappedBy == null) { this.inverseRemoveStrategy = null; this.writableMappedByMapping = null; diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodSingularAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodSingularAttribute.java index e7e57769a3..641af8bf93 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodSingularAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractMethodSingularAttribute.java @@ -29,6 +29,7 @@ import com.blazebit.persistence.view.spi.type.VersionBasicUserType; import com.blazebit.reflection.ReflectionUtils; +import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.ManagedType; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -62,9 +63,18 @@ public abstract class AbstractMethodSingularAttribute extends AbstractMeth private final Map, String> inheritanceSubtypes; @SuppressWarnings("unchecked") - public AbstractMethodSingularAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex) { - super(viewType, mapping, attributeIndex, context); - this.type = (Type) mapping.getType(context); + public AbstractMethodSingularAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex, EmbeddableOwner embeddableMapping) { + super(viewType, mapping, attributeIndex, context, embeddableMapping); + if (updateMappableAttribute != null && updateMappableAttribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED) { + if (embeddableMapping == null) { + embeddableMapping = new EmbeddableOwner(viewType.getJpaManagedType().getJavaType(), this.mapping); + } else { + embeddableMapping = embeddableMapping.withSubMapping(this.mapping); + } + } else { + embeddableMapping = null; + } + this.type = (Type) mapping.getType(context, embeddableMapping); if (mapping.isVersion()) { if (!(type instanceof BasicType) || !(((BasicType) type).getUserType() instanceof VersionBasicUserType)) { context.addError("Illegal non-version capable type '" + type + "' used for @Version attribute on the " + mapping.getErrorLocation() + "!"); @@ -96,10 +106,10 @@ public AbstractMethodSingularAttribute(ManagedViewTypeImplementor viewType, M boolean definesDeleteCascading = mapping.getCascadeTypes().contains(CascadeType.DELETE); boolean allowsDeleteCascading = updatable || mapping.getCascadeTypes().contains(CascadeType.AUTO); - this.readOnlySubtypes = (Set>) (Set) mapping.getReadOnlySubtypes(context); + this.readOnlySubtypes = (Set>) (Set) mapping.getReadOnlySubtypes(context, embeddableMapping); if (updatable) { - this.persistSubtypes = determinePersistSubtypeSet(type, mapping.getCascadeSubtypes(context), mapping.getCascadePersistSubtypes(context), context); + this.persistSubtypes = determinePersistSubtypeSet(type, mapping.getCascadeSubtypes(context, embeddableMapping), mapping.getCascadePersistSubtypes(context, embeddableMapping), context); this.persistCascaded = mapping.getCascadeTypes().contains(CascadeType.PERSIST) || mapping.getCascadeTypes().contains(CascadeType.AUTO) && !persistSubtypes.isEmpty(); } else { @@ -113,7 +123,7 @@ public AbstractMethodSingularAttribute(ManagedViewTypeImplementor viewType, M this.updateSubtypes = Collections.emptySet(); } else { // TODO: maybe allow to override mutability? - Set> updateCascadeAllowedSubtypes = determineUpdateSubtypeSet(type, mapping.getCascadeSubtypes(context), mapping.getCascadeUpdateSubtypes(context), context); + Set> updateCascadeAllowedSubtypes = determineUpdateSubtypeSet(type, mapping.getCascadeSubtypes(context, embeddableMapping), mapping.getCascadeUpdateSubtypes(context, embeddableMapping), context); boolean updateCascaded = mapping.getCascadeTypes().contains(CascadeType.UPDATE) || mapping.getCascadeTypes().contains(CascadeType.AUTO) && !updateCascadeAllowedSubtypes.isEmpty(); if (updateCascaded) { @@ -139,7 +149,7 @@ public AbstractMethodSingularAttribute(ManagedViewTypeImplementor viewType, M this.allowedSubtypes = createAllowedSubtypesSet(); this.optimisticLockProtected = determineOptimisticLockProtected(mapping, context, mutable); - this.inheritanceSubtypes = (Map, String>) (Map) mapping.getInheritanceSubtypes(context); + this.inheritanceSubtypes = (Map, String>) (Map) mapping.getInheritanceSubtypes(context, embeddableMapping); this.dirtyStateIndex = determineDirtyStateIndex(dirtyStateIndex); if (this.dirtyStateIndex == -1) { this.mappedBy = null; @@ -147,7 +157,7 @@ public AbstractMethodSingularAttribute(ManagedViewTypeImplementor viewType, M this.writableMappedByMapping = null; } else { ManagedType managedType = context.getEntityMetamodel().getManagedType(declaringType.getEntityClass()); - this.mappedBy = mapping.determineMappedBy(managedType, this.mapping, context); + this.mappedBy = mapping.determineMappedBy(managedType, this.mapping, context, embeddableMapping); if (this.mappedBy == null) { this.inverseRemoveStrategy = null; this.writableMappedByMapping = null; diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractParameterAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractParameterAttribute.java index f87e8968f3..74ad36fdeb 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractParameterAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractParameterAttribute.java @@ -41,8 +41,8 @@ public abstract class AbstractParameterAttribute extends AbstractAttribute private final int index; private final MappingConstructor declaringConstructor; - public AbstractParameterAttribute(MappingConstructorImpl constructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(constructor.getDeclaringType(), mapping, context); + public AbstractParameterAttribute(MappingConstructorImpl constructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(constructor.getDeclaringType(), mapping, context, embeddableMapping); this.index = mapping.getIndex(); this.declaringConstructor = constructor; diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractParameterPluralAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractParameterPluralAttribute.java index 9479883f55..fa6441e068 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractParameterPluralAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractParameterPluralAttribute.java @@ -40,10 +40,10 @@ public abstract class AbstractParameterPluralAttribute extends Abstract private final Comparator comparator; @SuppressWarnings("unchecked") - public AbstractParameterPluralAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(mappingConstructor, mapping, context); - this.elementType = (Type) mapping.getElementType(context); - this.elementInheritanceSubtypes = (Map, String>) (Map) mapping.getElementInheritanceSubtypes(context); + public AbstractParameterPluralAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(mappingConstructor, mapping, context, embeddableMapping); + this.elementType = (Type) mapping.getElementType(context, embeddableMapping); + this.elementInheritanceSubtypes = (Map, String>) (Map) mapping.getElementInheritanceSubtypes(context, embeddableMapping); this.sorted = mapping.isSorted(); this.ordered = mapping.getContainerBehavior() == AttributeMapping.ContainerBehavior.ORDERED; diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractParameterSingularAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractParameterSingularAttribute.java index 04d799fab0..94d95cb564 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractParameterSingularAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AbstractParameterSingularAttribute.java @@ -36,10 +36,10 @@ public abstract class AbstractParameterSingularAttribute extends AbstractP private final Map, String> inheritanceSubtypes; @SuppressWarnings("unchecked") - public AbstractParameterSingularAttribute(MappingConstructorImpl constructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(constructor, mapping, context); - this.type = (Type) mapping.getType(context); - this.inheritanceSubtypes = (Map, String>) (Map) mapping.getInheritanceSubtypes(context); + public AbstractParameterSingularAttribute(MappingConstructorImpl constructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(constructor, mapping, context, embeddableMapping); + this.type = (Type) mapping.getType(context, embeddableMapping); + this.inheritanceSubtypes = (Map, String>) (Map) mapping.getInheritanceSubtypes(context, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AttributeMapping.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AttributeMapping.java index 68d725e79b..724d457436 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AttributeMapping.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/AttributeMapping.java @@ -76,6 +76,9 @@ public abstract class AttributeMapping implements EntityViewAttributeMapping { protected ViewMapping typeMapping; protected ViewMapping keyViewMapping; protected ViewMapping elementViewMapping; + protected Map> embeddableTypeMap; + protected Map> embeddableKeyTypeMap; + protected Map> embeddableElementTypeMap; protected InheritanceViewMapping inheritanceSubtypeMappings; protected InheritanceViewMapping keyInheritanceSubtypeMappings; @@ -83,8 +86,12 @@ public abstract class AttributeMapping implements EntityViewAttributeMapping { protected Map, String> inheritanceSubtypes; protected Map, String> keyInheritanceSubtypes; protected Map, String> elementInheritanceSubtypes; + protected Map, String>> embeddableInheritanceSubtypesMap; + protected Map, String>> embeddableKeyInheritanceSubtypesMap; + protected Map, String>> embeddableElementInheritanceSubtypesMap; protected AbstractAttribute attribute; + protected Map> embeddableAttributeMap; public AttributeMapping(ViewMapping viewMapping, Annotation mapping, MetamodelBootContext context, boolean isCollection, Class declaredTypeClass, Class declaredKeyTypeClass, Class declaredElementTypeClass, java.lang.reflect.Type declaredType, java.lang.reflect.Type declaredKeyType, java.lang.reflect.Type declaredElementType, Map, String> inheritanceSubtypeClassMappings, Map, String> keyInheritanceSubtypeClassMappings, Map, String> elementInheritanceSubtypeClassMappings) { @@ -188,7 +195,7 @@ public boolean isSorted() { return containerBehavior == ContainerBehavior.SORTED; } - public abstract String determineMappedBy(ManagedType managedType, String mapping, MetamodelBuildingContext context); + public abstract String determineMappedBy(ManagedType managedType, String mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping); public abstract Map determineWritableMappedByMappings(ManagedType managedType, String mappedBy, MetamodelBuildingContext context); @@ -231,8 +238,8 @@ public Class getDeclaredElementType() { return declaredElementTypeClass; } - public Class getJavaType(MetamodelBuildingContext context) { - Type t = getType(context); + public Class getJavaType(MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + Type t = getType(context, embeddableMapping); if (t == null) { return null; } @@ -279,59 +286,155 @@ public Set> getElementTypes(List getType(MetamodelBuildingContext context) { - if (type != null) { - return type; - } - if (typeMapping == null) { - return type = context.getBasicType(viewMapping, declaredType, declaredTypeClass, getBaseTypes(getPossibleTargetTypes(context))); + public Type getType(MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + if (embeddableMapping == null) { + if (type != null) { + return type; + } + if (typeMapping == null) { + return type = context.getBasicType(viewMapping, declaredType, declaredTypeClass, getBaseTypes(getPossibleTargetTypes(context))); + } + return type = typeMapping.getManagedViewType(context, embeddableMapping); + } else { + if (embeddableTypeMap == null) { + embeddableTypeMap = new HashMap<>(1); + } + Type t = embeddableTypeMap.get(embeddableMapping); + if (t != null) { + return t; + } + if (typeMapping == null) { + t = context.getBasicType(viewMapping, declaredType, declaredTypeClass, getBaseTypes(getPossibleTargetTypes(context))); + } else { + t = typeMapping.getManagedViewType(context, embeddableMapping); + } + + embeddableTypeMap.put(embeddableMapping, t); + return t; } - return type = typeMapping.getManagedViewType(context); } - public Type getKeyType(MetamodelBuildingContext context) { - if (keyType != null) { - return keyType; - } - if (keyViewMapping == null) { - return keyType = context.getBasicType(viewMapping, declaredKeyType, declaredKeyTypeClass, getKeyTypes(getPossibleTargetTypes(context))); + public Type getKeyType(MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + if (embeddableMapping == null) { + if (keyType != null) { + return keyType; + } + if (keyViewMapping == null) { + return keyType = context.getBasicType(viewMapping, declaredKeyType, declaredKeyTypeClass, getKeyTypes(getPossibleTargetTypes(context))); + } + return keyType = keyViewMapping.getManagedViewType(context, embeddableMapping); + } else { + if (embeddableKeyTypeMap == null) { + embeddableKeyTypeMap = new HashMap<>(1); + } + Type t = embeddableKeyTypeMap.get(embeddableMapping); + if (t != null) { + return t; + } + if (keyViewMapping == null) { + t = context.getBasicType(viewMapping, declaredType, declaredTypeClass, getBaseTypes(getPossibleTargetTypes(context))); + } else { + t = keyViewMapping.getManagedViewType(context, embeddableMapping); + } + + embeddableKeyTypeMap.put(embeddableMapping, t); + return t; } - return keyType = keyViewMapping.getManagedViewType(context); } - public Type getElementType(MetamodelBuildingContext context) { - if (elementType != null) { - return elementType; - } - if (elementViewMapping == null) { - return elementType = context.getBasicType(viewMapping, declaredElementType, declaredElementTypeClass, getElementTypes(getPossibleTargetTypes(context))); + public Type getElementType(MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + if (embeddableMapping == null) { + if (elementType != null) { + return elementType; + } + if (elementViewMapping == null) { + return elementType = context.getBasicType(viewMapping, declaredElementType, declaredElementTypeClass, getElementTypes(getPossibleTargetTypes(context))); + } + return elementType = elementViewMapping.getManagedViewType(context, embeddableMapping); + } else { + if (embeddableElementTypeMap == null) { + embeddableElementTypeMap = new HashMap<>(1); + } + Type t = embeddableElementTypeMap.get(embeddableMapping); + if (t != null) { + return t; + } + if (elementViewMapping == null) { + t = context.getBasicType(viewMapping, declaredType, declaredTypeClass, getBaseTypes(getPossibleTargetTypes(context))); + } else { + t = elementViewMapping.getManagedViewType(context, embeddableMapping); + } + + embeddableElementTypeMap.put(embeddableMapping, t); + return t; } - return elementType = elementViewMapping.getManagedViewType(context); } - public Map, String> getInheritanceSubtypes(MetamodelBuildingContext context) { - if (inheritanceSubtypes != null) { - return inheritanceSubtypes; + public Map, String> getInheritanceSubtypes(MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + if (embeddableMapping == null) { + if (inheritanceSubtypes != null) { + return inheritanceSubtypes; + } + return inheritanceSubtypes = initializeInheritanceSubtypes(inheritanceSubtypeMappings, typeMapping, context, embeddableMapping); + } else { + if (embeddableInheritanceSubtypesMap == null) { + embeddableInheritanceSubtypesMap = new HashMap<>(1); + } + Map, String> subtypes = embeddableInheritanceSubtypesMap.get(embeddableMapping); + if (subtypes != null) { + return subtypes; + } + + subtypes = initializeInheritanceSubtypes(inheritanceSubtypeMappings, typeMapping, context, embeddableMapping); + embeddableInheritanceSubtypesMap.put(embeddableMapping, subtypes); + return subtypes; } - return inheritanceSubtypes = initializeInheritanceSubtypes(inheritanceSubtypeMappings, typeMapping, context); } - public Map, String> getKeyInheritanceSubtypes(MetamodelBuildingContext context) { - if (keyInheritanceSubtypes != null) { - return keyInheritanceSubtypes; + public Map, String> getKeyInheritanceSubtypes(MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + if (embeddableMapping == null) { + if (keyInheritanceSubtypes != null) { + return keyInheritanceSubtypes; + } + return keyInheritanceSubtypes = initializeInheritanceSubtypes(keyInheritanceSubtypeMappings, keyViewMapping, context, embeddableMapping); + } else { + if (embeddableKeyInheritanceSubtypesMap == null) { + embeddableKeyInheritanceSubtypesMap = new HashMap<>(1); + } + Map, String> subtypes = embeddableKeyInheritanceSubtypesMap.get(embeddableMapping); + if (subtypes != null) { + return subtypes; + } + + subtypes = initializeInheritanceSubtypes(keyInheritanceSubtypeMappings, keyViewMapping, context, embeddableMapping); + embeddableKeyInheritanceSubtypesMap.put(embeddableMapping, subtypes); + return subtypes; } - return keyInheritanceSubtypes = initializeInheritanceSubtypes(keyInheritanceSubtypeMappings, keyViewMapping, context); } - public Map, String> getElementInheritanceSubtypes(MetamodelBuildingContext context) { - if (elementInheritanceSubtypes != null) { - return elementInheritanceSubtypes; + public Map, String> getElementInheritanceSubtypes(MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + if (embeddableMapping == null) { + if (elementInheritanceSubtypes != null) { + return elementInheritanceSubtypes; + } + return elementInheritanceSubtypes = initializeInheritanceSubtypes(elementInheritanceSubtypeMappings, elementViewMapping, context, embeddableMapping); + } else { + if (embeddableElementInheritanceSubtypesMap == null) { + embeddableElementInheritanceSubtypesMap = new HashMap<>(1); + } + Map, String> subtypes = embeddableElementInheritanceSubtypesMap.get(embeddableMapping); + if (subtypes != null) { + return subtypes; + } + + subtypes = initializeInheritanceSubtypes(elementInheritanceSubtypeMappings, elementViewMapping, context, embeddableMapping); + embeddableElementInheritanceSubtypesMap.put(embeddableMapping, subtypes); + return subtypes; } - return elementInheritanceSubtypes = initializeInheritanceSubtypes(elementInheritanceSubtypeMappings, elementViewMapping, context); } @SuppressWarnings("unchecked") - private Map, String> initializeInheritanceSubtypes(InheritanceViewMapping inheritanceSubtypeMappings, ViewMapping viewMapping, MetamodelBuildingContext context) { + private Map, String> initializeInheritanceSubtypes(InheritanceViewMapping inheritanceSubtypeMappings, ViewMapping viewMapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { if (viewMapping == null || inheritanceSubtypeMappings == null || inheritanceSubtypeMappings.getInheritanceSubtypeMappings().isEmpty()) { return Collections.emptyMap(); } @@ -345,10 +448,10 @@ private Map, String> initializeInheritanceSubtypes mapping = ""; } } - map.put(mappingEntry.getKey().getManagedViewType(context), mapping); + map.put(mappingEntry.getKey().getManagedViewType(context, embeddableMapping), mapping); } - if (map.equals(viewMapping.getManagedViewType(context).getInheritanceSubtypeConfiguration())) { - return (Map, String>) (Map) viewMapping.getManagedViewType(context).getInheritanceSubtypeConfiguration(); + if (map.equals(viewMapping.getManagedViewType(context, embeddableMapping).getInheritanceSubtypeConfiguration())) { + return (Map, String>) (Map) viewMapping.getManagedViewType(context, embeddableMapping).getInheritanceSubtypeConfiguration(); } else { return Collections.unmodifiableMap(map); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ConvertedViewMapping.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ConvertedViewMapping.java index 4ceba7eb09..00ca94ea83 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ConvertedViewMapping.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ConvertedViewMapping.java @@ -50,12 +50,12 @@ public ConvertedViewMapping(ViewMapping delegate, TypeConverter converter, @Override @SuppressWarnings("unchecked") - public ManagedViewTypeImplementor getManagedViewType(MetamodelBuildingContext context) { + public ManagedViewTypeImplementor getManagedViewType(MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { if (convertedViewType != null) { return convertedViewType; } - ManagedViewTypeImplementor viewType = delegate.getManagedViewType(context); + ManagedViewTypeImplementor viewType = delegate.getManagedViewType(context, embeddableMapping); if (viewType instanceof FlatViewType) { return convertedViewType = new ConvertedFlatViewType((FlatViewTypeImplementor) viewType, convertedType, converter); } else { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/EmbeddableOwner.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/EmbeddableOwner.java new file mode 100644 index 0000000000..0dd401c295 --- /dev/null +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/EmbeddableOwner.java @@ -0,0 +1,68 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.view.impl.metamodel; + +/** + * + * @author Christian Beikov + * @since 1.3.0 + */ +public class EmbeddableOwner { + private final Class entityClass; + private final String embeddableMapping; + + public EmbeddableOwner(Class entityClass, String embeddableMapping) { + this.entityClass = entityClass; + this.embeddableMapping = embeddableMapping; + } + + public Class getEntityClass() { + return entityClass; + } + + public String getEmbeddableMapping() { + return embeddableMapping; + } + + public EmbeddableOwner withSubMapping(String mapping) { + return new EmbeddableOwner(entityClass, embeddableMapping + "." + mapping); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof EmbeddableOwner)) { + return false; + } + + EmbeddableOwner that = (EmbeddableOwner) o; + + if (!getEntityClass().equals(that.getEntityClass())) { + return false; + } + return getEmbeddableMapping().equals(that.getEmbeddableMapping()); + } + + @Override + public int hashCode() { + int result = getEntityClass().hashCode(); + result = 31 * result + getEmbeddableMapping().hashCode(); + return result; + } +} diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/FlatViewTypeImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/FlatViewTypeImpl.java index e2b0baea51..c442889ab9 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/FlatViewTypeImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/FlatViewTypeImpl.java @@ -16,6 +16,8 @@ package com.blazebit.persistence.view.impl.metamodel; +import com.blazebit.persistence.view.metamodel.FlatViewType; + import javax.persistence.metamodel.ManagedType; /** @@ -25,8 +27,8 @@ */ public class FlatViewTypeImpl extends ManagedViewTypeImpl implements FlatViewTypeImplementor { - public FlatViewTypeImpl(ViewMapping viewMapping, ManagedType managedType, MetamodelBuildingContext context) { - super(viewMapping, managedType, context); + public FlatViewTypeImpl(ViewMapping viewMapping, ManagedType managedType, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(viewMapping, managedType, context, embeddableMapping); } @Override @@ -38,4 +40,14 @@ protected boolean hasId() { public MappingType getMappingType() { return MappingType.FLAT_VIEW; } + + @Override + public int hashCode() { + return getJavaType().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof FlatViewType && getJavaType().equals(((FlatViewType) obj).getJavaType()); + } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ManagedViewTypeImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ManagedViewTypeImpl.java index e9ef8deec7..839f81de80 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ManagedViewTypeImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ManagedViewTypeImpl.java @@ -76,7 +76,7 @@ public abstract class ManagedViewTypeImpl implements ManagedViewTypeImplement private final boolean hasJoinFetchedCollections; @SuppressWarnings("unchecked") - public ManagedViewTypeImpl(ViewMapping viewMapping, ManagedType managedType, MetamodelBuildingContext context) { + public ManagedViewTypeImpl(ViewMapping viewMapping, ManagedType managedType, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { this.javaType = (Class) viewMapping.getEntityViewClass(); this.jpaManagedType = managedType; this.postCreateMethod = viewMapping.getPostCreateMethod(); @@ -147,14 +147,14 @@ public ManagedViewTypeImpl(ViewMapping viewMapping, ManagedType managedType, // The id and the version always have -1 as dirty state index because they can't be dirty in the traditional sense // Id can only be set on "new" objects and shouldn't be mutable, version acts as optimistic concurrency version if (mapping.isId()) { - attribute = mapping.getMethodAttribute(this, 0, -1, context); + attribute = mapping.getMethodAttribute(this, 0, -1, context, embeddableMapping); index--; } else { - attribute = mapping.getMethodAttribute(this, index, -1, context); + attribute = mapping.getMethodAttribute(this, index, -1, context, embeddableMapping); } } else { // Note that the dirty state index is only a "suggested" index, but the implementation can choose not to use it - attribute = mapping.getMethodAttribute(this, index, dirtyStateIndex, context); + attribute = mapping.getMethodAttribute(this, index, dirtyStateIndex, context, embeddableMapping); if (attribute.getDirtyStateIndex() != -1) { mutableAttributes.add(attribute); dirtyStateIndex++; @@ -182,15 +182,15 @@ public ManagedViewTypeImpl(ViewMapping viewMapping, ManagedType managedType, overallInheritanceSubtypeMappings.putAll(inheritanceViewMapping.getInheritanceSubtypeMappings()); } - this.overallInheritanceSubtypeConfiguration = new InheritanceSubtypeConfiguration<>(this, viewMapping, -1, new InheritanceViewMapping(overallInheritanceSubtypeMappings), context); - this.defaultInheritanceSubtypeConfiguration = new InheritanceSubtypeConfiguration<>(this, viewMapping, 0, viewMapping.getDefaultInheritanceViewMapping(), context, overallInheritanceSubtypeConfiguration); + this.overallInheritanceSubtypeConfiguration = new InheritanceSubtypeConfiguration<>(this, viewMapping, -1, new InheritanceViewMapping(overallInheritanceSubtypeMappings), context, embeddableMapping); + this.defaultInheritanceSubtypeConfiguration = new InheritanceSubtypeConfiguration<>(this, viewMapping, 0, viewMapping.getDefaultInheritanceViewMapping(), context, embeddableMapping, overallInheritanceSubtypeConfiguration); inheritanceSubtypeConfigurations.put(defaultInheritanceSubtypeConfiguration.inheritanceSubtypeConfiguration, defaultInheritanceSubtypeConfiguration); int configurationIndex = 1; for (InheritanceViewMapping inheritanceViewMapping : viewMapping.getInheritanceViewMappings()) { // Skip the default as it is handled a few lines before if (inheritanceViewMapping != viewMapping.getDefaultInheritanceViewMapping()) { - InheritanceSubtypeConfiguration subtypeConfiguration = new InheritanceSubtypeConfiguration<>(this, viewMapping, configurationIndex, inheritanceViewMapping, context, overallInheritanceSubtypeConfiguration); + InheritanceSubtypeConfiguration subtypeConfiguration = new InheritanceSubtypeConfiguration<>(this, viewMapping, configurationIndex, inheritanceViewMapping, context, embeddableMapping, overallInheritanceSubtypeConfiguration); inheritanceSubtypeConfigurations.put(subtypeConfiguration.inheritanceSubtypeConfiguration, subtypeConfiguration); configurationIndex++; } @@ -208,7 +208,7 @@ public ManagedViewTypeImpl(ViewMapping viewMapping, ManagedType managedType, if (constructorIndex.containsKey(constructorName)) { constructorName += constructorIndex.size(); } - MappingConstructorImpl mappingConstructor = new MappingConstructorImpl(this, constructorName, constructor, context); + MappingConstructorImpl mappingConstructor = new MappingConstructorImpl(this, constructorName, constructor, context, embeddableMapping); constructors.put(entry.getKey(), mappingConstructor); constructorIndex.put(constructorName, mappingConstructor); } @@ -562,15 +562,15 @@ public static class InheritanceSubtypeConfiguration { private final Map>> attributesClosure; private final Map, int[]> overallPositionAssignments; - public InheritanceSubtypeConfiguration(ManagedViewTypeImpl baseType, ViewMapping baseTypeViewMapping, int configurationIndex, InheritanceViewMapping inheritanceViewMapping, MetamodelBuildingContext context) { - this(baseType, baseTypeViewMapping, configurationIndex, inheritanceViewMapping, context, null); + public InheritanceSubtypeConfiguration(ManagedViewTypeImpl baseType, ViewMapping baseTypeViewMapping, int configurationIndex, InheritanceViewMapping inheritanceViewMapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + this(baseType, baseTypeViewMapping, configurationIndex, inheritanceViewMapping, context, embeddableMapping, null); } - public InheritanceSubtypeConfiguration(ManagedViewTypeImpl baseType, ViewMapping baseTypeViewMapping, int configurationIndex, InheritanceViewMapping inheritanceViewMapping, MetamodelBuildingContext context, InheritanceSubtypeConfiguration overallConfiguration) { + public InheritanceSubtypeConfiguration(ManagedViewTypeImpl baseType, ViewMapping baseTypeViewMapping, int configurationIndex, InheritanceViewMapping inheritanceViewMapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping, InheritanceSubtypeConfiguration overallConfiguration) { this.baseType = baseType; this.configurationIndex = configurationIndex; - ManagedViewTypeImpl[] orderedInheritanceSubtypes = createOrderedSubtypes(inheritanceViewMapping, context); - this.inheritanceSubtypeConfiguration = createInheritanceSubtypeConfiguration(inheritanceViewMapping, context); + ManagedViewTypeImpl[] orderedInheritanceSubtypes = createOrderedSubtypes(inheritanceViewMapping, context, embeddableMapping); + this.inheritanceSubtypeConfiguration = createInheritanceSubtypeConfiguration(inheritanceViewMapping, context, embeddableMapping); this.inheritanceSubtypes = Collections.unmodifiableSet(inheritanceSubtypeConfiguration.keySet()); this.inheritanceDiscriminatorMapping = createInheritanceDiscriminatorMapping(orderedInheritanceSubtypes); this.attributesClosure = createSubtypeAttributesClosure(orderedInheritanceSubtypes); @@ -658,14 +658,14 @@ public int[] getOverallPositionAssignment(ManagedViewTypeImplementor[] createOrderedSubtypes(InheritanceViewMapping inheritanceViewMapping, MetamodelBuildingContext context) { + private ManagedViewTypeImpl[] createOrderedSubtypes(InheritanceViewMapping inheritanceViewMapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { ManagedViewTypeImpl[] orderedSubtypes = new ManagedViewTypeImpl[inheritanceViewMapping.getInheritanceSubtypeMappings().size()]; int i = 0; for (ViewMapping mapping : inheritanceViewMapping.getInheritanceSubtypeMappings().keySet()) { if (mapping.getEntityViewClass() == baseType.javaType) { orderedSubtypes[i++] = baseType; } else { - orderedSubtypes[i++] = (ManagedViewTypeImpl) mapping.getManagedViewType(context); + orderedSubtypes[i++] = (ManagedViewTypeImpl) mapping.getManagedViewType(context, embeddableMapping); } } @@ -673,7 +673,7 @@ private ManagedViewTypeImpl[] createOrderedSubtypes(InheritanceView } @SuppressWarnings("unchecked") - private Map, String> createInheritanceSubtypeConfiguration(InheritanceViewMapping inheritanceViewMapping, MetamodelBuildingContext context) { + private Map, String> createInheritanceSubtypeConfiguration(InheritanceViewMapping inheritanceViewMapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { Map, String> configuration = new LinkedHashMap<>(inheritanceViewMapping.getInheritanceSubtypeMappings().size()); for (Map.Entry mappingEntry : inheritanceViewMapping.getInheritanceSubtypeMappings().entrySet()) { @@ -688,7 +688,7 @@ private Map, String> createInheritanceSu if (mappingEntry.getKey().getEntityViewClass() == baseType.javaType) { configuration.put(baseType, mapping); } else { - configuration.put((ManagedViewTypeImplementor) mappingEntry.getKey().getManagedViewType(context), mapping); + configuration.put((ManagedViewTypeImplementor) mappingEntry.getKey().getManagedViewType(context, embeddableMapping), mapping); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MappingConstructorImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MappingConstructorImpl.java index 8ea94ae3ba..063be7a5a7 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MappingConstructorImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MappingConstructorImpl.java @@ -46,7 +46,7 @@ public class MappingConstructorImpl implements MappingConstructor { private final boolean hasJoinFetchedCollections; @SuppressWarnings("unchecked") - public MappingConstructorImpl(ManagedViewTypeImplementor viewType, String name, ConstructorMapping mapping, MetamodelBuildingContext context) { + public MappingConstructorImpl(ManagedViewTypeImplementor viewType, String name, ConstructorMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { this.name = name; this.declaringType = viewType; this.javaConstructor = (Constructor) mapping.getConstructor(); @@ -58,7 +58,7 @@ public MappingConstructorImpl(ManagedViewTypeImplementor viewType, String nam boolean hasJoinFetchedCollections = false; for (int i = 0; i < parameterCount; i++) { - AbstractParameterAttribute parameter = parameterMappings.get(i).getParameterAttribute(this, context); + AbstractParameterAttribute parameter = parameterMappings.get(i).getParameterAttribute(this, context, embeddableMapping); hasJoinFetchedCollections = hasJoinFetchedCollections || parameter.hasJoinFetchedCollections(); parameters.add(parameter); overallParameters.add(parameter); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MethodAttributeMapping.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MethodAttributeMapping.java index 91cdd504bc..28515fb8bf 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MethodAttributeMapping.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/MethodAttributeMapping.java @@ -73,7 +73,7 @@ public class MethodAttributeMapping extends AttributeMapping implements EntityVi private Boolean isOrphanRemoval; private Boolean isOptimisticLockProtected; private String mappedBy; - private boolean mappedByResolved; + private Map embeddableMappedByMap; private InverseRemoveStrategy inverseRemoveStrategy; private Set cascadeTypes = Collections.singleton(CascadeType.AUTO); private Set> cascadeSubtypeClasses; @@ -88,6 +88,10 @@ public class MethodAttributeMapping extends AttributeMapping implements EntityVi private Set> cascadeSubtypes; private Set> cascadePersistSubtypes; private Set> cascadeUpdateSubtypes; + private Map>> embeddableReadOnlySubtypesMap; + private Map>> embeddableCascadeSubtypesMap; + private Map>> embeddableCascadePersistSubtypesMap; + private Map>> embeddableCascadeUpdateSubtypesMap; // TODO: attribute filter config @@ -164,7 +168,6 @@ public String getMappedBy() { @Override public void setMappedBy(String mappedBy) { this.mappedBy = mappedBy; - this.mappedByResolved = true; } @Override @@ -180,14 +183,23 @@ public void setInverseRemoveStrategy(InverseRemoveStrategy inverseRemoveStrategy this.inverseRemoveStrategy = inverseRemoveStrategy; } - public Set> getReadOnlySubtypes(MetamodelBuildingContext context) { + public Set> getReadOnlySubtypes(MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + Set> readOnlySubtypes; + if (embeddableMapping == null) { + readOnlySubtypes = this.readOnlySubtypes; + } else { + if (embeddableReadOnlySubtypesMap == null) { + embeddableReadOnlySubtypesMap = new HashMap<>(1); + } + readOnlySubtypes = embeddableReadOnlySubtypesMap.get(embeddableMapping); + } if (readOnlySubtypes != null) { return readOnlySubtypes; } - Set> subtypes = initializeCascadeSubtypes(readOnlySubtypeMappings, context); - com.blazebit.persistence.view.metamodel.Type elementType = getElementType(context); + Set> subtypes = initializeCascadeSubtypes(readOnlySubtypeMappings, context, embeddableMapping); + com.blazebit.persistence.view.metamodel.Type elementType = getElementType(context, embeddableMapping); if (elementType == null) { - elementType = getType(context); + elementType = getType(context, embeddableMapping); } if (elementType instanceof ManagedViewTypeImplementor) { if (subtypes.isEmpty()) { @@ -196,37 +208,82 @@ public Set> getReadOnlySubtypes(MetamodelBuildingC subtypes.add((ManagedViewTypeImplementor) elementType); } } - return readOnlySubtypes = subtypes; + if (embeddableMapping == null) { + this.readOnlySubtypes = subtypes; + } else { + embeddableReadOnlySubtypesMap.put(embeddableMapping, subtypes); + } + + return subtypes; } - public Set> getCascadeSubtypes(MetamodelBuildingContext context) { - if (cascadeSubtypes != null) { + public Set> getCascadeSubtypes(MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + if (embeddableMapping == null) { + if (cascadeSubtypes != null) { + return cascadeSubtypes; + } + return cascadeSubtypes = initializeCascadeSubtypes(cascadeSubtypeMappings, context, embeddableMapping); + } else { + if (embeddableCascadeSubtypesMap == null) { + embeddableCascadeSubtypesMap = new HashMap<>(1); + } + Set> cascadeSubtypes = embeddableCascadeSubtypesMap.get(embeddableMapping); + if (cascadeSubtypes != null) { + return cascadeSubtypes; + } + cascadeSubtypes = initializeCascadeSubtypes(cascadeSubtypeMappings, context, embeddableMapping); + embeddableCascadeSubtypesMap.put(embeddableMapping, cascadeSubtypes); return cascadeSubtypes; } - return cascadeSubtypes = initializeCascadeSubtypes(cascadeSubtypeMappings, context); } - public Set> getCascadePersistSubtypes(MetamodelBuildingContext context) { - if (cascadePersistSubtypes != null) { + public Set> getCascadePersistSubtypes(MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + if (embeddableMapping == null) { + if (cascadePersistSubtypes != null) { + return cascadePersistSubtypes; + } + return cascadePersistSubtypes = initializeCascadeSubtypes(cascadePersistSubtypeMappings, context, embeddableMapping); + } else { + if (embeddableCascadePersistSubtypesMap == null) { + embeddableCascadePersistSubtypesMap = new HashMap<>(1); + } + Set> cascadePersistSubtypes = embeddableCascadePersistSubtypesMap.get(embeddableMapping); + if (cascadePersistSubtypes != null) { + return cascadePersistSubtypes; + } + cascadePersistSubtypes = initializeCascadeSubtypes(cascadeSubtypeMappings, context, embeddableMapping); + embeddableCascadePersistSubtypesMap.put(embeddableMapping, cascadePersistSubtypes); return cascadePersistSubtypes; } - return cascadePersistSubtypes = initializeCascadeSubtypes(cascadePersistSubtypeMappings, context); } - public Set> getCascadeUpdateSubtypes(MetamodelBuildingContext context) { - if (cascadeUpdateSubtypes != null) { + public Set> getCascadeUpdateSubtypes(MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + if (embeddableMapping == null) { + if (cascadeUpdateSubtypes != null) { + return cascadeUpdateSubtypes; + } + return cascadeUpdateSubtypes = initializeCascadeSubtypes(cascadeUpdateSubtypeMappings, context, embeddableMapping); + } else { + if (embeddableCascadeUpdateSubtypesMap == null) { + embeddableCascadeUpdateSubtypesMap = new HashMap<>(1); + } + Set> cascadeUpdateSubtypes = embeddableCascadeUpdateSubtypesMap.get(embeddableMapping); + if (cascadeUpdateSubtypes != null) { + return cascadeUpdateSubtypes; + } + cascadeUpdateSubtypes = initializeCascadeSubtypes(cascadeSubtypeMappings, context, embeddableMapping); + embeddableCascadeUpdateSubtypesMap.put(embeddableMapping, cascadeUpdateSubtypes); return cascadeUpdateSubtypes; } - return cascadeUpdateSubtypes = initializeCascadeSubtypes(cascadeUpdateSubtypeMappings, context); } - private Set> initializeCascadeSubtypes(Map subtypeMappings, MetamodelBuildingContext context) { + private Set> initializeCascadeSubtypes(Map subtypeMappings, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { if (subtypeMappings == null || subtypeMappings.isEmpty()) { return Collections.emptySet(); } Set> subtypes = new HashSet<>(subtypeMappings.size()); for (ViewMapping mapping : subtypeMappings.keySet()) { - subtypes.add(mapping.getManagedViewType(context)); + subtypes.add(mapping.getManagedViewType(context, embeddableMapping)); } return subtypes; } @@ -306,12 +363,28 @@ public boolean validateDependencies(MetamodelBuildingContext context, Set managedType, String mapping, MetamodelBuildingContext context) { - if (mappedByResolved) { + public String determineMappedBy(ManagedType managedType, String mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + String mappedBy; + if (embeddableMapping == null) { + mappedBy = this.mappedBy; + } else { + if (embeddableMappedByMap == null) { + embeddableMappedByMap = new HashMap<>(1); + } + mappedBy = embeddableMappedByMap.get(embeddableMapping); + } + if (mappedBy != null) { + if (mappedBy.isEmpty()) { + return null; + } return mappedBy; } - mappedByResolved = true; + if (embeddableMapping == null) { + this.mappedBy = ""; + } else { + embeddableMappedByMap.put(embeddableMapping, ""); + } if (mapping == null || mapping.isEmpty()) { return null; @@ -321,24 +394,22 @@ public String determineMappedBy(ManagedType managedType, String mapping, Meta return null; } - // If we find a non-simple path, we don't even try to find a mapped by mapping - for (int i = 0; i < mapping.length(); i++) { - final char c = mapping.charAt(i); - if (!Character.isJavaIdentifierPart(c) && c != '.') { - return null; - } - } - try { AttributePath basicAttributePath = context.getJpaProvider().getJpaMetamodelAccessor().getAttributePath(context.getEntityMetamodel(), managedType, mapping); List> attributes = basicAttributePath.getAttributes(); - for (int i = 0; i < attributes.size() - 1; i++) { + for (int i = 1; i < attributes.size(); i++) { if (attributes.get(i).getDeclaringType().getPersistenceType() != Type.PersistenceType.EMBEDDABLE) { // If the mapping goes over a non-embeddable, we can't determine a mapped by attribute name return null; } } - return mappedBy = context.getJpaProvider().getMappedBy((EntityType) managedType, mapping); + mappedBy = context.getJpaProvider().getMappedBy((EntityType) managedType, mapping); + if (embeddableMapping == null) { + this.mappedBy = mappedBy; + } else { + embeddableMappedByMap.put(embeddableMapping, mappedBy); + } + return mappedBy; } catch (IllegalArgumentException ex) { // if the mapping is invalid, we skip the determination as the error will be analyzed further at a later stage return null; @@ -491,11 +562,27 @@ private static String methodReference(Method method) { // If you change something here don't forget to also update ParameterAttributeMapping#getParameterAttribute @SuppressWarnings("unchecked") - public AbstractMethodAttribute getMethodAttribute(ManagedViewTypeImplementor viewType, int attributeIndex, int dirtyStateIndex, MetamodelBuildingContext context) { + public AbstractMethodAttribute getMethodAttribute(ManagedViewTypeImplementor viewType, int attributeIndex, int dirtyStateIndex, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + AbstractAttribute attribute; + if (embeddableMapping == null) { + attribute = this.attribute; + } else { + if (embeddableAttributeMap == null) { + embeddableAttributeMap = new HashMap<>(1); + } + attribute = embeddableAttributeMap.get(embeddableMapping); + } if (attribute == null) { if (mapping instanceof MappingParameter) { - mappedByResolved = true; - attribute = new MappingMethodSingularAttribute(viewType, this, context, attributeIndex, dirtyStateIndex); + if (embeddableMapping == null) { + mappedBy = ""; + } else { + if (embeddableMappedByMap == null) { + embeddableMappedByMap = new HashMap<>(1); + } + embeddableMappedByMap.put(embeddableMapping, ""); + } + attribute = new MappingMethodSingularAttribute(viewType, this, context, attributeIndex, dirtyStateIndex, embeddableMapping); return (AbstractMethodAttribute) attribute; } @@ -504,28 +591,28 @@ private static String methodReference(Method method) { if (isCollection) { if (Collection.class == declaredTypeClass) { if (correlated) { - attribute = new CorrelatedMethodCollectionAttribute(viewType, this, context, attributeIndex, dirtyStateIndex); + attribute = new CorrelatedMethodCollectionAttribute(viewType, this, context, attributeIndex, dirtyStateIndex, embeddableMapping); } else { - attribute = new MappingMethodCollectionAttribute(viewType, this, context, attributeIndex, dirtyStateIndex); + attribute = new MappingMethodCollectionAttribute(viewType, this, context, attributeIndex, dirtyStateIndex, embeddableMapping); } } else if (List.class == declaredTypeClass) { if (correlated) { - attribute = new CorrelatedMethodListAttribute(viewType, this, context, attributeIndex, dirtyStateIndex); + attribute = new CorrelatedMethodListAttribute(viewType, this, context, attributeIndex, dirtyStateIndex, embeddableMapping); } else { - attribute = new MappingMethodListAttribute(viewType, this, context, attributeIndex, dirtyStateIndex); + attribute = new MappingMethodListAttribute(viewType, this, context, attributeIndex, dirtyStateIndex, embeddableMapping); } } else if (Set.class == declaredTypeClass || SortedSet.class == declaredTypeClass || NavigableSet.class == declaredTypeClass) { if (correlated) { - attribute = new CorrelatedMethodSetAttribute(viewType, this, context, attributeIndex, dirtyStateIndex); + attribute = new CorrelatedMethodSetAttribute(viewType, this, context, attributeIndex, dirtyStateIndex, embeddableMapping); } else { - attribute = new MappingMethodSetAttribute(viewType, this, context, attributeIndex, dirtyStateIndex); + attribute = new MappingMethodSetAttribute(viewType, this, context, attributeIndex, dirtyStateIndex, embeddableMapping); } } else if (Map.class == declaredTypeClass || SortedMap.class == declaredTypeClass || NavigableMap.class == declaredTypeClass) { if (correlated) { context.addError("The mapping defined on method '" + viewType.getJavaType().getName() + "." + method.getName() + "' uses a Map type with a correlated mapping which is unsupported!"); attribute = null; } else { - attribute = new MappingMethodMapAttribute(viewType, this, context, attributeIndex, dirtyStateIndex); + attribute = new MappingMethodMapAttribute(viewType, this, context, attributeIndex, dirtyStateIndex, embeddableMapping); } } else { context.addError("The mapping defined on method '" + viewType.getJavaType().getName() + "." + method.getName() + "' uses a an unknown collection type: " + declaredTypeClass); @@ -534,15 +621,21 @@ private static String methodReference(Method method) { if (mapping instanceof MappingSubquery) { attribute = new SubqueryMethodSingularAttribute(viewType, this, context, attributeIndex, dirtyStateIndex); } else if (correlated) { - attribute = new CorrelatedMethodSingularAttribute(viewType, this, context, attributeIndex, dirtyStateIndex); + attribute = new CorrelatedMethodSingularAttribute(viewType, this, context, attributeIndex, dirtyStateIndex, embeddableMapping); } else { - attribute = new MappingMethodSingularAttribute(viewType, this, context, attributeIndex, dirtyStateIndex); + attribute = new MappingMethodSingularAttribute(viewType, this, context, attributeIndex, dirtyStateIndex, embeddableMapping); } } } else if (dirtyStateIndex != -1) { throw new IllegalStateException("Already constructed attribute with dirtyStateIndex " + ((AbstractMethodAttribute) attribute).getDirtyStateIndex() + " but now a different index " + dirtyStateIndex + " is requested!"); } + if (embeddableMapping == null) { + this.attribute = attribute; + } else { + embeddableAttributeMap.put(embeddableMapping, attribute); + } + return (AbstractMethodAttribute) attribute; } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ParameterAttributeMapping.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ParameterAttributeMapping.java index e296a48914..7678bd79e9 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ParameterAttributeMapping.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ParameterAttributeMapping.java @@ -101,7 +101,7 @@ public String getMappedBy() { } @Override - public String determineMappedBy(ManagedType managedType, String mapping, MetamodelBuildingContext context) { + public String determineMappedBy(ManagedType managedType, String mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { return null; } @@ -121,10 +121,10 @@ public static String getLocation(Constructor constructor, int index) { // If you change something here don't forget to also update MethodAttributeMapping#getMethodAttribute @SuppressWarnings("unchecked") - public AbstractParameterAttribute getParameterAttribute(MappingConstructorImpl constructor, MetamodelBuildingContext context) { + public AbstractParameterAttribute getParameterAttribute(MappingConstructorImpl constructor, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { if (attribute == null) { if (mapping instanceof MappingParameter) { - attribute = new MappingParameterSingularAttribute(constructor, this, context); + attribute = new MappingParameterSingularAttribute(constructor, this, context, embeddableMapping); return (AbstractParameterAttribute) attribute; } @@ -133,27 +133,27 @@ public static String getLocation(Constructor constructor, int index) { if (isCollection) { if (Collection.class == declaredTypeClass) { if (correlated) { - attribute = new CorrelatedParameterCollectionAttribute(constructor, this, context); + attribute = new CorrelatedParameterCollectionAttribute(constructor, this, context, embeddableMapping); } else { - attribute = new MappingParameterCollectionAttribute(constructor, this, context); + attribute = new MappingParameterCollectionAttribute(constructor, this, context, embeddableMapping); } } else if (List.class == declaredTypeClass) { if (correlated) { - attribute = new CorrelatedParameterListAttribute(constructor, this, context); + attribute = new CorrelatedParameterListAttribute(constructor, this, context, embeddableMapping); } else { - attribute = new MappingParameterListAttribute(constructor, this, context); + attribute = new MappingParameterListAttribute(constructor, this, context, embeddableMapping); } } else if (Set.class == declaredTypeClass || SortedSet.class == declaredTypeClass || NavigableSet.class == declaredTypeClass) { if (correlated) { - attribute = new CorrelatedParameterSetAttribute(constructor, this, context); + attribute = new CorrelatedParameterSetAttribute(constructor, this, context, embeddableMapping); } else { - attribute = new MappingParameterSetAttribute(constructor, this, context); + attribute = new MappingParameterSetAttribute(constructor, this, context, embeddableMapping); } } else if (Map.class == declaredTypeClass || SortedMap.class == declaredTypeClass || NavigableMap.class == declaredTypeClass) { if (correlated) { context.addError("Parameter with the index '" + index + "' of the constructor '" + constructor.getJavaConstructor() + "' uses a Map type with a correlated mapping which is unsupported!"); } else { - attribute = new MappingParameterMapAttribute(constructor, this, context); + attribute = new MappingParameterMapAttribute(constructor, this, context, embeddableMapping); } } else { context.addError("Parameter with the index '" + index + "' of the constructor '" + constructor.getJavaConstructor() + "' uses an unknown collection type: " + declaredTypeClass); @@ -162,9 +162,9 @@ public static String getLocation(Constructor constructor, int index) { if (mapping instanceof MappingSubquery) { attribute = new SubqueryParameterSingularAttribute(constructor, this, context); } else if (correlated) { - attribute = new CorrelatedParameterSingularAttribute(constructor, this, context); + attribute = new CorrelatedParameterSingularAttribute(constructor, this, context, embeddableMapping); } else { - attribute = new MappingParameterSingularAttribute(constructor, this, context); + attribute = new MappingParameterSingularAttribute(constructor, this, context, embeddableMapping); } } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMapping.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMapping.java index d41d2e62c1..6163574b62 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMapping.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMapping.java @@ -70,7 +70,7 @@ public interface ViewMapping extends Comparable, EntityViewMapping void initializeViewMappings(MetamodelBuildingContext context, Runnable finishListener); - ManagedViewTypeImplementor getManagedViewType(MetamodelBuildingContext context); + ManagedViewTypeImplementor getManagedViewType(MetamodelBuildingContext context, EmbeddableOwner embeddableMapping); MethodAttributeMapping getIdAttribute(); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMappingImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMappingImpl.java index e5dde7c9e0..27a044ada1 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMappingImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMappingImpl.java @@ -31,6 +31,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; @@ -91,6 +92,7 @@ public class ViewMappingImpl implements ViewMapping { private boolean finished; private final List finishListeners = new ArrayList<>(); private ManagedViewTypeImplementor viewType; + private Map> embeddableViewTypeMap; public ViewMappingImpl(Class entityViewClass, Class entityClass, String name, MetamodelBootContext context) { this.entityViewClass = entityViewClass; @@ -470,7 +472,16 @@ private boolean containsAny(MetamodelBuildingContext context, Set> depe } @Override - public ManagedViewTypeImplementor getManagedViewType(MetamodelBuildingContext context) { + public ManagedViewTypeImplementor getManagedViewType(MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + ManagedViewTypeImplementor viewType; + if (embeddableMapping == null) { + viewType = this.viewType; + } else { + if (embeddableViewTypeMap == null) { + embeddableViewTypeMap = new HashMap<>(1); + } + viewType = embeddableViewTypeMap.get(embeddableMapping); + } if (viewType == null) { if (entityClass == null) { context.addError("The persistence unit metamodel doesn't contain the entity class configured in view mapping for entity view class: " + entityViewClass.getName()); @@ -554,12 +565,18 @@ public ManagedViewTypeImplementor getManagedViewType(MetamodelBuildingContext } if (idAttribute != null) { - return viewType = new ViewTypeImpl(this, managedType, context); + viewType = new ViewTypeImpl(this, managedType, context); } else { - return viewType = new FlatViewTypeImpl(this, managedType, context); + viewType = new FlatViewTypeImpl(this, managedType, context, embeddableMapping); } } + if (embeddableMapping == null) { + this.viewType = viewType; + } else { + embeddableViewTypeMap.put(embeddableMapping, viewType); + } + return viewType; } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMetamodelImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMetamodelImpl.java index 1a79736537..17fff5546e 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMetamodelImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewMetamodelImpl.java @@ -81,7 +81,7 @@ public ViewMetamodelImpl(EntityMetamodel entityMetamodel, MetamodelBuildingConte // Phase 3: Build the ManagedViewType instances representing the metamodel for (ViewMapping viewMapping : viewMappings) { - ManagedViewTypeImplementor managedView = viewMapping.getManagedViewType(context); + ManagedViewTypeImplementor managedView = viewMapping.getManagedViewType(context, null); managedViews.put(viewMapping.getEntityViewClass(), managedView); if (managedView instanceof FlatViewType) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewTypeImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewTypeImpl.java index fbad513d1f..ab466414ce 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewTypeImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/ViewTypeImpl.java @@ -43,7 +43,7 @@ public class ViewTypeImpl extends ManagedViewTypeImpl implements ViewTypeI private final Map viewFilters; public ViewTypeImpl(ViewMapping viewMapping, ManagedType managedType, MetamodelBuildingContext context) { - super(viewMapping, managedType, context); + super(viewMapping, managedType, context, null); String name = viewMapping.getName(); @@ -71,11 +71,11 @@ public ViewTypeImpl(ViewMapping viewMapping, ManagedType managedType, Metamod } this.viewFilters = Collections.unmodifiableMap(viewFilters); - this.idAttribute = viewMapping.getIdAttribute().getMethodAttribute(this, -1, -1, context); + this.idAttribute = viewMapping.getIdAttribute().getMethodAttribute(this, -1, -1, context, null); if (getLockMode() != LockMode.NONE) { if (viewMapping.getVersionAttribute() != null) { - this.versionAttribute = viewMapping.getVersionAttribute().getMethodAttribute(this, -1, -1, context); + this.versionAttribute = viewMapping.getVersionAttribute().getMethodAttribute(this, -1, -1, context, null); } else { this.versionAttribute = null; } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractMethodCollectionAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractMethodCollectionAttribute.java index ef3ab4e32f..fba56ea84e 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractMethodCollectionAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractMethodCollectionAttribute.java @@ -19,6 +19,7 @@ import com.blazebit.persistence.view.impl.collection.CollectionInstantiator; import com.blazebit.persistence.view.impl.collection.MapInstantiator; import com.blazebit.persistence.view.impl.metamodel.AbstractMethodPluralAttribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.MethodAttributeMapping; @@ -37,8 +38,8 @@ public abstract class AbstractMethodCollectionAttribute extends AbstractMe private final CollectionInstantiator collectionInstantiator; - public AbstractMethodCollectionAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex) { - super(viewType, mapping, context, attributeIndex, dirtyStateIndex); + public AbstractMethodCollectionAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex, EmbeddableOwner embeddableMapping) { + super(viewType, mapping, context, attributeIndex, dirtyStateIndex, embeddableMapping); this.collectionInstantiator = createCollectionInstantiator(context, createCollectionFactory(context), isIndexed(), isSorted(), isOrdered(), getComparator()); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractMethodListAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractMethodListAttribute.java index a6f659887e..2bcf7370c1 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractMethodListAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractMethodListAttribute.java @@ -19,6 +19,7 @@ import com.blazebit.persistence.view.impl.collection.CollectionInstantiator; import com.blazebit.persistence.view.impl.collection.MapInstantiator; import com.blazebit.persistence.view.impl.metamodel.AbstractMethodPluralAttribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.MethodAttributeMapping; @@ -38,8 +39,8 @@ public abstract class AbstractMethodListAttribute extends AbstractMethodPl private final boolean isIndexed; private final CollectionInstantiator collectionInstantiator; - public AbstractMethodListAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex) { - super(viewType, mapping, context, attributeIndex, dirtyStateIndex); + public AbstractMethodListAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex, EmbeddableOwner embeddableMapping) { + super(viewType, mapping, context, attributeIndex, dirtyStateIndex, embeddableMapping); this.isIndexed = mapping.determineIndexed(context, context.getEntityMetamodel().getManagedType(viewType.getEntityClass())); this.collectionInstantiator = createCollectionInstantiator(context, createCollectionFactory(context), isIndexed(), isSorted(), isOrdered(), getComparator()); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractMethodMapAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractMethodMapAttribute.java index 76fc9098c9..53adcfdc64 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractMethodMapAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractMethodMapAttribute.java @@ -19,6 +19,7 @@ import com.blazebit.persistence.view.impl.collection.CollectionInstantiator; import com.blazebit.persistence.view.impl.collection.MapInstantiator; import com.blazebit.persistence.view.impl.metamodel.AbstractMethodPluralAttribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.MethodAttributeMapping; @@ -40,10 +41,10 @@ public abstract class AbstractMethodMapAttribute extends AbstractMethod private final MapInstantiator mapInstantiator; @SuppressWarnings("unchecked") - public AbstractMethodMapAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex) { - super(viewType, mapping, context, attributeIndex, dirtyStateIndex); - this.keyType = (Type) mapping.getKeyType(context); - this.keyInheritanceSubtypes = (Map, String>) (Map) mapping.getKeyInheritanceSubtypes(context); + public AbstractMethodMapAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex, EmbeddableOwner embeddableMapping) { + super(viewType, mapping, context, attributeIndex, dirtyStateIndex, embeddableMapping); + this.keyType = (Type) mapping.getKeyType(context, embeddableMapping); + this.keyInheritanceSubtypes = (Map, String>) (Map) mapping.getKeyInheritanceSubtypes(context, embeddableMapping); this.mapInstantiator = createMapInstantiator(createMapFactory(context), isSorted(), isOrdered(), getComparator()); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractMethodSetAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractMethodSetAttribute.java index b6fbee7aee..46fb2719c6 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractMethodSetAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractMethodSetAttribute.java @@ -19,6 +19,7 @@ import com.blazebit.persistence.view.impl.collection.CollectionInstantiator; import com.blazebit.persistence.view.impl.collection.MapInstantiator; import com.blazebit.persistence.view.impl.metamodel.AbstractMethodPluralAttribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.MethodAttributeMapping; @@ -37,8 +38,8 @@ public abstract class AbstractMethodSetAttribute extends AbstractMethodPlu private final CollectionInstantiator collectionInstantiator; - public AbstractMethodSetAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex) { - super(viewType, mapping, context, attributeIndex, dirtyStateIndex); + public AbstractMethodSetAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex, EmbeddableOwner embeddableMapping) { + super(viewType, mapping, context, attributeIndex, dirtyStateIndex, embeddableMapping); this.collectionInstantiator = createCollectionInstantiator(context, createCollectionFactory(context), isIndexed(), isSorted(), isOrdered(), getComparator()); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractParameterCollectionAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractParameterCollectionAttribute.java index af4ee0c4bb..188f23c513 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractParameterCollectionAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractParameterCollectionAttribute.java @@ -19,6 +19,7 @@ import com.blazebit.persistence.view.impl.collection.CollectionInstantiator; import com.blazebit.persistence.view.impl.collection.MapInstantiator; import com.blazebit.persistence.view.impl.metamodel.AbstractParameterPluralAttribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MappingConstructorImpl; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; @@ -38,8 +39,8 @@ public abstract class AbstractParameterCollectionAttribute extends Abstrac private final CollectionInstantiator collectionInstantiator; - public AbstractParameterCollectionAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(mappingConstructor, mapping, context); + public AbstractParameterCollectionAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(mappingConstructor, mapping, context, embeddableMapping); this.collectionInstantiator = createCollectionInstantiator(context, null, isIndexed(), isSorted(), isOrdered(), getComparator()); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractParameterListAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractParameterListAttribute.java index 7645a1f69b..1bb7f1ed26 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractParameterListAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractParameterListAttribute.java @@ -19,6 +19,7 @@ import com.blazebit.persistence.view.impl.collection.CollectionInstantiator; import com.blazebit.persistence.view.impl.collection.MapInstantiator; import com.blazebit.persistence.view.impl.metamodel.AbstractParameterPluralAttribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MappingConstructorImpl; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; @@ -39,8 +40,8 @@ public abstract class AbstractParameterListAttribute extends AbstractParam private final boolean isIndexed; private final CollectionInstantiator collectionInstantiator; - public AbstractParameterListAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(mappingConstructor, mapping, context); + public AbstractParameterListAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(mappingConstructor, mapping, context, embeddableMapping); this.isIndexed = mapping.determineIndexed(context, context.getEntityMetamodel().getManagedType(mappingConstructor.getDeclaringType().getEntityClass())); this.collectionInstantiator = createCollectionInstantiator(context, null, isIndexed(), isSorted(), isOrdered(), getComparator()); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractParameterMapAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractParameterMapAttribute.java index 7b25cffec6..c77f51e7e6 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractParameterMapAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractParameterMapAttribute.java @@ -19,6 +19,7 @@ import com.blazebit.persistence.view.impl.collection.CollectionInstantiator; import com.blazebit.persistence.view.impl.collection.MapInstantiator; import com.blazebit.persistence.view.impl.metamodel.AbstractParameterPluralAttribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MappingConstructorImpl; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; @@ -41,10 +42,10 @@ public abstract class AbstractParameterMapAttribute extends AbstractPar private final MapInstantiator mapInstantiator; @SuppressWarnings("unchecked") - public AbstractParameterMapAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(mappingConstructor, mapping, context); - this.keyType = (Type) mapping.getKeyType(context); - this.keyInheritanceSubtypes = (Map, String>) (Map) mapping.getKeyInheritanceSubtypes(context); + public AbstractParameterMapAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(mappingConstructor, mapping, context, embeddableMapping); + this.keyType = (Type) mapping.getKeyType(context, embeddableMapping); + this.keyInheritanceSubtypes = (Map, String>) (Map) mapping.getKeyInheritanceSubtypes(context, embeddableMapping); this.mapInstantiator = createMapInstantiator(null, isSorted(), isOrdered(), getComparator()); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractParameterSetAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractParameterSetAttribute.java index 3801738baa..0cf5a615e4 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractParameterSetAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/AbstractParameterSetAttribute.java @@ -19,6 +19,7 @@ import com.blazebit.persistence.view.impl.collection.CollectionInstantiator; import com.blazebit.persistence.view.impl.collection.MapInstantiator; import com.blazebit.persistence.view.impl.metamodel.AbstractParameterPluralAttribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MappingConstructorImpl; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; @@ -38,8 +39,8 @@ public abstract class AbstractParameterSetAttribute extends AbstractParame private final CollectionInstantiator collectionInstantiator; - public AbstractParameterSetAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(mappingConstructor, mapping, context); + public AbstractParameterSetAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(mappingConstructor, mapping, context, embeddableMapping); this.collectionInstantiator = createCollectionInstantiator(context, null, isIndexed(), isSorted(), isOrdered(), getComparator()); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedMethodCollectionAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedMethodCollectionAttribute.java index 3b5d3580e5..a51d03de88 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedMethodCollectionAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedMethodCollectionAttribute.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.MethodAttributeMapping; @@ -30,8 +31,8 @@ */ public class CorrelatedMethodCollectionAttribute extends AbstractMethodCollectionAttribute implements CorrelatedAttribute> { - public CorrelatedMethodCollectionAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex) { - super(viewType, mapping, context, attributeIndex, dirtyStateIndex); + public CorrelatedMethodCollectionAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex, EmbeddableOwner embeddableMapping) { + super(viewType, mapping, context, attributeIndex, dirtyStateIndex, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedMethodListAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedMethodListAttribute.java index e298a336ab..8689933838 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedMethodListAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedMethodListAttribute.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.MethodAttributeMapping; @@ -30,8 +31,8 @@ */ public class CorrelatedMethodListAttribute extends AbstractMethodListAttribute implements CorrelatedAttribute> { - public CorrelatedMethodListAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex) { - super(viewType, mapping, context, attributeIndex, dirtyStateIndex); + public CorrelatedMethodListAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex, EmbeddableOwner embeddableMapping) { + super(viewType, mapping, context, attributeIndex, dirtyStateIndex, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedMethodSetAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedMethodSetAttribute.java index 9c5d0e8b9c..52fcf412d1 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedMethodSetAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedMethodSetAttribute.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.MethodAttributeMapping; @@ -30,8 +31,8 @@ */ public class CorrelatedMethodSetAttribute extends AbstractMethodSetAttribute implements CorrelatedAttribute> { - public CorrelatedMethodSetAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex) { - super(viewType, mapping, context, attributeIndex, dirtyStateIndex); + public CorrelatedMethodSetAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex, EmbeddableOwner embeddableMapping) { + super(viewType, mapping, context, attributeIndex, dirtyStateIndex, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedMethodSingularAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedMethodSingularAttribute.java index eb3e0fd0fa..7c71d6d4b7 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedMethodSingularAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedMethodSingularAttribute.java @@ -17,6 +17,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; import com.blazebit.persistence.view.impl.metamodel.AbstractMethodSingularAttribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.MethodAttributeMapping; @@ -29,8 +30,8 @@ */ public class CorrelatedMethodSingularAttribute extends AbstractMethodSingularAttribute implements CorrelatedAttribute { - public CorrelatedMethodSingularAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex) { - super(viewType, mapping, context, attributeIndex, dirtyStateIndex); + public CorrelatedMethodSingularAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex, EmbeddableOwner embeddableMapping) { + super(viewType, mapping, context, attributeIndex, dirtyStateIndex, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedParameterCollectionAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedParameterCollectionAttribute.java index ab4cde372e..66d243ac75 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedParameterCollectionAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedParameterCollectionAttribute.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.MappingConstructorImpl; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.ParameterAttributeMapping; @@ -30,8 +31,8 @@ */ public class CorrelatedParameterCollectionAttribute extends AbstractParameterCollectionAttribute implements CorrelatedAttribute> { - public CorrelatedParameterCollectionAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(mappingConstructor, mapping, context); + public CorrelatedParameterCollectionAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(mappingConstructor, mapping, context, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedParameterListAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedParameterListAttribute.java index 14bb34f8ea..3d7d3ce25c 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedParameterListAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedParameterListAttribute.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.MappingConstructorImpl; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.ParameterAttributeMapping; @@ -30,8 +31,8 @@ */ public class CorrelatedParameterListAttribute extends AbstractParameterListAttribute implements CorrelatedAttribute> { - public CorrelatedParameterListAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(mappingConstructor, mapping, context); + public CorrelatedParameterListAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(mappingConstructor, mapping, context, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedParameterSetAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedParameterSetAttribute.java index 114b79eb25..9c5f1052a8 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedParameterSetAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedParameterSetAttribute.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.MappingConstructorImpl; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.ParameterAttributeMapping; @@ -30,8 +31,8 @@ */ public class CorrelatedParameterSetAttribute extends AbstractParameterSetAttribute implements CorrelatedAttribute> { - public CorrelatedParameterSetAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(mappingConstructor, mapping, context); + public CorrelatedParameterSetAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(mappingConstructor, mapping, context, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedParameterSingularAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedParameterSingularAttribute.java index 4bc6174b4a..88c21f6d76 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedParameterSingularAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/CorrelatedParameterSingularAttribute.java @@ -17,6 +17,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; import com.blazebit.persistence.view.impl.metamodel.AbstractParameterSingularAttribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.MappingConstructorImpl; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.ParameterAttributeMapping; @@ -29,8 +30,8 @@ */ public class CorrelatedParameterSingularAttribute extends AbstractParameterSingularAttribute implements CorrelatedAttribute { - public CorrelatedParameterSingularAttribute(MappingConstructorImpl constructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(constructor, mapping, context); + public CorrelatedParameterSingularAttribute(MappingConstructorImpl constructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(constructor, mapping, context, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodCollectionAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodCollectionAttribute.java index 2789e5ad59..3794503770 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodCollectionAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodCollectionAttribute.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.MethodAttributeMapping; @@ -30,8 +31,8 @@ */ public class MappingMethodCollectionAttribute extends AbstractMethodCollectionAttribute implements MappingAttribute> { - public MappingMethodCollectionAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex) { - super(viewType, mapping, context, attributeIndex, dirtyStateIndex); + public MappingMethodCollectionAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex, EmbeddableOwner embeddableMapping) { + super(viewType, mapping, context, attributeIndex, dirtyStateIndex, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodListAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodListAttribute.java index 66e3592e85..4a0fd1520d 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodListAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodListAttribute.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.MethodAttributeMapping; @@ -31,8 +32,8 @@ public class MappingMethodListAttribute extends AbstractMethodListAttribute implements MappingAttribute> { - public MappingMethodListAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex) { - super(viewType, mapping, context, attributeIndex, dirtyStateIndex); + public MappingMethodListAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex, EmbeddableOwner embeddableMapping) { + super(viewType, mapping, context, attributeIndex, dirtyStateIndex, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodMapAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodMapAttribute.java index 290374c0b4..1d9185dffc 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodMapAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodMapAttribute.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.MethodAttributeMapping; @@ -31,8 +32,8 @@ public class MappingMethodMapAttribute extends AbstractMethodMapAttribute implements MappingAttribute> { @SuppressWarnings("unchecked") - public MappingMethodMapAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex) { - super(viewType, mapping, context, attributeIndex, dirtyStateIndex); + public MappingMethodMapAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex, EmbeddableOwner embeddableMapping) { + super(viewType, mapping, context, attributeIndex, dirtyStateIndex, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodSetAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodSetAttribute.java index a98ccd9e7a..13c56ee1ff 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodSetAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodSetAttribute.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.MethodAttributeMapping; @@ -30,8 +31,8 @@ */ public class MappingMethodSetAttribute extends AbstractMethodSetAttribute implements MappingAttribute> { - public MappingMethodSetAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex) { - super(viewType, mapping, context, attributeIndex, dirtyStateIndex); + public MappingMethodSetAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex, EmbeddableOwner embeddableMapping) { + super(viewType, mapping, context, attributeIndex, dirtyStateIndex, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodSingularAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodSingularAttribute.java index 0a2d08dadc..dfd87118bf 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodSingularAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingMethodSingularAttribute.java @@ -17,6 +17,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; import com.blazebit.persistence.view.impl.metamodel.AbstractMethodSingularAttribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.ManagedViewTypeImplementor; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.MethodAttributeMapping; @@ -29,8 +30,8 @@ */ public class MappingMethodSingularAttribute extends AbstractMethodSingularAttribute implements MappingAttribute { - public MappingMethodSingularAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex) { - super(viewType, mapping, context, attributeIndex, dirtyStateIndex); + public MappingMethodSingularAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex, EmbeddableOwner embeddableMapping) { + super(viewType, mapping, context, attributeIndex, dirtyStateIndex, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterCollectionAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterCollectionAttribute.java index 0ac9589572..80133e397e 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterCollectionAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterCollectionAttribute.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.MappingConstructorImpl; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.ParameterAttributeMapping; @@ -30,8 +31,8 @@ */ public class MappingParameterCollectionAttribute extends AbstractParameterCollectionAttribute implements MappingAttribute> { - public MappingParameterCollectionAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(mappingConstructor, mapping, context); + public MappingParameterCollectionAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(mappingConstructor, mapping, context, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterListAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterListAttribute.java index 41c6f807fd..3bdcbaf916 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterListAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterListAttribute.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.MappingConstructorImpl; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.ParameterAttributeMapping; @@ -30,8 +31,8 @@ */ public class MappingParameterListAttribute extends AbstractParameterListAttribute implements MappingAttribute> { - public MappingParameterListAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(mappingConstructor, mapping, context); + public MappingParameterListAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(mappingConstructor, mapping, context, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterMapAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterMapAttribute.java index e68d1547ef..bb45fa3052 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterMapAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterMapAttribute.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.MappingConstructorImpl; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.ParameterAttributeMapping; @@ -31,8 +32,8 @@ public class MappingParameterMapAttribute extends AbstractParameterMapAttribute implements MappingAttribute> { @SuppressWarnings("unchecked") - public MappingParameterMapAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(mappingConstructor, mapping, context); + public MappingParameterMapAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(mappingConstructor, mapping, context, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterSetAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterSetAttribute.java index 1cfe0a669e..85fbc201ed 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterSetAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterSetAttribute.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.MappingConstructorImpl; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.ParameterAttributeMapping; @@ -30,8 +31,8 @@ */ public class MappingParameterSetAttribute extends AbstractParameterSetAttribute implements MappingAttribute> { - public MappingParameterSetAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(mappingConstructor, mapping, context); + public MappingParameterSetAttribute(MappingConstructorImpl mappingConstructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(mappingConstructor, mapping, context, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterSingularAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterSingularAttribute.java index 46eb13b5db..198c30c503 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterSingularAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/MappingParameterSingularAttribute.java @@ -17,6 +17,7 @@ package com.blazebit.persistence.view.impl.metamodel.attribute; import com.blazebit.persistence.view.impl.metamodel.AbstractParameterSingularAttribute; +import com.blazebit.persistence.view.impl.metamodel.EmbeddableOwner; import com.blazebit.persistence.view.impl.metamodel.MappingConstructorImpl; import com.blazebit.persistence.view.impl.metamodel.MetamodelBuildingContext; import com.blazebit.persistence.view.impl.metamodel.ParameterAttributeMapping; @@ -29,8 +30,8 @@ */ public class MappingParameterSingularAttribute extends AbstractParameterSingularAttribute implements MappingAttribute { - public MappingParameterSingularAttribute(MappingConstructorImpl constructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(constructor, mapping, context); + public MappingParameterSingularAttribute(MappingConstructorImpl constructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context, EmbeddableOwner embeddableMapping) { + super(constructor, mapping, context, embeddableMapping); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/SubqueryMethodSingularAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/SubqueryMethodSingularAttribute.java index 140c0b40a3..9b42466581 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/SubqueryMethodSingularAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/SubqueryMethodSingularAttribute.java @@ -30,7 +30,7 @@ public class SubqueryMethodSingularAttribute extends AbstractMethodSingularAttribute implements SubqueryAttribute { public SubqueryMethodSingularAttribute(ManagedViewTypeImplementor viewType, MethodAttributeMapping mapping, MetamodelBuildingContext context, int attributeIndex, int dirtyStateIndex) { - super(viewType, mapping, context, attributeIndex, dirtyStateIndex); + super(viewType, mapping, context, attributeIndex, dirtyStateIndex, null); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/SubqueryParameterSingularAttribute.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/SubqueryParameterSingularAttribute.java index 7a459b6a7a..edcd2c37b1 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/SubqueryParameterSingularAttribute.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/metamodel/attribute/SubqueryParameterSingularAttribute.java @@ -30,7 +30,7 @@ public class SubqueryParameterSingularAttribute extends AbstractParameterSingularAttribute implements SubqueryAttribute { public SubqueryParameterSingularAttribute(MappingConstructorImpl constructor, ParameterAttributeMapping mapping, MetamodelBuildingContext context) { - super(constructor, mapping, context); + super(constructor, mapping, context, null); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/ProxyFactory.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/ProxyFactory.java index aa2cb58eef..0b29aa74ed 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/ProxyFactory.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/proxy/ProxyFactory.java @@ -292,7 +292,7 @@ private Class createProxyClass(EntityViewManager entityViewMana cc.addField(evmField); if (managedViewType.isUpdatable() || managedViewType.isCreatable()) { - if (managedViewType.getFlushMode() == FlushMode.LAZY || managedViewType.getFlushMode() == FlushMode.PARTIAL) { + if (true || managedViewType.getFlushMode() == FlushMode.LAZY || managedViewType.getFlushMode() == FlushMode.PARTIAL) { cc.addInterface(pool.get(DirtyStateTrackable.class.getName())); initialStateField = new CtField(pool.get(Object[].class.getName()), "$$_initialState", cc); initialStateField.setModifiers(getModifiers(false)); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/EntityViewUpdaterImpl.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/EntityViewUpdaterImpl.java index 5613eebb80..1892127136 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/EntityViewUpdaterImpl.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/EntityViewUpdaterImpl.java @@ -23,6 +23,7 @@ import com.blazebit.persistence.spi.ExtendedAttribute; import com.blazebit.persistence.spi.ExtendedManagedType; import com.blazebit.persistence.spi.JoinTable; +import com.blazebit.persistence.spi.JpaProvider; import com.blazebit.persistence.view.FlushMode; import com.blazebit.persistence.view.FlushStrategy; import com.blazebit.persistence.view.InverseRemoveStrategy; @@ -126,7 +127,7 @@ public class EntityViewUpdaterImpl implements EntityViewUpdater { private final String fullUpdateQueryString; @SuppressWarnings({ "unchecked", "rawtypes" }) - public EntityViewUpdaterImpl(EntityViewManagerImpl evm, ManagedViewTypeImplementor viewType, ManagedViewTypeImplementor declaredViewType) { + public EntityViewUpdaterImpl(EntityViewManagerImpl evm, ManagedViewTypeImplementor viewType, ManagedViewTypeImplementor declaredViewType, EntityViewUpdaterImpl owner, String ownerMapping) { Class entityClass = viewType.getEntityClass(); this.fullFlush = viewType.getFlushMode() == FlushMode.FULL; this.flushStrategy = viewType.getFlushStrategy(); @@ -275,7 +276,7 @@ public EntityViewUpdaterImpl(EntityViewManagerImpl evm, ManagedViewTypeImplement if (methodAttribute.getMapping() != null) { joinTableUnmappedEntityAttributes.remove(methodAttribute.getMapping()); } - DirtyAttributeFlusher flusher = createAttributeFlusher(evm, viewType, idAttributeName, flushStrategy, methodAttribute); + DirtyAttributeFlusher flusher = createAttributeFlusher(evm, viewType, idAttributeName, flushStrategy, methodAttribute, idFlusher, owner, ownerMapping); if (flusher != null) { if (sb != null) { int endIndex = sb.length(); @@ -427,7 +428,9 @@ public static ViewToEntityMapper createViewIdMapper(EntityViewManagerImpl evm, M Collections.>emptySet(), new ReferenceEntityLoader(evm, viewIdType, null), true, - viewIdType.isUpdatable() ? null : (Mapper) Mappers.forViewToEntityAttributeMapping(evm, viewIdType, viewIdType.getEntityClass()) + viewIdType.isUpdatable() ? null : (Mapper) Mappers.forViewToEntityAttributeMapping(evm, viewIdType, viewIdType.getEntityClass()), + null, + null ); } @@ -475,7 +478,7 @@ public static DirtyAttributeFlusher createIdFlusher(EntityVie buildComponentFlushers(evm, type.getJavaType(), attributeName + "_", attributeMapping + ".", "", attributes, componentFlushers); componentFlusherEntries = componentFlushers.entrySet().toArray(new Map.Entry[componentFlushers.size()]); } - TypeDescriptor typeDescriptor = TypeDescriptor.forType(evm, idAttribute, type); + TypeDescriptor typeDescriptor = TypeDescriptor.forType(evm, null, idAttribute, type, null, null); return new BasicAttributeFlusher<>(attributeName, attributeMapping, true, false, true, false, false, false, componentFlusherEntries, typeDescriptor, updateFragment, parameterName, entityAttributeAccessor, viewAttributeAccessor, null, null, null); } } @@ -600,8 +603,15 @@ public Query createUpdateQuery(UpdateContext context, MutableStateTrackable upda int initialLength = sb.length(); flusher.appendUpdateQueryFragment(context, sb, null, null, ", "); if (sb.length() == initialLength) { - queryString = null; - needsOptimisticLocking = false; + // If we still need optimistic locking, we just append a flush for the version increment + if (needsOptimisticLocking = fullFlusher.hasVersionFlusher() && flusher.isOptimisticLockProtected()) { + versionFlusher.appendUpdateQueryFragment(context, sb, null, null, ", "); + sb.append(updatePostfixString); + queryString = sb.toString(); + } else { + queryString = null; + needsOptimisticLocking = false; + } } else { sb.append(updatePostfixString); queryString = sb.toString(); @@ -613,7 +623,7 @@ public Query createUpdateQuery(UpdateContext context, MutableStateTrackable upda if (queryString != null) { query = context.getEntityManager().createQuery(queryString); if (idFlusher != null) { - idFlusher.flushQuery(context, WHERE_CLAUSE_PREFIX, query, updatableProxy, updatableProxy.$$_getId(), null); + idFlusher.flushQuery(context, WHERE_CLAUSE_PREFIX, query, updatableProxy, updatableProxy, updatableProxy.$$_getId(), null); } if (needsOptimisticLocking) { versionFlusher.flushQueryInitialVersion(context, WHERE_CLAUSE_PREFIX, query, updatableProxy, updatableProxy.$$_getVersion()); @@ -643,7 +653,7 @@ private void update(UpdateContext context, Object entity, MutableStateTrackable } else { int orphanRemovalStartIndex = context.getOrphanRemovalDeleters().size(); Query query = createUpdateQuery(context, updatableProxy, flusher); - flusher.flushQuery(context, null, query, updatableProxy, updatableProxy, null); + flusher.flushQuery(context, null, query, updatableProxy, updatableProxy, updatableProxy, null); if (query != null) { int updated = query.executeUpdate(); @@ -684,125 +694,283 @@ public void remove(UpdateContext context, Object viewId) { } @SuppressWarnings({"unchecked", "checkstyle:methodlength"}) - private static DirtyAttributeFlusher createAttributeFlusher(EntityViewManagerImpl evm, ManagedViewTypeImplementor viewType, String idAttributeName, FlushStrategy flushStrategy, AbstractMethodAttribute attribute) { + private DirtyAttributeFlusher createAttributeFlusher(EntityViewManagerImpl evm, ManagedViewTypeImplementor viewType, String idAttributeName, FlushStrategy flushStrategy, AbstractMethodAttribute attribute, DirtyAttributeFlusher ownerIdFlusher, EntityViewUpdaterImpl owner, String ownerMapping) { + if (attribute.isCollection()) { + String idMapping; + if (owner == null) { + idMapping = idAttributeName; + } else { + if (owner.idFlusher instanceof EmbeddableAttributeFlusher) { + idMapping = ((EmbeddableAttributeFlusher) owner.idFlusher).getMapping(); + } else { + idMapping = ((BasicAttributeFlusher) owner.idFlusher).getMapping(); + } + } + return createPluralAttributeFlusher(evm, viewType, idMapping, flushStrategy, attribute, owner == null ? ownerIdFlusher : owner.idFlusher, owner, ownerMapping); + } else { + return createSingularAttributeFlusher(evm, viewType, attribute, owner, ownerMapping); + } + } + + private DirtyAttributeFlusher createPluralAttributeFlusher(EntityViewManagerImpl evm, ManagedViewTypeImplementor viewType, String idAttributeName, FlushStrategy flushStrategy, AbstractMethodAttribute attribute, DirtyAttributeFlusher ownerIdFlusher, EntityViewUpdaterImpl owner, String ownerMapping) { EntityMetamodel entityMetamodel = evm.getMetamodel().getEntityMetamodel(); Class entityClass = viewType.getEntityClass(); String attributeName = attribute.getName(); String attributeMapping = attribute.getMapping(); AttributeAccessor entityAttributeAccessor = Accessors.forEntityMapping(evm, attribute); - String attributeLocation = attribute.getLocation(); - boolean cascadePersist = attribute.isPersistCascaded(); - boolean cascadeUpdate = attribute.isUpdateCascaded(); boolean cascadeDelete = attribute.isDeleteCascaded(); boolean viewOnlyDeleteCascaded = cascadeDelete && !entityMetamodel.getManagedType(ExtendedManagedType.class, entityClass).getAttribute(attributeMapping).isDeleteCascaded(); boolean optimisticLockProtected = attribute.isOptimisticLockProtected(); - Set> persistAllowedSubtypes = attribute.getPersistCascadeAllowedSubtypes(); - Set> updateAllowedSubtypes = attribute.getUpdateCascadeAllowedSubtypes(); + JpaProvider jpaProvider = evm.getJpaProvider(); + PluralAttribute pluralAttribute = (PluralAttribute) attribute; + InitialValueAttributeAccessor viewAttributeAccessor = Accessors.forMutableViewAttribute(evm, attribute); + TypeDescriptor elementDescriptor = TypeDescriptor.forType(evm, this, attribute, pluralAttribute.getElementType(), owner, ownerMapping); + boolean collectionUpdatable = attribute.isUpdatable(); + CollectionRemoveListener elementRemoveListener = createOrphanRemoveListener(attribute, elementDescriptor); + CollectionRemoveListener elementCascadeDeleteListener = createCascadeDeleteListener(attribute, elementDescriptor); + boolean jpaProviderDeletesCollection; + boolean supportsCollectionDml = jpaProvider.supportsInsertStatement(); + + if (elementDescriptor.getEntityIdAttributeName() != null) { + jpaProviderDeletesCollection = jpaProvider.supportsJoinTableCleanupOnDelete(); + } else { + jpaProviderDeletesCollection = jpaProvider.supportsCollectionTableCleanupOnDelete(); + } - if (attribute.isCollection()) { - PluralAttribute pluralAttribute = (PluralAttribute) attribute; - InitialValueAttributeAccessor viewAttributeAccessor = Accessors.forMutableViewAttribute(evm, attribute); - TypeDescriptor elementDescriptor = TypeDescriptor.forType(evm, attribute, pluralAttribute.getElementType()); - boolean collectionUpdatable = attribute.isUpdatable(); - CollectionRemoveListener elementRemoveListener = createOrphanRemoveListener(attribute, elementDescriptor); - CollectionRemoveListener elementCascadeDeleteListener = createCascadeDeleteListener(attribute, elementDescriptor); - boolean jpaProviderDeletesCollection; - - if (elementDescriptor.getEntityIdAttributeName() != null) { - jpaProviderDeletesCollection = evm.getJpaProvider().supportsJoinTableCleanupOnDelete(); - } else { - jpaProviderDeletesCollection = evm.getJpaProvider().supportsCollectionTableCleanupOnDelete(); - } + if (attribute instanceof MapAttribute) { + MapAttribute mapAttribute = (MapAttribute) attribute; + TypeDescriptor keyDescriptor = TypeDescriptor.forType(evm, this, attribute, mapAttribute.getKeyType(), owner, ownerMapping); + // TODO: currently there is no possibility to set this + CollectionRemoveListener keyRemoveListener = null; + CollectionRemoveListener keyCascadeDeleteListener = null; - if (attribute instanceof MapAttribute) { - MapAttribute mapAttribute = (MapAttribute) attribute; - TypeDescriptor keyDescriptor = TypeDescriptor.forType(evm, attribute, mapAttribute.getKeyType()); - // TODO: currently there is no possibility to set this - CollectionRemoveListener keyRemoveListener = null; - CollectionRemoveListener keyCascadeDeleteListener = null; + if (collectionUpdatable || keyDescriptor.shouldFlushMutations() || elementDescriptor.shouldFlushMutations() || shouldPassThrough(evm, viewType, attribute)) { + MapViewToEntityMapper mapper = new SimpleMapViewToEntityMapper(keyDescriptor.getViewToEntityMapper(), elementDescriptor.getViewToEntityMapper()); + MapViewToEntityMapper loadOnlyMapper = new SimpleMapViewToEntityMapper(keyDescriptor.getLoadOnlyViewToEntityMapper(), elementDescriptor.getLoadOnlyViewToEntityMapper()); - if (collectionUpdatable || keyDescriptor.shouldFlushMutations() || elementDescriptor.shouldFlushMutations() || shouldPassThrough(evm, viewType, attribute)) { - MapViewToEntityMapper mapper = new SimpleMapViewToEntityMapper(keyDescriptor.getViewToEntityMapper(), elementDescriptor.getViewToEntityMapper()); - MapViewToEntityMapper loadOnlyMapper = new SimpleMapViewToEntityMapper(keyDescriptor.getLoadOnlyViewToEntityMapper(), elementDescriptor.getLoadOnlyViewToEntityMapper()); + MapInstantiator mapInstantiator = attribute.getMapInstantiator(); + return new MapAttributeFlusher, ?, ?>>( + attributeName, + attributeMapping, + owner == null ? entityClass : owner.fullEntityLoader.getEntityClass(), + idAttributeName, + ownerMapping, + ownerIdFlusher, + createPluralAttributeSubFlusher(evm, viewType, attribute, "element", mapAttribute.getElementType(), owner, ownerMapping), + supportsCollectionDml, + flushStrategy, + entityAttributeAccessor, + viewAttributeAccessor, + optimisticLockProtected, + collectionUpdatable, + keyCascadeDeleteListener, + elementCascadeDeleteListener, + keyRemoveListener, + elementRemoveListener, + viewOnlyDeleteCascaded, + jpaProviderDeletesCollection, + keyDescriptor, + elementDescriptor, + mapper, + loadOnlyMapper, + mapInstantiator + ); + } else { + return null; + } + } else { + if (collectionUpdatable || elementDescriptor.shouldFlushMutations() || shouldPassThrough(evm, viewType, attribute)) { + InverseFlusher inverseFlusher = InverseFlusher.forAttribute(evm, viewType, attribute, elementDescriptor, owner, ownerMapping); + InverseRemoveStrategy inverseRemoveStrategy = attribute.getInverseRemoveStrategy(); - MapInstantiator mapInstantiator = attribute.getMapInstantiator(); - return new MapAttributeFlusher, ?, ?>>( + CollectionInstantiator collectionInstantiator = attribute.getCollectionInstantiator(); + if (pluralAttribute.isIndexed()) { + return new IndexedListAttributeFlusher>>( attributeName, attributeMapping, - entityClass, + owner == null ? entityClass : owner.fullEntityLoader.getEntityClass(), idAttributeName, + ownerMapping, + ownerIdFlusher, + createPluralAttributeSubFlusher(evm, viewType, attribute, "element", pluralAttribute.getElementType(), owner, ownerMapping), + supportsCollectionDml, flushStrategy, entityAttributeAccessor, viewAttributeAccessor, optimisticLockProtected, collectionUpdatable, - keyCascadeDeleteListener, + viewOnlyDeleteCascaded, + jpaProviderDeletesCollection, elementCascadeDeleteListener, - keyRemoveListener, elementRemoveListener, + collectionInstantiator, + elementDescriptor, + inverseFlusher, + inverseRemoveStrategy + ); + } else { + return new CollectionAttributeFlusher( + attributeName, + attributeMapping, + owner == null ? entityClass : owner.fullEntityLoader.getEntityClass(), + idAttributeName, + ownerMapping, + ownerIdFlusher, + createPluralAttributeSubFlusher(evm, viewType, attribute, "element", pluralAttribute.getElementType(), owner, ownerMapping), + supportsCollectionDml, + flushStrategy, + entityAttributeAccessor, + viewAttributeAccessor, + optimisticLockProtected, + collectionUpdatable, viewOnlyDeleteCascaded, jpaProviderDeletesCollection, - keyDescriptor, + elementCascadeDeleteListener, + elementRemoveListener, + collectionInstantiator, elementDescriptor, - mapper, - loadOnlyMapper, - mapInstantiator + inverseFlusher, + inverseRemoveStrategy ); - } else { - return null; } } else { - if (collectionUpdatable || elementDescriptor.shouldFlushMutations() || shouldPassThrough(evm, viewType, attribute)) { - InverseFlusher inverseFlusher = InverseFlusher.forAttribute(evm, viewType, attribute, elementDescriptor); - InverseRemoveStrategy inverseRemoveStrategy = attribute.getInverseRemoveStrategy(); + return null; + } + } + } - CollectionInstantiator collectionInstantiator = attribute.getCollectionInstantiator(); - if (pluralAttribute.isIndexed()) { - return new IndexedListAttributeFlusher>>( - attributeName, - attributeMapping, - entityClass, - idAttributeName, - flushStrategy, - entityAttributeAccessor, - viewAttributeAccessor, - optimisticLockProtected, - collectionUpdatable, - viewOnlyDeleteCascaded, - jpaProviderDeletesCollection, - elementCascadeDeleteListener, - elementRemoveListener, - collectionInstantiator, - elementDescriptor, - inverseFlusher, - inverseRemoveStrategy - ); - } else { - return new CollectionAttributeFlusher( - attributeName, - attributeMapping, - entityClass, - idAttributeName, - flushStrategy, - entityAttributeAccessor, - viewAttributeAccessor, - optimisticLockProtected, - collectionUpdatable, - viewOnlyDeleteCascaded, - jpaProviderDeletesCollection, - elementCascadeDeleteListener, - elementRemoveListener, - collectionInstantiator, - elementDescriptor, - inverseFlusher, - inverseRemoveStrategy - ); + private DirtyAttributeFlusher createPluralAttributeSubFlusher(EntityViewManagerImpl evm, ManagedViewTypeImplementor viewType, AbstractMethodAttribute attribute, String name, Type type, EntityViewUpdaterImpl owner, String ownerMapping) { + EntityMetamodel entityMetamodel = evm.getMetamodel().getEntityMetamodel(); + String attributeName = attribute.getName() + "_" + name; + String attributeMapping = attribute.getMapping(); + String attributeLocation = attribute.getLocation(); + Set> persistAllowedSubtypes = attribute.getPersistCascadeAllowedSubtypes(); + Set> updateAllowedSubtypes = attribute.getUpdateCascadeAllowedSubtypes(); + if (type instanceof ManagedViewType) { + ManagedViewType subviewType = (ManagedViewType) type; + if (entityMetamodel.getEntity(subviewType.getEntityClass()) == null) { + // A singular attribute where the subview refers to an embeddable type + EmbeddableUpdaterBasedViewToEntityMapper viewToEntityMapper = new EmbeddableUpdaterBasedViewToEntityMapper( + attributeLocation, + evm, + subviewType.getJavaType(), + persistAllowedSubtypes, + updateAllowedSubtypes, + new ReferenceEntityLoader(evm, subviewType, createViewIdMapper(evm, subviewType)), + false, + null, + owner, + ownerMapping + ); + + String parameterName = attributeName + "_"; + String updateFragment = attributeMapping + "."; + + return new EmbeddableAttributeFlusher<>( + attributeName, + attributeMapping, + updateFragment, + parameterName, + false, + false, + false, + null, + null, + viewToEntityMapper + ); + } else { + ViewTypeImplementor attributeViewType = (ViewTypeImplementor) subviewType; + InitialValueAttributeAccessor viewAttributeAccessor = Accessors.forMutableViewAttribute(evm, attribute); + AttributeAccessor subviewIdAccessor = Accessors.forViewId(evm, attributeViewType, true); + + String idMapping = ((MappingAttribute) attributeViewType.getIdAttribute()).getMapping(); + Attribute attributeIdAttribute = attributeViewType.getJpaManagedType().getAttribute(idMapping); + javax.persistence.metamodel.Type attributeIdAttributeType = entityMetamodel.type(JpaMetamodelUtils.resolveFieldClass(attributeViewType.getEntityClass(), attributeIdAttribute)); + List idComponentMappings; + boolean requiresComponentWiseSetInUpdate = true; + + if (requiresComponentWiseSetInUpdate && attributeIdAttributeType instanceof EmbeddableType) { + // If the identifier used for the association is an embeddable, we must collect the individual attribute components since updates don't work on embeddables directly + Set> idAttributeComponents = (Set) ((EmbeddableType) attributeIdAttributeType) + .getAttributes(); + idComponentMappings = new ArrayList<>(idAttributeComponents.size()); + for (Attribute idAttributeComponent : idAttributeComponents) { + idComponentMappings.add(attributeMapping + "." + idMapping + "." + idAttributeComponent); } } else { - return null; + idComponentMappings = Collections.singletonList(attributeMapping + "." + idMapping); } + + String[] idAttributeMappings = idComponentMappings.toArray(new String[idComponentMappings.size()]); + + ViewToEntityMapper viewToEntityMapper = createViewToEntityMapper(attributeLocation, evm, attributeViewType, false, false, persistAllowedSubtypes, updateAllowedSubtypes, owner, ownerMapping); + String parameterName = attributeName; + + return new SubviewAttributeFlusher<>( + attributeName, + attributeMapping, + false, + true, + false, + false, + false, + subviewType.getConverter(), + false, + idAttributeMappings, + parameterName, + false, + null, + viewAttributeAccessor, + subviewIdAccessor, + viewToEntityMapper, + null, + null + ); } - } else if (attribute.isSubview()) { + } else { + TypeDescriptor elementDescriptor = TypeDescriptor.forType(evm, this, attribute, type, owner, ownerMapping); + String parameterName = attributeName; + String updateFragment = attributeMapping; + + boolean supportsQueryFlush = !elementDescriptor.isJpaEmbeddable(); + return new BasicAttributeFlusher<>( + attributeName, + attributeMapping, + supportsQueryFlush, + false, + true, + false, + false, + false, + null, + elementDescriptor, + updateFragment, + parameterName, + null, + null, + null, + null, + null + ); + } + } + + private DirtyAttributeFlusher createSingularAttributeFlusher(EntityViewManagerImpl evm, ManagedViewTypeImplementor viewType, AbstractMethodAttribute attribute, EntityViewUpdaterImpl owner, String ownerMapping) { + EntityMetamodel entityMetamodel = evm.getMetamodel().getEntityMetamodel(); + Class entityClass = viewType.getEntityClass(); + String attributeName = attribute.getName(); + String attributeMapping = attribute.getMapping(); + AttributeAccessor entityAttributeAccessor = Accessors.forEntityMapping(evm, attribute); + String attributeLocation = attribute.getLocation(); + boolean cascadePersist = attribute.isPersistCascaded(); + boolean cascadeUpdate = attribute.isUpdateCascaded(); + boolean cascadeDelete = attribute.isDeleteCascaded(); + boolean viewOnlyDeleteCascaded = cascadeDelete && !entityMetamodel.getManagedType(ExtendedManagedType.class, entityClass).getAttribute(attributeMapping).isDeleteCascaded(); + boolean optimisticLockProtected = attribute.isOptimisticLockProtected(); + Set> persistAllowedSubtypes = attribute.getPersistCascadeAllowedSubtypes(); + Set> updateAllowedSubtypes = attribute.getUpdateCascadeAllowedSubtypes(); + JpaProvider jpaProvider = evm.getJpaProvider(); + if (attribute.isSubview()) { boolean shouldFlushUpdates = cascadeUpdate && !updateAllowedSubtypes.isEmpty(); boolean shouldFlushPersists = cascadePersist && !persistAllowedSubtypes.isEmpty(); @@ -822,11 +990,13 @@ private static DirtyAttributeFlusher createAttributeFlusher(EntityViewManagerImp updateAllowedSubtypes, new ReferenceEntityLoader(evm, subviewType, createViewIdMapper(evm, subviewType)), shouldFlushPersists, - null + null, + owner == null ? this : owner, + ownerMapping == null ? attributeMapping : ownerMapping + "." + attributeMapping ); CompositeAttributeFlusher nestedFlusher = (CompositeAttributeFlusher) viewToEntityMapper.getFullGraphNode(); - boolean supportsQueryFlush = nestedFlusher.supportsQueryFlush() && evm.getJpaProvider().supportsUpdateSetEmbeddable(); + boolean supportsQueryFlush = nestedFlusher.supportsQueryFlush() && jpaProvider.supportsUpdateSetEmbeddable(); String parameterName; String updateFragment; if (supportsQueryFlush) { @@ -854,7 +1024,7 @@ private static DirtyAttributeFlusher createAttributeFlusher(EntityViewManagerImp ViewTypeImplementor attributeViewType = (ViewTypeImplementor) subviewType; InitialValueAttributeAccessor viewAttributeAccessor = Accessors.forMutableViewAttribute(evm, attribute); AttributeAccessor subviewIdAccessor = Accessors.forViewId(evm, attributeViewType, true); - InverseFlusher inverseFlusher = InverseFlusher.forAttribute(evm, viewType, attribute, TypeDescriptor.forType(evm, attribute, subviewType)); + InverseFlusher inverseFlusher = InverseFlusher.forAttribute(evm, viewType, attribute, TypeDescriptor.forType(evm, this, attribute, subviewType, owner, ownerMapping), owner, ownerMapping); InverseRemoveStrategy inverseRemoveStrategy = attribute.getInverseRemoveStrategy(); ViewToEntityMapper viewToEntityMapper; boolean fetch = shouldFlushUpdates; @@ -869,7 +1039,7 @@ private static DirtyAttributeFlusher createAttributeFlusher(EntityViewManagerImp if (requiresComponentWiseSetInUpdate && attributeIdAttributeType instanceof EmbeddableType) { // If the identifier used for the association is an embeddable, we must collect the individual attribute components since updates don't work on embeddables directly Set> idAttributeComponents = (Set) ((EmbeddableType) attributeIdAttributeType) - .getAttributes(); + .getAttributes(); idComponentMappings = new ArrayList<>(idAttributeComponents.size()); for (Attribute idAttributeComponent : idAttributeComponents) { idComponentMappings.add(attributeMapping + "." + idMapping + "." + idAttributeComponent); @@ -881,7 +1051,7 @@ private static DirtyAttributeFlusher createAttributeFlusher(EntityViewManagerImp String[] idAttributeMappings = idComponentMappings.toArray(new String[idComponentMappings.size()]); if (attribute.isUpdatable()) { - viewToEntityMapper = createViewToEntityMapper(attributeLocation, evm, attributeViewType, cascadePersist, cascadeUpdate, persistAllowedSubtypes, updateAllowedSubtypes); + viewToEntityMapper = createViewToEntityMapper(attributeLocation, evm, attributeViewType, cascadePersist, cascadeUpdate, persistAllowedSubtypes, updateAllowedSubtypes, owner, ownerMapping); parameterName = attributeName; } else { if (shouldFlushUpdates) { @@ -893,8 +1063,8 @@ private static DirtyAttributeFlusher createAttributeFlusher(EntityViewManagerImp updateAllowedSubtypes, new ReferenceEntityLoader(evm, subviewType, createViewIdMapper(evm, subviewType)), Accessors.forViewId(evm, attributeViewType, true), - shouldFlushPersists - ); + shouldFlushPersists, + owner, ownerMapping); } else if (shouldFlushPersists) { viewToEntityMapper = new CreateOnlyViewToEntityMapper( attributeLocation, @@ -904,8 +1074,8 @@ private static DirtyAttributeFlusher createAttributeFlusher(EntityViewManagerImp updateAllowedSubtypes, null, null, - shouldFlushPersists - ); + shouldFlushPersists, + owner, ownerMapping); } else { viewToEntityMapper = new LoadOrPersistViewToEntityMapper( attributeLocation, @@ -915,8 +1085,8 @@ private static DirtyAttributeFlusher createAttributeFlusher(EntityViewManagerImp updateAllowedSubtypes, new ReferenceEntityLoader(evm, subviewType, createViewIdMapper(evm, subviewType)), null, - shouldFlushPersists - ); + shouldFlushPersists, + owner, ownerMapping); } } @@ -946,13 +1116,13 @@ private static DirtyAttributeFlusher createAttributeFlusher(EntityViewManagerImp } } else { BasicType attributeType = (BasicType) ((com.blazebit.persistence.view.metamodel.SingularAttribute) attribute).getType(); - TypeDescriptor elementDescriptor = TypeDescriptor.forType(evm, attribute, attributeType); + TypeDescriptor elementDescriptor = TypeDescriptor.forType(evm, this, attribute, attributeType, owner, ownerMapping); // Basic attributes like String, Integer but also JPA managed types boolean updatable = attribute.isUpdatable(); if (updatable || elementDescriptor.shouldFlushMutations() || shouldPassThrough(evm, viewType, attribute)) { // Basic attributes can normally be updated by queries - InverseFlusher inverseFlusher = InverseFlusher.forAttribute(evm, viewType, attribute, elementDescriptor); + InverseFlusher inverseFlusher = InverseFlusher.forAttribute(evm, viewType, attribute, elementDescriptor, owner, ownerMapping); InverseRemoveStrategy inverseRemoveStrategy = attribute.getInverseRemoveStrategy(); String parameterName = attributeName; String updateFragment = attributeMapping; @@ -980,8 +1150,26 @@ private static DirtyAttributeFlusher createAttributeFlusher(EntityViewManagerImp viewAttributeAccessor = Accessors.forViewAttribute(evm, attribute, true); } - boolean supportsQueryFlush = !elementDescriptor.isJpaEmbeddable() || evm.getJpaProvider().supportsUpdateSetEmbeddable(); - return new BasicAttributeFlusher<>(attributeName, attributeMapping, supportsQueryFlush, optimisticLockProtected, updatable, cascadeDelete, attribute.isOrphanRemoval(), viewOnlyDeleteCascaded, null, elementDescriptor, updateFragment, parameterName, entityAttributeAccessor, viewAttributeAccessor, deleter, inverseFlusher, inverseRemoveStrategy); + boolean supportsQueryFlush = !elementDescriptor.isJpaEmbeddable() || jpaProvider.supportsUpdateSetEmbeddable(); + return new BasicAttributeFlusher<>( + attributeName, + attributeMapping, + supportsQueryFlush, + optimisticLockProtected, + updatable, + cascadeDelete, + attribute.isOrphanRemoval(), + viewOnlyDeleteCascaded, + null, + elementDescriptor, + updateFragment, + parameterName, + entityAttributeAccessor, + viewAttributeAccessor, + deleter, + inverseFlusher, + inverseRemoveStrategy + ); } else { return null; } @@ -1019,9 +1207,9 @@ private static boolean shouldPassThrough(EntityViewManagerImpl evm, ManagedViewT && attribute.isUpdateMappable(); } - private static ViewToEntityMapper createViewToEntityMapper(String attributeLocation, EntityViewManagerImpl evm, ViewType viewType, boolean cascadePersist, boolean cascadeUpdate, Set> persistAllowedSubtypes, Set> updateAllowedSubtypes) { + private static ViewToEntityMapper createViewToEntityMapper(String attributeLocation, EntityViewManagerImpl evm, ViewType viewType, boolean cascadePersist, boolean cascadeUpdate, Set> persistAllowedSubtypes, Set> updateAllowedSubtypes, EntityViewUpdaterImpl owner, String ownerMapping) { EntityLoader entityLoader = new ReferenceEntityLoader(evm, viewType, createViewIdMapper(evm, viewType)); - AttributeAccessor viewIdAccessor = viewIdAccessor = Accessors.forViewId(evm, viewType, true); + AttributeAccessor viewIdAccessor = Accessors.forViewId(evm, viewType, true); Class viewTypeClass = viewType.getJavaType(); boolean mutable = viewType.isUpdatable() || viewType.isCreatable() || !persistAllowedSubtypes.isEmpty() || !updateAllowedSubtypes.isEmpty(); @@ -1034,7 +1222,10 @@ private static ViewToEntityMapper createViewToEntityMapper(String attributeLocat updateAllowedSubtypes, entityLoader, viewIdAccessor, - cascadePersist); + cascadePersist, + owner, + ownerMapping + ); } return new UpdaterBasedViewToEntityMapper( @@ -1045,7 +1236,9 @@ private static ViewToEntityMapper createViewToEntityMapper(String attributeLocat updateAllowedSubtypes, entityLoader, viewIdAccessor, - cascadePersist + cascadePersist, + owner, + ownerMapping ); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/AbstractPluralAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/AbstractPluralAttributeFlusher.java index a47405b7c7..02700809a1 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/AbstractPluralAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/AbstractPluralAttributeFlusher.java @@ -28,6 +28,7 @@ import javax.persistence.EntityManager; import javax.persistence.Query; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -40,6 +41,13 @@ public abstract class AbstractPluralAttributeFlusher ownerEntityClass; protected final String ownerIdAttributeName; + protected final String ownerMapping; + protected final DirtyAttributeFlusher ownerIdFlusher; + protected final boolean supportsCollectionDml; + protected final String ownerIdWhereFragment; + protected final String[] ownerIdBindFragments; + protected final String[] elementBindFragments; + protected final DirtyAttributeFlusher elementFlusher; protected final FlushStrategy flushStrategy; protected final AttributeAccessor entityAttributeMapper; protected final InitialValueAttributeAccessor viewAttributeAccessor; @@ -57,11 +65,28 @@ public abstract class AbstractPluralAttributeFlusher> elementFlushers; @SuppressWarnings("unchecked") - public AbstractPluralAttributeFlusher(String attributeName, String mapping, boolean fetch, Class ownerEntityClass, String ownerIdAttributeName, FlushStrategy flushStrategy, AttributeAccessor entityAttributeMapper, InitialValueAttributeAccessor viewAttributeAccessor, boolean optimisticLockProtected, boolean collectionUpdatable, - boolean viewOnlyDeleteCascaded, boolean jpaProviderDeletesCollection, CollectionRemoveListener cascadeDeleteListener, CollectionRemoveListener removeListener, TypeDescriptor elementDescriptor) { + public AbstractPluralAttributeFlusher(String attributeName, String mapping, boolean fetch, Class ownerEntityClass, String ownerIdAttributeName, String ownerMapping, DirtyAttributeFlusher ownerIdFlusher, DirtyAttributeFlusher elementFlusher, boolean supportsCollectionDml, FlushStrategy flushStrategy, AttributeAccessor entityAttributeMapper, + InitialValueAttributeAccessor viewAttributeAccessor, boolean optimisticLockProtected, boolean collectionUpdatable, boolean viewOnlyDeleteCascaded, boolean jpaProviderDeletesCollection, CollectionRemoveListener cascadeDeleteListener, CollectionRemoveListener removeListener, TypeDescriptor elementDescriptor) { super(attributeName, mapping, fetch, elementDescriptor.getViewToEntityMapper() == null ? null : elementDescriptor.getViewToEntityMapper().getFullGraphNode()); this.ownerEntityClass = ownerEntityClass; this.ownerIdAttributeName = ownerIdAttributeName; + this.ownerMapping = ownerMapping; + this.ownerIdFlusher = (DirtyAttributeFlusher) ownerIdFlusher; + this.supportsCollectionDml = supportsCollectionDml; + this.elementFlusher = (DirtyAttributeFlusher) elementFlusher; + StringBuilder sb = new StringBuilder(); + ownerIdFlusher.appendUpdateQueryFragment(null, sb, null, null, " AND "); + this.ownerIdWhereFragment = sb.toString(); + sb.setLength(0); + ownerIdFlusher.appendUpdateQueryFragment(null, sb, null, null, ","); + String[] fragments = sb.toString().split("\\s*(=|,)\\s*"); + for (int i = 1; i < fragments.length; i += 2) { + fragments[i] = "FUNCTION('TREAT_INTEGER', " + fragments[i] + ")"; + } + this.ownerIdBindFragments = fragments; + sb.setLength(0); + ownerIdFlusher.appendUpdateQueryFragment(null, sb, null, null, ","); + this.elementBindFragments = sb.toString().split("\\s*(=|,)\\s*"); this.flushStrategy = flushStrategy; this.entityAttributeMapper = entityAttributeMapper; this.viewAttributeAccessor = viewAttributeAccessor; @@ -103,6 +128,13 @@ protected AbstractPluralAttributeFlusher(AbstractPluralAttributeFlusher FetchGraphNode computeElementFetchGraphNode(List elementAttributeFlusher = elementFlushers.get(0); - if (elementFlushers.size() == 1) { - return elementAttributeFlusher; + CollectionElementAttributeFlusher elementAttributeFlusher = null; + List> filteredElementFlushers = null; + for (int i = 0; i < elementFlushers.size(); i++) { + CollectionElementAttributeFlusher flusher = elementFlushers.get(i); + if (flusher instanceof MergeCollectionElementAttributeFlusher) { + if (filteredElementFlushers == null) { + filteredElementFlushers = new ArrayList<>(); + for (int j = 0; j < i; j++) { + filteredElementFlushers.add(elementFlushers.get(j)); + } + } + } else { + if (elementAttributeFlusher == null) { + elementAttributeFlusher = flusher; + } + if (filteredElementFlushers != null) { + filteredElementFlushers.add(flusher); + } + } + } + if (filteredElementFlushers == null) { + if (elementFlushers.size() == 1) { + return elementAttributeFlusher; + } + } else { + if (filteredElementFlushers.isEmpty()) { + return null; + } + if (filteredElementFlushers.size() == 1) { + return elementAttributeFlusher; + } + elementFlushers = filteredElementFlushers; } return elementAttributeFlusher.mergeWith(elementFlushers); } @Override - public void appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { + public boolean appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { + return true; } @Override public boolean supportsQueryFlush() { // TODO: Maybe also when collectionUpdatable is false? - return mapping == null || flushStrategy != FlushStrategy.ENTITY && !fetch && flushOperation == PluralFlushOperation.ELEMENT_ONLY; + // When we have no mapping, this is a correlated attribute, so there are no collection operations, only cascades + // When dirty checking figured out we only have element flushes, we also don't have collection operations + return supportsCollectionDml || mapping == null || flushStrategy != FlushStrategy.ENTITY && !fetch && flushOperation == PluralFlushOperation.ELEMENT_ONLY; } @Override @@ -147,18 +211,18 @@ public boolean loadForEntityFlush() { } @Override - public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { + public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object ownerView, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { if (!supportsQueryFlush()) { throw new UnsupportedOperationException("Query flush not supported for configuration!"); } for (CollectionElementAttributeFlusher elementFlusher : elementFlushers) { - elementFlusher.flushQuery(context, null, null, view, value, ownerAwareDeleter); + elementFlusher.flushQuery(context, null, null, ownerView, view, value, ownerAwareDeleter); } } protected final V getEntityAttributeValue(E entity) { - if (entityAttributeMapper == null) { + if (entityAttributeMapper == null || entity == null) { return null; } V value = (V) entityAttributeMapper.getValue(entity); @@ -172,8 +236,16 @@ protected final V getEntityAttributeValue(E entity) { protected abstract V createJpaCollection(); + protected final String getMapping() { + if (ownerMapping == null) { + return mapping; + } else { + return ownerMapping + "." + mapping; + } + } + @SuppressWarnings("unchecked") - protected void invokeFlushOperation(UpdateContext context, Object view, E entity, V value) { + protected void invokeFlushOperation(UpdateContext context, Object ownerView, Object view, E entity, V value) { switch (flushOperation) { case COLLECTION_REPLAY_AND_ELEMENT: if (flushStrategy == FlushStrategy.ENTITY) { @@ -182,13 +254,13 @@ protected void invokeFlushOperation(UpdateContext context, Object view, E entity } } else { for (CollectionElementAttributeFlusher elementFlusher : elementFlushers) { - elementFlusher.flushQuery(context, null, null, view, value, null); + elementFlusher.flushQuery(context, null, null, ownerView, view, value, null); } } - invokeCollectionAction(context, getEntityAttributeValue(entity), collectionActions); + invokeCollectionAction(context, ownerView, view, getEntityAttributeValue(entity), value, collectionActions); return; case COLLECTION_REPLAY_ONLY: - invokeCollectionAction(context, getEntityAttributeValue(entity), collectionActions); + invokeCollectionAction(context, ownerView, view, getEntityAttributeValue(entity), value, collectionActions); return; case COLLECTION_REPLACE_AND_ELEMENT: if (flushStrategy == FlushStrategy.ENTITY) { @@ -197,23 +269,23 @@ protected void invokeFlushOperation(UpdateContext context, Object view, E entity } } else { for (CollectionElementAttributeFlusher elementFlusher : elementFlushers) { - elementFlusher.flushQuery(context, null, null, view, value, null); + elementFlusher.flushQuery(context, null, null, ownerView, view, value, null); } } - replaceCollection(context, entity, value); + replaceCollection(context, ownerView, view, entity, value); return; case COLLECTION_REPLACE_ONLY: - replaceCollection(context, entity, value); + replaceCollection(context, ownerView, view, entity, value); return; case ELEMENT_ONLY: - mergeCollectionElements(context, view, entity, value); + mergeCollectionElements(context, ownerView, view, entity, value); return; default: throw new UnsupportedOperationException("Unsupported flush operation: " + flushOperation); } } - protected abstract void invokeCollectionAction(UpdateContext context, V targetCollection, List collectionActions); + protected abstract void invokeCollectionAction(UpdateContext context, Object ownerView, Object view, V targetCollection, Object value, List collectionActions); protected abstract V replaceWithRecordingCollection(UpdateContext context, Object view, V value, List actions); @@ -268,15 +340,23 @@ protected final void persistIfNeeded(EntityManager em, Object object, BasicUserT } } - protected abstract boolean mergeCollectionElements(UpdateContext context, Object view, E entity, V value); + protected abstract boolean mergeCollectionElements(UpdateContext context, Object ownerView, Object view, E entity, V value); + + protected abstract void replaceCollection(UpdateContext context, Object ownerView, Object view, E entity, V value); + + protected abstract boolean isIndexed(); - protected abstract void replaceCollection(UpdateContext context, E entity, V value); + protected abstract List getElementFlushActions(UpdateContext context, TypeDescriptor elementDescriptor, V current); @SuppressWarnings("unchecked") protected final DirtyAttributeFlusher getElementOnlyFlusher(UpdateContext context, V current) { - List> elementFlushers = getElementFlushers(context, current); + List actions = new ArrayList<>(); + List> elementFlushers = getElementFlushers(context, current, actions); // A "null" element flusher list is given when a fetch and compare is more appropriate if (elementFlushers == null) { + if (!actions.isEmpty()) { + return partialFlusher(true, PluralFlushOperation.COLLECTION_REPLAY_ONLY, actions, Collections.EMPTY_LIST); + } return this; } @@ -312,7 +392,7 @@ protected final DirtyAttributeFlusher getReplaceOrMergeOnlyFlusher(Upda @SuppressWarnings("unchecked") protected final DirtyAttributeFlusher getReplaceOrMergeAndElementFlusher(UpdateContext context, V initial, V current) { - List> elementFlushers = getElementFlushers(context, current); + List> elementFlushers = getElementFlushers(context, current, null); // A "null" element flusher list is given when a fetch and compare is more appropriate if (elementFlushers == null) { return this; @@ -357,14 +437,14 @@ protected final DirtyAttributeFlusher getReplayAndElementFlusher(Update return partialFlusher(true, PluralFlushOperation.COLLECTION_REPLAY_AND_ELEMENT, collectionActions, elementFlushers); } - protected abstract List> getElementFlushers(UpdateContext context, V current); + protected abstract List> getElementFlushers(UpdateContext context, V current, List actions); - protected final boolean determineElementFlushers(UpdateContext context, TypeDescriptor typeDescriptor, List> elementFlushers, Iterable current) { + protected final boolean determineElementFlushers(UpdateContext context, TypeDescriptor typeDescriptor, List> elementFlushers, Iterable values, List actions, V current) { if (typeDescriptor.shouldFlushMutations()) { if (typeDescriptor.isSubview()) { final ViewToEntityMapper mapper = typeDescriptor.getViewToEntityMapper(); if (typeDescriptor.isIdentifiable()) { - for (Object o : current) { + for (Object o : values) { if (o instanceof MutableStateTrackable) { MutableStateTrackable element = (MutableStateTrackable) o; @SuppressWarnings("unchecked") @@ -375,33 +455,34 @@ protected final boolean determineElementFlushers(UpdateContext context, TypeDesc } } } else { - for (Object o : current) { - if (o instanceof MutableStateTrackable) { - MutableStateTrackable element = (MutableStateTrackable) o; - @SuppressWarnings("unchecked") - DirtyAttributeFlusher flusher = (DirtyAttributeFlusher) (DirtyAttributeFlusher) mapper.getNestedDirtyFlusher(context, element, (DirtyAttributeFlusher) null); - if (flusher != null) { - // We can't merge flat view elements separately so we need to replace the element in the collection - // This is signalled by returning null - return true; + if (typeDescriptor.supportsDirtyCheck() && !typeDescriptor.isIdentifiable() && isIndexed()) { + ((List) actions).addAll(getElementFlushActions(context, typeDescriptor, current)); + return true; + } else { + for (Object o : values) { + if (o instanceof MutableStateTrackable) { + MutableStateTrackable element = (MutableStateTrackable) o; + @SuppressWarnings("unchecked") + DirtyAttributeFlusher flusher = (DirtyAttributeFlusher) (DirtyAttributeFlusher) mapper.getNestedDirtyFlusher(context, element, (DirtyAttributeFlusher) null); + if (flusher != null) { + // We can't merge flat view elements separately so we need to replace the element in the collection + // This is signalled by returning null + return true; + } } } } - } } else if (typeDescriptor.isJpaEntity()) { - for (Object element : current) { + for (Object element : values) { if (typeDescriptor.getBasicUserType().shouldPersist(element) && typeDescriptor.shouldJpaPersist()) { elementFlushers.add(new PersistCollectionElementAttributeFlusher(element, optimisticLockProtected)); - } else if (typeDescriptor.shouldJpaMerge()) { - // We can't replace the original object efficiently in the backing collection which is required because em.merge returns a new object - // And since merges need the current state, we rather fetch the collection and merge/persist only during the actual flushing - // This is signalled by returning null - return true; + } else if (element != null && typeDescriptor.shouldJpaMerge()) { + elementFlushers.add(new MergeCollectionElementAttributeFlusher(element, optimisticLockProtected)); } } } else if (typeDescriptor.getBasicUserType().supportsDirtyChecking()) { - for (Object element : current) { + for (Object element : values) { String[] dirtyProperties = typeDescriptor.getBasicUserType().getDirtyProperties(element); if (dirtyProperties != null) { // We can't merge basic elements separately so we need to replace the element in the collection @@ -409,6 +490,10 @@ protected final boolean determineElementFlushers(UpdateContext context, TypeDesc return true; } } + } else if (canFlushSeparateCollectionOperations()) { + // We can't merge basic elements separately so we need to replace the element in the collection + // This is signalled by returning null + return true; } else { throw new IllegalArgumentException("Element flushers for non-identifiable type not determinable: " + typeDescriptor); } @@ -417,6 +502,8 @@ protected final boolean determineElementFlushers(UpdateContext context, TypeDesc return false; } + protected abstract boolean canFlushSeparateCollectionOperations(); + protected abstract AbstractPluralAttributeFlusher partialFlusher(boolean fetch, PluralFlushOperation operation, List collectionActions, List> elementFlushers); /** diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/BasicAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/BasicAttributeFlusher.java index 7ad50b1683..ab2bf189ce 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/BasicAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/BasicAttributeFlusher.java @@ -147,8 +147,12 @@ public String getAttributeName() { return attributeName; } + public String getMapping() { + return mapping; + } + @Override - public void appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { + public boolean appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { // It must be updatable and the value must have changed if ((updatable || isPassThrough()) && (flushOperation == null || update) && inverseFlusher == null) { if (updateFragment != null) { @@ -169,8 +173,11 @@ public void appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, S componentFlushers[i].getValue().appendUpdateQueryFragment(context, sb, mappingPrefix, parameterPrefix, separator); } } + return true; } } + + return false; } @Override @@ -217,7 +224,7 @@ public boolean loadForEntityFlush() { } @Override - public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { + public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object ownerView, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { V finalValue; if (flushOperation == null) { finalValue = value; @@ -259,7 +266,7 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer } else { for (int i = 0; i < componentFlushers.length; i++) { Object val = componentFlushers[i].getKey().getValue(finalValue); - componentFlushers[i].getValue().flushQuery(context, parameterPrefix, query, view, val, ownerAwareDeleter); + componentFlushers[i].getValue().flushQuery(context, parameterPrefix, query, ownerView, view, val, ownerAwareDeleter); } } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionAttributeFlusher.java index 31f73f317b..86ba11eeaf 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionAttributeFlusher.java @@ -17,6 +17,7 @@ package com.blazebit.persistence.view.impl.update.flush; import com.blazebit.persistence.DeleteCriteriaBuilder; +import com.blazebit.persistence.InsertCriteriaBuilder; import com.blazebit.persistence.view.FlushStrategy; import com.blazebit.persistence.view.InverseRemoveStrategy; import com.blazebit.persistence.view.impl.EntityViewManagerImpl; @@ -41,10 +42,13 @@ import javax.persistence.Query; import javax.persistence.Tuple; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -62,9 +66,11 @@ public class CollectionAttributeFlusher> extends Abst private final InverseCollectionElementAttributeFlusher.Strategy inverseRemoveStrategy; @SuppressWarnings("unchecked") - public CollectionAttributeFlusher(String attributeName, String mapping, Class ownerEntityClass, String ownerIdAttributeName, FlushStrategy flushStrategy, AttributeAccessor entityAttributeAccessor, InitialValueAttributeAccessor viewAttributeAccessor, boolean optimisticLockProtected, boolean collectionUpdatable, - boolean viewOnlyDeleteCascaded, boolean jpaProviderDeletesCollection, CollectionRemoveListener cascadeDeleteListener, CollectionRemoveListener removeListener, CollectionInstantiator collectionInstantiator, TypeDescriptor elementDescriptor, InverseFlusher inverseFlusher, InverseRemoveStrategy inverseRemoveStrategy) { - super(attributeName, mapping, collectionUpdatable || elementDescriptor.shouldFlushMutations(), ownerEntityClass, ownerIdAttributeName, flushStrategy, entityAttributeAccessor, viewAttributeAccessor, optimisticLockProtected, collectionUpdatable, viewOnlyDeleteCascaded, jpaProviderDeletesCollection, cascadeDeleteListener, removeListener, elementDescriptor); + public CollectionAttributeFlusher(String attributeName, String mapping, Class ownerEntityClass, String ownerIdAttributeName, String ownerMapping, DirtyAttributeFlusher ownerIdFlusher, DirtyAttributeFlusher elementFlusher, boolean supportsCollectionDml, FlushStrategy flushStrategy, AttributeAccessor entityAttributeAccessor, + InitialValueAttributeAccessor viewAttributeAccessor, boolean optimisticLockProtected, boolean collectionUpdatable, boolean viewOnlyDeleteCascaded, boolean jpaProviderDeletesCollection, CollectionRemoveListener cascadeDeleteListener, CollectionRemoveListener removeListener, CollectionInstantiator collectionInstantiator, + TypeDescriptor elementDescriptor, InverseFlusher inverseFlusher, InverseRemoveStrategy inverseRemoveStrategy) { + super(attributeName, mapping, collectionUpdatable || elementDescriptor.shouldFlushMutations(), ownerEntityClass, ownerIdAttributeName, ownerMapping, ownerIdFlusher, elementFlusher, supportsCollectionDml, flushStrategy, entityAttributeAccessor, viewAttributeAccessor, optimisticLockProtected, collectionUpdatable, viewOnlyDeleteCascaded, jpaProviderDeletesCollection, + cascadeDeleteListener, removeListener, elementDescriptor); this.collectionInstantiator = collectionInstantiator; this.inverseFlusher = inverseFlusher; this.inverseRemoveStrategy = InverseCollectionElementAttributeFlusher.Strategy.of(inverseRemoveStrategy); @@ -132,20 +138,24 @@ public Object getNewInitialValue(UpdateContext context, V clonedValue, V current } @Override - protected void invokeCollectionAction(UpdateContext context, V targetCollection, List> collectionActions) { + protected void invokeCollectionAction(UpdateContext context, Object ownerView, Object view, V targetCollection, Object value, List> collectionActions) { final ViewToEntityMapper viewToEntityMapper = elementDescriptor.getLoadOnlyViewToEntityMapper(); - if (targetCollection == null) { - // When the target collection is null this means that there is no collection role in the entity + if (mapping == null) { + // When the mapping is null this means that there is no collection role in the entity // This happens for correlated attributes and we will just provide an empty collection for applying actions targetCollection = createCollection(0); for (CollectionAction action : (List>) (List) collectionActions) { action.doAction(targetCollection, context, viewToEntityMapper, removeListener); } } else { - // NOTE: We don't care if the actual collection and the initial collection differ - // If an error is desired, a user should configure optimistic locking - for (CollectionAction action : (List>) (List) collectionActions) { - action.doAction(targetCollection, context, viewToEntityMapper, removeListener); + if (flushStrategy == FlushStrategy.QUERY) { + flushCollectionOperations(context, ownerView, view, (V) value, null, collectionActions); + } else { + // NOTE: We don't care if the actual collection and the initial collection differ + // If an error is desired, a user should configure optimistic locking + for (CollectionAction action : (List>) (List) collectionActions) { + action.doAction(targetCollection, context, viewToEntityMapper, removeListener); + } } } } @@ -239,40 +249,36 @@ public FetchGraphNode mergeWith(List> fetchG } @Override - public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { + public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object ownerView, Object view, V current, UnmappedOwnerAwareDeleter ownerAwareDeleter) { if (!supportsQueryFlush()) { throw new UnsupportedOperationException("Query flush not supported for configuration!"); } - if (elementFlushers != null) { - if (!(value instanceof RecordingCollection)) { + if (flushOperation != null) { + if (!(current instanceof RecordingCollection)) { List>> actions = new ArrayList<>(); actions.add(new CollectionClearAction()); - if (value != null && !value.isEmpty()) { - actions.add(new CollectionAddAllAction(value, collectionInstantiator.allowsDuplicates())); + if (current != null && !current.isEmpty()) { + actions.add(new CollectionAddAllAction(current, collectionInstantiator.allowsDuplicates())); } - value = replaceWithRecordingCollection(context, view, value, actions); - } - for (CollectionElementAttributeFlusher elementFlusher : elementFlushers) { - elementFlusher.flushQuery(context, null, null, view, value, ownerAwareDeleter); + current = replaceWithRecordingCollection(context, view, current, actions); } + invokeFlushOperation(context, ownerView, view, null, current); } else { - boolean isRecording = value instanceof RecordingCollection; + boolean isRecording = current instanceof RecordingCollection; if (isRecording) { - RecordingCollection, ?> recordingCollection = (RecordingCollection, ?>) value; - Map added; - Map removed; - - if (entityAttributeMapper != null && recordingCollection.hasActions()) { - Map[] addedAndRemoved = getAddedAndRemovedElements(recordingCollection, context); - added = addedAndRemoved[0]; - removed = addedAndRemoved[1]; - } else { - added = removed = Collections.emptyMap(); - } - + RecordingCollection, ?> recordingCollection = (RecordingCollection, ?>) current; if (inverseFlusher != null) { - visitInverseElementFlushersForActions(context, recordingCollection, added, removed, new ElementFlusherQueryExecutor(context, null, view)); + Map added; + Map removed; + if (entityAttributeMapper != null && recordingCollection.hasActions()) { + Map[] addedAndRemoved = getAddedAndRemovedElementsForInverseFlusher(recordingCollection, context); + added = addedAndRemoved[0]; + removed = addedAndRemoved[1]; + } else { + added = removed = Collections.emptyMap(); + } + visitInverseElementFlushersForActions(context, recordingCollection, added, removed, new ElementFlusherQueryExecutor(context, null, ownerView)); } else { if (entityAttributeMapper == null) { // We have a correlation mapping here @@ -281,50 +287,272 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer } } + List embeddables = null; if (elementDescriptor.shouldFlushMutations()) { if (elementDescriptor.shouldJpaPersistOrMerge()) { mergeAndRequeue(context, recordingCollection, (Collection) recordingCollection.getDelegate()); - } else if (elementDescriptor.isSubview() && elementDescriptor.isIdentifiable()) { - flushCollectionViewElements(context, value); + } else if (elementDescriptor.isSubview() && (elementDescriptor.isIdentifiable() || isIndexed())) { + embeddables = flushCollectionViewElements(context, current); } } - if (entityAttributeMapper != null) { - // TODO: use collection DML to add and remove + if (entityAttributeMapper != null && collectionUpdatable) { + V initial = (V) viewAttributeAccessor.getInitialValue(view); + if (initial instanceof RecordingCollection) { + initial = (V) ((RecordingCollection) initial).getInitialVersion(); + } + if (recordingCollection.hasActions()) { + recordingCollection.resetActions(context); + } + // If the initial object was null like it happens during full flushing, we can only replace the collection + if (initial == null) { + replaceCollection(context, ownerView, view, null, current); + } else { + flushCollectionOperations(context, ownerView, view, initial, current, embeddables, (FusedCollectionActions) null); + } } } } else { - List>> actions = new ArrayList<>(); - actions.add(new CollectionClearAction()); - if (value != null && !value.isEmpty()) { - actions.add(new CollectionAddAllAction(value, collectionInstantiator.allowsDuplicates())); + EqualityChecker equalityChecker; + if (elementDescriptor.isSubview()) { + equalityChecker = EqualsEqualityChecker.INSTANCE; + } else { + equalityChecker = new IdentityEqualityChecker(elementDescriptor.getBasicUserType()); } - value = replaceWithRecordingCollection(context, view, value, actions); - if (entityAttributeMapper == null) { - // We have a correlation mapping here + V initial = (V) viewAttributeAccessor.getInitialValue(view); + if (initial instanceof RecordingCollection) { + initial = (V) ((RecordingCollection) initial).getInitialVersion(); } + List>> actions; + if (initial == null || !elementDescriptor.supportsDeepEqualityCheck() || elementDescriptor.getBasicUserType() != null && !elementDescriptor.getBasicUserType().supportsDeepCloning()) { + actions = replaceActions(current); + } else { + actions = determineCollectionActions(context, initial, current, equalityChecker); + } + current = replaceWithRecordingCollection(context, view, current, actions); + List embeddables = null; if (elementDescriptor.shouldFlushMutations()) { if (elementDescriptor.shouldJpaPersistOrMerge()) { - mergeAndRequeue(context, null, (Collection) value); - } else if (elementDescriptor.isSubview() && elementDescriptor.isIdentifiable()) { - flushCollectionViewElements(context, value); + mergeAndRequeue(context, null, (Collection) current); + } else if (elementDescriptor.isSubview() && (elementDescriptor.isIdentifiable() || isIndexed())) { + embeddables = flushCollectionViewElements(context, current); + } + } + + if (entityAttributeMapper != null && collectionUpdatable) { + // If the initial object was null like it happens during full flushing, we can only replace the collection + if (initial == null) { + replaceCollection(context, ownerView, view, null, current); + } else { + flushCollectionOperations(context, ownerView, view, initial, current, embeddables, (FusedCollectionActions) null); + } + } + } + } + } + + protected final DeleteCriteriaBuilder createCollectionDeleter(UpdateContext context) { + DeleteCriteriaBuilder deleteCb = context.getEntityViewManager().getCriteriaBuilderFactory().deleteCollection(context.getEntityManager(), ownerEntityClass, "e", getMapping()); + deleteCb.setWhereExpression(ownerIdWhereFragment); + return deleteCb; + } + + protected Collection appendRemoveSpecific(UpdateContext context, DeleteCriteriaBuilder deleteCb, FusedCollectionActions fusedCollectionActions) { + deleteCb.where("e." + getMapping()).in(fusedCollectionActions.getRemoved(context)); + return new HashSet<>(fusedCollectionActions.getRemoved()); + } + + protected Map getEntityReferencesForCollectionOperation(UpdateContext context, Map objects) { + Map entityReferences = new LinkedHashMap<>(objects.size()); + ViewToEntityMapper loadOnlyViewToEntityMapper = elementDescriptor.getLoadOnlyViewToEntityMapper(); + for (Map.Entry entry : objects.entrySet()) { + entityReferences.put(loadOnlyViewToEntityMapper.applyToEntity(context, null, entry.getKey()), entry.getValue()); + } + return entityReferences; + } + + protected List getEntityReferencesForCollectionOperation(UpdateContext context, Collection objects) { + List entityReferences = new ArrayList<>(objects.size()); + ViewToEntityMapper loadOnlyViewToEntityMapper = elementDescriptor.getLoadOnlyViewToEntityMapper(); + for (Object o : objects) { + if (o != null) { + entityReferences.add(loadOnlyViewToEntityMapper.applyToEntity(context, null, o)); + } + } + return entityReferences; + } + + protected boolean deleteElements(UpdateContext context, Object ownerView, Object view, V value, boolean removeSpecific, FusedCollectionActions fusedCollectionActions) { + DeleteCriteriaBuilder deleteCb = null; + boolean removedAll = true; + Collection removedObjects = Collections.emptyList(); + if (fusedCollectionActions != null) { + if (fusedCollectionActions.getRemoveCount() > 0) { + deleteCb = createCollectionDeleter(context); + if (removeSpecific) { + String entityIdAttributeName = elementDescriptor.getEntityIdAttributeName(); + if (entityIdAttributeName != null) { + removedObjects = appendRemoveSpecific(context, deleteCb, fusedCollectionActions); + removedAll = false; } } + } else { + removedAll = false; + } + } else { + deleteCb = createCollectionDeleter(context); + } + + if (deleteCb != null) { + Query deleteQuery = deleteCb.getQuery(); + ownerIdFlusher.flushQuery(context, null, deleteQuery, ownerView, view, ownerIdFlusher.getViewAttributeAccessor().getValue(ownerView), null); + deleteQuery.executeUpdate(); + if (removedAll) { + return true; + } + if (removeListener != null) { + for (Object removedObject : removedObjects) { + removeListener.onCollectionRemove(context, removedObject); + } + } + } + + // TODO: Think about allowing to use batching when we implement #657 + + return false; + } + + protected void addElements(UpdateContext context, Object ownerView, Object view, Collection removedAllObjects, boolean flushAtOnce, V value, List embeddablesToUpdate, FusedCollectionActions fusedCollectionActions) { + Collection elementsToAdd; + if (fusedCollectionActions == null || !removedAllObjects.isEmpty()) { + removedAllObjects.removeAll(value); + if (elementDescriptor.getViewToEntityMapper() == null) { + elementsToAdd = (Collection) value; + } else { + elementsToAdd = getEntityReferencesForCollectionOperation(context, (Collection) value); + } + } else { + removedAllObjects.removeAll(fusedCollectionActions.getAdded()); + elementsToAdd = fusedCollectionActions.getAdded(context); + } + if (elementsToAdd == null || elementsToAdd.isEmpty() || elementsToAdd.size() == 1 && elementsToAdd.iterator().next() == null) { + return; + } + + String mapping = getMapping(); + InsertCriteriaBuilder insertCb = context.getEntityViewManager().getCriteriaBuilderFactory().insertCollection(context.getEntityManager(), ownerEntityClass, mapping); + + String entityIdAttributeName = elementDescriptor.getEntityIdAttributeName(); + if (flushAtOnce) { + if (entityIdAttributeName == null) { + insertCb.fromValues((Class) elementDescriptor.getJpaType(), "val", elementsToAdd); + } else { + insertCb.fromIdentifiableValues((Class) elementDescriptor.getJpaType(), "val", elementsToAdd); + } + } else { + if (entityIdAttributeName == null) { + insertCb.fromValues((Class) elementDescriptor.getJpaType(), "val", 1); + } else { + insertCb.fromIdentifiableValues((Class) elementDescriptor.getJpaType(), "val", 1); + } + } + for (int i = 0; i < ownerIdBindFragments.length; i += 2) { + insertCb.bind(ownerIdBindFragments[i]).select(ownerIdBindFragments[i + 1]); + } + insertCb.bind(mapping).select("val"); + Query insertQuery = insertCb.getQuery(); + ownerIdFlusher.flushQuery(context, null, insertQuery, ownerView, view, ownerIdFlusher.getViewAttributeAccessor().getValue(ownerView), null); - if (entityAttributeMapper != null) { - // TODO: use collection DML to add and remove + boolean checkTransient = elementDescriptor.isJpaEntity() && !elementDescriptor.shouldJpaPersist(); + if (flushAtOnce) { + if (checkTransient) { + for (Object o : elementsToAdd) { + if (elementDescriptor.getBasicUserType().shouldPersist(o)) { + throw new IllegalStateException("Collection " + attributeName + " references an unsaved transient instance - save the transient instance before flushing: " + o); + } + } + } + insertQuery.executeUpdate(); + } else { + // TODO: Use batching when we implement #657 + Object[] singletonArray = new Object[1]; + List singletonList = Arrays.asList(singletonArray); + for (Object o : elementsToAdd) { + if (o != null) { + if (checkTransient && elementDescriptor.getBasicUserType().shouldPersist(o)) { + throw new IllegalStateException("Collection " + attributeName + " references an unsaved transient instance - save the transient instance before flushing: " + o); + } + singletonArray[0] = o; + insertQuery.setParameter("val", singletonList); + insertQuery.executeUpdate(); } } } } + @Override + protected boolean canFlushSeparateCollectionOperations() { + return !collectionInstantiator.allowsDuplicates(); + } + + @Override + protected boolean isIndexed() { + return false; + } + + @Override + protected List> getElementFlushActions(UpdateContext context, TypeDescriptor elementDescriptor, V current) { + throw new UnsupportedOperationException("Not indexed!"); + } + + protected void flushCollectionOperations(UpdateContext context, Object ownerView, Object view, V value, List embeddablesToUpdate, List> collectionActions) { + FusedCollectionActions fusedCollectionActions = null; + // We can't selectively delete/add if duplicates are allowed. Bags always need to be recreated + if (canFlushSeparateCollectionOperations()) { + if (collectionActions.isEmpty()) { + if (embeddablesToUpdate != null) { + addElements(context, ownerView, view, Collections.emptyList(), true, value, embeddablesToUpdate, null); + } + return; + } else if (!(collectionActions.get(0) instanceof CollectionClearAction)) { + // The replace action is handled specially + fusedCollectionActions = getFusedOperations(collectionActions); + } + } + flushCollectionOperations(context, ownerView, view, null, value, embeddablesToUpdate, fusedCollectionActions); + } + + protected void flushCollectionOperations(UpdateContext context, Object ownerView, Object view, V initial, V value, List embeddablesToUpdate, FusedCollectionActions fusedCollectionActions) { + boolean removeSpecific = fusedCollectionActions != null && fusedCollectionActions.operationCount() < value.size() + 1; + Collection removedAllObjects; + if (deleteElements(context, ownerView, view, value, removeSpecific, fusedCollectionActions)) { + if (fusedCollectionActions == null) { + if (initial == null) { + removedAllObjects = Collections.emptyList(); + } else { + removedAllObjects = new ArrayList<>(initial); + } + } else { + removedAllObjects = new ArrayList<>(fusedCollectionActions.getRemoved()); + } + } else { + removedAllObjects = Collections.emptyList(); + } + addElements(context, ownerView, view, removedAllObjects, true, value, embeddablesToUpdate, fusedCollectionActions); + if (removeListener != null) { + for (Object removedObject : removedAllObjects) { + removeListener.onCollectionRemove(context, removedObject); + } + } + } + @Override @SuppressWarnings("unchecked") public boolean flushEntity(UpdateContext context, E entity, Object view, V value, Runnable postReplaceListener) { if (flushOperation != null) { - replaceWithRecordingCollection(context, view, value, collectionActions); - invokeFlushOperation(context, view, entity, value); + value = replaceWithRecordingCollection(context, view, value, collectionActions); + invokeFlushOperation(context, null, view, entity, value); return true; } if (collectionUpdatable) { @@ -339,7 +567,7 @@ public boolean flushEntity(UpdateContext context, E entity, Object view, V value Map added; Map removed; if (recordingCollection.hasActions()) { - Map[] addedAndRemoved = getAddedAndRemovedElements(recordingCollection, context); + Map[] addedAndRemoved = getAddedAndRemovedElementsForInverseFlusher(recordingCollection, context); added = addedAndRemoved[0]; removed = addedAndRemoved[1]; } else { @@ -395,11 +623,7 @@ public boolean flushEntity(UpdateContext context, E entity, Object view, V value } } } else { - actions = new ArrayList<>(); - actions.add(new CollectionClearAction()); - if (value != null && !value.isEmpty()) { - actions.add(new CollectionAddAllAction(value, collectionInstantiator.allowsDuplicates())); - } + actions = replaceActions(value); value = replaceWithRecordingCollection(context, view, value, actions); if (fetch) { @@ -444,22 +668,31 @@ public boolean flushEntity(UpdateContext context, E entity, Object view, V value } if (replace) { - replaceCollection(context, entity, value); + replaceCollection(context, null, view, entity, value); return true; } return wasDirty; } else if (elementDescriptor.shouldFlushMutations()) { if (value != null && !value.isEmpty()) { - return mergeCollectionElements(context, view, entity, value); + return mergeCollectionElements(context, null, view, entity, value); } return false; } else { // Only pass through is possible here - replaceCollection(context, entity, value); + replaceCollection(context, null, view, entity, value); return true; } } + protected List>> replaceActions(V value) { + List>> actions = new ArrayList<>(); + actions.add(new CollectionClearAction()); + if (value != null && !value.isEmpty()) { + actions.add(new CollectionAddAllAction(value, collectionInstantiator.allowsDuplicates())); + } + return actions; + } + @Override public List remove(UpdateContext context, E entity, Object view, V value) { V collection; @@ -517,14 +750,15 @@ public List removeByOwnerId(UpdateContext context, Object id) private List removeByOwnerId(UpdateContext context, Object ownerId, boolean cascade) { EntityViewManagerImpl evm = context.getEntityViewManager(); + String mapping = getMapping(); if (cascade) { List elementIds; if (inverseFlusher == null) { // If there is no inverseFlusher/mapped by attribute, the collection has a join table if (evm.getDbmsDialect().supportsReturningColumns()) { - List tuples = evm.getCriteriaBuilderFactory().deleteCollection(context.getEntityManager(), ownerEntityClass, "e", attributeName) + List tuples = evm.getCriteriaBuilderFactory().deleteCollection(context.getEntityManager(), ownerEntityClass, "e", mapping) .where(ownerIdAttributeName).eq(ownerId) - .executeWithReturning(attributeName + "." + elementDescriptor.getEntityIdAttributeName()) + .executeWithReturning(mapping + "." + elementDescriptor.getEntityIdAttributeName()) .getResultList(); elementIds = new ArrayList<>(tuples.size()); @@ -534,11 +768,11 @@ private List removeByOwnerId(UpdateContext context, Object own } else { elementIds = (List) evm.getCriteriaBuilderFactory().create(context.getEntityManager(), ownerEntityClass, "e") .where(ownerIdAttributeName).eq(ownerId) - .select("e." + attributeName + "." + elementDescriptor.getEntityIdAttributeName()) + .select("e." + mapping + "." + elementDescriptor.getEntityIdAttributeName()) .getResultList(); if (!elementIds.isEmpty() && !jpaProviderDeletesCollection) { // We must always delete this, otherwise we might get a constraint violation because of the cascading delete - DeleteCriteriaBuilder cb = evm.getCriteriaBuilderFactory().deleteCollection(context.getEntityManager(), ownerEntityClass, "e", attributeName); + DeleteCriteriaBuilder cb = evm.getCriteriaBuilderFactory().deleteCollection(context.getEntityManager(), ownerEntityClass, "e", mapping); cb.where(ownerIdAttributeName).eq(ownerId); cb.executeUpdate(); } @@ -550,8 +784,8 @@ private List removeByOwnerId(UpdateContext context, Object own } } else if (!jpaProviderDeletesCollection) { // delete from Entity(collectionRole) e where e.id = :id - DeleteCriteriaBuilder cb = evm.getCriteriaBuilderFactory().deleteCollection(context.getEntityManager(), ownerEntityClass, "e", attributeName); - cb.where("e." + ownerIdAttributeName).eq(ownerId); + DeleteCriteriaBuilder cb = evm.getCriteriaBuilderFactory().deleteCollection(context.getEntityManager(), ownerEntityClass, "e", mapping); + cb.where(ownerIdAttributeName).eq(ownerId); cb.executeUpdate(); } @@ -593,7 +827,7 @@ public boolean isViewOnlyDeleteCascaded() { } @Override - protected boolean mergeCollectionElements(UpdateContext context, Object view, E entity, V value) { + protected boolean mergeCollectionElements(UpdateContext context, Object ownerView, Object view, E entity, V value) { if (elementFlushers != null) { if (flushStrategy == FlushStrategy.ENTITY || !supportsQueryFlush()) { for (CollectionElementAttributeFlusher elementFlusher : elementFlushers) { @@ -601,7 +835,7 @@ protected boolean mergeCollectionElements(UpdateContext context, Object view, E } } else { for (CollectionElementAttributeFlusher elementFlusher : elementFlushers) { - elementFlusher.flushQuery(context, null, null, view, value, null); + elementFlusher.flushQuery(context, null, null, ownerView, view, value, null); } } return !elementFlushers.isEmpty(); @@ -657,37 +891,59 @@ protected boolean mergeAndRequeue(UpdateContext context, RecordingCollection rec return true; } - private void flushCollectionViewElements(UpdateContext context, V value) { + private List flushCollectionViewElements(UpdateContext context, V value) { final ViewToEntityMapper viewToEntityMapper = elementDescriptor.getViewToEntityMapper(); final Iterator iter = getRecordingIterator(value); + List embeddables = new ArrayList<>(); try { while (iter.hasNext()) { Object elem = iter.next(); - viewToEntityMapper.applyToEntity(context, null, elem); + Object embeddable = viewToEntityMapper.applyToEntity(context, null, elem); + if (!elementDescriptor.isIdentifiable() && mapping != null) { + // Only query flushing of an element collection can bring us here + embeddables.add(embeddable); + } } } finally { resetRecordingIterator(value); } + return embeddables; } @Override - protected void replaceCollection(UpdateContext context, E entity, V value) { - if (entityAttributeMapper != null) { - if (elementDescriptor.isSubview()) { - Collection newCollection = (Collection) createJpaCollection(value.size()); - final ViewToEntityMapper viewToEntityMapper = elementDescriptor.getViewToEntityMapper(); - final Iterator iter = getRecordingIterator(value); - try { - while (iter.hasNext()) { - Object elem = iter.next(); - newCollection.add(viewToEntityMapper.applyToEntity(context, null, elem)); + protected void replaceCollection(UpdateContext context, Object ownerView, Object view, E entity, V value) { + if (flushStrategy == FlushStrategy.QUERY) { + Collection removedAllObjects; + if (deleteElements(context, ownerView, view, value, false, null)) { + // TODO: We should load the initial value + removedAllObjects = Collections.emptyList(); + } else { + removedAllObjects = Collections.emptyList(); + } + addElements(context, ownerView, view, removedAllObjects, true, value, null, null); + if (removeListener != null) { + for (Object removedObject : removedAllObjects) { + removeListener.onCollectionRemove(context, removedObject); + } + } + } else { + if (entityAttributeMapper != null) { + if (elementDescriptor.isSubview()) { + Collection newCollection = (Collection) createJpaCollection(value.size()); + final ViewToEntityMapper viewToEntityMapper = elementDescriptor.getViewToEntityMapper(); + final Iterator iter = getRecordingIterator(value); + try { + while (iter.hasNext()) { + Object elem = iter.next(); + newCollection.add(viewToEntityMapper.applyToEntity(context, null, elem)); + } + } finally { + resetRecordingIterator(value); } - } finally { - resetRecordingIterator(value); + entityAttributeMapper.setValue(entity, newCollection); + } else { + entityAttributeMapper.setValue(entity, value); } - entityAttributeMapper.setValue(entity, newCollection); - } else { - entityAttributeMapper.setValue(entity, value); } } } @@ -844,7 +1100,7 @@ public DirtyAttributeFlusher, E, V> getDirtyFlu Map removed = new IdentityHashMap<>(); if (initial != null) { for (Object o : (Collection) initial) { - removed.put(o, o); + removed.put(o, (Object) o); } } @@ -864,7 +1120,7 @@ public DirtyAttributeFlusher, E, V> getDirtyFlu Map added = new IdentityHashMap<>(); Map removed = Collections.emptyMap(); for (Object o : (Collection) current) { - added.put(o, o); + added.put(o, (Object) o); } List> elementFlushers = getInverseElementFlushersForActions(context, (Collection) current, added, removed); @@ -872,16 +1128,53 @@ public DirtyAttributeFlusher, E, V> getDirtyFlu } return partialFlusher(false, PluralFlushOperation.COLLECTION_REPLACE_ONLY, Collections.EMPTY_LIST, Collections.>emptyList()); } + if (initial instanceof RecordingCollection) { + initial = ((RecordingCollection) initial).getInitialVersion(); + } + // If the elements are mutable, replacing the collection and merging elements might lead to N+1 queries // Since collections rarely change drastically, loading the old collection it is probably a good way to avoid many queries if (elementDescriptor.shouldFlushMutations()) { if (elementDescriptor.supportsDirtyCheck()) { // Check elements for dirtyness return determineDirtyFlusherForNewCollection(context, (V) initial, (V) current); - } else if (elementDescriptor.supportsDeepEqualityCheck() || elementDescriptor.isJpaEntity()) { - // If we can determine equality, we fetch and merge the elements - // We also fetch if we have entities since we assume collection rarely change drastically - return this; + } else if (elementDescriptor.supportsDeepEqualityCheck()) { + if (canFlushSeparateCollectionOperations() && elementDescriptor.getBasicUserType() != null && elementDescriptor.getBasicUserType().supportsDeepCloning()) { + // If we can determine equality, we fetch and merge or replace the elements + EqualityChecker equalityChecker; + if (elementDescriptor.isSubview()) { + equalityChecker = EqualsEqualityChecker.INSTANCE; + } else { + equalityChecker = new IdentityEqualityChecker(elementDescriptor.getBasicUserType()); + } + List>> actions; + if (initial == null) { + actions = replaceActions((V) current); + } else { + actions = determineCollectionActions(context, (V) initial, (V) current, equalityChecker); + } + if (actions.isEmpty()) { + return null; + } + return partialFlusher(true, PluralFlushOperation.COLLECTION_REPLAY_ONLY, actions, Collections.>emptyList()); + } else { + return this; + } + } else if (elementDescriptor.isJpaEntity()) { + // When we have a JPA entity, we fetch everything + EqualityChecker equalityChecker; + if (elementDescriptor.isSubview()) { + equalityChecker = EqualsEqualityChecker.INSTANCE; + } else { + equalityChecker = new IdentityEqualityChecker(elementDescriptor.getBasicUserType()); + } + List>> actions; + if (initial == null) { + actions = replaceActions((V) current); + } else { + actions = determineCollectionActions(context, (V) initial, (V) current, equalityChecker); + } + return partialFlusher(true, PluralFlushOperation.COLLECTION_REPLAY_AND_ELEMENT, actions, getElementFlushers(context, (V) current, actions)); } else { // Always reset the actions as that indicates changes if (current instanceof RecordingCollection) { @@ -898,6 +1191,10 @@ public DirtyAttributeFlusher, E, V> getDirtyFlu if (initial == null || !(initial instanceof RecordingCollection) && ((Collection) initial).isEmpty()) { return null; } + // Skip doing anything if the collections kept being empty + if (current instanceof RecordingCollection && !((RecordingCollection) current).hasActions() && ((RecordingCollection) current).isEmpty()) { + return null; + } if (elementDescriptor.shouldFlushMutations()) { if (elementDescriptor.supportsDirtyCheck()) { if (current instanceof RecordingCollection) { @@ -909,12 +1206,14 @@ public DirtyAttributeFlusher, E, V> getDirtyFlu } else if (elementDescriptor.supportsDeepEqualityCheck() || elementDescriptor.isJpaEntity()) { // If we can determine equality, we fetch and merge the elements // We also fetch if we have entities since we assume collection rarely change drastically - if (current instanceof RecordingCollection && !((RecordingCollection) current).hasActions() && ((RecordingCollection) current).isEmpty()) { - // But skip doing anything if the collections kept being empty - return null; - } else { - return this; + if (current instanceof RecordingCollection) { + List> actions = ((RecordingCollection) current).resetActions(context); + if (elementDescriptor.isIdentifiable()) { + return partialFlusher(true, PluralFlushOperation.COLLECTION_REPLAY_AND_ELEMENT, actions, getElementFlushers(context, (V) current, actions)); + } } + + return this; } else { // Always reset the actions as that indicates changes if (current instanceof RecordingCollection) { @@ -975,9 +1274,9 @@ protected DirtyAttributeFlusher, E, V> determin Map[] addedAndRemoved; // Always reset the actions as that indicates changes if (current instanceof RecordingCollection) { - addedAndRemoved = getAddedAndRemovedElements((RecordingCollection) current, context); + addedAndRemoved = getAddedAndRemovedElementsForInverseFlusher((RecordingCollection) current, context); } else { - addedAndRemoved = getAddedAndRemovedElements(current, collectionActions); + addedAndRemoved = getAddedAndRemovedElementsForInverseFlusher(current, collectionActions); } // Inverse collections must convert collection actions to element flush actions Map added = addedAndRemoved[0]; @@ -1006,7 +1305,7 @@ protected DirtyAttributeFlusher, E, V> determin } // If we determine possible collection actions, we try to apply them, but if not if (elementDescriptor.shouldFlushMutations()) { - List> elementFlushers = getElementFlushers(context, current); + List> elementFlushers = getElementFlushers(context, current, collectionActions); // A "null" element flusher list is given when a fetch and compare is more appropriate if (elementFlushers == null) { return this; @@ -1058,29 +1357,32 @@ protected final List>> determineCollectionActions // We try to find a common prefix and from that on, we infer actions List>> actions = new ArrayList<>(); Object[] objectsToAdd = current.toArray(); - - final AttributeAccessor subviewIdAccessor = elementDescriptor.getViewToEntityMapper().getViewIdAccessor(); - final CollectionRemoveAllAction removeAllAction = new CollectionRemoveAllAction<>(0, collectionInstantiator.allowsDuplicates()); int addSize = objectsToAdd.length; - OUTER: for (Object initialObject : initial) { - Object initialViewId = subviewIdAccessor.getValue(initialObject); - for (int i = 0; i < objectsToAdd.length; i++) { - Object currentObject = objectsToAdd[i]; - if (currentObject != REMOVED_MARKER) { - Object currentViewId = subviewIdAccessor.getValue(currentObject); - if (initialViewId.equals(currentViewId)) { - objectsToAdd[i] = REMOVED_MARKER; - addSize--; - continue OUTER; + if (initial != null && !initial.isEmpty()) { + final AttributeAccessor subviewIdAccessor = elementDescriptor.getViewToEntityMapper().getViewIdAccessor(); + final CollectionRemoveAllAction removeAllAction = new CollectionRemoveAllAction<>(0, collectionInstantiator.allowsDuplicates()); + + OUTER: + for (Object initialObject : initial) { + Object initialViewId = subviewIdAccessor.getValue(initialObject); + for (int i = 0; i < objectsToAdd.length; i++) { + Object currentObject = objectsToAdd[i]; + if (currentObject != REMOVED_MARKER) { + Object currentViewId = subviewIdAccessor.getValue(currentObject); + if (initialViewId.equals(currentViewId)) { + objectsToAdd[i] = REMOVED_MARKER; + addSize--; + continue OUTER; + } } } + removeAllAction.add(initialObject); } - removeAllAction.add(initialObject); - } - if (!removeAllAction.isEmpty()) { - actions.add((CollectionAction>) (CollectionAction) removeAllAction); + if (!removeAllAction.isEmpty()) { + actions.add((CollectionAction>) (CollectionAction) removeAllAction); + } } addAddAction(actions, objectsToAdd, addSize); @@ -1092,25 +1394,29 @@ protected final List>> determineCollectionActions // We try to find a common prefix and from that on, we infer actions List>> actions = new ArrayList<>(); Object[] objectsToAdd = current.toArray(); - final CollectionRemoveAllAction removeAllAction = new CollectionRemoveAllAction<>(0, collectionInstantiator.allowsDuplicates()); int addSize = objectsToAdd.length; - OUTER: for (Object initialObject : initial) { - for (int i = 0; i < objectsToAdd.length; i++) { - Object currentObject = objectsToAdd[i]; - if (currentObject != REMOVED_MARKER) { - if (equalityChecker.isEqual(context, initialObject, currentObject)) { - objectsToAdd[i] = REMOVED_MARKER; - addSize--; - continue OUTER; + if (initial != null && !initial.isEmpty()) { + final CollectionRemoveAllAction removeAllAction = new CollectionRemoveAllAction<>(0, collectionInstantiator.allowsDuplicates()); + + OUTER: + for (Object initialObject : initial) { + for (int i = 0; i < objectsToAdd.length; i++) { + Object currentObject = objectsToAdd[i]; + if (currentObject != REMOVED_MARKER) { + if (equalityChecker.isEqual(context, initialObject, currentObject)) { + objectsToAdd[i] = REMOVED_MARKER; + addSize--; + continue OUTER; + } } } + removeAllAction.add(initialObject); } - removeAllAction.add(initialObject); - } - if (!removeAllAction.isEmpty()) { - actions.add((CollectionAction>) (CollectionAction) removeAllAction); + if (!removeAllAction.isEmpty()) { + actions.add((CollectionAction>) (CollectionAction) removeAllAction); + } } addAddAction(actions, objectsToAdd, addSize); @@ -1141,9 +1447,9 @@ protected List>> determineCollectionActions(Updat } @Override - protected final List> getElementFlushers(UpdateContext context, V current) { + protected List> getElementFlushers(UpdateContext context, V current, List> actions) { List> elementFlushers = new ArrayList<>(); - if (determineElementFlushers(context, elementDescriptor, elementFlushers, current)) { + if (determineElementFlushers(context, elementDescriptor, elementFlushers, current, actions, current)) { return null; } @@ -1316,7 +1622,7 @@ public void onAddedAndUpdatedInverseElement(DirtyAttributeFlusher flush @Override public void onUpdatedInverseElement(DirtyAttributeFlusher flusher, Object element) { new UpdateCollectionElementAttributeFlusher<>(flusher, element, optimisticLockProtected, elementDescriptor.getViewToEntityMapper()) - .flushQuery(context, null, null, null, null, null); + .flushQuery(context, null, null, null, null, null, null); } @Override @@ -1381,13 +1687,15 @@ private void visitInverseElementFlushersForActions(UpdateContext context, Iterab listener.onUpdatedInverseElement(flusher, element); } } else if (elementDescriptor.shouldJpaMerge()) { - // Although we can't replace the original object in the backing collection, we don't care in case of inverse collections - CollectionElementAttributeFlusher flusher = new MergeCollectionElementAttributeFlusher<>(element, optimisticLockProtected); - Object addedElement = added.remove(element); - if (addedElement != null) { - listener.onAddedAndUpdatedInverseElement(flusher, element); - } else { - listener.onUpdatedInverseElement(flusher, element); + if (element != null) { + // Although we can't replace the original object in the backing collection, we don't care in case of inverse collections + CollectionElementAttributeFlusher flusher = new MergeCollectionElementAttributeFlusher<>(element, optimisticLockProtected); + Object addedElement = added.remove(element); + if (addedElement != null) { + listener.onAddedAndUpdatedInverseElement(flusher, element); + } else { + listener.onUpdatedInverseElement(flusher, element); + } } } else { Object addedElement = added.remove(element); @@ -1404,6 +1712,7 @@ private void visitInverseElementFlushersForActions(UpdateContext context, Iterab @Override protected DirtyAttributeFlusher, E, V> getDirtyFlusherForRecordingCollection(UpdateContext context, V initial, RecordingCollection collection) { if (collection.hasActions()) { + List> actions = collection.resetActions(context); boolean queueable = areActionsQueueable(collection); if (queueable) { @@ -1414,29 +1723,29 @@ protected DirtyAttributeFlusher, E, V> getDirty } // Check elements for dirtyness @SuppressWarnings("unchecked") - List> elementFlushers = getElementFlushers(context, (V) collection); + List> elementFlushers = getElementFlushers(context, (V) collection, actions); // A "null" element flusher list is given when a fetch and compare is more appropriate if (elementFlushers == null) { return this; } - int actionUnrelatedDirtyCount = getActionUnrelatedDirtyObjectCount(initial, elementFlushers, collection.getActions()); + int actionUnrelatedDirtyCount = getActionUnrelatedDirtyObjectCount(initial, elementFlushers, actions); // At some point we might want to consider a threshold here instead if (actionUnrelatedDirtyCount == 0) { // If the dirty objects are the ones which are added/removed via collection actions, we don't need to load the collection - return partialFlusher(false, PluralFlushOperation.COLLECTION_REPLAY_AND_ELEMENT, collection.resetActions(context), elementFlushers); + return partialFlusher(false, PluralFlushOperation.COLLECTION_REPLAY_AND_ELEMENT, actions, elementFlushers); } else { // If some objects are dirty that previously existed in the collection, we should load the collection - return partialFlusher(true, PluralFlushOperation.COLLECTION_REPLAY_AND_ELEMENT, collection.resetActions(context), elementFlushers); + return partialFlusher(true, PluralFlushOperation.COLLECTION_REPLAY_AND_ELEMENT, actions, elementFlushers); } } else { // If the operations are queueable and elements should not receive update cascades, we don't need to load the collection - return partialFlusher(false, PluralFlushOperation.COLLECTION_REPLAY_ONLY, collection.resetActions(context), Collections.>emptyList()); + return partialFlusher(false, PluralFlushOperation.COLLECTION_REPLAY_ONLY, actions, Collections.>emptyList()); } } else if (inverseFlusher != null) { // Inverse collections must convert collection actions to element flush actions - Map[] addedAndRemoved = getAddedAndRemovedElements(collection, context); + Map[] addedAndRemoved = getAddedAndRemovedElementsForInverseFlusher(actions); Map added = addedAndRemoved[0]; Map removed = addedAndRemoved[1]; List> elementFlushers = getInverseElementFlushersForActions(context, collection, added, removed); @@ -1448,14 +1757,14 @@ protected DirtyAttributeFlusher, E, V> getDirty if (elementDescriptor.isBasic()) { return this; } - List> elementFlushers = getElementFlushers(context, (V) collection); + List> elementFlushers = getElementFlushers(context, (V) collection, actions); // A "null" element flusher list is given when a fetch and compare is more appropriate if (elementFlushers == null) { return this; } - return getReplayAndElementFlusher(context, initial, (V) collection, collection.resetActions(context), elementFlushers); + return getReplayAndElementFlusher(context, initial, (V) collection, actions, elementFlushers); } else { - return getReplayOnlyFlusher(context, initial, (V) collection, collection.resetActions(context)); + return getReplayOnlyFlusher(context, initial, (V) collection, actions); } } } @@ -1474,8 +1783,40 @@ protected DirtyAttributeFlusher, E, V> getDirty } @SuppressWarnings("unchecked") - private Map[] getAddedAndRemovedElements(RecordingCollection collection, UpdateContext context) { + protected FusedCollectionActions getFusedOperations(List> collectionActions) { + if (collectionInstantiator.allowsDuplicates()) { + // Don't support bags + return null; + } + + Map added = new IdentityHashMap<>(); + Map removed = new IdentityHashMap<>(); + for (CollectionAction> a : collectionActions) { + Collection addedObjects = a.getAddedObjects(); + Collection removedObjects = a.getRemovedObjects(); + + for (Object addedObject : addedObjects) { + removed.remove(addedObject); + } + for (Object removedObject : removedObjects) { + added.remove(removedObject); + removed.put(removedObject, removedObject); + } + for (Object addedObject : addedObjects) { + added.put(addedObject, addedObject); + } + } + return new FusedCollectionElementActions(elementDescriptor.getViewToEntityMapper() == null ? null : elementDescriptor.getLoadOnlyViewToEntityMapper(), removed, added); + } + + @SuppressWarnings("unchecked") + private Map[] getAddedAndRemovedElementsForInverseFlusher(RecordingCollection collection, UpdateContext context) { List> collectionActions = collection.resetActions(context); + return getAddedAndRemovedElementsForInverseFlusher(collectionActions); + } + + @SuppressWarnings("unchecked") + private Map[] getAddedAndRemovedElementsForInverseFlusher(List> collectionActions) { Map added = new IdentityHashMap<>(); Map removed = new IdentityHashMap<>(); for (CollectionAction> a : collectionActions) { @@ -1497,7 +1838,7 @@ private Map[] getAddedAndRemovedElements(RecordingCollection[] getAddedAndRemovedElements(Collection collection, List>> collectionActions) { + private Map[] getAddedAndRemovedElementsForInverseFlusher(Collection collection, List>> collectionActions) { Map added = new IdentityHashMap<>(); Map removed = new IdentityHashMap<>(); for (CollectionAction> a : collectionActions) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionElementAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionElementAttributeFlusher.java index 549c45ab0c..4a554f763f 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionElementAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CollectionElementAttributeFlusher.java @@ -56,8 +56,8 @@ public Object getNewInitialValue(UpdateContext context, V clonedValue, V current } @Override - public void appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { - nestedGraphNode.appendUpdateQueryFragment(context, sb, mappingPrefix, parameterPrefix, separator); + public boolean appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { + return nestedGraphNode.appendUpdateQueryFragment(context, sb, mappingPrefix, parameterPrefix, separator); } @Override @@ -71,8 +71,8 @@ public boolean loadForEntityFlush() { } @Override - public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { - nestedGraphNode.flushQuery(context, parameterPrefix, null, null, (V) element, ownerAwareDeleter); + public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object ownerView, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { + nestedGraphNode.flushQuery(context, parameterPrefix, null, ownerView, null, (V) element, ownerAwareDeleter); } @Override diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CompositeAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CompositeAttributeFlusher.java index ba8dbd28f8..6e1550e595 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CompositeAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/CompositeAttributeFlusher.java @@ -273,21 +273,19 @@ public Object getNewInitialValue(UpdateContext context, Object clonedValue, Obje } @Override - public void appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { + public boolean appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { int clauseEndIndex = sb.length(); - if (optimisticLockProtected && versionFlusher != null) { - versionFlusher.appendUpdateQueryFragment(context, sb, mappingPrefix, parameterPrefix, separator); - // If something was appended, we also append a comma - if (clauseEndIndex != sb.length()) { - clauseEndIndex = sb.length(); - sb.append(separator); - } - } + boolean wasDirty = false; + boolean optimisticLock = false; for (int i = 0; i < flushers.length; i++) { - if (flushers[i] != null) { + DirtyAttributeFlusher flusher = flushers[i]; + if (flusher != null) { int endIndex = sb.length(); - flushers[i].appendUpdateQueryFragment(context, sb, mappingPrefix, parameterPrefix, separator); + if (flusher.appendUpdateQueryFragment(context, sb, mappingPrefix, parameterPrefix, separator)) { + wasDirty = true; + optimisticLock |= flusher.isOptimisticLockProtected(); + } // If something was appended, we also append a comma if (endIndex != sb.length()) { @@ -296,11 +294,20 @@ public void appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, S } } } + if (optimisticLock && optimisticLockProtected && versionFlusher != null) { + versionFlusher.appendUpdateQueryFragment(context, sb, mappingPrefix, parameterPrefix, separator); + // If something was appended, we also append a comma + if (clauseEndIndex != sb.length()) { + clauseEndIndex = sb.length(); + sb.append(separator); + } + } if (clauseEndIndex + separator.length() == sb.length()) { // Remove the last comma sb.setLength(clauseEndIndex); } + return wasDirty; } @Override @@ -314,7 +321,7 @@ public boolean loadForEntityFlush() { } @Override - public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object view, Object value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { + public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object ownerView, Object view, Object value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { if (element != null) { value = element; } else if (value == null) { @@ -337,7 +344,7 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer } deferredFlushers.add(i); } else { - flusher.flushQuery(context, parameterPrefix, query, view, null, ownerAwareDeleter == null ? null : ownerAwareDeleter.getSubDeleter(flusher)); + flusher.flushQuery(context, parameterPrefix, query, ownerView, view, null, ownerAwareDeleter == null ? null : ownerAwareDeleter.getSubDeleter(flusher)); } } } @@ -345,7 +352,7 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer for (int i = 0; i < deferredFlushers.size(); i++) { final int index = deferredFlushers.get(i); final DirtyAttributeFlusher flusher = flushers[index]; - flusher.flushQuery(context, parameterPrefix, query, view, null, ownerAwareDeleter == null ? null : ownerAwareDeleter.getSubDeleter(flusher)); + flusher.flushQuery(context, parameterPrefix, query, ownerView, view, null, ownerAwareDeleter == null ? null : ownerAwareDeleter.getSubDeleter(flusher)); } } @@ -359,7 +366,7 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer for (int i = 0; i < flushers.length; i++) { DirtyAttributeFlusher flusher = flushers[i]; if (flusher != null) { - flusher.flushQuery(context, parameterPrefix, query, view, flusher.getViewAttributeAccessor().getValue(value), ownerAwareDeleter); + flusher.flushQuery(context, parameterPrefix, query, ownerView, view, flusher.getViewAttributeAccessor().getValue(value), ownerAwareDeleter); } } return; @@ -379,13 +386,9 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer return; } - if (optimisticLockProtected && versionFlusher != null) { - context.getInitialStateResetter().addVersionedView(element, element.$$_getVersion()); - versionFlusher.flushQuery(context, parameterPrefix, query, value, element.$$_getVersion(), null); - } - Object[] state = element.$$_getMutableState(); List deferredFlushers = null; + boolean optimisticLock = false; if (value instanceof DirtyStateTrackable) { Object[] initialState = ((DirtyStateTrackable) value).$$_getInitialState(); @@ -394,6 +397,7 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer for (int i = 0; i < state.length; i++) { DirtyAttributeFlusher flusher = flushers[i]; if (flusher != null) { + optimisticLock |= flusher.isOptimisticLockProtected(); if (flusher.requiresFlushAfterPersist(state[i])) { if (deferredFlushers == null) { deferredFlushers = new ArrayList<>(); @@ -401,7 +405,7 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer deferredFlushers.add(i); } else { Object newInitialValue = flusher.cloneDeep(value, initialState[i], state[i]); - flusher.flushQuery(context, parameterPrefix, query, value, state[i], unmappedOwnerAwareCascadeDeleters == null ? null : unmappedOwnerAwareCascadeDeleters[i]); + flusher.flushQuery(context, parameterPrefix, query, ownerView, value, state[i], unmappedOwnerAwareCascadeDeleters == null ? null : unmappedOwnerAwareCascadeDeleters[i]); initialState[i] = flusher.getNewInitialValue(context, newInitialValue, state[i]); } } @@ -410,13 +414,14 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer for (int i = 0; i < state.length; i++) { DirtyAttributeFlusher flusher = flushers[i]; if (flusher != null) { + optimisticLock |= flusher.isOptimisticLockProtected(); if (flusher.requiresFlushAfterPersist(state[i])) { if (deferredFlushers == null) { deferredFlushers = new ArrayList<>(); } deferredFlushers.add(i); } else { - flusher.flushQuery(context, parameterPrefix, query, value, state[i], unmappedOwnerAwareCascadeDeleters == null ? null : unmappedOwnerAwareCascadeDeleters[i]); + flusher.flushQuery(context, parameterPrefix, query, ownerView, value, state[i], unmappedOwnerAwareCascadeDeleters == null ? null : unmappedOwnerAwareCascadeDeleters[i]); } } } @@ -424,11 +429,18 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer // Pass through flushers for (int i = state.length; i < flushers.length; i++) { - if (flushers[i] != null) { - flushers[i].flushQuery(context, parameterPrefix, query, value, flushers[i].getViewAttributeAccessor().getValue(value), unmappedOwnerAwareCascadeDeleters == null ? null : unmappedOwnerAwareCascadeDeleters[i]); + DirtyAttributeFlusher flusher = flushers[i]; + if (flusher != null) { + optimisticLock |= flusher.isOptimisticLockProtected(); + flusher.flushQuery(context, parameterPrefix, query, ownerView, value, flusher.getViewAttributeAccessor().getValue(value), unmappedOwnerAwareCascadeDeleters == null ? null : unmappedOwnerAwareCascadeDeleters[i]); } } + if (optimisticLock && optimisticLockProtected && versionFlusher != null) { + context.getInitialStateResetter().addVersionedView(element, element.$$_getVersion()); + versionFlusher.flushQuery(context, parameterPrefix, query, ownerView, value, element.$$_getVersion(), null); + } + if (deferredFlushers != null) { if (value instanceof DirtyStateTrackable) { Object[] initialState = ((DirtyStateTrackable) value).$$_getInitialState(); @@ -436,14 +448,14 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer final int index = deferredFlushers.get(i); final DirtyAttributeFlusher flusher = flushers[index]; Object newInitialValue = flusher.cloneDeep(value, initialState[index], state[index]); - flusher.flushQuery(context, parameterPrefix, query, value, state[index], unmappedOwnerAwareCascadeDeleters == null ? null : unmappedOwnerAwareCascadeDeleters[index]); + flusher.flushQuery(context, parameterPrefix, query, ownerView, value, state[index], unmappedOwnerAwareCascadeDeleters == null ? null : unmappedOwnerAwareCascadeDeleters[index]); initialState[index] = flusher.getNewInitialValue(context, newInitialValue, state[index]); } } else { for (int i = 0; i < deferredFlushers.size(); i++) { final int index = deferredFlushers.get(i); final DirtyAttributeFlusher flusher = flushers[index]; - flusher.flushQuery(context, parameterPrefix, query, value, state[index], unmappedOwnerAwareCascadeDeleters == null ? null : unmappedOwnerAwareCascadeDeleters[index]); + flusher.flushQuery(context, parameterPrefix, query, ownerView, value, state[index], unmappedOwnerAwareCascadeDeleters == null ? null : unmappedOwnerAwareCascadeDeleters[index]); } } } @@ -745,7 +757,7 @@ public List remove(UpdateContext context, Object entity, Objec } } - remove(context, entity, updatableProxy, updatableProxy.$$_getId(), updatableProxy.$$_getVersion(), false); + remove(context, entity, updatableProxy, updatableProxy, updatableProxy.$$_getId(), updatableProxy.$$_getVersion(), false); for (PostFlushDeleter postFlushDeleter : postFlushDeleters) { postFlushDeleter.execute(context); @@ -760,7 +772,7 @@ public List remove(UpdateContext context, Object entity, Objec } } else { if (context.addRemovedObject(entityView)) { - remove(context, entity, entityView, entityView.$$_getId(), entityView.$$_getVersion(), true); + remove(context, entity, entityView, entityView, entityView.$$_getId(), entityView.$$_getVersion(), true); } } return Collections.emptyList(); @@ -768,7 +780,7 @@ public List remove(UpdateContext context, Object entity, Objec @Override public void remove(UpdateContext context, Object viewId) { - remove(context, null, null, viewId, null, true); + remove(context, null, null, null, viewId, null, true); } @Override @@ -777,7 +789,7 @@ public void removeFromEntity(UpdateContext context, Object entity) { throw new UnsupportedOperationException(); } - private void remove(UpdateContext context, Object entity, Object view, Object viewId, Object version, boolean cascadeMappedDeletes) { + private void remove(UpdateContext context, Object entity, Object ownerView, Object view, Object viewId, Object version, boolean cascadeMappedDeletes) { if (flushStrategy == FlushStrategy.ENTITY) { if (entity == null) { entity = referenceEntityLoader.toEntity(context, viewId); @@ -887,7 +899,7 @@ private void remove(UpdateContext context, Object entity, Object view, Object vi if (doDelete) { if (version != null && versionFlusher != null) { Query query = context.getEntityManager().createQuery(versionedDeleteQuery); - idFlusher.flushQuery(context, EntityViewUpdaterImpl.WHERE_CLAUSE_PREFIX, query, view, viewId, null); + idFlusher.flushQuery(context, EntityViewUpdaterImpl.WHERE_CLAUSE_PREFIX, query, ownerView, view, viewId, null); versionFlusher.flushQueryInitialVersion(context, EntityViewUpdaterImpl.WHERE_CLAUSE_PREFIX, query, view, version); int updated = query.executeUpdate(); if (updated != 1) { @@ -895,7 +907,7 @@ private void remove(UpdateContext context, Object entity, Object view, Object vi } } else { Query query = context.getEntityManager().createQuery(deleteQuery); - idFlusher.flushQuery(context, EntityViewUpdaterImpl.WHERE_CLAUSE_PREFIX, query, view, viewId, null); + idFlusher.flushQuery(context, EntityViewUpdaterImpl.WHERE_CLAUSE_PREFIX, query, ownerView, view, viewId, null); query.executeUpdate(); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/DirtyAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/DirtyAttributeFlusher.java index b6492f610a..0c064cca93 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/DirtyAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/DirtyAttributeFlusher.java @@ -32,7 +32,7 @@ public interface DirtyAttributeFlusher, public DirtyAttributeFlusher getDirtyFlusher(UpdateContext context, Object view, Object initial, Object current); - public void appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator); + public boolean appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator); public void appendFetchJoinQueryFragment(String base, StringBuilder sb); @@ -42,7 +42,7 @@ public interface DirtyAttributeFlusher, public Object getNewInitialValue(UpdateContext context, V clonedValue, V currentValue); - public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter); + public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object ownerView, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter); public boolean flushEntity(UpdateContext context, E entity, Object view, V value, Runnable postReplaceListener); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/EmbeddableAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/EmbeddableAttributeFlusher.java index bdc326386c..cc04491b7c 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/EmbeddableAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/EmbeddableAttributeFlusher.java @@ -98,7 +98,7 @@ public boolean loadForEntityFlush() { } @Override - public void appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { + public boolean appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { String mapping; String parameter; if (mappingPrefix == null) { @@ -112,13 +112,14 @@ public void appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, S sb.append(mapping); sb.append(" = :"); sb.append(parameter); + return true; } else { - nestedGraphNode.appendUpdateQueryFragment(context, sb, mapping, parameter, separator); + return nestedGraphNode.appendUpdateQueryFragment(context, sb, mapping, parameter, separator); } } @Override - public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { + public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object ownerView, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { try { String parameter; if (parameterPrefix == null) { @@ -129,7 +130,7 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer if (supportsQueryFlush) { query.setParameter(parameter, viewToEntityMapper.applyToEntity(context, null, value)); } else { - nestedGraphNode.flushQuery(context, parameter, query, view, value, ownerAwareDeleter); + nestedGraphNode.flushQuery(context, parameter, query, ownerView, view, value, ownerAwareDeleter); } } finally { if (value instanceof MutableStateTrackable) { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionActions.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionActions.java new file mode 100644 index 0000000000..984b650238 --- /dev/null +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionActions.java @@ -0,0 +1,45 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.view.impl.update.flush; + +import com.blazebit.persistence.view.impl.update.UpdateContext; + +import java.util.Collection; + +/** + * + * @author Christian Beikov + * @since 1.3.0 + */ +public interface FusedCollectionActions { + + public int operationCount(); + + public int getRemoveCount(); + + public int getAddCount(); + + public int getUpdateCount(); + + public Collection getRemoved(); + + public Collection getRemoved(UpdateContext context); + + public Collection getAdded(); + + public Collection getAdded(UpdateContext context); +} diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionElementActions.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionElementActions.java new file mode 100644 index 0000000000..ae5a28148d --- /dev/null +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionElementActions.java @@ -0,0 +1,99 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.view.impl.update.flush; + +import com.blazebit.persistence.view.impl.entity.ViewToEntityMapper; +import com.blazebit.persistence.view.impl.update.UpdateContext; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * + * @author Christian Beikov + * @since 1.3.0 + */ +public class FusedCollectionElementActions implements FusedCollectionActions { + + private final ViewToEntityMapper loadOnlyViewToEntityMapper; + private final Map removed; + private final Map added; + + public FusedCollectionElementActions(ViewToEntityMapper loadOnlyViewToEntityMapper, Map removed, Map added) { + this.loadOnlyViewToEntityMapper = loadOnlyViewToEntityMapper; + this.removed = removed; + this.added = added; + } + + @Override + public int operationCount() { + return removed.size() + added.size(); + } + + @Override + public int getRemoveCount() { + return removed.size(); + } + + @Override + public int getAddCount() { + return added.size(); + } + + @Override + public int getUpdateCount() { + return 0; + } + + @Override + public Collection getAdded() { + return added.keySet(); + } + + @Override + public Collection getAdded(UpdateContext context) { + if (loadOnlyViewToEntityMapper == null) { + return added.keySet(); + } else { + return getEntityReferencesForCollectionOperation(context, added.keySet()); + } + } + + public Collection getRemoved() { + return removed.keySet(); + } + + public Collection getRemoved(UpdateContext context) { + if (loadOnlyViewToEntityMapper == null) { + return removed.keySet(); + } else { + return getEntityReferencesForCollectionOperation(context, removed.keySet()); + } + } + + private List getEntityReferencesForCollectionOperation(UpdateContext context, Collection objects) { + List entityReferences = new ArrayList<>(objects.size()); + for (Object o : objects) { + if (o != null) { + entityReferences.add(loadOnlyViewToEntityMapper.applyToEntity(context, null, o)); + } + } + return entityReferences; + } +} diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionIndexActions.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionIndexActions.java new file mode 100644 index 0000000000..2276394b4d --- /dev/null +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionIndexActions.java @@ -0,0 +1,348 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.view.impl.update.flush; + +import com.blazebit.persistence.view.impl.collection.ListAction; +import com.blazebit.persistence.view.impl.update.UpdateContext; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * + * @author Christian Beikov + * @since 1.3.0 + */ +public class FusedCollectionIndexActions implements FusedCollectionActions { + + private final List removeRangeOperations; + private final List indexTranslateOperations; + private final List replaceOperations; + private final List appendedObjects; + private final int appendIndex; + private final int removeCount; + private final int addCount; + private final int updateCount; + + public FusedCollectionIndexActions(List> collectionActions) { + List translateOperations = new ArrayList<>(); + List replaceOperations = new ArrayList<>(); + List appendedObjects = new ArrayList<>(); + int appendIndex = Integer.MAX_VALUE; + for (ListAction collectionAction : collectionActions) { + List> insertedObjectMap = collectionAction.getInsertedObjectEntries(); + List> appendedObjectMap = collectionAction.getAppendedObjectEntries(); + List> removedObjectMap = collectionAction.getRemovedObjectEntries(); + List> trimmedObjectMap = collectionAction.getTrimmedObjectEntries(); + + for (Map.Entry entry : removedObjectMap) { + int index = applyIndexTranslations(translateOperations, -entry.getValue()); + RemoveOperation removeOperation = new RemoveOperation(index, entry.getKey()); + addTranslateOperation(translateOperations, index, Integer.MAX_VALUE, -1, removeOperation, null); + } + for (Map.Entry entry : trimmedObjectMap) { + int index = applyIndexTranslations(translateOperations, -entry.getValue()); + appendIndex = Math.min(index, appendIndex); + RemoveOperation removeOperation = new RemoveOperation(index, entry.getKey()); + addTranslateOperation(translateOperations, index, Integer.MAX_VALUE, -1, removeOperation, null); + } + for (Map.Entry entry : insertedObjectMap) { + int index = entry.getValue(); + ReplaceOperation replaceOperation = new ReplaceOperation(index, entry.getKey()); + replaceOperations.add(replaceOperation); + addTranslateOperation(translateOperations, index, Integer.MAX_VALUE, 1, null, replaceOperation); + } + for (Map.Entry entry : appendedObjectMap) { + appendIndex = Math.min(entry.getValue(), appendIndex); + appendedObjects.add(entry.getKey()); + } + } + + SortedSet removeOperations = new TreeSet<>(); + if (appendIndex != Integer.MAX_VALUE) { + for (int i = 0; i < translateOperations.size(); i++) { + IndexTranslateOperation indexTranslateOperation = translateOperations.get(i); + removeOperations.addAll(indexTranslateOperation.removeOperations); + if (indexTranslateOperation.endIndex == Integer.MAX_VALUE) { + // If we have a translate operation without removes that starts after the append index, we can ignore it + // Also, we can ignore translate operations that remove a tail fully i.e. do not leave holes + if (indexTranslateOperation.startIndex > appendIndex && indexTranslateOperation.removeOperations.isEmpty() || indexTranslateOperation.startIndex + indexTranslateOperation.removeOperations.size() == appendIndex + 1) { + translateOperations.remove(i); + i--; + } else { + indexTranslateOperation.endIndex = appendIndex; + } + } + } + } else { + for (int i = 0; i < translateOperations.size(); i++) { + IndexTranslateOperation indexTranslateOperation = translateOperations.get(i); + removeOperations.addAll(indexTranslateOperation.removeOperations); + } + } + + List removeRangeOperations = new ArrayList<>(); + Iterator iterator = removeOperations.iterator(); + RemoveRangeOperation lastRangeOp = null; + while (iterator.hasNext()) { + RemoveOperation removeOp = iterator.next(); + if (lastRangeOp == null || lastRangeOp.endIndex != removeOp.index) { + List removedObjects = new ArrayList<>(); + removedObjects.add(removeOp.removedObject); + lastRangeOp = new RemoveRangeOperation(removeOp.index, removeOp.index + 1, removedObjects); + removeRangeOperations.add(lastRangeOp); + } else { + lastRangeOp.endIndex++; + lastRangeOp.removedObjects.add(removeOp.removedObject); + } + } + + int updateCount = translateOperations.size(); + + int addCount = appendedObjects.size(); + for (int i = 0; i < replaceOperations.size(); i++) { + ReplaceOperation replaceOperation = replaceOperations.get(i); + if (replaceOperation.oldObject == null) { + addCount++; + } else { + updateCount++; + } + } + + this.removeRangeOperations = removeRangeOperations; + this.indexTranslateOperations = translateOperations; + this.replaceOperations = replaceOperations; + this.appendedObjects = appendedObjects; + this.appendIndex = appendIndex; + this.removeCount = removeRangeOperations.size(); + this.addCount = addCount; + this.updateCount = updateCount; + } + + private static void addTranslateOperation(List translateOperations, int startIndex, int endIndex, int offset, RemoveOperation removeOperation, ReplaceOperation replaceOperation) { + if (translateOperations.isEmpty()) { + translateOperations.add(new IndexTranslateOperation(startIndex, endIndex, offset, removeOperation == null ? Collections.emptyList() : Collections.singletonList(removeOperation))); + } else { + for (int i = 0; i < translateOperations.size(); i++) { + IndexTranslateOperation indexTranslateOperation = translateOperations.get(i); + if (indexTranslateOperation.startIndex <= startIndex && indexTranslateOperation.endIndex >= endIndex) { + int indexDiff = Math.abs(indexTranslateOperation.startIndex - startIndex); + // TODO: Currently we only handle adds following removes, to handle the other way round we need the replace operation in the translate operation + if (indexDiff == 0 && indexTranslateOperation.offset + offset == 0 && indexTranslateOperation.removeOperations.size() == 1 && replaceOperation != null) { + // A remove followed an insert or the other way round allow to remove the translation operation + translateOperations.remove(i); + replaceOperation.oldObject = indexTranslateOperation.removeOperations.get(0).removedObject; + return; + } else if (indexDiff == 1 && indexTranslateOperation.endIndex == endIndex) { + // Neighbouring index translations with the same endIndex are merged + List newList; + if (removeOperation == null) { + newList = indexTranslateOperation.removeOperations; + } else { + newList = new ArrayList<>(indexTranslateOperation.removeOperations.size() + 1); + newList.addAll(indexTranslateOperation.removeOperations); + newList.add(removeOperation); + } + translateOperations.set(i, new IndexTranslateOperation(Math.min(indexTranslateOperation.startIndex, startIndex), endIndex, indexTranslateOperation.offset + offset, newList)); + return; + } else { + translateOperations.set(i, new IndexTranslateOperation(indexTranslateOperation.startIndex, startIndex, indexTranslateOperation.offset, indexTranslateOperation.removeOperations)); + translateOperations.add(i + 1, new IndexTranslateOperation(startIndex, endIndex, offset, removeOperation == null ? Collections.emptyList() : Collections.singletonList(removeOperation))); + return; + } + } + } + + translateOperations.add(new IndexTranslateOperation(startIndex, endIndex, offset, removeOperation == null ? Collections.emptyList() : Collections.singletonList(removeOperation))); + } + } + + private static int applyIndexTranslations(List indexTranslateOperations, int index) { + int absIndex = Math.abs(index); + for (int i = 0; i < indexTranslateOperations.size(); i++) { + IndexTranslateOperation indexTranslateOperation = indexTranslateOperations.get(i); + if (absIndex >= indexTranslateOperation.startIndex && absIndex <= indexTranslateOperation.endIndex) { + index += indexTranslateOperation.offset; + absIndex = Math.abs(index); + } + } + + return absIndex; + } + + @Override + public int operationCount() { + return addCount + removeCount + updateCount; + } + + @Override + public int getRemoveCount() { + return removeCount; + } + + @Override + public int getAddCount() { + return addCount; + } + + @Override + public int getUpdateCount() { + return updateCount; + } + + @Override + public Collection getRemoved() { + List objects = new ArrayList<>(removeCount); + for (int i = 0; i < removeRangeOperations.size(); i++) { + RemoveRangeOperation removeRangeOperation = removeRangeOperations.get(i); + objects.addAll(removeRangeOperation.removedObjects); + } + return objects; + } + + @Override + public Collection getRemoved(UpdateContext context) { + List indexes = new ArrayList<>(removeCount); + for (int i = 0; i < removeRangeOperations.size(); i++) { + RemoveRangeOperation removeRangeOperation = removeRangeOperations.get(i); + for (int j = removeRangeOperation.startIndex; j < removeRangeOperation.endIndex; j++) { + indexes.add(j); + } + } + return (Collection) (Collection) indexes; + } + + public List getTranslations() { + return indexTranslateOperations; + } + + @Override + public Collection getAdded() { + return appendedObjects; + } + + @Override + public Collection getAdded(UpdateContext context) { + return appendedObjects; + } + + public int getAppendIndex() { + return appendIndex; + } + + public List getReplaces() { + return replaceOperations; + } + + /** + * + * @author Christian Beikov + * @since 1.3.0 + */ + private static class RemoveOperation implements Comparable { + private final int index; + private final Object removedObject; + + public RemoveOperation(int index, Object removedObject) { + this.index = index; + this.removedObject = removedObject; + } + + @Override + public int compareTo(RemoveOperation o) { + return Integer.compare(index, o.index); + } + } + + /** + * + * @author Christian Beikov + * @since 1.3.0 + */ + private static class RemoveRangeOperation { + private int startIndex; + private int endIndex; + private final List removedObjects; + + public RemoveRangeOperation(int startIndex, int endIndex, List removedObjects) { + this.startIndex = startIndex; + this.endIndex = endIndex; + this.removedObjects = removedObjects; + } + } + + /** + * + * @author Christian Beikov + * @since 1.3.0 + */ + public static class IndexTranslateOperation { + private int startIndex; + private int endIndex; + private int offset; + private final List removeOperations; + + public IndexTranslateOperation(int startIndex, int endIndex, int offset, List removeOperations) { + this.startIndex = startIndex; + this.endIndex = endIndex; + this.offset = offset; + this.removeOperations = removeOperations; + } + + public int getStartIndex() { + return startIndex; + } + + public int getEndIndex() { + return endIndex; + } + + public int getOffset() { + return offset; + } + } + + /** + * + * @author Christian Beikov + * @since 1.3.0 + */ + public static class ReplaceOperation { + private final int index; + private final Object newObject; + private Object oldObject; + + public ReplaceOperation(int index, Object newObject) { + this.index = index; + this.newObject = newObject; + } + + public int getIndex() { + return index; + } + + public Object getNewObject() { + return newObject; + } + } +} diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/IndexedListAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/IndexedListAttributeFlusher.java index 2ef6dbb3b7..38f1007b14 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/IndexedListAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/IndexedListAttributeFlusher.java @@ -16,24 +16,34 @@ package com.blazebit.persistence.view.impl.update.flush; +import com.blazebit.persistence.DeleteCriteriaBuilder; +import com.blazebit.persistence.InsertCriteriaBuilder; +import com.blazebit.persistence.UpdateCriteriaBuilder; import com.blazebit.persistence.view.FlushStrategy; import com.blazebit.persistence.view.InverseRemoveStrategy; import com.blazebit.persistence.view.impl.accessor.AttributeAccessor; import com.blazebit.persistence.view.impl.accessor.InitialValueAttributeAccessor; import com.blazebit.persistence.view.impl.collection.CollectionAction; -import com.blazebit.persistence.view.impl.collection.CollectionAddAllAction; +import com.blazebit.persistence.view.impl.collection.CollectionClearAction; import com.blazebit.persistence.view.impl.collection.CollectionInstantiator; +import com.blazebit.persistence.view.impl.collection.ListAction; +import com.blazebit.persistence.view.impl.collection.ListAddAction; +import com.blazebit.persistence.view.impl.collection.ListAddAllAction; import com.blazebit.persistence.view.impl.collection.ListRemoveAction; +import com.blazebit.persistence.view.impl.collection.ListSetAction; import com.blazebit.persistence.view.impl.collection.RecordingCollection; import com.blazebit.persistence.view.impl.collection.RecordingList; import com.blazebit.persistence.view.impl.collection.CollectionRemoveListener; import com.blazebit.persistence.view.impl.entity.ViewToEntityMapper; import com.blazebit.persistence.view.impl.proxy.DirtyStateTrackable; +import com.blazebit.persistence.view.impl.proxy.MutableStateTrackable; import com.blazebit.persistence.view.impl.update.UpdateContext; import com.blazebit.persistence.view.spi.type.BasicUserType; import javax.persistence.EntityManager; +import javax.persistence.Query; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -44,10 +54,13 @@ * @since 1.2.0 */ public class IndexedListAttributeFlusher> extends CollectionAttributeFlusher { + @SuppressWarnings("unchecked") - public IndexedListAttributeFlusher(String attributeName, String mapping, Class ownerEntityClass, String ownerIdAttributeName, FlushStrategy flushStrategy, AttributeAccessor attributeMapper, InitialValueAttributeAccessor viewAttributeAccessor, boolean optimisticLockProtected, boolean collectionUpdatable, - boolean viewOnlyDeleteCascaded, boolean jpaProviderDeletesCollection, CollectionRemoveListener cascadeDeleteListener, CollectionRemoveListener removeListener, CollectionInstantiator collectionInstantiator, TypeDescriptor elementDescriptor, InverseFlusher inverseFlusher, InverseRemoveStrategy inverseRemoveStrategy) { - super(attributeName, mapping, ownerEntityClass, ownerIdAttributeName, flushStrategy, attributeMapper, viewAttributeAccessor, optimisticLockProtected, collectionUpdatable, viewOnlyDeleteCascaded, jpaProviderDeletesCollection, cascadeDeleteListener, removeListener, collectionInstantiator, elementDescriptor, inverseFlusher, inverseRemoveStrategy); + public IndexedListAttributeFlusher(String attributeName, String mapping, Class ownerEntityClass, String ownerIdAttributeName, String ownerMapping, DirtyAttributeFlusher ownerIdFlusher, DirtyAttributeFlusher elementFlusher, boolean supportsCollectionDml, FlushStrategy flushStrategy, AttributeAccessor attributeMapper, InitialValueAttributeAccessor viewAttributeAccessor, + boolean optimisticLockProtected, boolean collectionUpdatable, boolean viewOnlyDeleteCascaded, boolean jpaProviderDeletesCollection, CollectionRemoveListener cascadeDeleteListener, CollectionRemoveListener removeListener, CollectionInstantiator collectionInstantiator, TypeDescriptor elementDescriptor, InverseFlusher inverseFlusher, + InverseRemoveStrategy inverseRemoveStrategy) { + super(attributeName, mapping, ownerEntityClass, ownerIdAttributeName, ownerMapping, ownerIdFlusher, elementFlusher, supportsCollectionDml, flushStrategy, attributeMapper, viewAttributeAccessor, optimisticLockProtected, collectionUpdatable, viewOnlyDeleteCascaded, jpaProviderDeletesCollection, cascadeDeleteListener, removeListener, collectionInstantiator, elementDescriptor, + inverseFlusher, inverseRemoveStrategy); } public IndexedListAttributeFlusher(IndexedListAttributeFlusher original, boolean fetch) { @@ -154,6 +167,193 @@ public DirtyKind getDirtyKind(V initial, V current) { return DirtyKind.NONE; } + @Override + protected List>> replaceActions(V value) { + List>> actions = new ArrayList<>(); + actions.add(new CollectionClearAction()); + if (value != null && !value.isEmpty()) { + actions.add(new ListAddAllAction(0, true, value)); + } + return actions; + } + + @Override + protected Collection appendRemoveSpecific(UpdateContext context, DeleteCriteriaBuilder deleteCb, FusedCollectionActions fusedCollectionActions) { + deleteCb.where("INDEX(e." + getMapping() + ")").in(fusedCollectionActions.getRemoved(context)); + return fusedCollectionActions.getRemoved(); + } + + @Override + protected void addElements(UpdateContext context, Object ownerView, Object view, Collection removedAllObjects, boolean flushAtOnce, V value, List embeddablesToUpdate, FusedCollectionActions fusedCollectionActions) { + Collection appends; + int appendIndex; + String mapping = getMapping(); + if (fusedCollectionActions == null || !removedAllObjects.isEmpty()) { + appends = (Collection) value; + removedAllObjects.removeAll(appends); + appendIndex = 0; + if (embeddablesToUpdate != null && !embeddablesToUpdate.isEmpty()) { + UpdateCriteriaBuilder updateCb = context.getEntityViewManager().getCriteriaBuilderFactory().updateCollection(context.getEntityManager(), ownerEntityClass, "e", mapping); + updateCb.setExpression(mapping, ":element"); + updateCb.setWhereExpression(ownerIdWhereFragment); + updateCb.where("INDEX(" + mapping + ")").eqExpression(":idx"); + Query query = updateCb.getQuery(); + ownerIdFlusher.flushQuery(context, null, query, ownerView, view, ownerIdFlusher.getViewAttributeAccessor().getValue(view), null); + + for (int i = 0; i < embeddablesToUpdate.size(); i++) { + query.setParameter("idx", i); + query.setParameter("element", embeddablesToUpdate.get(i)); + query.executeUpdate(); + } + } + } else { + FusedCollectionIndexActions indexActions = (FusedCollectionIndexActions) fusedCollectionActions; + List translations = indexActions.getTranslations(); + if (translations.size() != 0) { + UpdateCriteriaBuilder updateCb = context.getEntityViewManager().getCriteriaBuilderFactory().updateCollection(context.getEntityManager(), ownerEntityClass, "e", mapping); + updateCb.setExpression("INDEX(" + mapping + ")", "INDEX(" + mapping + ") + :offset"); + updateCb.setWhereExpression(ownerIdWhereFragment); + updateCb.where("INDEX(" + mapping + ")").geExpression(":minIdx"); + updateCb.where("INDEX(" + mapping + ")").ltExpression(":maxIdx"); + Query query = updateCb.getQuery(); + ownerIdFlusher.flushQuery(context, null, query, ownerView, view, ownerIdFlusher.getViewAttributeAccessor().getValue(view), null); + for (int i = 0; i < translations.size(); i++) { + FusedCollectionIndexActions.IndexTranslateOperation translation = translations.get(i); + query.setParameter("minIdx", translation.getStartIndex()); + query.setParameter("maxIdx", translation.getEndIndex()); + query.setParameter("offset", translation.getOffset()); + query.executeUpdate(); + } + } + + List replaces = indexActions.getReplaces(); + if (replaces.size() != 0 || embeddablesToUpdate != null && !embeddablesToUpdate.isEmpty()) { + UpdateCriteriaBuilder updateCb = context.getEntityViewManager().getCriteriaBuilderFactory().updateCollection(context.getEntityManager(), ownerEntityClass, "e", mapping); + updateCb.setExpression(mapping, ":element"); + updateCb.setWhereExpression(ownerIdWhereFragment); + updateCb.where("INDEX(" + mapping + ")").eqExpression(":idx"); + Query query = updateCb.getQuery(); + + if (replaces.size() != 0) { + ownerIdFlusher.flushQuery(context, null, query, ownerView, view, ownerIdFlusher.getViewAttributeAccessor().getValue(view), null); + boolean checkTransient = elementDescriptor.isJpaEntity() && !elementDescriptor.shouldJpaPersist(); + if (elementDescriptor.getViewToEntityMapper() == null) { + for (int i = 0; i < replaces.size(); i++) { + FusedCollectionIndexActions.ReplaceOperation replace = replaces.get(i); + if (checkTransient && elementDescriptor.getBasicUserType().shouldPersist(replace.getNewObject())) { + throw new IllegalStateException("Collection " + attributeName + " references an unsaved transient instance - save the transient instance before flushing: " + replace.getNewObject()); + } + query.setParameter("idx", replace.getIndex()); + query.setParameter("element", replace.getNewObject()); + query.executeUpdate(); + } + } else { + ViewToEntityMapper loadOnlyViewToEntityMapper = elementDescriptor.getLoadOnlyViewToEntityMapper(); + for (int i = 0; i < replaces.size(); i++) { + FusedCollectionIndexActions.ReplaceOperation replace = replaces.get(i); + query.setParameter("idx", replace.getIndex()); + query.setParameter("element", loadOnlyViewToEntityMapper.applyToEntity(context, null, replace.getNewObject())); + query.executeUpdate(); + } + } + } + if (embeddablesToUpdate != null && !embeddablesToUpdate.isEmpty()) { + for (int i = 0; i < embeddablesToUpdate.size(); i++) { + query.setParameter("idx", i); + query.setParameter("element", embeddablesToUpdate.get(i)); + query.executeUpdate(); + } + } + } + + appends = indexActions.getAdded(context); + appendIndex = indexActions.getAppendIndex(); + removedAllObjects.removeAll(indexActions.getAdded()); + } + + if (appends.size() > 1 || appends.size() == 1 && appends.iterator().next() != null) { + InsertCriteriaBuilder insertCb = context.getEntityViewManager().getCriteriaBuilderFactory().insertCollection(context.getEntityManager(), ownerEntityClass, mapping); + + String entityIdAttributeName = elementDescriptor.getEntityIdAttributeName(); + if (entityIdAttributeName == null) { + insertCb.fromValues((Class) elementDescriptor.getJpaType(), "val", 1); + } else { + insertCb.fromIdentifiableValues((Class) elementDescriptor.getJpaType(), "val", 1); + } + insertCb.bind("INDEX(" + mapping + ")").select("FUNCTION('TREAT_INTEGER', :idx)"); + for (int i = 0; i < ownerIdBindFragments.length; i += 2) { + insertCb.bind(ownerIdBindFragments[i]).select(ownerIdBindFragments[i + 1]); + } + insertCb.bind(mapping).select("val"); + Query query = insertCb.getQuery(); + ownerIdFlusher.flushQuery(context, null, query, ownerView, view, ownerIdFlusher.getViewAttributeAccessor().getValue(view), null); + + // TODO: Use batching when we implement #657 + Object[] singletonArray = new Object[1]; + List singletonList = Arrays.asList(singletonArray); + if (elementDescriptor.getViewToEntityMapper() == null) { + boolean checkTransient = elementDescriptor.isJpaEntity() && !elementDescriptor.shouldJpaPersist(); + for (Object object : appends) { + if (object != null) { + if (checkTransient && elementDescriptor.getBasicUserType().shouldPersist(object)) { + throw new IllegalStateException("Collection " + attributeName + " references an unsaved transient instance - save the transient instance before flushing: " + object); + } + singletonArray[0] = object; + query.setParameter("idx", appendIndex++); + query.setParameter("val", singletonList); + query.executeUpdate(); + } + } + } else { + ViewToEntityMapper loadOnlyViewToEntityMapper = elementDescriptor.getLoadOnlyViewToEntityMapper(); + for (Object object : appends) { + if (object != null) { + singletonArray[0] = loadOnlyViewToEntityMapper.applyToEntity(context, null, object); + query.setParameter("idx", appendIndex++); + query.setParameter("val", singletonList); + query.executeUpdate(); + } + } + } + } + } + + @Override + protected boolean canFlushSeparateCollectionOperations() { + return true; + } + + @Override + protected boolean isIndexed() { + return true; + } + + @Override + protected List> getElementFlushActions(UpdateContext context, TypeDescriptor typeDescriptor, V current) { + List> actions = new ArrayList<>(); + final ViewToEntityMapper mapper = typeDescriptor.getViewToEntityMapper(); + for (int i = 0; i < current.size(); i++) { + Object o = current.get(i); + if (o instanceof MutableStateTrackable) { + MutableStateTrackable element = (MutableStateTrackable) o; + @SuppressWarnings("unchecked") + DirtyAttributeFlusher flusher = (DirtyAttributeFlusher) (DirtyAttributeFlusher) mapper.getNestedDirtyFlusher(context, element, (DirtyAttributeFlusher) null); + if (flusher != null) { + // Using last = false is intentional to actually get a proper update instead of a delete and insert + actions.add(new ListSetAction<>(i, false, element, current)); + } + } + } + + return actions; + } + + @Override + @SuppressWarnings("unchecked") + protected FusedCollectionActions getFusedOperations(List> collectionActions) { + return new FusedCollectionIndexActions((List>) (List) collectionActions); + } + @Override protected List>> determineJpaCollectionActions(UpdateContext context, V jpaCollection, V value, EqualityChecker equalityChecker) { // We try to find a common prefix and from that on, we infer actions @@ -174,8 +374,11 @@ protected List>> determineJpaCollectionActions(Up } } else { // JPA element was removed, remove all following elements and Keep the same index 'i' - for (int j = i; j < jpaSize; j++) { - actions.add((CollectionAction>) (CollectionAction) new ListRemoveAction<>(i, jpaCollection)); + if (i < jpaSize) { + for (int j = i; j < jpaSize - 1; j++) { + actions.add((CollectionAction>) (CollectionAction) new ListRemoveAction<>(i, false, jpaCollection)); + } + actions.add((CollectionAction>) (CollectionAction) new ListRemoveAction<>(i, true, jpaCollection)); } // Break since there are no more elements to check @@ -184,12 +387,17 @@ protected List>> determineJpaCollectionActions(Up } } // Remove remaining elements in the list that couldn't be matched - for (int i = lastUnmatchedIndex; i < jpaSize; i++) { - actions.add((CollectionAction>) (CollectionAction) new ListRemoveAction<>(lastUnmatchedIndex, jpaCollection)); + if (lastUnmatchedIndex < jpaSize) { + for (int i = lastUnmatchedIndex; i < jpaSize - 1; i++) { + actions.add((CollectionAction>) (CollectionAction) new ListRemoveAction<>(lastUnmatchedIndex, false, jpaCollection)); + } + actions.add((CollectionAction>) (CollectionAction) new ListRemoveAction<>(lastUnmatchedIndex, true, jpaCollection)); } // Add new elements that are not matched if (lastUnmatchedIndex < value.size()) { - actions.add((CollectionAction>) (CollectionAction) new CollectionAddAllAction<>(value.subList(lastUnmatchedIndex, value.size()), true)); + for (int i = lastUnmatchedIndex; i < value.size(); i++) { + actions.add((CollectionAction>) (CollectionAction) new ListAddAction<>(lastUnmatchedIndex, true, value.get(i))); + } } return actions; @@ -199,62 +407,75 @@ protected List>> determineJpaCollectionActions(Up protected List>> determineCollectionActions(UpdateContext context, V initial, V current, EqualityChecker equalityChecker) { // We try to find a common prefix and from that on, we infer actions List>> actions = new ArrayList<>(); - int initialSize = initial.size(); int lastUnmatchedIndex = 0; - if (elementDescriptor.isSubview() && elementDescriptor.isIdentifiable()) { - final AttributeAccessor subviewIdAccessor = elementDescriptor.getViewToEntityMapper().getViewIdAccessor(); + if (initial != null && !initial.isEmpty()) { + int initialSize = initial.size(); + if (elementDescriptor.isSubview() && elementDescriptor.isIdentifiable()) { + final AttributeAccessor subviewIdAccessor = elementDescriptor.getViewToEntityMapper().getViewIdAccessor(); - for (int i = 0; i < initialSize; i++) { - Object initialViewId = subviewIdAccessor.getValue(initial.get(i)); - if (i < current.size()) { - Object currentViewId = subviewIdAccessor.getValue(current.get(i)); - if (!initialViewId.equals(currentViewId)) { - break; + for (int i = 0; i < initialSize; i++) { + Object initialViewId = subviewIdAccessor.getValue(initial.get(i)); + if (i < current.size()) { + Object currentViewId = subviewIdAccessor.getValue(current.get(i)); + if (!initialViewId.equals(currentViewId)) { + break; + } else { + lastUnmatchedIndex++; + } } else { - lastUnmatchedIndex++; - } - } else { - // remove all following elements and Keep the same index 'i' - for (int j = i; j < initialSize; j++) { - actions.add((CollectionAction>) (CollectionAction) new ListRemoveAction<>(i, initial)); - } + // remove all following elements and Keep the same index 'i' + if (i < initialSize) { + for (int j = i; j < initialSize - 1; j++) { + actions.add((CollectionAction>) (CollectionAction) new ListRemoveAction<>(i, false, initial)); + } + actions.add((CollectionAction>) (CollectionAction) new ListRemoveAction<>(i, true, initial)); + } - // Break since there are no more elements to check - lastUnmatchedIndex = initialSize; - break; - } - } - } else { - for (int i = 0; i < initialSize; i++) { - Object initialElement = initial.get(i); - if (i < current.size()) { - Object viewElement = current.get(i); - if (!equalityChecker.isEqual(context, initialElement, viewElement)) { + // Break since there are no more elements to check + lastUnmatchedIndex = initialSize; break; - } else { - lastUnmatchedIndex++; - } - } else { - // remove all following elements and Keep the same index 'i' - for (int j = i; j < initialSize; j++) { - actions.add((CollectionAction>) (CollectionAction) new ListRemoveAction<>(i, initial)); } + } + } else { + for (int i = 0; i < initialSize; i++) { + Object initialElement = initial.get(i); + if (i < current.size()) { + Object viewElement = current.get(i); + if (!equalityChecker.isEqual(context, initialElement, viewElement)) { + break; + } else { + lastUnmatchedIndex++; + } + } else { + // remove all following elements and Keep the same index 'i' + if (i < initialSize) { + for (int j = i; j < initialSize - 1; j++) { + actions.add((CollectionAction>) (CollectionAction) new ListRemoveAction<>(i, false, initial)); + } + actions.add((CollectionAction>) (CollectionAction) new ListRemoveAction<>(i, true, initial)); + } - // Break since there are no more elements to check - lastUnmatchedIndex = initialSize; - break; + // Break since there are no more elements to check + lastUnmatchedIndex = initialSize; + break; + } } } - } - // Remove remaining elements in the list that couldn't be matched - for (int i = lastUnmatchedIndex; i < initialSize; i++) { - actions.add((CollectionAction>) (CollectionAction) new ListRemoveAction<>(lastUnmatchedIndex, initial)); + // Remove remaining elements in the list that couldn't be matched + if (lastUnmatchedIndex < initialSize) { + for (int i = lastUnmatchedIndex; i < initialSize - 1; i++) { + actions.add((CollectionAction>) (CollectionAction) new ListRemoveAction<>(lastUnmatchedIndex, false, initial)); + } + actions.add((CollectionAction>) (CollectionAction) new ListRemoveAction<>(lastUnmatchedIndex, true, initial)); + } } // Add new elements that are not matched if (lastUnmatchedIndex < current.size()) { - actions.add((CollectionAction>) (CollectionAction) new CollectionAddAllAction<>(current.subList(lastUnmatchedIndex, current.size()), true)); + for (int i = lastUnmatchedIndex; i < current.size(); i++) { + actions.add((CollectionAction>) (CollectionAction) new ListAddAction<>(lastUnmatchedIndex, true, current.get(i))); + } } return actions; diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/InverseCollectionElementAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/InverseCollectionElementAttributeFlusher.java index effef7a501..4e22a5a466 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/InverseCollectionElementAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/InverseCollectionElementAttributeFlusher.java @@ -38,11 +38,11 @@ public InverseCollectionElementAttributeFlusher(DirtyAttributeFlusher n } @Override - public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { + public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object ownerView, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { if (strategy == Strategy.REMOVE) { inverseFlusher.removeElement(context, null, element); } else if (strategy != Strategy.IGNORE) { - inverseFlusher.flushQuerySetElement(context, (V) element, view, strategy == Strategy.SET_NULL ? null : view, parameterPrefix, (DirtyAttributeFlusher) nestedGraphNode); + inverseFlusher.flushQuerySetElement(context, (V) element, ownerView, strategy == Strategy.SET_NULL ? null : ownerView, parameterPrefix, (DirtyAttributeFlusher) nestedGraphNode); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/InverseFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/InverseFlusher.java index e19f9c7822..41ad656c04 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/InverseFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/InverseFlusher.java @@ -104,7 +104,7 @@ public InverseFlusher(Class parentEntityClass, String attributeName, String p this.childEntityToEntityMapper = childEntityToEntityMapper; } - public static InverseFlusher forAttribute(EntityViewManagerImpl evm, ManagedViewType viewType, AbstractMethodAttribute attribute, TypeDescriptor childTypeDescriptor) { + public static InverseFlusher forAttribute(EntityViewManagerImpl evm, ManagedViewType viewType, AbstractMethodAttribute attribute, TypeDescriptor childTypeDescriptor, EntityViewUpdaterImpl owner, String ownerMapping) { if (attribute.getMappedBy() != null) { String attributeLocation = attribute.getLocation(); Type elementType = attribute instanceof PluralAttribute ? ((PluralAttribute) attribute).getElementType() : ((SingularAttribute) attribute).getType(); @@ -154,7 +154,9 @@ public static InverseFlusher forAttribute(EntityViewManagerImpl evm, Mana attribute.getUpdateCascadeAllowedSubtypes(), new ReferenceEntityLoader(evm, childViewType, EntityViewUpdaterImpl.createViewIdMapper(evm, childViewType)), Accessors.forViewId(evm, childViewType, true), - true + true, + owner, + ownerMapping ); } else if (childTypeDescriptor.isJpaEntity()) { Class childType = elementType.getJavaType(); @@ -165,6 +167,12 @@ public static InverseFlusher forAttribute(EntityViewManagerImpl evm, Mana childType, attribute.getWritableMappedByMappings() ); + parentEntityOnChildEntityAddMapper = parentEntityOnChildEntityRemoveMapper = (Mapper) Mappers.forEntityAttributeMapping( + evm, + viewType.getEntityClass(), + elementEntityClass, + attribute.getWritableMappedByMappings() + ); } } else { if (childTypeDescriptor.isSubview()) { @@ -183,7 +191,9 @@ public static InverseFlusher forAttribute(EntityViewManagerImpl evm, Mana attribute.getUpdateCascadeAllowedSubtypes(), new ReferenceEntityLoader(evm, childViewType, EntityViewUpdaterImpl.createViewIdMapper(evm, childViewType)), Accessors.forViewId(evm, childViewType, true), - true + true, + owner, + ownerMapping ); parentEntityOnChildEntityAddMapper = parentEntityOnChildEntityRemoveMapper = Mappers.forAccessor(parentReferenceAttributeAccessor); parentEntityOnChildViewMapper = (Mapper) Mappers.forViewConvertToViewAttributeMapping( @@ -201,6 +211,7 @@ public static InverseFlusher forAttribute(EntityViewManagerImpl evm, Mana childType, attribute.getMappedBy() ); + parentEntityOnChildEntityAddMapper = parentEntityOnChildEntityRemoveMapper = Mappers.forAccessor(parentReferenceAttributeAccessor); parentEntityOnChildViewMapper = Mappers.forAccessor(parentReferenceAttributeAccessor); } } @@ -221,7 +232,7 @@ public static InverseFlusher forAttribute(EntityViewManagerImpl evm, Mana null, null, null, - TypeDescriptor.forInverseCollectionAttribute(new EntityBasicUserType(evm.getJpaProvider())) + TypeDescriptor.forInverseCollectionAttribute(viewType.getEntityClass(), new EntityBasicUserType(evm.getJpaProvider())) ); } else { parentEntityOnChildEntityRemoveMapper = new NullMapper<>(parentEntityOnChildEntityRemoveMapper); @@ -378,9 +389,9 @@ private void flushQuerySetEntityOnElement(UpdateContext context, Object element, int orphanRemovalStartIndex = context.getOrphanRemovalDeleters().size(); Query q = elementToEntityMapper.createInverseUpdateQuery(context, element, nestedGraphNode, parentReferenceAttributeFlusher); if (nestedGraphNode != null) { - nestedGraphNode.flushQuery(context, parameterPrefix, q, null, element, null); + nestedGraphNode.flushQuery(context, parameterPrefix, q, null, null, element, null); } - parentReferenceAttributeFlusher.flushQuery(context, parameterPrefix, q, null, newValue, null); + parentReferenceAttributeFlusher.flushQuery(context, parameterPrefix, q, null, null, newValue, null); if (q != null) { int updated = q.executeUpdate(); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/MapAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/MapAttributeFlusher.java index 6ccae8df52..5f68c8f104 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/MapAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/MapAttributeFlusher.java @@ -68,10 +68,11 @@ public class MapAttributeFlusher> extends AbstractPluralA private final BasicDirtyChecker keyDirtyChecker; @SuppressWarnings("unchecked") - public MapAttributeFlusher(String attributeName, String mapping, Class ownerEntityClass, String ownerIdAttributeName, FlushStrategy flushStrategy, AttributeAccessor attributeMapper, InitialValueAttributeAccessor viewAttributeAccessor, boolean optimisticLockProtected, boolean collectionUpdatable, - CollectionRemoveListener keyCascadeDeleteListener, CollectionRemoveListener elementCascadeDeleteListener, CollectionRemoveListener keyRemoveListener, CollectionRemoveListener elementRemoveListener, + public MapAttributeFlusher(String attributeName, String mapping, Class ownerEntityClass, String ownerIdAttributeName, String ownerMapping, DirtyAttributeFlusher ownerIdFlusher, DirtyAttributeFlusher elementFlusher, boolean supportsCollectionDml, FlushStrategy flushStrategy, AttributeAccessor attributeMapper, InitialValueAttributeAccessor viewAttributeAccessor, + boolean optimisticLockProtected, boolean collectionUpdatable, CollectionRemoveListener keyCascadeDeleteListener, CollectionRemoveListener elementCascadeDeleteListener, CollectionRemoveListener keyRemoveListener, CollectionRemoveListener elementRemoveListener, boolean viewOnlyDeleteCascaded, boolean jpaProviderDeletesCollection, TypeDescriptor keyDescriptor, TypeDescriptor elementDescriptor, MapViewToEntityMapper mapper, MapViewToEntityMapper loadOnlyMapper, MapInstantiator mapInstantiator) { - super(attributeName, mapping, collectionUpdatable || elementDescriptor.isMutable(), ownerEntityClass, ownerIdAttributeName, flushStrategy, attributeMapper, viewAttributeAccessor, optimisticLockProtected, collectionUpdatable, viewOnlyDeleteCascaded, jpaProviderDeletesCollection, elementCascadeDeleteListener, elementRemoveListener, elementDescriptor); + super(attributeName, mapping, collectionUpdatable || elementDescriptor.isMutable(), ownerEntityClass, ownerIdAttributeName, ownerMapping, ownerIdFlusher, elementFlusher, supportsCollectionDml, flushStrategy, attributeMapper, viewAttributeAccessor, optimisticLockProtected, collectionUpdatable, viewOnlyDeleteCascaded, jpaProviderDeletesCollection, elementCascadeDeleteListener, elementRemoveListener, + elementDescriptor); this.mapInstantiator = mapInstantiator; this.keyDescriptor = keyDescriptor; if (keyDescriptor.isSubview() || keyDescriptor.isJpaEntity()) { @@ -181,7 +182,17 @@ public boolean isPassThrough() { } @Override - protected void invokeCollectionAction(UpdateContext context, V targetCollection, List> collectionActions) { + protected boolean isIndexed() { + return true; + } + + @Override + protected List> getElementFlushActions(UpdateContext context, TypeDescriptor elementDescriptor, V current) { + throw new UnsupportedOperationException("Not yet implemented!"); + } + + @Override + protected void invokeCollectionAction(UpdateContext context, Object ownerView, Object view, V targetCollection, Object value, List> collectionActions) { if (targetCollection == null) { // When the target collection is null this means that there is no collection role in the entity // This happens for correlated attributes and we will just provide an empty collection for applying actions @@ -227,8 +238,8 @@ protected V replaceWithRecordingCollection(UpdateContext context, Object view, V @SuppressWarnings("unchecked") public boolean flushEntity(UpdateContext context, E entity, Object view, V value, Runnable postReplaceListener) { if (flushOperation != null) { - replaceWithRecordingCollection(context, view, value, collectionActions); - invokeFlushOperation(context, view, entity, value); + value = replaceWithRecordingCollection(context, view, value, collectionActions); + invokeFlushOperation(context, null, view, entity, value); return true; } if (collectionUpdatable) { @@ -424,15 +435,15 @@ public boolean flushEntity(UpdateContext context, E entity, Object view, V value } if (replace) { - replaceCollection(context, entity, value); + replaceCollection(context, null, view, entity, value); return true; } return wasDirty; } else if (keyDescriptor.shouldFlushMutations() || elementDescriptor.shouldFlushMutations()) { - return mergeCollectionElements(context, view, entity, value); + return mergeCollectionElements(context, null, view, entity, value); } else { // Only pass through is possible here - replaceCollection(context, entity, value); + replaceCollection(context, null, view, entity, value); return true; } } @@ -600,6 +611,11 @@ public void removeFromEntity(UpdateContext context, E entity) { } } + @Override + protected boolean canFlushSeparateCollectionOperations() { + return true; + } + @Override public boolean requiresDeleteCascadeAfterRemove() { return false; @@ -664,7 +680,7 @@ private boolean mergeAndRequeue(UpdateContext context, RecordingMap recordingCol } @Override - protected boolean mergeCollectionElements(UpdateContext context, Object view, E entity, V value) { + protected boolean mergeCollectionElements(UpdateContext context, Object ownerView, Object view, E entity, V value) { if (elementFlushers != null) { if (flushStrategy == FlushStrategy.ENTITY) { for (CollectionElementAttributeFlusher elementFlusher : elementFlushers) { @@ -672,7 +688,7 @@ protected boolean mergeCollectionElements(UpdateContext context, Object view, E } } else { for (CollectionElementAttributeFlusher elementFlusher : elementFlushers) { - elementFlusher.flushQuery(context, null, null, view, value, null); + elementFlusher.flushQuery(context, null, null, ownerView, view, value, null); } } return !elementFlushers.isEmpty(); @@ -729,7 +745,7 @@ protected boolean mergeCollectionElements(UpdateContext context, Object view, E } @Override - protected void replaceCollection(UpdateContext context, E entity, V value) { + protected void replaceCollection(UpdateContext context, Object ownerView, Object view, E entity, V value) { if (entityAttributeMapper != null) { if (keyDescriptor.isSubview() || elementDescriptor.isSubview()) { Map newMap = (Map) createJpaMap(value.size()); @@ -1158,7 +1174,7 @@ protected DirtyAttributeFlusher, E, V> determineDirtyF } // If we determine possible collection actions, we try to apply them, but if not if (elementDescriptor.shouldFlushMutations()) { - List> elementFlushers = getElementFlushers(context, current); + List> elementFlushers = getElementFlushers(context, current, collectionActions); // A "null" element flusher list is given when a fetch and compare is more appropriate if (elementFlushers == null) { return this; @@ -1307,12 +1323,12 @@ protected List>> determineCollectionActions(Update } @Override - protected final List> getElementFlushers(UpdateContext context, V current) { + protected List> getElementFlushers(UpdateContext context, V current, List> actions) { List> elementFlushers = new ArrayList<>(); - if (determineElementFlushers(context, keyDescriptor, elementFlushers, current.keySet())) { + if (determineElementFlushers(context, keyDescriptor, elementFlushers, current.keySet(), actions, current)) { return null; } - if (determineElementFlushers(context, elementDescriptor, elementFlushers, current.values())) { + if (determineElementFlushers(context, elementDescriptor, elementFlushers, current.values(), actions, current)) { return null; } @@ -1335,19 +1351,20 @@ protected boolean collectionEquals(V initial, V current) { @Override protected DirtyAttributeFlusher, E, V> getDirtyFlusherForRecordingCollection(UpdateContext context, V initial, RecordingMap collection) { if (collection.hasActions()) { + List> actions = collection.resetActions(context); if (keyDescriptor.shouldFlushMutations() || elementDescriptor.shouldFlushMutations()) { // When no mapper is given, we have basic types so we need to fetch and merge accordingly if (keyDescriptor.isBasic() || elementDescriptor.isBasic()) { return this; } - List> elementFlushers = getElementFlushers(context, (V) collection); + List> elementFlushers = getElementFlushers(context, (V) collection, actions); // A "null" element flusher list is given when a fetch and compare is more appropriate if (elementFlushers == null) { return this; } - return getReplayAndElementFlusher(context, initial, (V) collection, collection.resetActions(context), elementFlushers); + return getReplayAndElementFlusher(context, initial, (V) collection, actions, elementFlushers); } else { - return getReplayOnlyFlusher(context, initial, (V) collection, collection.resetActions(context)); + return getReplayOnlyFlusher(context, initial, (V) collection, actions); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/MergeCollectionElementAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/MergeCollectionElementAttributeFlusher.java index b73475645e..ab2584d39c 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/MergeCollectionElementAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/MergeCollectionElementAttributeFlusher.java @@ -16,6 +16,8 @@ package com.blazebit.persistence.view.impl.update.flush; +import com.blazebit.persistence.view.impl.collection.RecordingCollection; +import com.blazebit.persistence.view.impl.collection.RecordingReplacingIterator; import com.blazebit.persistence.view.impl.update.UpdateContext; import javax.persistence.Query; @@ -42,18 +44,35 @@ public void appendFetchJoinQueryFragment(String base, StringBuilder sb) { } @Override - public void appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { + public boolean appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { + return false; } @Override - public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { - context.getEntityManager().merge(element); + public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object ownerView, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { + RecordingReplacingIterator recordingIterator = (RecordingReplacingIterator) ((RecordingCollection) value).recordingIterator(); + try { + while (recordingIterator.hasNext()) { + if (recordingIterator.next() == element) { + break; + } + } + Object newObject = context.getEntityManager().merge(element); + recordingIterator.replace(); + recordingIterator.add(newObject); + + while (recordingIterator.hasNext()) { + recordingIterator.next(); + } + } finally { + ((RecordingCollection) value).resetRecordingIterator(); + } } @Override @SuppressWarnings("unchecked") public boolean flushEntity(UpdateContext context, E entity, Object view, V value, Runnable postReplaceListener) { - context.getEntityManager().merge(element); + flushQuery(context, null, null, null, view, value, null); return true; } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/ParentCollectionReferenceAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/ParentCollectionReferenceAttributeFlusher.java index a2813613e9..3dc28d038f 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/ParentCollectionReferenceAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/ParentCollectionReferenceAttributeFlusher.java @@ -33,7 +33,7 @@ public class ParentCollectionReferenceAttributeFlusher paren } @Override - public void appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { + public boolean appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { if (writableMappings != null) { if (mappingPrefix == null) { sb.append(updateQueryFragments[0]); @@ -86,13 +86,15 @@ public void appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, S sb.append(parameterPrefix).append(updateQueryFragments[i + 1]); } } + + return true; } else { - super.appendUpdateQueryFragment(context, sb, mappingPrefix, parameterPrefix, separator); + return super.appendUpdateQueryFragment(context, sb, mappingPrefix, parameterPrefix, separator); } } @Override - public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { + public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object ownerView, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { if (query != null && writableMappings != null) { for (int i = 0; i < parameterAccessors.length; i++) { Map.Entry parameterAccessor = parameterAccessors[i]; @@ -105,7 +107,7 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer query.setParameter(parameter, parameterAccessor.getValue().getValue(value)); } } else { - super.flushQuery(context, parameterPrefix, query, view, value, ownerAwareDeleter); + super.flushQuery(context, parameterPrefix, query, ownerView, view, value, ownerAwareDeleter); } } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/PersistCollectionElementAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/PersistCollectionElementAttributeFlusher.java index 292cb12dca..bdc17cd15a 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/PersistCollectionElementAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/PersistCollectionElementAttributeFlusher.java @@ -42,11 +42,12 @@ public void appendFetchJoinQueryFragment(String base, StringBuilder sb) { } @Override - public void appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { + public boolean appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { + return false; } @Override - public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { + public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object ownerView, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { context.getEntityManager().persist(element); } diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/SubviewAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/SubviewAttributeFlusher.java index d69e7d929e..a753161333 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/SubviewAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/SubviewAttributeFlusher.java @@ -136,14 +136,16 @@ public Object getNewInitialValue(UpdateContext context, V clonedValue, V current } @Override - public void appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { + public boolean appendUpdateQueryFragment(UpdateContext context, StringBuilder sb, String mappingPrefix, String parameterPrefix, String separator) { if (update && (updatable || isPassThrough()) && inverseFlusher == null) { if (mappingPrefix == null) { - elementIdFlusher.appendUpdateQueryFragment(context, sb, mapping + ".", parameterName + "_", separator); + return elementIdFlusher.appendUpdateQueryFragment(context, sb, mapping + ".", parameterName + "_", separator); } else { - elementIdFlusher.appendUpdateQueryFragment(context, sb, mappingPrefix + mapping + ".", parameterPrefix + parameterName + "_", separator); + return elementIdFlusher.appendUpdateQueryFragment(context, sb, mappingPrefix + mapping + ".", parameterPrefix + parameterName + "_", separator); } } + + return false; } @Override @@ -157,7 +159,7 @@ public boolean loadForEntityFlush() { } @Override - public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { + public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object ownerView, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { V finalValue; if (flushOperation == null) { finalValue = value; @@ -192,7 +194,7 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer } else { int orphanRemovalStartIndex = context.getOrphanRemovalDeleters().size(); Query q = viewToEntityMapper.createUpdateQuery(context, finalValue, nestedFlusher); - nestedFlusher.flushQuery(context, parameterPrefix, q, null, finalValue, ownerAwareDeleter); + nestedFlusher.flushQuery(context, parameterPrefix, q, finalValue, null, finalValue, ownerAwareDeleter); if (q != null) { int updated = q.executeUpdate(); @@ -211,9 +213,9 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer if (inverseFlusher == null) { Object realValue = v == null ? null : subviewIdAccessor.getValue(finalValue); if (parameterPrefix == null) { - elementIdFlusher.flushQuery(context, parameterName + "_", query, null, realValue, ownerAwareDeleter); + elementIdFlusher.flushQuery(context, parameterName + "_", query, finalValue, null, realValue, ownerAwareDeleter); } else { - elementIdFlusher.flushQuery(context, parameterPrefix + parameterName + "_", query, null, realValue, ownerAwareDeleter); + elementIdFlusher.flushQuery(context, parameterPrefix + parameterName + "_", query, finalValue, null, realValue, ownerAwareDeleter); } } } @@ -234,7 +236,7 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer } else { int orphanRemovalStartIndex = context.getOrphanRemovalDeleters().size(); Query q = viewToEntityMapper.createUpdateQuery(context, value, nestedFlusher); - nestedFlusher.flushQuery(context, parameterPrefix, q, null, value, ownerAwareDeleter); + nestedFlusher.flushQuery(context, parameterPrefix, q, value, null, value, ownerAwareDeleter); if (q != null) { int updated = q.executeUpdate(); @@ -252,9 +254,9 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer if (inverseFlusher == null) { Object realValue = v == null ? null : subviewIdAccessor.getValue(value); if (parameterPrefix == null) { - elementIdFlusher.flushQuery(context, parameterName + "_", query, null, realValue, ownerAwareDeleter); + elementIdFlusher.flushQuery(context, parameterName + "_", query, value, null, realValue, ownerAwareDeleter); } else { - elementIdFlusher.flushQuery(context, parameterPrefix + parameterName + "_", query, null, realValue, ownerAwareDeleter); + elementIdFlusher.flushQuery(context, parameterPrefix + parameterName + "_", query, value, null, realValue, ownerAwareDeleter); } } } @@ -273,7 +275,7 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer if (nestedFlusher != null && nestedFlusher != viewToEntityMapper.getFullGraphNode()) { int orphanRemovalStartIndex = context.getOrphanRemovalDeleters().size(); Query q = viewToEntityMapper.createUpdateQuery(context, realValue, nestedFlusher); - nestedFlusher.flushQuery(context, parameterPrefix, q, null, realValue, ownerAwareDeleter); + nestedFlusher.flushQuery(context, parameterPrefix, q, realValue, null, realValue, ownerAwareDeleter); if (q != null) { int updated = q.executeUpdate(); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/TypeDescriptor.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/TypeDescriptor.java index d0094348f7..23a0fe9ac5 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/TypeDescriptor.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/TypeDescriptor.java @@ -64,6 +64,7 @@ public class TypeDescriptor { private final boolean cascadePersist; private final boolean cascadeUpdate; private final String entityIdAttributeName; + private final Class jpaType; private final TypeConverter converter; private final BasicUserType basicUserType; private final EntityToEntityMapper entityToEntityMapper; @@ -71,7 +72,7 @@ public class TypeDescriptor { private final ViewToEntityMapper loadOnlyViewToEntityMapper; public TypeDescriptor(boolean mutable, boolean identifiable, boolean jpaManaged, boolean jpaEntity, boolean shouldJpaMerge, boolean shouldJpaPersist, boolean cascadePersist, boolean cascadeUpdate, String entityIdAttributeName, - TypeConverter converter, BasicUserType basicUserType, EntityToEntityMapper entityToEntityMapper, ViewToEntityMapper viewToEntityMapper, ViewToEntityMapper loadOnlyViewToEntityMapper) { + Class jpaType, TypeConverter converter, BasicUserType basicUserType, EntityToEntityMapper entityToEntityMapper, ViewToEntityMapper viewToEntityMapper, ViewToEntityMapper loadOnlyViewToEntityMapper) { this.mutable = mutable; this.identifiable = identifiable; this.jpaManaged = jpaManaged; @@ -81,6 +82,7 @@ public TypeDescriptor(boolean mutable, boolean identifiable, boolean jpaManaged, this.cascadePersist = cascadePersist; this.cascadeUpdate = cascadeUpdate; this.entityIdAttributeName = entityIdAttributeName; + this.jpaType = jpaType; this.converter = converter; this.basicUserType = basicUserType; this.entityToEntityMapper = entityToEntityMapper; @@ -88,7 +90,7 @@ public TypeDescriptor(boolean mutable, boolean identifiable, boolean jpaManaged, this.loadOnlyViewToEntityMapper = loadOnlyViewToEntityMapper; } - public static TypeDescriptor forType(EntityViewManagerImpl evm, AbstractMethodAttribute attribute, Type type) { + public static TypeDescriptor forType(EntityViewManagerImpl evm, EntityViewUpdaterImpl updater, AbstractMethodAttribute attribute, Type type, EntityViewUpdaterImpl owner, String ownerMapping) { EntityMetamodel entityMetamodel = evm.getMetamodel().getEntityMetamodel(); String attributeLocation = attribute.getLocation(); boolean cascadePersist = attribute.isPersistCascaded(); @@ -107,8 +109,10 @@ public static TypeDescriptor forType(EntityViewManagerImpl evm, AbstractMethodAt TypeConverter converter = (TypeConverter) type.getConverter(); BasicUserType basicUserType; String entityIdAttributeName = null; + Class jpaType; // TODO: currently we only check if the declared type is mutable, but we have to let the collection flusher which types are considered updatable/creatable if (type instanceof BasicType) { + jpaType = type.getJavaType(); basicUserType = (BasicUserType) ((BasicType) type).getUserType(); mutable = basicUserType.isMutable(); identifiable = jpaEntity || !jpaManaged; @@ -139,18 +143,21 @@ public static TypeDescriptor forType(EntityViewManagerImpl evm, AbstractMethodAt entityToEntityMapper = new DefaultEntityToEntityMapper( cascadePersist, cascadeUpdate, + jpaType, basicUserType, new DefaultEntityLoaderFetchGraphNode( evm, attribute.getName(), (EntityType) managedType, fetchGraph ), - deleter); + deleter + ); } } else { ManagedViewType elementType = (ManagedViewType) type; + jpaType = elementType.getEntityClass(); basicUserType = null; mutable = elementType.isUpdatable() || elementType.isCreatable() || !persistAllowedSubtypes.isEmpty() || !updateAllowedSubtypes.isEmpty(); - viewToEntityMapper = createViewToEntityMapper(attributeLocation, evm, elementType, cascadePersist, cascadeUpdate, persistAllowedSubtypes, updateAllowedSubtypes); - loadOnlyViewToEntityMapper = createLoadOnlyViewToEntityMapper(attributeLocation, evm, elementType, cascadePersist, cascadeUpdate, persistAllowedSubtypes, updateAllowedSubtypes); + viewToEntityMapper = createViewToEntityMapper(evm, updater, attribute.getMapping(), attributeLocation, elementType, cascadePersist, cascadeUpdate, persistAllowedSubtypes, updateAllowedSubtypes, owner, ownerMapping); + loadOnlyViewToEntityMapper = createLoadOnlyViewToEntityMapper(attributeLocation, evm, elementType, cascadePersist, cascadeUpdate, persistAllowedSubtypes, updateAllowedSubtypes, owner, ownerMapping); identifiable = viewToEntityMapper.getViewIdAccessor() != null; SingularAttribute idAttribute = evm.getMetamodel().getEntityMetamodel().getManagedType(ExtendedManagedType.class, elementType.getEntityClass()).getIdAttribute(); if (idAttribute != null) { @@ -172,6 +179,7 @@ public static TypeDescriptor forType(EntityViewManagerImpl evm, AbstractMethodAt shouldFlushPersists, shouldFlushUpdates, entityIdAttributeName, + jpaType, converter, basicUserType, entityToEntityMapper, @@ -194,12 +202,13 @@ public static TypeDescriptor forInverseAttribute(ViewToEntityMapper viewToEntity null, null, null, + null, viewToEntityMapper, viewToEntityMapper ); } - public static TypeDescriptor forInverseCollectionAttribute(BasicUserType basicUserType) { + public static TypeDescriptor forInverseCollectionAttribute(Class entityClass, BasicUserType basicUserType) { return new TypeDescriptor( false, true, @@ -210,6 +219,7 @@ public static TypeDescriptor forInverseCollectionAttribute(BasicUserType basi false, false, null, + entityClass, null, (BasicUserType) basicUserType, null, @@ -233,6 +243,7 @@ public static TypeDescriptor forEntityComponentType() { null, null, null, + null, null ); } @@ -271,7 +282,7 @@ public static TypeDescriptor forEntityComponentType() { return fetchGraph; } - private static ViewToEntityMapper createViewToEntityMapper(String attributeLocation, EntityViewManagerImpl evm, ManagedViewType viewType, boolean cascadePersist, boolean cascadeUpdate, Set> persistAllowedSubtypes, Set> updateAllowedSubtypes) { + private static ViewToEntityMapper createViewToEntityMapper(EntityViewManagerImpl evm, EntityViewUpdaterImpl updater, String attributeMapping, String attributeLocation, ManagedViewType viewType, boolean cascadePersist, boolean cascadeUpdate, Set> persistAllowedSubtypes, Set> updateAllowedSubtypes, EntityViewUpdaterImpl owner, String ownerMapping) { EntityLoader entityLoader = new ReferenceEntityLoader(evm, viewType, EntityViewUpdaterImpl.createViewIdMapper(evm, viewType)); AttributeAccessor viewIdAccessor = null; if (viewType instanceof ViewType) { @@ -287,7 +298,9 @@ private static ViewToEntityMapper createViewToEntityMapper(String attributeLocat updateAllowedSubtypes, entityLoader, viewIdAccessor, - cascadePersist + cascadePersist, + owner, + ownerMapping ); } @@ -300,7 +313,9 @@ private static ViewToEntityMapper createViewToEntityMapper(String attributeLocat updateAllowedSubtypes, entityLoader, cascadePersist, - null + null, + owner == null ? updater : owner, + ownerMapping == null ? attributeMapping : ownerMapping + "." + attributeMapping ); } else { return new UpdaterBasedViewToEntityMapper( @@ -311,12 +326,14 @@ private static ViewToEntityMapper createViewToEntityMapper(String attributeLocat updateAllowedSubtypes, entityLoader, viewIdAccessor, - cascadePersist + cascadePersist, + owner, + ownerMapping ); } } - private static ViewToEntityMapper createLoadOnlyViewToEntityMapper(String attributeLocation, EntityViewManagerImpl evm, ManagedViewType viewType, boolean cascadePersist, boolean cascadeUpdate, Set> persistAllowedSubtypes, Set> updateAllowedSubtypes) { + private static ViewToEntityMapper createLoadOnlyViewToEntityMapper(String attributeLocation, EntityViewManagerImpl evm, ManagedViewType viewType, boolean cascadePersist, boolean cascadeUpdate, Set> persistAllowedSubtypes, Set> updateAllowedSubtypes, EntityViewUpdaterImpl owner, String ownerMapping) { EntityLoader entityLoader = new ReferenceEntityLoader(evm, viewType, EntityViewUpdaterImpl.createViewIdMapper(evm, viewType)); AttributeAccessor viewIdAccessor = null; @@ -333,7 +350,9 @@ private static ViewToEntityMapper createLoadOnlyViewToEntityMapper(String attrib updateAllowedSubtypes, entityLoader, cascadePersist, - null + null, + owner, + ownerMapping ); } else { Class viewTypeClass = viewType.getJavaType(); @@ -345,11 +364,17 @@ private static ViewToEntityMapper createLoadOnlyViewToEntityMapper(String attrib updateAllowedSubtypes, entityLoader, viewIdAccessor, - cascadePersist + cascadePersist, + owner, + ownerMapping ); } } + public Class getJpaType() { + return jpaType; + } + public boolean isJpaEmbeddable() { return jpaManaged && !jpaEntity; } @@ -375,7 +400,7 @@ public boolean supportsEqualityCheck() { } public boolean supportsDeepEqualityCheck() { - return basicUserType == null || basicUserType != null && basicUserType.supportsDeepEqualChecking(); + return basicUserType == null || basicUserType.supportsDeepEqualChecking(); } public boolean supportsDirtyCheck() { diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/UpdateCollectionElementAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/UpdateCollectionElementAttributeFlusher.java index f8f81794d9..ea7e201e58 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/UpdateCollectionElementAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/UpdateCollectionElementAttributeFlusher.java @@ -37,7 +37,7 @@ public UpdateCollectionElementAttributeFlusher(DirtyAttributeFlusher ne } @Override - public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { + public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object ownerView, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { int orphanRemovalStartIndex = context.getOrphanRemovalDeleters().size(); Query q = null; if (viewToEntityMapper != null) { @@ -45,11 +45,11 @@ public void flushQuery(UpdateContext context, String parameterPrefix, Query quer nestedGraphNode.flushEntity(context, null, (V) element, (V) element, null); } else { q = viewToEntityMapper.createUpdateQuery(context, element, nestedGraphNode); - nestedGraphNode.flushQuery(context, parameterPrefix, q, null, (V) element, ownerAwareDeleter); + nestedGraphNode.flushQuery(context, parameterPrefix, q, ownerView, null, (V) element, ownerAwareDeleter); } } else { q = viewToEntityMapper.createUpdateQuery(context, element, nestedGraphNode); - nestedGraphNode.flushQuery(context, parameterPrefix, q, null, (V) element, ownerAwareDeleter); + nestedGraphNode.flushQuery(context, parameterPrefix, q, ownerView, null, (V) element, ownerAwareDeleter); } if (q != null) { int updated = q.executeUpdate(); diff --git a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/VersionAttributeFlusher.java b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/VersionAttributeFlusher.java index ee50ddef15..4389f59cd6 100644 --- a/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/VersionAttributeFlusher.java +++ b/entity-view/impl/src/main/java/com/blazebit/persistence/view/impl/update/flush/VersionAttributeFlusher.java @@ -47,6 +47,7 @@ public VersionAttributeFlusher(String attributeName, String mapping, VersionBasi false, null, null, + null, userType, null, null, @@ -60,7 +61,7 @@ public final V nextValue(V value) { } @Override - public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { + public void flushQuery(UpdateContext context, String parameterPrefix, Query query, Object ownerView, Object view, V value, UnmappedOwnerAwareDeleter ownerAwareDeleter) { if (query != null) { String parameter; if (parameterPrefix == null) { diff --git a/entity-view/impl/src/test/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionIndexActionsTest.java b/entity-view/impl/src/test/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionIndexActionsTest.java new file mode 100644 index 0000000000..97f7c69d0d --- /dev/null +++ b/entity-view/impl/src/test/java/com/blazebit/persistence/view/impl/update/flush/FusedCollectionIndexActionsTest.java @@ -0,0 +1,98 @@ +/* + * Copyright 2014 - 2018 Blazebit. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.blazebit.persistence.view.impl.update.flush; + +import com.blazebit.persistence.view.impl.collection.ListAction; +import com.blazebit.persistence.view.impl.collection.ListAddAction; +import com.blazebit.persistence.view.impl.collection.ListRemoveAction; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +/** + * + * @author Christian Beikov + * @since 1.3.0 + */ +public class FusedCollectionIndexActionsTest { + + @Test + public void testRemoveAndAppend() { + List objects = Arrays.asList("o1", "o2", "o3", "o4"); + FusedCollectionIndexActions indexActions = new FusedCollectionIndexActions(Arrays.>asList( + new ListRemoveAction<>(1, false, objects), + new ListAddAction<>(3, true, "o5") + )); + Assert.assertEquals(1, indexActions.getRemoveCount()); + Assert.assertEquals(1, indexActions.getAddCount()); + Assert.assertEquals(1, indexActions.getUpdateCount()); + } + + @Test + public void testFuseRemoveAndInsert() { + List objects = Arrays.asList("o1", "o2", "o3", "o4"); + FusedCollectionIndexActions indexActions = new FusedCollectionIndexActions(Arrays.>asList( + new ListRemoveAction<>(1, false, objects), + new ListAddAction<>(1, false, "o2New") + )); + Assert.assertEquals(0, indexActions.getRemoveCount()); + Assert.assertEquals(0, indexActions.getAddCount()); + Assert.assertEquals(1, indexActions.getUpdateCount()); + } + + @Test + public void testFuseRemovesAndTranslates() { + List objects = Arrays.asList("o1", "o2", "o3", "o4"); + FusedCollectionIndexActions indexActions = new FusedCollectionIndexActions(Arrays.>asList( + new ListRemoveAction<>(1, false, objects), + new ListRemoveAction<>(1, false, objects) + )); + Assert.assertEquals(1, indexActions.getRemoveCount()); + Assert.assertEquals(0, indexActions.getAddCount()); + Assert.assertEquals(1, indexActions.getUpdateCount()); + } + + @Test + public void testMultipleRemoveRangesWithLast() { + List objects = Arrays.asList("o1", "o2", "o3", "o4", "o5", "o6"); + FusedCollectionIndexActions indexActions = new FusedCollectionIndexActions(Arrays.>asList( + new ListRemoveAction<>(1, false, objects), + new ListRemoveAction<>(1, false, objects), + new ListRemoveAction<>(2, false, objects), + new ListRemoveAction<>(2, true, objects) + )); + Assert.assertEquals(2, indexActions.getRemoveCount()); + Assert.assertEquals(0, indexActions.getAddCount()); + Assert.assertEquals(1, indexActions.getUpdateCount()); + } + + @Test + public void testMultipleRemoveRanges() { + List objects = Arrays.asList("o1", "o2", "o3", "o4", "o5", "o6", "o7"); + FusedCollectionIndexActions indexActions = new FusedCollectionIndexActions(Arrays.>asList( + new ListRemoveAction<>(1, false, objects), + new ListRemoveAction<>(1, false, objects), + new ListRemoveAction<>(2, false, objects), + new ListRemoveAction<>(2, false, objects) + )); + Assert.assertEquals(2, indexActions.getRemoveCount()); + Assert.assertEquals(0, indexActions.getAddCount()); + Assert.assertEquals(2, indexActions.getUpdateCount()); + } +} diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/AbstractEntityViewUpdateDocumentTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/AbstractEntityViewUpdateDocumentTest.java index d852cd6c82..4eb9d93ec7 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/AbstractEntityViewUpdateDocumentTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/AbstractEntityViewUpdateDocumentTest.java @@ -134,6 +134,7 @@ protected void restartTransactionAndReload() { .where("id").in(p1.getId(), p2.getId(), p3.getId(), p4.getId()) .getResultList(); cbf.create(em, Document.class) + .fetch("strings", "stringMap", "names", "nameMap", "people") .where("id").in(doc1.getId(), doc2.getId()) .getResultList(); doc1 = em.find(Document.class, doc1.getId()); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/AbstractEntityViewUpdateTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/AbstractEntityViewUpdateTest.java index 9dfc53f953..20a9337a6b 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/AbstractEntityViewUpdateTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/AbstractEntityViewUpdateTest.java @@ -35,7 +35,6 @@ import javax.persistence.EntityManager; import javax.persistence.EntityTransaction; import java.util.Collection; -import java.util.List; import java.util.Map; import static org.junit.Assert.*; @@ -216,7 +215,7 @@ protected void assertNoUpdateFullFetchAndReload(T docView) { restartTransactionAndReload(); clearQueries(); update(docView); - fullFetch(assertQuerySequence()).validate(); + fullFetch(assertUnorderedQuerySequence()).validate(); } protected AssertStatementBuilder assertQueriesAfterUpdate(T docView) { @@ -239,7 +238,7 @@ protected void assertVersionDiff(long oldVersion, long currentVersion, long diff } protected void validateNoChange(T docView) { - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/basic/mutable/EntityViewUpdateMutableBasicCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/basic/mutable/EntityViewUpdateMutableBasicCollectionsTest.java index bf98b0a742..8dfc81edb5 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/basic/mutable/EntityViewUpdateMutableBasicCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/basic/mutable/EntityViewUpdateMutableBasicCollectionsTest.java @@ -66,14 +66,20 @@ public void testUpdateReplaceCollection() { // Then // Assert that the document and the strings are loaded in full mode. // During dirty detection we should be able to figure out that nothing changed - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { - fullFetch(builder); + if (!isQueryStrategy()) { + fullFetch(builder); + } - if (version) { + if (version || isQueryStrategy()) { builder.update(Document.class); } + + if (isQueryStrategy()) { + assertReplaceAnd(builder); + } } builder.validate(); @@ -94,13 +100,18 @@ public void testUpdateAddToCollection() { // Then // Assert that the document and the strings are loaded, but only a relation insert is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - fullFetch(builder); + if (!isQueryStrategy()) { + fullFetch(builder); + } - if (version) { + if (version || isQueryStrategy() && isFullMode()) { builder.update(Document.class); } + if (isQueryStrategy() && isFullMode()) { + assertReplaceAnd(builder); + } builder.assertInsert() .forRelation(Document.class, "strings") @@ -123,21 +134,26 @@ public void testUpdateAddToNewCollection() { // Then // In partial mode, only the document is loaded. In full mode, the strings are also loaded // Since we load the strings in the full mode, we do a proper diff and can compute that only a single item was added - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - if (isFullMode()) { - fullFetch(builder); - } else { - if (preferLoadingAndDiffingOverRecreate()) { + if (!isQueryStrategy()) { + if (isFullMode()) { fullFetch(builder); } else { - assertReplaceAnd(builder); + if (preferLoadingAndDiffingOverRecreate()) { + fullFetch(builder); + } else { + assertReplaceAnd(builder); + } } } - if (version) { + if (version || isQueryStrategy() && isFullMode()) { builder.update(Document.class); } + if (isQueryStrategy() && isFullMode()) { + assertReplaceAnd(builder); + } builder.assertInsert() .forRelation(Document.class, "strings") @@ -180,12 +196,6 @@ private AssertStatementBuilder assertReplaceAnd(AssertStatementBuilder builder) .and(); } - @Override - protected boolean isQueryStrategy() { - // Collection changes always need to be applied on the entity model, can't do that via a query - return false; - } - @Override protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { return builder.assertSelect() @@ -196,6 +206,12 @@ protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { @Override protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { + assertReplaceAnd(builder); + if (doc1.getStrings().size() > 1) { + builder.assertInsert() + .forRelation(Document.class, "strings") + .and(); + } return builder.assertUpdate() .forEntity(Document.class) .and(); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/basic/mutable/EntityViewUpdateMutableBasicMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/basic/mutable/EntityViewUpdateMutableBasicMapsTest.java index 2df8f5a0a0..28fb3f9b22 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/basic/mutable/EntityViewUpdateMutableBasicMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/basic/mutable/EntityViewUpdateMutableBasicMapsTest.java @@ -67,7 +67,7 @@ public void testUpdateReplaceCollection() { // Then // Assert that the document and the strings are loaded in full mode. // During dirty detection we should be able to figure out that nothing changed - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -95,7 +95,7 @@ public void testUpdateAddToCollection() { // Then // Assert that the document and the strings are loaded, but only a relation insert is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); @@ -124,7 +124,7 @@ public void testUpdateAddToNewCollection() { // Then // In partial mode, only the document is loaded. In full mode, the strings are also loaded // Since we load the strings in the full mode, we do a proper diff and can compute that only a single item was added - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -189,12 +189,6 @@ private AssertStatementBuilder assertReplaceAnd(AssertStatementBuilder builder) .and(); } - @Override - protected boolean isQueryStrategy() { - // Collection changes always need to be applied on the entity model, can't do that via a query - return false; - } - @Override protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { return builder.assertSelect() diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/basic/mutable/EntityViewUpdateMutableBasicTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/basic/mutable/EntityViewUpdateMutableBasicTest.java index 2c2595ae92..9f305ba271 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/basic/mutable/EntityViewUpdateMutableBasicTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/basic/mutable/EntityViewUpdateMutableBasicTest.java @@ -145,7 +145,7 @@ public void testMutateMutable() { private void fullFetchUpdateAndReload(UpdatableDocumentBasicView docView) { // Assert that not only the document is loaded and finally also updated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/converter/EntityViewUpdateBlobTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/converter/EntityViewUpdateBlobTest.java index 1ba0b86104..cdde16426d 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/converter/EntityViewUpdateBlobTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/converter/EntityViewUpdateBlobTest.java @@ -176,7 +176,7 @@ public void testUpdateNothingWhenExistingBlob() throws Exception { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (!isQueryStrategy()) { fullFetch(builder); @@ -200,7 +200,7 @@ public void testSetBlob() throws Exception { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); } @@ -242,7 +242,7 @@ public void work(EntityManager em) { }); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/creatable/EntityViewUpdateCorrelatedCreatableSubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/creatable/EntityViewUpdateCorrelatedCreatableSubviewTest.java index 37baca0f95..055e7f46d7 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/creatable/EntityViewUpdateCorrelatedCreatableSubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/creatable/EntityViewUpdateCorrelatedCreatableSubviewTest.java @@ -75,7 +75,7 @@ public void testSimpleUpdate() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { if (isFullMode()) { @@ -105,7 +105,7 @@ public void testUpdateWithSubview() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (!isQueryStrategy()) { @@ -134,7 +134,7 @@ public void testUpdateWithModifySubview() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (!isQueryStrategy()) { @@ -162,7 +162,7 @@ public void testUpdateWithModifyExisting() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -196,7 +196,7 @@ public void testUpdateToNull() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (!isQueryStrategy()) { @@ -225,7 +225,7 @@ public void testUpdateWithPersonCreateView() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { if (isFullMode()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/immutable/EntityViewUpdateCorrelatedImmutableSubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/immutable/EntityViewUpdateCorrelatedImmutableSubviewCollectionsTest.java index 033d102481..b8347d98db 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/immutable/EntityViewUpdateCorrelatedImmutableSubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/immutable/EntityViewUpdateCorrelatedImmutableSubviewCollectionsTest.java @@ -73,7 +73,7 @@ public void testUpdateReplaceCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (isQueryStrategy()) { @@ -104,7 +104,7 @@ public void testUpdateAddToCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (isQueryStrategy()) { @@ -138,7 +138,7 @@ public void testUpdateAddToNewCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (isQueryStrategy()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/immutable/EntityViewUpdateCorrelatedImmutableSubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/immutable/EntityViewUpdateCorrelatedImmutableSubviewTest.java index 51da906f53..fdbb562cd6 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/immutable/EntityViewUpdateCorrelatedImmutableSubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/immutable/EntityViewUpdateCorrelatedImmutableSubviewTest.java @@ -69,7 +69,7 @@ public void testSimpleUpdate() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); @@ -96,7 +96,7 @@ public void testUpdateWithSubview() { // Then // Assert that no update happens since the subview is not considered updatable - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -129,7 +129,7 @@ public void testUpdateWithModifySubview() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -161,7 +161,7 @@ public void testUpdateWithModifyExisting() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -194,7 +194,7 @@ public void testUpdateToNull() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/mutable/EntityViewUpdateCorrelatedMutableSubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/mutable/EntityViewUpdateCorrelatedMutableSubviewTest.java index 769d023ffc..c8dad8eb5f 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/mutable/EntityViewUpdateCorrelatedMutableSubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/mutable/EntityViewUpdateCorrelatedMutableSubviewTest.java @@ -34,8 +34,6 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import javax.print.Doc; - import static org.junit.Assert.assertEquals; /** @@ -215,7 +213,7 @@ public void testUpdateToNull() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (!isQueryStrategy()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/mutableonly/EntityViewUpdateCorrelatedMutableOnlySubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/mutableonly/EntityViewUpdateCorrelatedMutableOnlySubviewCollectionsTest.java index 4f932bd085..2e727ab786 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/mutableonly/EntityViewUpdateCorrelatedMutableOnlySubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/mutableonly/EntityViewUpdateCorrelatedMutableOnlySubviewCollectionsTest.java @@ -78,7 +78,7 @@ public void testUpdateReplaceCollection() { } private void validateMutableOnlyNoChange(UpdatableDocumentWithCollectionsView docView) { - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); } @@ -120,7 +120,7 @@ public void testModifyCollectionElement() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/mutableonly/EntityViewUpdateCorrelatedMutableOnlySubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/mutableonly/EntityViewUpdateCorrelatedMutableOnlySubviewTest.java index 0baec7a587..c03e91e446 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/mutableonly/EntityViewUpdateCorrelatedMutableOnlySubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/mutableonly/EntityViewUpdateCorrelatedMutableOnlySubviewTest.java @@ -69,7 +69,7 @@ public void testSimpleUpdate() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -118,7 +118,7 @@ public void testUpdateWithModifyExisting() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.update(Person.class); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/updatableonly/EntityViewUpdateCorrelatedUpdatableOnlySubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/updatableonly/EntityViewUpdateCorrelatedUpdatableOnlySubviewCollectionsTest.java index 04d10609e3..1d8166104d 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/updatableonly/EntityViewUpdateCorrelatedUpdatableOnlySubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/updatableonly/EntityViewUpdateCorrelatedUpdatableOnlySubviewCollectionsTest.java @@ -73,7 +73,7 @@ public void testUpdateReplaceCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (isQueryStrategy()) { @@ -104,7 +104,7 @@ public void testUpdateAddToCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode() || version) { @@ -140,7 +140,7 @@ public void testUpdateAddToNewCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode() || version) { @@ -175,7 +175,7 @@ public void testUpdateAddToCollectionAndModifySubview() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode() || version) { @@ -212,7 +212,7 @@ public void testUpdateAddToNewCollectionAndModifySubview() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode() || version) { @@ -247,7 +247,7 @@ public void testUpdateModifySubviewInCollection() { // Then // Nothing is loaded since nothing that should be cascaded changed - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (isQueryStrategy()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/updatableonly/EntityViewUpdateCorrelatedUpdatableOnlySubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/updatableonly/EntityViewUpdateCorrelatedUpdatableOnlySubviewTest.java index ef4ee3e387..f1094560b9 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/updatableonly/EntityViewUpdateCorrelatedUpdatableOnlySubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/correlated/updatableonly/EntityViewUpdateCorrelatedUpdatableOnlySubviewTest.java @@ -69,7 +69,7 @@ public void testSimpleUpdate() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); @@ -95,7 +95,7 @@ public void testUpdateWithSubview() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (isQueryStrategy()) { @@ -127,7 +127,7 @@ public void testUpdateWithModifySubview() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (isQueryStrategy()) { @@ -157,7 +157,7 @@ public void testUpdateWithModifyExisting() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -189,7 +189,7 @@ public void testUpdateToNull() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (isQueryStrategy()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/embeddable/nested/mutable/EntityViewUpdateMutableNestedEmbeddableCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/embeddable/nested/mutable/EntityViewUpdateMutableNestedEmbeddableCollectionsTest.java index 6af78e7e63..31a602fb1a 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/embeddable/nested/mutable/EntityViewUpdateMutableNestedEmbeddableCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/embeddable/nested/mutable/EntityViewUpdateMutableNestedEmbeddableCollectionsTest.java @@ -77,33 +77,52 @@ public void testUpdateReplaceCollection() { // Then AssertStatementBuilder builder = assertUnorderedQuerySequence(); + int fullDiff = 0; if (isFullMode()) { - fullFetch(builder); - // Since we detect nothing changed, there is no need to flush for version increments - // See com.blazebit.persistence.testsuite.entity.EmbeddableTestEntityEmbeddable.elementCollection2 - // Apparently, Hibernate has problems with handling this correctly - builder.assertDelete() - .forRelation(EmbeddableTestEntity.class, "embeddable.elementCollection2") - .and(); + if (isQueryStrategy()) { + builder.delete(EmbeddableTestEntity.class, "embeddable.oneToMany2") + .insert(EmbeddableTestEntity.class, "embeddable.oneToMany2"); + if (version) { + builder.update(EmbeddableTestEntity.class); + } + fullDiff = 1; + } else { + fullFetch(builder); + // Since we detect nothing changed, there is no need to flush for version increments + // See com.blazebit.persistence.testsuite.entity.EmbeddableTestEntityEmbeddable.elementCollection2 + // Apparently, Hibernate has problems with handling this correctly + builder.assertDelete() + .forRelation(EmbeddableTestEntity.class, "embeddable.elementCollection2") + .and(); + } } builder.validate(); - assertVersionDiff(oldVersion, docView.getVersion(), 0, 0); + assertVersionDiff(oldVersion, docView.getVersion(), 0, fullDiff); AssertStatementBuilder afterBuilder = assertQueriesAfterUpdate(docView); if (isFullMode()) { - fullFetch(afterBuilder); - // Since we detect nothing changed, there is no need to flush for version increments - // See com.blazebit.persistence.testsuite.entity.EmbeddableTestEntityEmbeddable.elementCollection2 - // Apparently, Hibernate has problems with handling this correctly - afterBuilder.assertDelete() - .forRelation(EmbeddableTestEntity.class, "embeddable.elementCollection2") - .and(); + if (isQueryStrategy()) { + afterBuilder.delete(EmbeddableTestEntity.class, "embeddable.oneToMany2") + .insert(EmbeddableTestEntity.class, "embeddable.oneToMany2"); + if (version) { + afterBuilder.update(EmbeddableTestEntity.class); + } + fullDiff = 2; + } else { + fullFetch(afterBuilder); + // Since we detect nothing changed, there is no need to flush for version increments + // See com.blazebit.persistence.testsuite.entity.EmbeddableTestEntityEmbeddable.elementCollection2 + // Apparently, Hibernate has problems with handling this correctly + afterBuilder.assertDelete() + .forRelation(EmbeddableTestEntity.class, "embeddable.elementCollection2") + .and(); + } } afterBuilder.validate(); - assertVersionDiff(oldVersion, docView.getVersion(), 0, 0); + assertVersionDiff(oldVersion, docView.getVersion(), 0, fullDiff); assertSubviewEquals(entity1.getEmbeddable().getOneToMany2(), docView.getEmbeddable().getOneToMany2()); } @@ -117,30 +136,24 @@ public void testUpdateAddToCollection() { // Then AssertStatementBuilder builder = assertUnorderedQuerySequence(); - if (isFullMode()) { - fullFetch(builder); - - if (version) { - builder.update(EmbeddableTestEntity.class); + if (isQueryStrategy()) { + if (isFullMode()) { + builder.delete(EmbeddableTestEntity.class, "embeddable.oneToMany2"); } } else { - if (preferLoadingAndDiffingOverRecreate()) { + if (isFullMode()) { fullFetch(builder); - - if (version) { - builder.update(EmbeddableTestEntity.class); - } } else { - builder.select(EmbeddableTestEntity.class); + if (preferLoadingAndDiffingOverRecreate()) { + fullFetch(builder); - if (version) { - builder.update(EmbeddableTestEntity.class); + } else { + builder.select(EmbeddableTestEntity.class); + + assertReplaceAnd(builder); } - assertReplaceAnd(builder); } - } - if (!isQueryStrategy()) { // See com.blazebit.persistence.testsuite.entity.EmbeddableTestEntityEmbeddable.elementCollection2 // Apparently, Hibernate has problems with handling this correctly builder.assertDelete() @@ -148,18 +161,28 @@ public void testUpdateAddToCollection() { .and(); } - builder.assertInsert() - .forRelation(EmbeddableTestEntity.class, "embeddable.oneToMany2") - .and() + if (version) { + builder.update(EmbeddableTestEntity.class); + } + + builder.insert(EmbeddableTestEntity.class, "embeddable.oneToMany2") .validate(); assertVersionDiff(oldVersion, docView.getVersion(), 1, 1); AssertStatementBuilder afterBuilder = assertQueriesAfterUpdate(docView); + int fullDiff = 1; if (isFullMode()) { - fullFetch(afterBuilder); - // Since we detect nothing changed, there is no need to flush for version increments - if (!isQueryStrategy()) { + if (isQueryStrategy()) { + afterBuilder.delete(EmbeddableTestEntity.class, "embeddable.oneToMany2") + .insert(EmbeddableTestEntity.class, "embeddable.oneToMany2"); + if (version) { + afterBuilder.update(EmbeddableTestEntity.class); + } + fullDiff = 2; + } else { + fullFetch(afterBuilder); + // Since we detect nothing changed, there is no need to flush for version increments // Note that we delete the collection twice, because entity1 and entity2 are in the persistence context // See com.blazebit.persistence.testsuite.entity.EmbeddableTestEntityEmbeddable.elementCollection2 // Apparently, Hibernate has problems with handling this correctly @@ -172,7 +195,7 @@ public void testUpdateAddToCollection() { } } - assertVersionDiff(oldVersion, docView.getVersion(), 1, 1); + assertVersionDiff(oldVersion, docView.getVersion(), 1, fullDiff); afterBuilder.validate(); assertSubviewEquals(entity1.getEmbeddable().getOneToMany2(), docView.getEmbeddable().getOneToMany2()); @@ -188,31 +211,22 @@ public void testUpdateAddToNewCollection() { // Then AssertStatementBuilder builder = assertUnorderedQuerySequence(); - if (isFullMode()) { - fullFetch(builder); - - if (version) { - builder.update(EmbeddableTestEntity.class); + if (isQueryStrategy()) { + if (isFullMode()) { + builder.delete(EmbeddableTestEntity.class, "embeddable.oneToMany2"); } } else { - if (preferLoadingAndDiffingOverRecreate()) { + if (isFullMode()) { fullFetch(builder); - - if (version) { - builder.update(EmbeddableTestEntity.class); - } } else { - builder.select(EmbeddableTestEntity.class); - - if (version) { - builder.update(EmbeddableTestEntity.class); + if (preferLoadingAndDiffingOverRecreate()) { + fullFetch(builder); + } else { + builder.select(EmbeddableTestEntity.class); + assertReplaceAnd(builder); } - assertReplaceAnd(builder); } - } - - if (!isQueryStrategy()) { // See com.blazebit.persistence.testsuite.entity.EmbeddableTestEntityEmbeddable.elementCollection2 // Apparently, Hibernate has problems with handling this correctly builder.assertDelete() @@ -220,6 +234,10 @@ public void testUpdateAddToNewCollection() { .and(); } + if (version) { + builder.update(EmbeddableTestEntity.class); + } + builder.assertInsert() .forRelation(EmbeddableTestEntity.class, "embeddable.oneToMany2") .validate(); @@ -227,10 +245,18 @@ public void testUpdateAddToNewCollection() { AssertStatementBuilder afterBuilder = assertQueriesAfterUpdate(docView); + int fullDiff = 1; if (isFullMode()) { - fullFetch(afterBuilder); - // Since we detect nothing changed, there is no need to flush for version increments - if (!isQueryStrategy()) { + if (isQueryStrategy()) { + afterBuilder.delete(EmbeddableTestEntity.class, "embeddable.oneToMany2") + .insert(EmbeddableTestEntity.class, "embeddable.oneToMany2"); + if (version) { + afterBuilder.update(EmbeddableTestEntity.class); + } + fullDiff = 2; + } else { + fullFetch(afterBuilder); + // Since we detect nothing changed, there is no need to flush for version increments // Note that we delete the collection twice, because entity1 and entity2 are in the persistence context // See com.blazebit.persistence.testsuite.entity.EmbeddableTestEntityEmbeddable.elementCollection2 // Apparently, Hibernate has problems with handling this correctly @@ -243,7 +269,7 @@ public void testUpdateAddToNewCollection() { } } - assertVersionDiff(oldVersion, docView.getVersion(), 1, 1); + assertVersionDiff(oldVersion, docView.getVersion(), 1, fullDiff); afterBuilder.validate(); assertSubviewEquals(entity1.getEmbeddable().getOneToMany2(), docView.getEmbeddable().getOneToMany2()); @@ -312,12 +338,8 @@ private void assertMutableChangeModel(UpdatableEmbeddableEntityWithCollectionsVi } private AssertStatementBuilder assertReplaceAnd(AssertStatementBuilder builder) { - return builder.assertDelete() - .forRelation(EmbeddableTestEntity.class, "embeddable.oneToMany2") - .and() - .assertInsert() - .forRelation(EmbeddableTestEntity.class, "embeddable.oneToMany2") - .and(); + return builder.delete(EmbeddableTestEntity.class, "embeddable.oneToMany2") + .insert(EmbeddableTestEntity.class, "embeddable.oneToMany2"); } private void assertSubviewEquals(Set entities, Set views) { @@ -341,12 +363,6 @@ private void assertSubviewEquals(Set entities, Set 0) { + builder.insert(Document.class, "people"); + if (doc1.getPeople().size() > 1) { + builder.insert(Document.class, "people"); + } + } + } else { + fullFetch(builder); + } + if (version || isQueryStrategy()) { + versionUpdate(builder); + } + } else if (!isQueryStrategy()) { + fullFetch(builder); + } + return builder; + } } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/creatable/EntityViewUpdateCreatableEntityMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/creatable/EntityViewUpdateCreatableEntityMapsTest.java index 673f475017..5acdc89295 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/creatable/EntityViewUpdateCreatableEntityMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/creatable/EntityViewUpdateCreatableEntityMapsTest.java @@ -55,7 +55,7 @@ public void testUpdateReplaceCollection() { // Then // We only fetch the document and the collection in full mode // During dirty detection we can figure out that effectively nothing changed - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -79,7 +79,7 @@ public void testUpdateAddToCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // The added person is not loaded, only a single relation insert is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (version) { @@ -101,7 +101,7 @@ public void testUpdateAddToNewCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // The added person is not loaded, only a single relation insert is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (version) { @@ -124,7 +124,7 @@ public void testUpdateAddToCollectionAndModifyEntity() { // Then // Assert that the document and the people are loaded i.e. a full fetch // The added person is not loaded, only a single relation insert is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (version) { @@ -147,7 +147,7 @@ public void testUpdateAddToNewCollectionAndModifyEntity() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Finally a single relation insert is done but without an update - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (version) { @@ -184,7 +184,7 @@ public void testUpdateAddNullToCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Finally a single relation insert is done for the null element if supported - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (version) { @@ -215,7 +215,7 @@ public void testUpdateSetCollectionToNull() { // Then // Assert that only the document is loaded // Since only an existing person was update, only a single update is generated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -245,7 +245,7 @@ public void testUpdateAddNewEntityToCollection() { // Assert that the document and the people are loaded i.e. a full fetch // In addition, the new person is loaded because of the merge invocation // Finally the person is persisted and a single relation insert is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder) .insert(Person.class); @@ -261,12 +261,6 @@ public void testUpdateAddNewEntityToCollection() { assertEquals(doc1.getContacts(), docView.getContacts()); } - @Override - protected boolean isQueryStrategy() { - // Collection changes always need to be applied on the entity model, can't do that via a query - return false; - } - @Override protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { return builder.assertSelect() diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/creatable/EntityViewUpdateCreatableEntityTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/creatable/EntityViewUpdateCreatableEntityTest.java index c88bf53f06..c2ffc1b317 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/creatable/EntityViewUpdateCreatableEntityTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/creatable/EntityViewUpdateCreatableEntityTest.java @@ -54,7 +54,7 @@ public void testSimpleUpdate() { // Then // Assert that not only the document is loaded - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); @@ -76,7 +76,7 @@ public void testUpdateWithEntity() { // Then // Since the responsiblePerson changed we don't need to load the old responsiblePerson // Unfortunately, the new responsiblePerson has to be loaded by the JPA provider since it has to be merged - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { if (isFullMode()) { @@ -102,7 +102,7 @@ public void testUpdateWithModifyEntity() { // Then // Since the responsiblePerson changed we don't need to load the old responsiblePerson // Unfortunately, the new responsiblePerson has to be loaded by the JPA provider since it has to be merged - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { if (isFullMode()) { @@ -128,7 +128,7 @@ public void testUpdateWithModifyExisting() { // Then // Since we update the old responsiblePerson, load it along with the document for updating it later - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -158,7 +158,7 @@ public void testUpdateToNull() { // Then // Since the responsiblePerson changed we don't need to load the old responsiblePerson // Since the new responsiblePerson is null, we don't need to do anything further - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { if (isFullMode()) { @@ -183,7 +183,7 @@ public void testUpdateToNewPerson() { // Then // Since the responsiblePerson changed we don't need to load the old responsiblePerson // The new responsiblePerson will be persisted - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { if (isFullMode()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutable/EntityViewUpdateMutableEntityCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutable/EntityViewUpdateMutableEntityCollectionsTest.java index d904d047ed..46cc2b4282 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutable/EntityViewUpdateMutableEntityCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutable/EntityViewUpdateMutableEntityCollectionsTest.java @@ -57,10 +57,18 @@ public void testUpdateReplaceCollection() { // Then // Since entities are mutable, assert that the document and the people are loaded always loaded. // During dirty detection we should be able to figure out that the collection didn't change - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - fullFetch(builder); - if (version) { + if (isQueryStrategy()) { + builder.select(Person.class); + if (isFullMode()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + } + } else { + fullFetch(builder); + } + if (version || isQueryStrategy() && isFullMode()) { builder.update(Document.class); } @@ -68,8 +76,16 @@ public void testUpdateReplaceCollection() { // Unfortunately, even after an update, we have to reload the entity to merge again AssertStatementBuilder afterBuilder = assertQueriesAfterUpdate(docView); - fullFetch(afterBuilder); - if (version) { + if (isQueryStrategy()) { + afterBuilder.select(Person.class); + if (isFullMode()) { + afterBuilder.delete(Document.class, "people") + .insert(Document.class, "people"); + } + } else { + fullFetch(afterBuilder); + } + if (version || isQueryStrategy() && isFullMode()) { afterBuilder.update(Document.class); } afterBuilder.validate(); @@ -84,21 +100,39 @@ public void testUpdateAddToCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // In addition, the new person is loaded because of the merge invocation, but only a single relation insert is done - AssertStatementBuilder builder = assertQuerySequence(); - - fullFetch(builder) - .select(Person.class); - if (version) { + AssertStatementBuilder builder = assertUnorderedQuerySequence(); + + if (isQueryStrategy()) { + builder.select(Person.class); + builder.select(Person.class); + if (isFullMode()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + } + } else { + fullFetch(builder) + .select(Person.class); + } + if (version || isQueryStrategy() && isFullMode()) { builder.update(Document.class); } - builder.assertInsert() - .forRelation(Document.class, "people") + builder.insert(Document.class, "people") .validate(); // Unfortunately, even after an update, we have to reload the entity to merge again AssertStatementBuilder afterBuilder = assertQueriesAfterUpdate(docView); - fullFetch(afterBuilder); - if (version) { + if (isQueryStrategy()) { + afterBuilder.select(Person.class); + afterBuilder.select(Person.class); + if (isFullMode()) { + afterBuilder.delete(Document.class, "people") + .insert(Document.class, "people") + .insert(Document.class, "people"); + } + } else { + fullFetch(afterBuilder); + } + if (version || isQueryStrategy() && isFullMode()) { afterBuilder.update(Document.class); } afterBuilder.validate(); @@ -113,21 +147,39 @@ public void testUpdateAddToNewCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // In addition, the new person is loaded because of the merge invocation, but only a single relation insert is done - AssertStatementBuilder builder = assertQuerySequence(); - - fullFetch(builder) - .select(Person.class); - if (version) { + AssertStatementBuilder builder = assertUnorderedQuerySequence(); + + if (isQueryStrategy()) { + builder.select(Person.class); + builder.select(Person.class); + if (isFullMode()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + } + } else { + fullFetch(builder) + .select(Person.class); + } + if (version || isQueryStrategy() && isFullMode()) { builder.update(Document.class); } - builder.assertInsert() - .forRelation(Document.class, "people") + builder.insert(Document.class, "people") .validate(); // Unfortunately, even after an update, we have to reload the entity to merge again AssertStatementBuilder afterBuilder = assertQueriesAfterUpdate(docView); - fullFetch(afterBuilder); - if (version) { + if (isQueryStrategy()) { + afterBuilder.select(Person.class); + afterBuilder.select(Person.class); + if (isFullMode()) { + afterBuilder.delete(Document.class, "people") + .insert(Document.class, "people") + .insert(Document.class, "people"); + } + } else { + fullFetch(afterBuilder); + } + if (version || isQueryStrategy() && isFullMode()) { afterBuilder.update(Document.class); } afterBuilder.validate(); @@ -143,22 +195,40 @@ public void testUpdateAddToCollectionAndModifyEntity() { // Assert that the document and the people are loaded i.e. a full fetch // In addition, the new person is loaded because of the merge invocation // Finally a single relation insert is done and an update to the person is done - AssertStatementBuilder builder = assertQuerySequence(); - - fullFetch(builder) - .select(Person.class); - if (version) { + AssertStatementBuilder builder = assertUnorderedQuerySequence(); + + if (isQueryStrategy()) { + builder.select(Person.class); + builder.select(Person.class); + if (isFullMode()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + } + } else { + fullFetch(builder) + .select(Person.class); + } + if (version || isQueryStrategy() && isFullMode()) { builder.update(Document.class); } builder.update(Person.class) - .assertInsert() - .forRelation(Document.class, "people") + .insert(Document.class, "people") .validate(); // Unfortunately, even after an update, we have to reload the entity to merge again AssertStatementBuilder afterBuilder = assertQueriesAfterUpdate(docView); - fullFetch(afterBuilder); - if (version) { + if (isQueryStrategy()) { + afterBuilder.select(Person.class); + afterBuilder.select(Person.class); + if (isFullMode()) { + afterBuilder.delete(Document.class, "people") + .insert(Document.class, "people") + .insert(Document.class, "people"); + } + } else { + fullFetch(afterBuilder); + } + if (version || isQueryStrategy() && isFullMode()) { afterBuilder.update(Document.class); } afterBuilder.validate(); @@ -175,22 +245,40 @@ public void testUpdateAddToNewCollectionAndModifyEntity() { // Assert that the document and the people are loaded i.e. a full fetch // In addition, the new person is loaded because of the merge invocation // Finally a single relation insert is done and an update to the person is done - AssertStatementBuilder builder = assertQuerySequence(); - - fullFetch(builder) - .select(Person.class); - if (version) { + AssertStatementBuilder builder = assertUnorderedQuerySequence(); + + if (isQueryStrategy()) { + builder.select(Person.class); + builder.select(Person.class); + if (isFullMode()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + } + } else { + fullFetch(builder) + .select(Person.class); + } + if (version || isQueryStrategy() && isFullMode()) { builder.update(Document.class); } builder.update(Person.class) - .assertInsert() - .forRelation(Document.class, "people") + .insert(Document.class, "people") .validate(); // Unfortunately, even after an update, we have to reload the entity to merge again AssertStatementBuilder afterBuilder = assertQueriesAfterUpdate(docView); - fullFetch(afterBuilder); - if (version) { + if (isQueryStrategy()) { + afterBuilder.select(Person.class); + afterBuilder.select(Person.class); + if (isFullMode()) { + afterBuilder.delete(Document.class, "people") + .insert(Document.class, "people") + .insert(Document.class, "people"); + } + } else { + fullFetch(afterBuilder); + } + if (version || isQueryStrategy() && isFullMode()) { afterBuilder.update(Document.class); } afterBuilder.validate(); @@ -206,10 +294,18 @@ public void testUpdateModifyEntityInCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Since only an existing person was update, only a single update is generated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - fullFetch(builder); - if (version) { + if (isQueryStrategy()) { + builder.select(Person.class); + if (isFullMode()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + } + } else { + fullFetch(builder); + } + if (version || isQueryStrategy() && isFullMode()) { builder.update(Document.class); } builder.update(Person.class) @@ -217,8 +313,16 @@ public void testUpdateModifyEntityInCollection() { // Unfortunately, even after an update, we have to reload the entity to merge again AssertStatementBuilder afterBuilder = assertQueriesAfterUpdate(docView); - fullFetch(afterBuilder); - if (version) { + if (isQueryStrategy()) { + afterBuilder.select(Person.class); + if (isFullMode()) { + afterBuilder.delete(Document.class, "people") + .insert(Document.class, "people"); + } + } else { + fullFetch(afterBuilder); + } + if (version || isQueryStrategy() && isFullMode()) { afterBuilder.update(Document.class); } afterBuilder.validate(); @@ -234,25 +338,39 @@ public void testUpdateAddNullToCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Finally a single relation insert is done for the null element if supported - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - fullFetch(builder); - if (version) { + if (isQueryStrategy()) { + builder.select(Person.class); + if (isFullMode()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + } + } else { + fullFetch(builder); + } + if (version || isQueryStrategy() && isFullMode()) { builder.update(Document.class); } if (supportsNullCollectionElements()) { - builder.assertInsert() - .forRelation(Document.class, "people") - .and(); + builder.insert(Document.class, "people"); } builder.validate(); // Unfortunately, even after an update, we have to reload the entity to merge again AssertStatementBuilder afterBuilder = assertQueriesAfterUpdate(docView); - fullFetch(afterBuilder); - if (version) { + if (isQueryStrategy()) { + afterBuilder.select(Person.class); + if (isFullMode()) { + afterBuilder.delete(Document.class, "people") + .insert(Document.class, "people"); + } + } else { + fullFetch(afterBuilder); + } + if (version || isQueryStrategy() && isFullMode()) { afterBuilder.update(Document.class); } afterBuilder.validate(); @@ -270,27 +388,36 @@ public void testUpdateSetCollectionToNull() { // Then // Assert that only the document is loaded // Since only an existing person was update, only a single update is generated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - if (isFullMode()) { - fullFetch(builder); + if (isQueryStrategy()) { + if (version || isFullMode()) { + builder.update(Document.class); + } } else { - builder.select(Document.class); - } - if (version) { - builder.update(Document.class); + if (isFullMode()) { + fullFetch(builder); + } else { + builder.select(Document.class); + } + if (version) { + builder.update(Document.class); + } } - builder.assertDelete() - .forRelation(Document.class, "people") + builder.delete(Document.class, "people") .validate(); // Since the collection is empty we don't have to care for collection element changes AssertStatementBuilder afterBuilder = assertQueriesAfterUpdate(docView); if (isFullMode()) { - fullFetch(afterBuilder); - if (version) { + if (isQueryStrategy()) { + afterBuilder.delete(Document.class, "people"); + } else { + fullFetch(afterBuilder); + } + if (version || isQueryStrategy()) { afterBuilder.update(Document.class); } } @@ -309,29 +436,47 @@ public void testUpdateAddNewEntityToCollection() { // Assert that the document and the people are loaded i.e. a full fetch // In addition, the new person is loaded because of the merge invocation // Finally the person is persisted and a single relation insert is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); + + if (isQueryStrategy()) { + builder.select(Person.class); + if (isFullMode()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + } + } else { + fullFetch(builder); + } + builder.insert(Person.class); - fullFetch(builder) - .insert(Person.class); - if (version) { + if (version || isQueryStrategy() && isFullMode()) { builder.update(Document.class); } - builder.assertInsert() - .forRelation(Document.class, "people") + builder.insert(Document.class, "people") .validate(); // Unfortunately, even after an update, we have to reload the entity to merge again // This time we even have to re-load owned associations because they aren't lazy and could be dirty AssertStatementBuilder afterBuilder = assertQueriesAfterUpdate(docView); - fullFetch(afterBuilder) - .assertSelect() + if (isQueryStrategy()) { + afterBuilder.select(Person.class); + afterBuilder.select(Person.class); + if (isFullMode()) { + afterBuilder.delete(Document.class, "people") + .insert(Document.class, "people") + .insert(Document.class, "people"); + } + } else { + fullFetch(afterBuilder); + } + afterBuilder.assertSelect() .fetching(Person.class, "favoriteDocuments") .fetching(Document.class) .and() .assertSelect() .fetching(Person.class, "localized") .and(); - if (version) { + if (version || isQueryStrategy() && isFullMode()) { afterBuilder.update(Document.class); } if (doesJpaMergeOfRecentlyPersistedEntityForceUpdate()) { @@ -350,12 +495,6 @@ public void testUpdateAddNewEntityToCollection() { } } - @Override - protected boolean isQueryStrategy() { - // Collection changes always need to be applied on the entity model, can't do that via a query - return false; - } - @Override protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { return builder.assertSelect() diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutable/EntityViewUpdateMutableEntityMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutable/EntityViewUpdateMutableEntityMapsTest.java index a9a9ffe576..e6577c1e3d 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutable/EntityViewUpdateMutableEntityMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutable/EntityViewUpdateMutableEntityMapsTest.java @@ -55,7 +55,7 @@ public void testUpdateReplaceCollection() { // Then // Since entities are mutable, assert that the document and the people are loaded always loaded. // During dirty detection we should be able to figure out that the collection didn't change - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (version) { @@ -81,7 +81,7 @@ public void testUpdateAddToCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // In addition, the new person is loaded because of the merge invocation, but only a single relation insert is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder) .select(Person.class); @@ -110,7 +110,7 @@ public void testUpdateAddToNewCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // In addition, the new person is loaded because of the merge invocation, but only a single relation insert is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder) .select(Person.class); @@ -140,7 +140,7 @@ public void testUpdateAddToCollectionAndModifyEntity() { // Assert that the document and the people are loaded i.e. a full fetch // In addition, the new person is loaded because of the merge invocation // Finally a single relation insert is done and an update to the person is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder) .select(Person.class); @@ -172,7 +172,7 @@ public void testUpdateAddToNewCollectionAndModifyEntity() { // Assert that the document and the people are loaded i.e. a full fetch // In addition, the new person is loaded because of the merge invocation // Finally a single relation insert is done and an update to the person is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder) .select(Person.class); @@ -204,7 +204,7 @@ public void testUpdateModifyEntityInCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Since only an existing person was update, only a single update is generated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (version) { @@ -232,7 +232,7 @@ public void testUpdateAddNullToCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Finally a single relation insert is done for the null element if supported - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (version) { @@ -269,7 +269,7 @@ public void testUpdateSetCollectionToNull() { // Then // Assert that only the document is loaded // Since only an existing person was update, only a single update is generated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -308,7 +308,7 @@ public void testUpdateAddNewEntityToCollection() { // Assert that the document and the people are loaded i.e. a full fetch // In addition, the new person is loaded because of the merge invocation // Finally the person is persisted and a single relation insert is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder) .insert(Person.class); @@ -340,12 +340,6 @@ public void testUpdateAddNewEntityToCollection() { assertEquals(doc1.getContacts(), docView.getContacts()); } - @Override - protected boolean isQueryStrategy() { - // Collection changes always need to be applied on the entity model, can't do that via a query - return false; - } - @Override protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { return builder.assertSelect() diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutable/EntityViewUpdateMutableEntityTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutable/EntityViewUpdateMutableEntityTest.java index 5bd792b69c..8698b40edc 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutable/EntityViewUpdateMutableEntityTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutable/EntityViewUpdateMutableEntityTest.java @@ -56,7 +56,7 @@ public void testSimpleUpdate() { // Assert that not only the document is loaded, but also always the responsiblePerson // This might be unexpected for partial strategies // but since we don't know if entities are dirty, we need to be conservative and load the object - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.assertSelect() @@ -102,7 +102,7 @@ public void testUpdateWithEntity() { // Then // Since the responsiblePerson changed we don't need to load the old responsiblePerson // Unfortunately, the new responsiblePerson has to be loaded by the JPA provider since it has to be merged - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { if (isFullMode()) { @@ -147,7 +147,7 @@ public void testUpdateWithModifyEntity() { // Then // Since the responsiblePerson changed we don't need to load the old responsiblePerson // Unfortunately, the new responsiblePerson has to be loaded by the JPA provider since it has to be merged - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { if (isFullMode()) { @@ -195,20 +195,22 @@ public void testUpdateWithModifyExisting() { // Then // Since we update the old responsiblePerson, load it along with the document for updating it later - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.assertSelect() .fetching(Person.class) .and(); + + if (isFullMode() || version) { + builder.update(Document.class); + } } else { fullFetch(builder); - } - if (isQueryStrategy() && isFullMode()) { - builder.update(Document.class); - } else if (version) { - builder.update(Document.class); + if (version) { + builder.update(Document.class); + } } builder.update(Person.class); @@ -247,7 +249,7 @@ public void testUpdateToNull() { // Then // Since the responsiblePerson changed we don't need to load the old responsiblePerson // Since the new responsiblePerson is null, we don't need to do anything further - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { if (isFullMode()) { @@ -287,7 +289,7 @@ public void testUpdateToNewPerson() { // Then // Since the responsiblePerson changed we don't need to load the old responsiblePerson // The new responsiblePerson will be persisted - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { if (isFullMode()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutableonly/EntityViewUpdateMutableOnlyEntityCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutableonly/EntityViewUpdateMutableOnlyEntityCollectionsTest.java index 3a241e80e5..e64e57b6ba 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutableonly/EntityViewUpdateMutableOnlyEntityCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutableonly/EntityViewUpdateMutableOnlyEntityCollectionsTest.java @@ -78,10 +78,14 @@ public void testUpdateModifyEntityInCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Since only an existing person was update, only a single update is generated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - fullFetch(builder); - if (version) { + if (isQueryStrategy()) { + builder.select(Person.class); + } else { + fullFetch(builder); + } + if (version || isQueryStrategy() && isFullMode()) { versionUpdate(builder); } builder.update(Person.class) @@ -89,8 +93,12 @@ public void testUpdateModifyEntityInCollection() { // Unfortunately, even after an update, we have to reload the entity to merge again AssertStatementBuilder afterBuilder = assertQueriesAfterUpdate(docView); - fullFetch(afterBuilder); - if (version) { + if (isQueryStrategy()) { + afterBuilder.select(Person.class); + } else { + fullFetch(afterBuilder); + } + if (version || isQueryStrategy() && isFullMode()) { versionUpdate(afterBuilder); } afterBuilder.validate(); @@ -109,12 +117,6 @@ public void testUpdateSetCollectionToNull() { } } - @Override - protected boolean isQueryStrategy() { - // Collection changes always need to be applied on the entity model, can't do that via a query - return false; - } - @Override protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { return builder.assertSelect() diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutableonly/EntityViewUpdateMutableOnlyEntityMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutableonly/EntityViewUpdateMutableOnlyEntityMapsTest.java index 972ba765ee..29cd778b34 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutableonly/EntityViewUpdateMutableOnlyEntityMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutableonly/EntityViewUpdateMutableOnlyEntityMapsTest.java @@ -78,7 +78,7 @@ public void testUpdateModifyEntityInCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Since only an existing person was update, only a single update is generated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (version) { @@ -120,12 +120,6 @@ public void testUpdateAddNewEntityToCollection() { } } - @Override - protected boolean isQueryStrategy() { - // Collection changes always need to be applied on the entity model, can't do that via a query - return false; - } - @Override protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { return builder.assertSelect() diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutableonly/EntityViewUpdateMutableOnlyEntityTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutableonly/EntityViewUpdateMutableOnlyEntityTest.java index 258a366f95..ba6fc316ac 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutableonly/EntityViewUpdateMutableOnlyEntityTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/mutableonly/EntityViewUpdateMutableOnlyEntityTest.java @@ -57,7 +57,7 @@ public void testSimpleUpdate() { // Assert that not only the document is loaded, but also always the responsiblePerson // This might be unexpected for partial strategies // but since we don't know if entities are dirty, we need to be conservative and load the object - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.assertSelect() @@ -112,7 +112,7 @@ public void testUpdateWithModifyExisting() { // Then // Since we update the old responsiblePerson, load it along with the document for updating it later - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.assertSelect() diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/updatableonly/EntityViewUpdateUpdatableOnlyEntityCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/updatableonly/EntityViewUpdateUpdatableOnlyEntityCollectionsTest.java index 598425a893..9d6fbe2f0b 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/updatableonly/EntityViewUpdateUpdatableOnlyEntityCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/updatableonly/EntityViewUpdateUpdatableOnlyEntityCollectionsTest.java @@ -55,12 +55,16 @@ public void testUpdateReplaceCollection() { // Then // Since entities are not mutable we can detect nothing changed - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { - fullFetch(builder); - if (version) { - versionUpdate(builder); + if (isQueryStrategy()) { + fullUpdate(builder); + } else { + fullFetch(builder); + if (version) { + versionUpdate(builder); + } } } @@ -78,15 +82,19 @@ public void testUpdateAddToCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Finally a single relation insert is done. In partial modes that don't do fetching, a collection recreation is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - if (preferLoadingAndDiffingOverRecreate()) { - fullFetch(builder); - } else { + if (isFullMode()) { + if (isQueryStrategy()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + } else { + fullFetch(builder); + } + } else if (!isQueryStrategy()) { fullFetch(builder); } - - if (version) { + if (version || isQueryStrategy() && isFullMode()) { versionUpdate(builder); } builder.assertInsert() @@ -107,25 +115,19 @@ public void testUpdateAddToNewCollection() { // Assert that the document and the people are loaded in full mode i.e. a full fetch // When fetching like in full mode, we can do a proper diff and see that a single insert is enough // But partial strategies currently favor not fetching, but collection recreations instead - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - if (preferLoadingAndDiffingOverRecreate()) { - fullFetch(builder); - } else { - if (isFullMode()) { - fullFetch(builder); + if (isFullMode()) { + if (isQueryStrategy()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); } else { - builder.select(Document.class) - .assertDelete() - .forRelation(Document.class, "people") - .and() - .assertInsert() - .forRelation(Document.class, "people") - .and(); + fullFetch(builder); } + } else if (!isQueryStrategy()) { + fullFetch(builder); } - - if (version) { + if (version || isQueryStrategy() && isFullMode()) { versionUpdate(builder); } builder.assertInsert() @@ -145,10 +147,19 @@ public void testUpdateAddToCollectionAndModifyEntity() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Finally a single relation insert is done but no update for the person - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - fullFetch(builder); - if (version) { + if (isFullMode()) { + if (isQueryStrategy()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + } else { + fullFetch(builder); + } + } else if (!isQueryStrategy()) { + fullFetch(builder); + } + if (version || isQueryStrategy() && isFullMode()) { versionUpdate(builder); } builder.assertInsert() @@ -168,30 +179,23 @@ public void testUpdateAddToNewCollectionAndModifyEntity() { // Assert that the document and the people are loaded in full mode i.e. a full fetch // When fetching like in full mode, we can do a proper diff and see that a single insert is enough // But partial strategies currently favor not fetching, but collection recreations instead - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - if (preferLoadingAndDiffingOverRecreate()) { - fullFetch(builder); - } else { - if (isFullMode()) { - fullFetch(builder); + if (isFullMode()) { + if (isQueryStrategy()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); } else { - builder.select(Document.class) - .assertDelete() - .forRelation(Document.class, "people") - .and() - .assertInsert() - .forRelation(Document.class, "people") - .and(); + fullFetch(builder); } + } else if (!isQueryStrategy()) { + fullFetch(builder); } - - if (version) { + if (version || isQueryStrategy() && isFullMode()) { versionUpdate(builder); } - builder.assertInsert() - .forRelation(Document.class, "people") - .and(); + + builder.insert(Document.class, "people"); builder.validate(); assertNoUpdateAndReload(docView); @@ -207,11 +211,16 @@ public void testUpdateModifyEntityInCollection() { // Then // Assert that the document and the people are loaded in full mode i.e. a full fetch // Since no collection was changed, no insters or updates are done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { - fullFetch(builder); - if (version) { + if (isQueryStrategy()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + } else { + fullFetch(builder); + } + if (version || isQueryStrategy()) { versionUpdate(builder); } } @@ -230,17 +239,20 @@ public void testUpdateAddNullToCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Finally a single relation insert is done for the null element if supported - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - fullFetch(builder); - if (version) { - versionUpdate(builder); + if (isFullMode()) { + if (isQueryStrategy()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + } else { + fullFetch(builder); + } + } else if (!isQueryStrategy()) { + fullFetch(builder); } - - if (supportsNullCollectionElements()) { - builder.assertInsert() - .forRelation(Document.class, "people") - .and(); + if (version || isQueryStrategy() && isFullMode()) { + versionUpdate(builder); } builder.validate(); @@ -262,14 +274,16 @@ public void testUpdateSetCollectionToNull() { // Then // Assert that only the document is loaded // Since only an existing person was update, only a single update is generated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { - fullFetch(builder); - } else { + if (!isQueryStrategy()) { + fullFetch(builder); + } + } else if (!isQueryStrategy()) { builder.select(Document.class); } - if (version) { + if (version || isQueryStrategy() && isFullMode()) { versionUpdate(builder); } @@ -281,8 +295,12 @@ public void testUpdateSetCollectionToNull() { AssertStatementBuilder afterBuilder = assertQueriesAfterUpdate(docView); if (isFullMode()) { - fullFetch(afterBuilder); - if (version) { + if (isQueryStrategy()) { + afterBuilder.delete(Document.class, "people"); + } else { + fullFetch(afterBuilder); + } + if (version || isQueryStrategy()) { versionUpdate(afterBuilder); } } @@ -301,25 +319,20 @@ public void testUpdateAddNewEntityToCollection() { } catch (PersistenceException | IllegalStateException ex) { // Then assertTrue(ex.getMessage().contains("transient")); - AssertStatementBuilder builder = assertQuerySequence(); - - fullFetch(builder); + if (!isQueryStrategy()) { + AssertStatementBuilder builder = assertUnorderedQuerySequence(); + fullFetch(builder); - if (version) { - versionUpdate(builder); + if (version) { + versionUpdate(builder); + } + builder.validate(); } - builder.validate(); restartTransactionAndReload(); assertEquals(1, doc1.getPeople().size()); } } - @Override - protected boolean isQueryStrategy() { - // Collection changes always need to be applied on the entity model, can't do that via a query - return false; - } - @Override protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { return builder.assertSelect() @@ -333,4 +346,25 @@ protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { protected AssertStatementBuilder versionUpdate(AssertStatementBuilder builder) { return builder.update(Document.class); } + + @Override + protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { + if (isFullMode()) { + if (isQueryStrategy()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + if (doc1.getPeople().size() > 1) { + builder.insert(Document.class, "people"); + } + } else { + fullFetch(builder); + } + if (version || isQueryStrategy()) { + versionUpdate(builder); + } + } else if (!isQueryStrategy()) { + builder.select(Document.class); + } + return builder; + } } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/updatableonly/EntityViewUpdateUpdatableOnlyEntityMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/updatableonly/EntityViewUpdateUpdatableOnlyEntityMapsTest.java index 4fea4608dd..200d1017af 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/updatableonly/EntityViewUpdateUpdatableOnlyEntityMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/updatableonly/EntityViewUpdateUpdatableOnlyEntityMapsTest.java @@ -55,7 +55,7 @@ public void testUpdateReplaceCollection() { // Then // Since entities are not mutable we can detect nothing changed - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -78,7 +78,7 @@ public void testUpdateAddToCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Finally a single relation insert is done. In partial modes that don't do fetching, a collection recreation is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (preferLoadingAndDiffingOverRecreate()) { fullFetch(builder); @@ -107,7 +107,7 @@ public void testUpdateAddToNewCollection() { // Assert that the document and the people are loaded in full mode i.e. a full fetch // When fetching like in full mode, we can do a proper diff and see that a single insert is enough // But partial strategies currently favor not fetching, but collection recreations instead - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (preferLoadingAndDiffingOverRecreate()) { fullFetch(builder); @@ -145,7 +145,7 @@ public void testUpdateAddToCollectionAndModifyEntity() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Finally a single relation insert is done but no update for the person - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (version) { @@ -168,7 +168,7 @@ public void testUpdateAddToNewCollectionAndModifyEntity() { // Assert that the document and the people are loaded in full mode i.e. a full fetch // When fetching like in full mode, we can do a proper diff and see that a single insert is enough // But partial strategies currently favor not fetching, but collection recreations instead - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (preferLoadingAndDiffingOverRecreate()) { fullFetch(builder); @@ -207,7 +207,7 @@ public void testUpdateModifyEntityInCollection() { // Then // Assert that the document and the people are loaded in full mode i.e. a full fetch // Since no collection was changed, no insters or updates are done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -230,7 +230,7 @@ public void testUpdateAddNullToCollection() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Finally a single relation insert is done for the null element if supported - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (version) { @@ -262,7 +262,7 @@ public void testUpdateSetCollectionToNull() { // Then // Assert that only the document is loaded // Since only an existing person was update, only a single update is generated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -301,7 +301,7 @@ public void testUpdateAddNewEntityToCollection() { } catch (PersistenceException | IllegalStateException ex) { // Then assertTrue(ex.getMessage().contains("transient")); - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (version) { @@ -313,12 +313,6 @@ public void testUpdateAddNewEntityToCollection() { } } - @Override - protected boolean isQueryStrategy() { - // Collection changes always need to be applied on the entity model, can't do that via a query - return false; - } - @Override protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { return builder.assertSelect() diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/updatableonly/EntityViewUpdateUpdatableOnlyEntityTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/updatableonly/EntityViewUpdateUpdatableOnlyEntityTest.java index 89f1d655ac..57e5ccc286 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/updatableonly/EntityViewUpdateUpdatableOnlyEntityTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/entity/updatableonly/EntityViewUpdateUpdatableOnlyEntityTest.java @@ -54,7 +54,7 @@ public void testSimpleUpdate() { // Then // Assert that not only the document is loaded - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); @@ -75,7 +75,7 @@ public void testUpdateWithEntity() { // Then // Since the responsiblePerson changed we don't need to load the old responsiblePerson - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); @@ -96,7 +96,7 @@ public void testUpdateWithModifyEntity() { // Then // Since the responsiblePerson changed we don't need to load the old responsiblePerson - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); @@ -119,7 +119,7 @@ public void testUpdateWithModifyExisting() { // Then // Since updates aren't cascaded, the responsiblePerson does not need to be loaded - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -149,7 +149,7 @@ public void testUpdateToNull() { // Then // Since the responsiblePerson changed we don't need to load the old responsiblePerson // Since the new responsiblePerson is null, we don't need to do anything further - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); @@ -186,7 +186,7 @@ public void testUpdateToNewPerson() { } catch (PersistenceException | IllegalStateException ex) { // Then assertTrue(ex.getMessage().contains("transient")); - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { if (isFullMode()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/EntityViewUpdateNestedMutableFlatViewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/EntityViewUpdateNestedMutableFlatViewCollectionsTest.java index d8a68a85c1..cf7d616d68 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/EntityViewUpdateNestedMutableFlatViewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/EntityViewUpdateNestedMutableFlatViewCollectionsTest.java @@ -80,18 +80,25 @@ public void testUpdateCollectionElement() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Finally the person is updated because the primary name changed - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - fullFetch(builder); - if (version) { + if (isQueryStrategy()) { + if (isFullMode()) { + builder.update(Document.class, "nameContainers"); + } + } else { + fullFetch(builder); + } + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } - builder.assertDelete() - .forRelation(Document.class, "nameContainers") - .and() - .assertInsert() - .forRelation(Document.class, "nameContainers") - .validate(); + if (isFullMode()) { + builder.delete(Document.class, "nameContainers") + .insert(Document.class, "nameContainers"); + } else { + builder.update(Document.class, "nameContainers"); + } + builder.validate(); assertNoUpdateAndReload(docView); assertEquals("newPers", doc1.getNameContainers().get(0).getNameObject().getPrimaryName()); @@ -131,10 +138,10 @@ protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { @Override protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { - fullFetch(builder); - if (version) { - builder.update(Document.class); - } + builder.update(Document.class, "nameContainers") + .delete(Document.class, "nameContainers") + .insert(Document.class, "nameContainers"); + builder.update(Document.class); return builder; } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/EntityViewUpdateNestedMutableFlatViewMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/EntityViewUpdateNestedMutableFlatViewMapsTest.java index 3ce931ab8f..4cfff72294 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/EntityViewUpdateNestedMutableFlatViewMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/EntityViewUpdateNestedMutableFlatViewMapsTest.java @@ -80,7 +80,7 @@ public void testUpdateCollectionElement() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Finally the person is updated because the primary name changed - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (version) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/EntityViewUpdateNestedMutableFlatViewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/EntityViewUpdateNestedMutableFlatViewTest.java index a56455f435..54fc79478a 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/EntityViewUpdateNestedMutableFlatViewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/nested/mutable/EntityViewUpdateNestedMutableFlatViewTest.java @@ -71,7 +71,7 @@ public void testUpdateWithSubview() { // Then // Since the owner's name changed we, have to load the document and the owner // We apply the change which results in an update - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/simple/mutable/EntityViewUpdateSimpleMutableFlatViewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/simple/mutable/EntityViewUpdateSimpleMutableFlatViewCollectionsTest.java index 819e5c0246..6191d32bc1 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/simple/mutable/EntityViewUpdateSimpleMutableFlatViewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/simple/mutable/EntityViewUpdateSimpleMutableFlatViewCollectionsTest.java @@ -75,19 +75,26 @@ public void testUpdateCollectionElement() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Finally the person is updated because the primary name changed - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - fullFetch(builder); + if (isQueryStrategy()) { + if (isFullMode()) { + builder.update(Document.class, "names"); + } + } else { + fullFetch(builder); + } - if (version) { + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } - builder.assertDelete() - .forRelation(Document.class, "names") - .and() - .assertInsert() - .forRelation(Document.class, "names") - .validate(); + if (isFullMode()) { + builder.delete(Document.class, "names") + .insert(Document.class, "names"); + } else { + builder.update(Document.class, "names"); + } + builder.validate(); assertNoUpdateAndReload(docView); assertEquals("newPers", doc1.getNames().get(0).getPrimaryName()); @@ -127,10 +134,10 @@ protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { @Override protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { - fullFetch(builder); - if (version) { - builder.update(Document.class); - } + builder.delete(Document.class, "names") + .insert(Document.class, "names") + .update(Document.class, "names"); + builder.update(Document.class); return builder; } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/simple/mutable/EntityViewUpdateSimpleMutableFlatViewMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/simple/mutable/EntityViewUpdateSimpleMutableFlatViewMapsTest.java index 67a6dbfc60..ddc59799a5 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/simple/mutable/EntityViewUpdateSimpleMutableFlatViewMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/simple/mutable/EntityViewUpdateSimpleMutableFlatViewMapsTest.java @@ -75,7 +75,7 @@ public void testUpdateCollectionElement() { // Then // Assert that the document and the people are loaded i.e. a full fetch // Finally the person is updated because the primary name changed - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (version) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/simple/mutable/EntityViewUpdateSimpleMutableFlatViewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/simple/mutable/EntityViewUpdateSimpleMutableFlatViewTest.java index ba638c6716..2b0db9a83d 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/simple/mutable/EntityViewUpdateSimpleMutableFlatViewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/flatview/simple/mutable/EntityViewUpdateSimpleMutableFlatViewTest.java @@ -69,7 +69,7 @@ public void testSimpleUpdate() { // Then // Since the only the documents primaryName changed we only need to load the document // In full mode, the person also has to be loaded - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { if (isFullMode()) { @@ -100,7 +100,7 @@ public void testUpdateWithSubview() { // Then // Since the owner's name changed we, have to load the document and the owner // We apply the change which results in an update - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/cascade/nested/EntityViewRemoveNestedSubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/cascade/nested/EntityViewRemoveNestedSubviewCollectionsTest.java index 35b357b8ff..c4cb86bac9 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/cascade/nested/EntityViewRemoveNestedSubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/cascade/nested/EntityViewRemoveNestedSubviewCollectionsTest.java @@ -71,7 +71,7 @@ public void testSimpleRemove() { remove(docView); // Then - AssertStatementBuilder builder = assertQuerySequence().unordered(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { // Hibernate loads the entities before deleting? @@ -109,7 +109,7 @@ public void testRemoveById() { remove(UpdatableDocumentWithCollectionsView.class, doc1.getId()); // Then - AssertStatementBuilder builder = assertQuerySequence().unordered(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { // Hibernate loads the entities before deleting? diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/cascade/nested/EntityViewRemoveNestedSubviewMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/cascade/nested/EntityViewRemoveNestedSubviewMapsTest.java index 45b86f0fe7..aa795255b7 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/cascade/nested/EntityViewRemoveNestedSubviewMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/cascade/nested/EntityViewRemoveNestedSubviewMapsTest.java @@ -71,7 +71,7 @@ public void testSimpleRemove() { remove(docView); // Then - AssertStatementBuilder builder = assertQuerySequence().unordered(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { // Hibernate loads the entities before deleting? @@ -109,7 +109,7 @@ public void testRemoveById() { remove(UpdatableDocumentWithMapsView.class, doc1.getId()); // Then - AssertStatementBuilder builder = assertQuerySequence().unordered(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { // Hibernate loads the entities before deleting? diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/cascade/nested/EntityViewRemoveNestedSubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/cascade/nested/EntityViewRemoveNestedSubviewTest.java index 35be26b809..31bc67da40 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/cascade/nested/EntityViewRemoveNestedSubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/cascade/nested/EntityViewRemoveNestedSubviewTest.java @@ -72,7 +72,7 @@ public void testSimpleRemove() { remove(docView); // Then - AssertStatementBuilder builder = assertQuerySequence().unordered(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { // Hibernate loads the entities before deleting? @@ -110,7 +110,7 @@ public void testRemoveById() { remove(UpdatableDocumentView.class, doc1.getId()); // Then - AssertStatementBuilder builder = assertQuerySequence().unordered(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { // Hibernate loads the entities before deleting? diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/AbstractEntityViewOrphanRemoveDocumentTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/AbstractEntityViewOrphanRemoveDocumentTest.java index 92486fca71..c2ab3cc5e1 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/AbstractEntityViewOrphanRemoveDocumentTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/AbstractEntityViewOrphanRemoveDocumentTest.java @@ -146,6 +146,10 @@ public void work(EntityManager em) { protected void restartTransactionAndReload() { restartTransaction(); // Load into PC, then access via find + cbf.create(em, Document.class) + .fetch("people") + .where("id").in(ids(doc1)) + .getResultList(); cbf.create(em, Person.class) .where("id").in(ids(p1, p2, p3, p4, p5, p6, p7, p8, p9)) .getResultList(); @@ -211,23 +215,21 @@ protected AssertStatementBuilder deleteDocumentOwned(AssertStatementBuilder buil protected AssertStatementBuilder deleteDocumentOwned(AssertStatementBuilder builder, boolean simpleDelete) { if (!isQueryStrategy() || simpleDelete || !dbmsDialect.supportsModificationQueryInWithClause()) { - builder - .assertDelete().forRelation(Document.class, "contacts").and() - .assertDelete().forRelation(Document.class, "contacts2").and() - .assertDelete().forRelation(Document.class, "people").and() - .assertDelete().forRelation(Document.class, "peopleListBag").and() - .assertDelete().forRelation(Document.class, "peopleCollectionBag").and(); + builder.delete(Document.class, "contacts") + .delete(Document.class, "contacts2") + .delete(Document.class, "people") + .delete(Document.class, "peopleListBag") + .delete(Document.class, "peopleCollectionBag"); } if (supportsNestedEmbeddables()) { - builder - .assertDelete().forRelation(Document.class, "nameContainerMap").and() - .assertDelete().forRelation(Document.class, "nameContainers").and(); + builder.delete(Document.class, "nameContainerMap") + .delete(Document.class, "nameContainers"); } return builder - .assertDelete().forRelation(Document.class, "nameMap").and() - .assertDelete().forRelation(Document.class, "names").and() - .assertDelete().forRelation(Document.class, "stringMap").and() - .assertDelete().forRelation(Document.class, "strings").and() + .delete(Document.class, "nameMap") + .delete(Document.class, "names") + .delete(Document.class, "stringMap") + .delete(Document.class, "strings") ; } @@ -237,14 +239,10 @@ protected AssertStatementBuilder deletePersonOwned(AssertStatementBuilder builde protected AssertStatementBuilder deletePersonOwned(AssertStatementBuilder builder, boolean simpleDelete) { if (!isQueryStrategy() || simpleDelete || !dbmsDialect.supportsModificationQueryInWithClause()) { - builder - .assertDelete().forRelation(Person.class, "favoriteDocuments").and() - ; + builder.delete(Person.class, "favoriteDocuments"); } - return builder - .assertDelete().forRelation(Person.class, "localized").and() - ; + return builder.delete(Person.class, "localized"); } @Override diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/nested/EntityViewOrphanRemoveNestedSubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/nested/EntityViewOrphanRemoveNestedSubviewCollectionsTest.java index 2c40f1888e..e39ac282d4 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/nested/EntityViewOrphanRemoveNestedSubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/nested/EntityViewOrphanRemoveNestedSubviewCollectionsTest.java @@ -21,14 +21,12 @@ import com.blazebit.persistence.testsuite.base.jpa.category.NoEclipselink; import com.blazebit.persistence.testsuite.entity.Document; import com.blazebit.persistence.testsuite.entity.Person; -import com.blazebit.persistence.testsuite.entity.Version; import com.blazebit.persistence.view.FlushMode; import com.blazebit.persistence.view.FlushStrategy; import com.blazebit.persistence.view.spi.EntityViewConfiguration; import com.blazebit.persistence.view.testsuite.update.remove.orphan.AbstractEntityViewOrphanRemoveDocumentTest; import com.blazebit.persistence.view.testsuite.update.remove.orphan.nested.model.FriendPersonCreateView; import com.blazebit.persistence.view.testsuite.update.remove.orphan.nested.model.FriendPersonView; -import com.blazebit.persistence.view.testsuite.update.remove.orphan.nested.model.UpdatableDocumentView; import com.blazebit.persistence.view.testsuite.update.remove.orphan.nested.model.UpdatableDocumentWithCollectionsView; import com.blazebit.persistence.view.testsuite.update.remove.orphan.nested.model.UpdatableResponsiblePersonView; import org.junit.Test; @@ -139,9 +137,14 @@ public void testRemoveCascade() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence().unordered(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - if (!isQueryStrategy()) { + if (isQueryStrategy()) { + if (isFullMode()) { + builder.update(Person.class); + builder.insert(Document.class, "people"); + } + } else { // Hibernate loads the entities before deleting? builder.assertSelect() .fetching(Document.class) @@ -149,29 +152,16 @@ public void testRemoveCascade() { .fetching(Person.class) .and() .select(Person.class); - } else if (isQueryStrategy()) { - // This is just temporary until #507 is fixed - builder.assertSelect() - .fetching(Document.class) - .fetching(Document.class, "people") - .fetching(Person.class) - .and(); - - if (isFullMode()) { - builder.update(Person.class); - } } - if (version) { + if (version || isQueryStrategy() && isFullMode()) { builder.update(Document.class); } deletePersonOwned(builder, true); deletePersonOwned(builder, true); builder.delete(Person.class); builder.delete(Person.class); - builder.assertDelete() - .forRelation(Document.class, "people") - .and(); + builder.delete(Document.class, "people"); builder.validate(); restartTransactionAndReload(); @@ -181,27 +171,30 @@ public void testRemoveCascade() { } public AssertStatementBuilder assertUpdateAndRemove() { - AssertStatementBuilder builder = assertQuerySequence().unordered(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - if (isFullMode() || !isQueryStrategy()) { + if (isQueryStrategy()) { + if (isFullMode()) { + builder.delete(Document.class, "people"); + for (Person person : doc1.getPeople()) { + builder.insert(Document.class, "people"); + } + } + } else { // Hibernate loads the entities before deleting? builder.assertSelect() .fetching(Document.class) .fetching(Document.class, "people") .fetching(Person.class) .and(); - if (!isQueryStrategy()) { - builder.select(Person.class); - } + builder.select(Person.class); } if (isFullMode() && isQueryStrategy()) { builder.update(Person.class); } - // Since we switch to entity flushing because of the collection, we avoid the document flush even in full mode - // This will be fixed with #507 - if (version) { // || isQueryStrategy() && isFullMode()) { + if (version || isQueryStrategy() && isFullMode()) { builder.update(Document.class); } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/nested/EntityViewOrphanRemoveNestedSubviewMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/nested/EntityViewOrphanRemoveNestedSubviewMapsTest.java index 025f586a84..195a2b2ca9 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/nested/EntityViewOrphanRemoveNestedSubviewMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/nested/EntityViewOrphanRemoveNestedSubviewMapsTest.java @@ -21,7 +21,6 @@ import com.blazebit.persistence.testsuite.base.jpa.category.NoEclipselink; import com.blazebit.persistence.testsuite.entity.Document; import com.blazebit.persistence.testsuite.entity.Person; -import com.blazebit.persistence.testsuite.entity.Version; import com.blazebit.persistence.view.FlushMode; import com.blazebit.persistence.view.FlushStrategy; import com.blazebit.persistence.view.spi.EntityViewConfiguration; @@ -139,7 +138,7 @@ public void testRemoveCascade() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence().unordered(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { // Hibernate loads the entities before deleting? @@ -181,7 +180,7 @@ public void testRemoveCascade() { } public AssertStatementBuilder assertUpdateAndRemove() { - AssertStatementBuilder builder = assertQuerySequence().unordered(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode() || !isQueryStrategy()) { // Hibernate loads the entities before deleting? diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/nested/EntityViewOrphanRemoveNestedSubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/nested/EntityViewOrphanRemoveNestedSubviewTest.java index d395cf256c..700cf4cfcc 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/nested/EntityViewOrphanRemoveNestedSubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/remove/orphan/nested/EntityViewOrphanRemoveNestedSubviewTest.java @@ -137,7 +137,7 @@ public void testSetNullCascade() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence().unordered(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { // Hibernate loads the entities before deleting? @@ -168,7 +168,7 @@ public void testSetNullCascade() { } public AssertStatementBuilder assertUpdateAndRemove() { - AssertStatementBuilder builder = assertQuerySequence().unordered(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { // Hibernate loads the entities before deleting? diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/rollback/EntityViewUpdateRollbackCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/rollback/EntityViewUpdateRollbackCollectionsTest.java index 052386b984..c7bbdac30d 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/rollback/EntityViewUpdateRollbackCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/rollback/EntityViewUpdateRollbackCollectionsTest.java @@ -72,16 +72,21 @@ public void testUpdateAddToCollection() { // Then 2 // Assert that the document and the strings are loaded, but only a relation insert is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - fullFetch(builder); + if (isQueryStrategy()) { + if (isFullMode()) { + assertReplaceAnd(builder); + } + } else { + fullFetch(builder); + } - if (version) { + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } - builder.assertInsert() - .forRelation(Document.class, "strings") + builder.insert(Document.class, "strings") .validate(); assertNoUpdateAndReload(docView); @@ -110,19 +115,17 @@ public void testUpdateAddToNewCollection() { // Then 2 // In partial mode, only the document is loaded. In full mode, the strings are also loaded // Since we load the strings in the full mode, we do a proper diff and can compute that only a single item was added - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - if (isFullMode()) { - fullFetch(builder); - } else { - if (preferLoadingAndDiffingOverRecreate()) { - fullFetch(builder); - } else { + if (isQueryStrategy()) { + if (isFullMode()) { assertReplaceAnd(builder); } + } else { + fullFetch(builder); } - if (version) { + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } @@ -134,12 +137,12 @@ public void testUpdateAddToNewCollection() { } private AssertStatementBuilder assertReplaceAnd(AssertStatementBuilder builder) { - return builder.assertDelete() - .forRelation(Document.class, "strings") - .and() - .assertInsert() - .forRelation(Document.class, "strings") - .and(); + builder.delete(Document.class, "strings") + .insert(Document.class, "strings"); + if (doc1.getStrings().size() > 1) { + builder.insert(Document.class, "strings"); + } + return builder; } @Override @@ -152,11 +155,8 @@ protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { @Override protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { - if (version) { - return versionUpdate(fullFetch(builder)); - } else { - return fullFetch(builder); - } + assertReplaceAnd(builder); + return versionUpdate(builder); } @Override diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/graph/EntityViewUpdateSubviewGraphTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/graph/EntityViewUpdateSubviewGraphTest.java index df50a5b633..9ada384c8f 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/graph/EntityViewUpdateSubviewGraphTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/graph/EntityViewUpdateSubviewGraphTest.java @@ -101,26 +101,17 @@ public void testUpdateAddToCollectionAndSet() { // Then AssertStatementBuilder builder = assertUnorderedQuerySequence(); - if (isFullMode()) { - fullFetch(builder); - } else { - // Only need to load the "people" to add an element - // Since "partners" is an inverse collection, we transform the add to an update - builder.assertSelect() - .fetching(Document.class) - .fetching(Document.class, "people") - .fetching(Person.class) - .and(); - } - if (isQueryStrategy()) { if (isFullMode()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people") + .insert(Document.class, "people"); + builder.delete(Person.class, "favoriteDocuments"); builder.update(Person.class) + .update(Person.class) .update(Person.class); } - builder.assertUpdate() - .forEntity(Person.class) - .and(); + builder.update(Person.class); if (isFullMode()) { builder.update(Person.class) @@ -130,10 +121,22 @@ public void testUpdateAddToCollectionAndSet() { .update(Person.class) .update(Person.class); } - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { + if (isFullMode()) { + fullFetch(builder); + } else { + // Only need to load the "people" to add an element + // Since "partners" is an inverse collection, we transform the add to an update + builder.assertSelect() + .fetching(Document.class) + .fetching(Document.class, "people") + .fetching(Person.class) + .and(); + } + if (!isFullMode() && version) { builder.update(Document.class); } @@ -185,20 +188,21 @@ public void testUpdateDifferentNestedGraphs() { if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(builder); + builder.delete(Document.class, "people") + .insert(Document.class, "people") + .insert(Document.class, "people") + .insert(Document.class, "people"); + builder.delete(Person.class, "favoriteDocuments"); + builder.update(Person.class); } - builder.assertUpdate() - .forEntity(Person.class) - .and(); + builder.update(Person.class); if (isFullMode()) { builder.update(Person.class) .update(Person.class); } - builder.assertUpdate() - .forEntity(Document.class) - .and(); + builder.update(Document.class); if (isFullMode()) { builder.update(Person.class) .update(Document.class) @@ -206,7 +210,7 @@ public void testUpdateDifferentNestedGraphs() { .update(Person.class) .update(Person.class); } - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { @@ -241,7 +245,12 @@ public void testUpdateDifferentNestedGraphs() { AssertStatementBuilder afterBuilder = assertQueriesAfterUpdate(docView); if (isFullMode()) { if (isQueryStrategy()) { - fullFetch(afterBuilder) + afterBuilder.delete(Document.class, "people") + .insert(Document.class, "people") + .insert(Document.class, "people") + .insert(Document.class, "people"); + afterBuilder.delete(Person.class, "favoriteDocuments"); + afterBuilder.update(Person.class) .update(Person.class) .update(Person.class) .update(Person.class) @@ -251,9 +260,7 @@ public void testUpdateDifferentNestedGraphs() { .update(Person.class) .update(Person.class) .update(Person.class); - if (version) { - afterBuilder.update(Document.class); - } + afterBuilder.update(Document.class); } else { fullFetch(afterBuilder); if (version) { @@ -265,36 +272,6 @@ public void testUpdateDifferentNestedGraphs() { assertSubviewEquals(doc1.getPeople(), docView.getPeople()); } - public void assertSimpleSubviewEquals(Collection persons, Collection personSubviews) { - if (persons == null) { - assertNull(personSubviews); - return; - } - - assertNotNull(personSubviews); - assertEquals(persons.size(), personSubviews.size()); - for (Person p : persons) { - boolean found = false; - for (UpdatableFriendPersonView pSub : personSubviews) { - if (p.getName().equals(pSub.getName())) { - found = true; - if (p.getFriend() == null) { - assertNull(pSub.getFriend()); - } else { - assertNotNull(pSub.getFriend()); - assertEquals(p.getFriend().getId(), pSub.getFriend().getId()); - assertEquals(p.getFriend().getName(), pSub.getFriend().getName()); - } - break; - } - } - - if (!found) { - Assert.fail("Could not find a person subview instance with the name: " + p.getName()); - } - } - } - public void assertSubviewEquals(Collection persons, Collection personSubviews) { if (persons == null) { assertNull(personSubviews); @@ -352,7 +329,12 @@ protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { @Override protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { - fullFetch(builder) + builder.delete(Document.class, "people") + .insert(Document.class, "people") + .insert(Document.class, "people") + .insert(Document.class, "people") + .delete(Person.class, "favoriteDocuments") + .update(Person.class) .update(Person.class) .update(Person.class) .update(Person.class) @@ -362,9 +344,7 @@ protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { .update(Person.class) .update(Person.class) .update(Person.class); - if (version) { - builder.update(Document.class); - } + builder.update(Document.class); return builder; } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/creatable/EntityViewUpdateNestedCreatableSubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/creatable/EntityViewUpdateNestedCreatableSubviewCollectionsTest.java index 66fe50774f..258ce2cac2 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/creatable/EntityViewUpdateNestedCreatableSubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/creatable/EntityViewUpdateNestedCreatableSubviewCollectionsTest.java @@ -78,15 +78,12 @@ public void testUpdateWithPersonCreateView() { // Then // Assert that only the document is loaded, as we don't need to load the old person - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { - if (isFullMode()) { - fullFetch(builder); - } builder.insert(Person.class); builder.update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { @@ -155,12 +152,8 @@ protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { @Override protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { - fullFetch(builder) - .update(Person.class); - if (version) { - versionUpdate(builder); - } - + versionUpdate(builder); + builder.update(Person.class); return builder; } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/creatable/EntityViewUpdateNestedCreatableSubviewMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/creatable/EntityViewUpdateNestedCreatableSubviewMapsTest.java index 7cea4ccd61..a979b70394 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/creatable/EntityViewUpdateNestedCreatableSubviewMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/creatable/EntityViewUpdateNestedCreatableSubviewMapsTest.java @@ -78,7 +78,7 @@ public void testUpdateWithPersonCreateView() { // Then // Assert that only the document is loaded, as we don't need to load the old person - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/creatable/EntityViewUpdateNestedCreatableSubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/creatable/EntityViewUpdateNestedCreatableSubviewTest.java index 60de6895cb..b3bec84fea 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/creatable/EntityViewUpdateNestedCreatableSubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/creatable/EntityViewUpdateNestedCreatableSubviewTest.java @@ -75,7 +75,7 @@ public void testSimpleUpdate() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -109,7 +109,7 @@ public void testUpdateWithSubview() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.update(Person.class); @@ -143,7 +143,7 @@ public void testUpdateWithModifySubview() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.update(Person.class); @@ -176,7 +176,7 @@ public void testUpdateWithModifyExisting() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -209,7 +209,7 @@ public void testUpdateToNull() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.update(Person.class); @@ -243,7 +243,7 @@ public void testUpdateWithPersonCreateView() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/immutable/EntityViewUpdateNestedImmutableSubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/immutable/EntityViewUpdateNestedImmutableSubviewCollectionsTest.java index fa69500c96..eb6fb1455b 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/immutable/EntityViewUpdateNestedImmutableSubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/immutable/EntityViewUpdateNestedImmutableSubviewCollectionsTest.java @@ -76,21 +76,22 @@ public void testUpdateAddToCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); - - fullFetch(builder); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { + assertReplaceAnd(builder); builder.update(Person.class).update(Person.class); } } else { + fullFetch(builder); + if (isFullMode()) { // In full mode we need to select the added element for cascading the update builder.select(Person.class); } } - if (version) { + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } @@ -115,21 +116,22 @@ public void testUpdateAddToNewCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); - - fullFetch(builder); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { + assertReplaceAnd(builder); builder.update(Person.class).update(Person.class); } } else { + fullFetch(builder); + if (isFullMode()) { // In full mode we need to select the added element for cascading the update builder.select(Person.class); } } - if (version) { + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } @@ -154,21 +156,22 @@ public void testUpdateAddToCollectionAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); - - fullFetch(builder); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { + assertReplaceAnd(builder); builder.update(Person.class).update(Person.class); } } else { + fullFetch(builder); + if (isFullMode()) { // In full mode we need to select the added element for cascading the update builder.select(Person.class); } } - if (version) { + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } @@ -197,21 +200,22 @@ public void testUpdateAddToNewCollectionAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); - - fullFetch(builder); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { + assertReplaceAnd(builder); builder.update(Person.class).update(Person.class); } } else { + fullFetch(builder); + if (isFullMode()) { // In full mode we need to select the added element for cascading the update builder.select(Person.class); } } - if (version) { + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } @@ -238,14 +242,14 @@ public void testUpdateModifyCollectionElement() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(builder); + assertReplaceAnd(builder); } builder.update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { @@ -276,14 +280,12 @@ public void testUpdateModifyCollectionElementCopy() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(builder).update(Person.class); - if (version) { - versionUpdate(builder); - } + assertReplaceAnd(builder).update(Person.class); + versionUpdate(builder); } } else { if (isFullMode()) { @@ -316,14 +318,14 @@ public void testUpdateModifyCollectionElementAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(builder); + assertReplaceAnd(builder); } builder.update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { @@ -354,14 +356,14 @@ public void testUpdateModifyCollectionElementSetToNull() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(builder); + assertReplaceAnd(builder); } builder.update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { @@ -414,10 +416,8 @@ private void assertNoCollectionUpdateAndReload(UpdatableDocumentWithCollectionsV if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(afterBuilder).update(Person.class); - if (version) { - versionUpdate(afterBuilder); - } + assertReplaceAnd(afterBuilder).update(Person.class); + versionUpdate(afterBuilder); } } else { if (isFullMode()) { @@ -431,6 +431,15 @@ private void assertNoCollectionUpdateAndReload(UpdatableDocumentWithCollectionsV afterBuilder.validate(); } + private AssertStatementBuilder assertReplaceAnd(AssertStatementBuilder builder) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + if (doc1.getPeople().size() > 1) { + builder.insert(Document.class, "people"); + } + return builder; + } + @Override protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { return builder.assertSelect() @@ -442,12 +451,10 @@ protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { @Override protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { - fullFetch(builder) + assertReplaceAnd(builder) .update(Person.class) .update(Person.class); - if (version) { - builder.update(Document.class); - } + builder.update(Document.class); return builder; } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/immutable/EntityViewUpdateNestedImmutableSubviewMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/immutable/EntityViewUpdateNestedImmutableSubviewMapsTest.java index a07c47e04f..dff66c2110 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/immutable/EntityViewUpdateNestedImmutableSubviewMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/immutable/EntityViewUpdateNestedImmutableSubviewMapsTest.java @@ -76,7 +76,7 @@ public void testUpdateAddToCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); @@ -116,7 +116,7 @@ public void testUpdateAddToNewCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); @@ -156,7 +156,7 @@ public void testUpdateAddToCollectionAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); @@ -200,7 +200,7 @@ public void testUpdateAddToNewCollectionAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); @@ -242,7 +242,7 @@ public void testUpdateModifyCollectionElement() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -280,7 +280,7 @@ public void testUpdateModifyCollectionElementCopy() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -320,7 +320,7 @@ public void testUpdateModifyCollectionElementAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -358,7 +358,7 @@ public void testUpdateModifyCollectionElementSetToNull() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/immutable/EntityViewUpdateNestedImmutableSubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/immutable/EntityViewUpdateNestedImmutableSubviewTest.java index 0da36b51e6..4c67f0c8a6 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/immutable/EntityViewUpdateNestedImmutableSubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/immutable/EntityViewUpdateNestedImmutableSubviewTest.java @@ -72,7 +72,7 @@ public void testSimpleUpdate() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -106,7 +106,7 @@ public void testUpdateWithSubview() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.update(Person.class); @@ -140,7 +140,7 @@ public void testUpdateWithModifySubview() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.update(Person.class); @@ -173,7 +173,7 @@ public void testUpdateWithModifyExisting() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -206,7 +206,7 @@ public void testUpdateToNull() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.update(Person.class); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutable/EntityViewUpdateNestedMutableSubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutable/EntityViewUpdateNestedMutableSubviewCollectionsTest.java index 97b953a3ee..ebeb89f83b 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutable/EntityViewUpdateNestedMutableSubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutable/EntityViewUpdateNestedMutableSubviewCollectionsTest.java @@ -76,7 +76,7 @@ public void testUpdateAddToCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (isQueryStrategy()) { @@ -90,11 +90,13 @@ public void testUpdateAddToCollection() { } } } else { - builder.assertSelect() - .fetching(Document.class) - .fetching(Document.class, "people") - .fetching(Person.class) - .and(); + if (!isQueryStrategy()) { + builder.assertSelect() + .fetching(Document.class) + .fetching(Document.class, "people") + .fetching(Person.class) + .and(); + } if (version) { builder.update(Document.class); @@ -122,7 +124,7 @@ public void testUpdateAddToNewCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (isQueryStrategy()) { @@ -136,11 +138,13 @@ public void testUpdateAddToNewCollection() { } } } else { - builder.assertSelect() - .fetching(Document.class) - .fetching(Document.class, "people") - .fetching(Person.class) - .and(); + if (!isQueryStrategy()) { + builder.assertSelect() + .fetching(Document.class) + .fetching(Document.class, "people") + .fetching(Person.class) + .and(); + } if (version) { builder.update(Document.class); @@ -168,11 +172,11 @@ public void testUpdateAddToCollectionAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { - fullFetch(builder); if (isFullMode()) { + assertReplaceAnd(builder); builder.update(Person.class); builder.update(Person.class); builder.update(Person.class); @@ -180,7 +184,7 @@ public void testUpdateAddToCollectionAndModify() { builder.update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { @@ -220,11 +224,11 @@ public void testUpdateAddToNewCollectionAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { - fullFetch(builder); if (isFullMode()) { + assertReplaceAnd(builder); builder.update(Person.class); builder.update(Person.class); builder.update(Person.class); @@ -232,15 +236,15 @@ public void testUpdateAddToNewCollectionAndModify() { builder.update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { - fullFetch(builder) - .assertSelect() - .fetching(Person.class) - .fetching(Person.class) - .and(); + fullFetch(builder) + .assertSelect() + .fetching(Person.class) + .fetching(Person.class) + .and(); if (version) { builder.update(Document.class); @@ -270,17 +274,17 @@ public void testUpdateModifyCollectionElement() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(builder) + assertReplaceAnd(builder) .update(Person.class); } builder.update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { @@ -321,16 +325,16 @@ public void testUpdateModifyCollectionElementCopy() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(builder); + assertReplaceAnd(builder); builder.update(Person.class); } builder.update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { @@ -363,17 +367,17 @@ public void testUpdateModifyCollectionElementAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(builder); + assertReplaceAnd(builder); } builder.update(Person.class) .update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { @@ -415,15 +419,15 @@ public void testUpdateModifyCollectionElementSetToNull() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(builder); + assertReplaceAnd(builder); } builder.update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { @@ -485,11 +489,9 @@ private void assertNoCollectionUpdateAndReload(UpdatableDocumentWithCollectionsV if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(afterBuilder) + assertReplaceAnd(afterBuilder) .update(Person.class); - if (version) { - versionUpdate(afterBuilder); - } + versionUpdate(afterBuilder); } } else { if (isFullMode()) { @@ -508,11 +510,9 @@ private void assertNoCollectionUpdateFullAndReload(UpdatableDocumentWithCollecti if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(afterBuilder).update(Person.class) + assertReplaceAnd(afterBuilder).update(Person.class) .update(Person.class); - if (version) { - versionUpdate(afterBuilder); - } + versionUpdate(afterBuilder); } } else { if (isFullMode()) { @@ -526,6 +526,15 @@ private void assertNoCollectionUpdateFullAndReload(UpdatableDocumentWithCollecti afterBuilder.validate(); } + private AssertStatementBuilder assertReplaceAnd(AssertStatementBuilder builder) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + if (doc1.getPeople().size() > 1) { + builder.insert(Document.class, "people"); + } + return builder; + } + @Override protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { return builder.assertSelect() @@ -538,14 +547,12 @@ protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { @Override protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { - fullFetch(builder) + assertReplaceAnd(builder) .update(Person.class) .update(Person.class) .update(Person.class) .update(Person.class); - if (version) { - builder.update(Document.class); - } + builder.update(Document.class); return builder; } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutable/EntityViewUpdateNestedMutableSubviewMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutable/EntityViewUpdateNestedMutableSubviewMapsTest.java index 41d2f51d20..4e818693cb 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutable/EntityViewUpdateNestedMutableSubviewMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutable/EntityViewUpdateNestedMutableSubviewMapsTest.java @@ -76,7 +76,7 @@ public void testUpdateAddToCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (isQueryStrategy()) { @@ -123,7 +123,7 @@ public void testUpdateAddToNewCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (isQueryStrategy()) { @@ -169,7 +169,7 @@ public void testUpdateAddToCollectionAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { fullFetch(builder); @@ -220,7 +220,7 @@ public void testUpdateAddToNewCollectionAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { fullFetch(builder); @@ -268,7 +268,7 @@ public void testUpdateModifyCollectionElement() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -320,7 +320,7 @@ public void testUpdateModifyCollectionElementCopy() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -363,7 +363,7 @@ public void testUpdateModifyCollectionElementAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -416,7 +416,7 @@ public void testUpdateModifyCollectionElementSetToNull() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutable/EntityViewUpdateNestedMutableSubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutable/EntityViewUpdateNestedMutableSubviewTest.java index c84279a9dc..6edb0a7298 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutable/EntityViewUpdateNestedMutableSubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutable/EntityViewUpdateNestedMutableSubviewTest.java @@ -72,7 +72,7 @@ public void testSimpleUpdate() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -107,7 +107,7 @@ public void testUpdateWithSubview() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -154,7 +154,7 @@ public void testUpdateWithModifySubview() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.update(Person.class) @@ -201,7 +201,7 @@ public void testUpdateWithModifyExisting() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -239,7 +239,7 @@ public void testUpdateToNull() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.update(Person.class); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutableonly/EntityViewUpdateNestedMutableOnlySubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutableonly/EntityViewUpdateNestedMutableOnlySubviewCollectionsTest.java index 20791c9b83..3b2f7c1378 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutableonly/EntityViewUpdateNestedMutableOnlySubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutableonly/EntityViewUpdateNestedMutableOnlySubviewCollectionsTest.java @@ -76,7 +76,7 @@ public void testUpdateAddToCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (isQueryStrategy()) { @@ -90,11 +90,13 @@ public void testUpdateAddToCollection() { } } } else { - builder.assertSelect() - .fetching(Document.class) - .fetching(Document.class, "people") - .fetching(Person.class) - .and(); + if (!isQueryStrategy()) { + builder.assertSelect() + .fetching(Document.class) + .fetching(Document.class, "people") + .fetching(Person.class) + .and(); + } if (version) { builder.update(Document.class); @@ -122,7 +124,7 @@ public void testUpdateAddToNewCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (isQueryStrategy()) { @@ -136,11 +138,13 @@ public void testUpdateAddToNewCollection() { } } } else { - builder.assertSelect() - .fetching(Document.class) - .fetching(Document.class, "people") - .fetching(Person.class) - .and(); + if (!isQueryStrategy()) { + builder.assertSelect() + .fetching(Document.class) + .fetching(Document.class, "people") + .fetching(Person.class) + .and(); + } if (version) { builder.update(Document.class); @@ -168,25 +172,26 @@ public void testUpdateAddToCollectionAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { - fullFetch(builder); if (isFullMode()) { builder.update(Person.class); builder.update(Person.class); builder.update(Person.class); + builder.delete(Document.class, "people") + .insert(Document.class, "people"); } builder.update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { fullFetch(builder) .assertSelect() - .fetching(Person.class) - .fetching(Person.class) + .fetching(Person.class) + .fetching(Person.class) .and(); if (version) { builder.update(Document.class); @@ -219,18 +224,19 @@ public void testUpdateAddToNewCollectionAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { - fullFetch(builder); if (isFullMode()) { builder.update(Person.class); builder.update(Person.class); builder.update(Person.class); + builder.delete(Document.class, "people") + .insert(Document.class, "people"); } builder.update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { @@ -284,17 +290,18 @@ public void testUpdateModifyCollectionElementCopy() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(builder); builder.update(Person.class); + builder.delete(Document.class, "people") + .insert(Document.class, "people"); } builder.update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { @@ -375,32 +382,16 @@ public void assertSubviewEquals(Collection persons, Collection 1) { + builder.insert(Document.class, "people"); } + builder.update(Document.class); return builder; } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutableonly/EntityViewUpdateNestedMutableOnlySubviewMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutableonly/EntityViewUpdateNestedMutableOnlySubviewMapsTest.java index b023723bfd..fce6cc1378 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutableonly/EntityViewUpdateNestedMutableOnlySubviewMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutableonly/EntityViewUpdateNestedMutableOnlySubviewMapsTest.java @@ -76,7 +76,7 @@ public void testUpdateAddToCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (isQueryStrategy()) { @@ -123,7 +123,7 @@ public void testUpdateAddToNewCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { if (isQueryStrategy()) { @@ -169,7 +169,7 @@ public void testUpdateAddToCollectionAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { fullFetch(builder); @@ -220,7 +220,7 @@ public void testUpdateAddToNewCollectionAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { fullFetch(builder); @@ -286,7 +286,7 @@ public void testUpdateModifyCollectionElementCopy() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutableonly/EntityViewUpdateNestedMutableOnlySubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutableonly/EntityViewUpdateNestedMutableOnlySubviewTest.java index fe73fc1626..a76e77306c 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutableonly/EntityViewUpdateNestedMutableOnlySubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/mutableonly/EntityViewUpdateNestedMutableOnlySubviewTest.java @@ -73,7 +73,7 @@ public void testSimpleUpdate() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -123,7 +123,7 @@ public void testUpdateWithModifyExisting() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewCollectionsTest.java index 991a588380..42e4b33d0c 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewCollectionsTest.java @@ -76,21 +76,21 @@ public void testUpdateAddToCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); - - fullFetch(builder); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { + assertReplaceAnd(builder); builder.update(Person.class).update(Person.class); } } else { + fullFetch(builder); if (isFullMode()) { // In full mode we need to select the added element for cascading the update builder.select(Person.class); } } - if (version) { + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } @@ -115,21 +115,22 @@ public void testUpdateAddToNewCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); - - fullFetch(builder); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { + assertReplaceAnd(builder); builder.update(Person.class).update(Person.class); } } else { + fullFetch(builder); + if (isFullMode()) { // In full mode we need to select the added element for cascading the update builder.select(Person.class); } } - if (version) { + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } @@ -154,21 +155,22 @@ public void testUpdateAddToCollectionAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); - - fullFetch(builder); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { + assertReplaceAnd(builder); builder.update(Person.class).update(Person.class); } } else { + fullFetch(builder); + if (isFullMode()) { // In full mode we need to select the added element for cascading the update builder.select(Person.class); } } - if (version) { + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } @@ -197,21 +199,22 @@ public void testUpdateAddToNewCollectionAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); - - fullFetch(builder); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { + assertReplaceAnd(builder); builder.update(Person.class).update(Person.class); } } else { + fullFetch(builder); + if (isFullMode()) { // In full mode we need to select the added element for cascading the update builder.select(Person.class); } } - if (version) { + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } @@ -238,14 +241,14 @@ public void testUpdateModifyCollectionElement() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(builder); + assertReplaceAnd(builder); } builder.update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { @@ -276,14 +279,12 @@ public void testUpdateModifyCollectionElementCopy() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(builder).update(Person.class); - if (version) { - versionUpdate(builder); - } + assertReplaceAnd(builder).update(Person.class); + versionUpdate(builder); } } else { if (isFullMode()) { @@ -316,14 +317,14 @@ public void testUpdateModifyCollectionElementAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(builder); + assertReplaceAnd(builder); } builder.update(Person.class); - if (version) { + if (version ||isFullMode()) { builder.update(Document.class); } } else { @@ -354,14 +355,14 @@ public void testUpdateModifyCollectionElementSetToNull() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(builder); + assertReplaceAnd(builder); } builder.update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { @@ -414,10 +415,8 @@ private void assertNoCollectionUpdateAndReload(UpdatableDocumentWithCollectionsV if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(afterBuilder).update(Person.class); - if (version) { - versionUpdate(afterBuilder); - } + assertReplaceAnd(afterBuilder).update(Person.class); + versionUpdate(afterBuilder); } } else { if (isFullMode()) { @@ -431,6 +430,17 @@ private void assertNoCollectionUpdateAndReload(UpdatableDocumentWithCollectionsV afterBuilder.validate(); } + protected AssertStatementBuilder assertReplaceAnd(AssertStatementBuilder builder) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + + if (doc1.getPeople().size() > 1) { + builder.insert(Document.class, "people"); + } + + return builder; + } + @Override protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { return builder.assertSelect() @@ -442,12 +452,10 @@ protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { @Override protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { - fullFetch(builder) - .update(Person.class) + assertReplaceAnd(builder); + builder.update(Person.class) .update(Person.class); - if (version) { - builder.update(Document.class); - } + builder.update(Document.class); return builder; } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewMapsTest.java index b78a73e869..37c5c4ddc2 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewMapsTest.java @@ -76,7 +76,7 @@ public void testUpdateAddToCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); @@ -116,7 +116,7 @@ public void testUpdateAddToNewCollection() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); @@ -156,7 +156,7 @@ public void testUpdateAddToCollectionAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); @@ -200,7 +200,7 @@ public void testUpdateAddToNewCollectionAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); @@ -242,7 +242,7 @@ public void testUpdateModifyCollectionElement() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -280,7 +280,7 @@ public void testUpdateModifyCollectionElementCopy() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -320,7 +320,7 @@ public void testUpdateModifyCollectionElementAndModify() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -358,7 +358,7 @@ public void testUpdateModifyCollectionElementSetToNull() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewTest.java index fc5827c26b..b07a96884d 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/nested/updatableonly/EntityViewUpdateNestedUpdatableOnlySubviewTest.java @@ -72,7 +72,7 @@ public void testSimpleUpdate() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -106,7 +106,7 @@ public void testUpdateWithSubview() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.update(Person.class); @@ -140,7 +140,7 @@ public void testUpdateWithModifySubview() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.update(Person.class); @@ -173,7 +173,7 @@ public void testUpdateWithModifyExisting() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -206,7 +206,7 @@ public void testUpdateToNull() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.update(Person.class); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/creatable/EntityViewUpdateSimpleCreatableSubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/creatable/EntityViewUpdateSimpleCreatableSubviewCollectionsTest.java index 887cb950fa..5785698b2d 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/creatable/EntityViewUpdateSimpleCreatableSubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/creatable/EntityViewUpdateSimpleCreatableSubviewCollectionsTest.java @@ -76,19 +76,25 @@ public void testUpdateWithPersonCreateView() { // Then // Assert that only the document is loaded, as we don't need to load the old person - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - if (isFullMode()) { - fullFetch(builder); + if (isQueryStrategy()) { + if (isFullMode()) { + assertReplaceAnd(builder); + } } else { -// builder.select(Document.class); - // Adding elements to a list requires full fetching - fullFetch(builder); + if (isFullMode()) { + fullFetch(builder); + } else { + // builder.select(Document.class); + // Adding elements to a list requires full fetching + fullFetch(builder); + } } builder.insert(Person.class); - if (version) { + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } builder.assertInsert() @@ -125,10 +131,20 @@ public static void assertSubviewEquals(Collection persons, Collection 1) { + builder.insert(Document.class, "people"); + } + return builder; + } + @Override - protected boolean isQueryStrategy() { - // Collection changes always need to be applied on the entity model, can't do that via a query - return false; + protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { + assertReplaceAnd(builder); + versionUpdate(builder); + return builder; } @Override diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/creatable/EntityViewUpdateSimpleCreatableSubviewMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/creatable/EntityViewUpdateSimpleCreatableSubviewMapsTest.java index ce13f0b597..1b2735cdc9 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/creatable/EntityViewUpdateSimpleCreatableSubviewMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/creatable/EntityViewUpdateSimpleCreatableSubviewMapsTest.java @@ -76,7 +76,7 @@ public void testUpdateWithPersonCreateView() { // Then // Assert that only the document is loaded, as we don't need to load the old person - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -127,12 +127,6 @@ public static void assertSubviewEquals(Map persons, Map persons, Collection 1) { + builder.insert(Document.class, "people"); + } + return builder; } @Override - protected boolean isQueryStrategy() { - // Collection changes always need to be applied on the entity model, can't do that via a query - return false; + protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { + return versionUpdate(assertReplaceAnd(builder)); } @Override diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/immutable/EntityViewUpdateSimpleImmutableSubviewMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/immutable/EntityViewUpdateSimpleImmutableSubviewMapsTest.java index 3f6ced12d8..746b5077c9 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/immutable/EntityViewUpdateSimpleImmutableSubviewMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/immutable/EntityViewUpdateSimpleImmutableSubviewMapsTest.java @@ -76,7 +76,7 @@ public void testUpdateReplaceCollection() { // Assert that the document and the people are loaded in full mode. // During dirty detection we should be able to figure out that nothing changed // So partial modes wouldn't load anything and both won't cause any updates - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -105,7 +105,7 @@ public void testUpdateAddToCollection() { // Then // Assert that the document and the people are loaded, but only a relation insert is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); @@ -136,7 +136,7 @@ public void testUpdateAddToNewCollection() { // Then // In partial mode, only the document is loaded. In full mode, the people are also loaded // Since we load the people in the full mode, we do a proper diff and can compute that only a single item was added - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -193,12 +193,6 @@ private AssertStatementBuilder assertReplaceAnd(AssertStatementBuilder builder) .and(); } - @Override - protected boolean isQueryStrategy() { - // Collection changes always need to be applied on the entity model, can't do that via a query - return false; - } - @Override protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { return builder.assertSelect() diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/immutable/EntityViewUpdateSimpleImmutableSubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/immutable/EntityViewUpdateSimpleImmutableSubviewTest.java index ab26e77f3c..6e681315d1 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/immutable/EntityViewUpdateSimpleImmutableSubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/immutable/EntityViewUpdateSimpleImmutableSubviewTest.java @@ -70,7 +70,7 @@ public void testSimpleUpdate() { // Then // Assert that only the document is loaded and finally also updated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); @@ -98,7 +98,7 @@ public void testUpdateWithSubview() { // Then // Assert that only the document is loaded and finally also updated // There is no need to actually load the person - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); @@ -124,7 +124,7 @@ public void testUpdateWithModifySubview() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); @@ -149,7 +149,7 @@ public void testUpdateWithModifyExisting() { update(docView); // Then - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -184,7 +184,7 @@ public void testUpdateToNull() { // Then // Assert that only the document is loaded and finally also updated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewCollectionsTest.java index 868088ca2c..d6d232c717 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewCollectionsTest.java @@ -76,14 +76,17 @@ public void testUpdateReplaceCollection() { // Assert that the document and the people are loaded in full mode. // During dirty detection we should be able to figure out that nothing changed // So partial modes wouldn't load anything and both won't cause any updates - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { - fullFetch(builder); if (isQueryStrategy()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); builder.update(Person.class); + } else { + fullFetch(builder); } - if (version) { + if (version || isQueryStrategy()) { builder.update(Document.class); } } @@ -93,11 +96,10 @@ public void testUpdateReplaceCollection() { AssertStatementBuilder afterBuilder = assertQueriesAfterUpdate(docView); if (isQueryStrategy()) { if (isFullMode()) { - fullFetch(afterBuilder) + afterBuilder.delete(Document.class, "people") + .insert(Document.class, "people") .update(Person.class); - if (version) { - afterBuilder.update(Document.class); - } + afterBuilder.update(Document.class); } } else { if (isFullMode()) { @@ -127,11 +129,15 @@ public void testUpdateAddToCollection() { // Assert that the document and the people are loaded, but only a relation insert is done // The full mode also has to load the person that is added and apply the changes // But since nothing is changed, no update is subsequently generated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - fullFetch(builder); + if (!isQueryStrategy()) { + fullFetch(builder); + } if (isFullMode()) { if (isQueryStrategy()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); builder.update(Person.class) .update(Person.class); } else { @@ -139,7 +145,7 @@ public void testUpdateAddToCollection() { } } - if (version) { + if (version || isQueryStrategy() && isFullMode()) { builder.update(Document.class); } @@ -168,11 +174,15 @@ public void testUpdateAddToNewCollection() { // Since we load the people in the full mode, we do a proper diff and can compute that only a single item was added // The full mode also has to load the person that is added and apply the changes // But since nothing is changed, no update is subsequently generated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - fullFetch(builder); + if (!isQueryStrategy()) { + fullFetch(builder); + } if (isFullMode()) { if (isQueryStrategy()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); builder.update(Person.class) .update(Person.class); } else { @@ -180,7 +190,7 @@ public void testUpdateAddToNewCollection() { } } - if (version) { + if (version || isQueryStrategy() && isFullMode()) { builder.update(Document.class); } @@ -208,21 +218,22 @@ public void testUpdateAddToCollectionAndModify() { // Assert that the document and the people are loaded i.e. a full fetch // In addition, the new person is loaded because it is dirty // Finally a single relation insert is done and an update to the person is done - AssertStatementBuilder builder = assertQuerySequence(); - - fullFetch(builder); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); builder.update(Person.class); } builder.update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { + fullFetch(builder); builder.select(Person.class); if (version) { builder.update(Document.class); @@ -257,21 +268,23 @@ public void testUpdateAddToNewCollectionAndModify() { // Assert that the document and the people are loaded i.e. a full fetch // In addition, the new person is loaded because it is dirty // Finally a single relation insert is done and an update to the person is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - fullFetch(builder); if (isQueryStrategy()) { if (isFullMode()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); builder.update(Person.class); } builder.update(Person.class); - if (version) { + if (version || isFullMode()) { builder.update(Document.class); } } else { + fullFetch(builder); builder.select(Person.class); if (version) { builder.update(Document.class); @@ -323,10 +336,18 @@ protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { @Override protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { - fullFetch(builder) - .update(Person.class) + if (isQueryStrategy()) { + builder.delete(Document.class, "people") + .insert(Document.class, "people"); + if (doc1.getPeople().size() > 1) { + builder.insert(Document.class, "people"); + } + } else { + fullFetch(builder); + } + builder.update(Person.class) .update(Person.class); - if (version) { + if (version | isQueryStrategy() && isFullMode()) { versionUpdate(builder); } return builder; diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewMapsTest.java index 946aa9aedf..6c45193fa4 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewMapsTest.java @@ -76,7 +76,7 @@ public void testUpdateReplaceCollection() { // Assert that the document and the people are loaded in full mode. // During dirty detection we should be able to figure out that nothing changed // So partial modes wouldn't load anything and both won't cause any updates - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -108,7 +108,7 @@ public void testUpdateAddToCollection() { // Assert that the document and the people are loaded, but only a relation insert is done // The full mode also has to load the person that is added and apply the changes // But since nothing is changed, no update is subsequently generated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (isQueryStrategy()) { @@ -151,7 +151,7 @@ public void testUpdateAddToNewCollection() { // Since we load the people in the full mode, we do a proper diff and can compute that only a single item was added // The full mode also has to load the person that is added and apply the changes // But since nothing is changed, no update is subsequently generated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (isQueryStrategy()) { @@ -193,7 +193,7 @@ public void testUpdateAddToCollectionAndModify() { // Assert that the document and the people are loaded i.e. a full fetch // In addition, the new person is loaded because it is dirty // Finally a single relation insert is done and an update to the person is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (isQueryStrategy()) { @@ -241,7 +241,7 @@ public void testUpdateAddToNewCollectionAndModify() { // Assert that the document and the people are loaded i.e. a full fetch // In addition, the new person is loaded because it is dirty // Finally a single relation insert is done and an update to the person is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); if (isQueryStrategy()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewTest.java index 80be32d0f1..c4733f0aa8 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutable/EntityViewUpdateSimpleMutableSubviewTest.java @@ -72,7 +72,7 @@ public void testSimpleUpdate() { // Then // Since only the document changed we don't need to load the owner // Just assert the update is properly done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -108,7 +108,7 @@ public void testUpdateWithSubview() { // Then // Since the owner changed we don't need to load the old owner // The new owner also doesn't have to be loaded, except in full mode - AssertStatementBuilder builder = assertQuerySequence();; + AssertStatementBuilder builder = assertUnorderedQuerySequence();; if (isQueryStrategy()) { if (isFullMode()) { @@ -145,7 +145,7 @@ public void testUpdateWithModifySubview() { // Then // Since the owner changed we don't need to load the old owner // Now the new owner has to be loaded as it is updated as well - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.update(Person.class) @@ -180,7 +180,7 @@ public void testUpdateWithModifyExisting() { // Then // Since we update the old responsiblePerson, load it along with the document for updating it later - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.update(Person.class); @@ -214,7 +214,7 @@ public void testUpdateToNull() { // Then // Assert that only the document is loaded and finally also updated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { if (isFullMode()) { diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutableonly/EntityViewUpdateSimpleMutableOnlySubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutableonly/EntityViewUpdateSimpleMutableOnlySubviewCollectionsTest.java index facd23a4f7..991b750323 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutableonly/EntityViewUpdateSimpleMutableOnlySubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutableonly/EntityViewUpdateSimpleMutableOnlySubviewCollectionsTest.java @@ -78,7 +78,7 @@ public void testUpdateReplaceCollection() { } private void validateMutableOnlyNoChange(UpdatableDocumentWithCollectionsView docView) { - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); } diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutableonly/EntityViewUpdateSimpleMutableOnlySubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutableonly/EntityViewUpdateSimpleMutableOnlySubviewTest.java index cd96134f40..2f3b67581b 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutableonly/EntityViewUpdateSimpleMutableOnlySubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/mutableonly/EntityViewUpdateSimpleMutableOnlySubviewTest.java @@ -73,7 +73,7 @@ public void testSimpleUpdate() { // Then // Since only the document changed we don't need to load the owner // Just assert the update is properly done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -123,7 +123,7 @@ public void testUpdateWithModifyExisting() { // Then // Since we update the old responsiblePerson, load it along with the document for updating it later - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { builder.update(Person.class); diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewCollectionsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewCollectionsTest.java index 6157f38ed0..20f317cc66 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewCollectionsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewCollectionsTest.java @@ -76,12 +76,16 @@ public void testUpdateReplaceCollection() { // Assert that the document and the people are loaded in full mode. // During dirty detection we should be able to figure out that nothing changed // So partial modes wouldn't load anything and both won't cause any updates - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { - fullFetch(builder); + if (isQueryStrategy()) { + assertReplaceAnd(builder); + } else { + fullFetch(builder); + } - if (version) { + if (version || isQueryStrategy()) { builder.update(Document.class); } } @@ -105,11 +109,17 @@ public void testUpdateAddToCollection() { // Then // Assert that the document and the people are loaded, but only a relation insert is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - fullFetch(builder); + if (isQueryStrategy()) { + if (isFullMode()) { + assertReplaceAnd(builder); + } + } else { + fullFetch(builder); + } - if (version) { + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } @@ -136,19 +146,25 @@ public void testUpdateAddToNewCollection() { // Then // In partial mode, only the document is loaded. In full mode, the people are also loaded // Since we load the people in the full mode, we do a proper diff and can compute that only a single item was added - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - if (isFullMode()) { - fullFetch(builder); + if (isQueryStrategy()) { + if (isFullMode()) { + assertReplaceAnd(builder); + } } else { - if (preferLoadingAndDiffingOverRecreate()) { + if (isFullMode()) { fullFetch(builder); } else { - assertReplaceAnd(builder); + if (preferLoadingAndDiffingOverRecreate()) { + fullFetch(builder); + } else { + assertReplaceAnd(builder); + } } } - if (version) { + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } @@ -174,19 +190,24 @@ public void testUpdateAddToCollectionAndModifySubview() { // Then // In partial mode, only the document is loaded. In full mode, the people are also loaded // Since we load the people in the full mode, we do a proper diff and can compute that only a single item was added - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - if (isFullMode()) { - fullFetch(builder); + if (isQueryStrategy()) { + if (isFullMode()) { + assertReplaceAnd(builder); + } } else { - if (preferLoadingAndDiffingOverRecreate()) { + if (isFullMode()) { fullFetch(builder); } else { - assertReplaceAnd(builder); + if (preferLoadingAndDiffingOverRecreate()) { + fullFetch(builder); + } else { + assertReplaceAnd(builder); + } } } - - if (version) { + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } @@ -214,19 +235,25 @@ public void testUpdateAddToNewCollectionAndModifySubview() { // Then // In partial mode, only the document is loaded. In full mode, the people are also loaded // Since we load the people in the full mode, we do a proper diff and can compute that only a single item was added - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); - if (isFullMode()) { - fullFetch(builder); + if (isQueryStrategy()) { + if (isFullMode()) { + assertReplaceAnd(builder); + } } else { - if (preferLoadingAndDiffingOverRecreate()) { + if (isFullMode()) { fullFetch(builder); } else { - assertReplaceAnd(builder); + if (preferLoadingAndDiffingOverRecreate()) { + fullFetch(builder); + } else { + assertReplaceAnd(builder); + } } } - if (version) { + if (version || isFullMode() && isQueryStrategy()) { builder.update(Document.class); } @@ -250,12 +277,16 @@ public void testUpdateModifySubviewInCollection() { // Then // Nothing is loaded since nothing that should be cascaded changed - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { - fullFetch(builder); + if (isQueryStrategy()) { + assertReplaceAnd(builder); + } else { + fullFetch(builder); + } - if (version) { + if (version || isQueryStrategy()) { builder.update(Document.class); } } @@ -289,18 +320,18 @@ public static void assertSubviewEquals(Collection persons, Collection 1) { + builder.insert(Document.class, "people"); + } + return builder; } @Override - protected boolean isQueryStrategy() { - // Collection changes always need to be applied on the entity model, can't do that via a query - return false; + protected AssertStatementBuilder fullUpdate(AssertStatementBuilder builder) { + assertReplaceAnd(builder); + return versionUpdate(builder); } @Override diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewMapsTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewMapsTest.java index b219fdbe1b..3ef4261a65 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewMapsTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewMapsTest.java @@ -76,7 +76,7 @@ public void testUpdateReplaceCollection() { // Assert that the document and the people are loaded in full mode. // During dirty detection we should be able to figure out that nothing changed // So partial modes wouldn't load anything and both won't cause any updates - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -104,7 +104,7 @@ public void testUpdateAddToCollection() { // Then // Assert that the document and the people are loaded, but only a relation insert is done - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); fullFetch(builder); @@ -135,7 +135,7 @@ public void testUpdateAddToNewCollection() { // Then // In partial mode, only the document is loaded. In full mode, the people are also loaded // Since we load the people in the full mode, we do a proper diff and can compute that only a single item was added - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -173,7 +173,7 @@ public void testUpdateAddToCollectionAndModifySubview() { // Then // In partial mode, only the document is loaded. In full mode, the people are also loaded // Since we load the people in the full mode, we do a proper diff and can compute that only a single item was added - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -213,7 +213,7 @@ public void testUpdateAddToNewCollectionAndModifySubview() { // Then // In partial mode, only the document is loaded. In full mode, the people are also loaded // Since we load the people in the full mode, we do a proper diff and can compute that only a single item was added - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -249,7 +249,7 @@ public void testUpdateModifySubviewInCollection() { // Then // Nothing is loaded since nothing that should be cascaded changed - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isFullMode()) { fullFetch(builder); @@ -297,12 +297,6 @@ private AssertStatementBuilder assertReplaceAnd(AssertStatementBuilder builder) .and(); } - @Override - protected boolean isQueryStrategy() { - // Collection changes always need to be applied on the entity model, can't do that via a query - return false; - } - @Override protected AssertStatementBuilder fullFetch(AssertStatementBuilder builder) { return builder.assertSelect() diff --git a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewTest.java b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewTest.java index bc3fc752c2..b52d455c8a 100644 --- a/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewTest.java +++ b/entity-view/testsuite/src/test/java/com/blazebit/persistence/view/testsuite/update/subview/simple/updatableonly/EntityViewUpdateSimpleUpdatableOnlySubviewTest.java @@ -70,7 +70,7 @@ public void testSimpleUpdate() { // Then // Assert that only the document is loaded and finally also updated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); @@ -98,7 +98,7 @@ public void testUpdateWithSubview() { // Then // Assert that only the document is loaded and finally also updated // There is no need to actually load the person - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); @@ -126,7 +126,7 @@ public void testUpdateWithModifySubview() { // Then // Assert that only the document is loaded and finally also updated // But the person is not updated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); @@ -152,7 +152,7 @@ public void testUpdateWithModifyExisting() { // Then // Nothing is loaded since nothing that should be cascaded changed - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (isQueryStrategy()) { if (isFullMode()) { @@ -185,7 +185,7 @@ public void testUpdateToNull() { // Then // Assert that only the document is loaded and finally also updated - AssertStatementBuilder builder = assertQuerySequence(); + AssertStatementBuilder builder = assertUnorderedQuerySequence(); if (!isQueryStrategy()) { fullFetch(builder); diff --git a/integration/datanucleus-5.1/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleus51JpaProvider.java b/integration/datanucleus-5.1/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleus51JpaProvider.java index f9c7af5798..ef91776f87 100644 --- a/integration/datanucleus-5.1/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleus51JpaProvider.java +++ b/integration/datanucleus-5.1/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleus51JpaProvider.java @@ -251,11 +251,21 @@ public String[] getColumnNames(EntityType ownerType, String attributeName) { return EMPTY; } + @Override + public String[] getColumnNames(EntityType ownerType, String elementCollectionPath, String attributeName) { + return EMPTY; + } + @Override public String[] getColumnTypes(EntityType ownerType, String attributeName) { return EMPTY; } + @Override + public String[] getColumnTypes(EntityType ownerType, String elementCollectionPath, String attributeName) { + return EMPTY; + } + private AttributeImpl getAttribute(ManagedType ownerType, String attributeName) { if (attributeName.indexOf('.') == -1) { return (AttributeImpl) ownerType.getAttribute(attributeName); @@ -344,8 +354,10 @@ public JoinTable getJoinTable(EntityType ownerType, String attributeName) { return new JoinTable( tableName, + null, idColumnMapping, keyMapping, + null, targetIdColumnMapping ); } @@ -374,12 +386,22 @@ public boolean isOrphanRemoval(ManagedType ownerType, String attributeName) { return attribute != null && attribute.getMetadata().isCascadeRemoveOrphans(); } + @Override + public boolean isOrphanRemoval(ManagedType ownerType, String elementCollectionPath, String attributeName) { + return false; + } + @Override public boolean isDeleteCascaded(ManagedType ownerType, String attributeName) { AttributeImpl attribute = getAttribute(ownerType, attributeName); return attribute != null && attribute.getMetadata().isCascadeDelete(); } + @Override + public boolean isDeleteCascaded(ManagedType ownerType, String elementCollectionPath, String attributeName) { + return false; + } + @Override public boolean containsEntity(EntityManager em, Class entityClass, Object id) { ExecutionContext ec = em.unwrap(ExecutionContext.class); @@ -432,8 +454,13 @@ public boolean supportsJoinTableCleanupOnDelete() { } @Override - public boolean supportsJoinElementCollectionsOnCorrelatedInverseAssociations() { - return true; + public boolean needsCorrelationPredicateWhenCorrelatingWithWhereClause() { + return false; + } + + @Override + public boolean supportsSingleValuedAssociationNaturalIdExpressions() { + return false; } @Override @@ -462,6 +489,11 @@ public List getIdentifierOrUniqueKeyEmbeddedPropertyNames(EntityType return Collections.emptyList(); } + @Override + public List getIdentifierOrUniqueKeyEmbeddedPropertyNames(EntityType owner, String elementCollectionPath, String attributeName) { + return Collections.emptyList(); + } + @Override public Object getIdentifier(Object entity) { return persistenceUnitUtil.getIdentifier(entity); diff --git a/integration/datanucleus/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleusJpaProvider.java b/integration/datanucleus/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleusJpaProvider.java index 0aa8adfa80..57899ec812 100644 --- a/integration/datanucleus/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleusJpaProvider.java +++ b/integration/datanucleus/src/main/java/com/blazebit/persistence/integration/datanucleus/DataNucleusJpaProvider.java @@ -251,11 +251,21 @@ public String[] getColumnNames(EntityType ownerType, String attributeName) { return EMPTY; } + @Override + public String[] getColumnNames(EntityType ownerType, String elementCollectionPath, String attributeName) { + return EMPTY; + } + @Override public String[] getColumnTypes(EntityType ownerType, String attributeName) { return EMPTY; } + @Override + public String[] getColumnTypes(EntityType ownerType, String elementCollectionPath, String attributeName) { + return EMPTY; + } + private AttributeImpl getAttribute(ManagedType ownerType, String attributeName) { if (attributeName.indexOf('.') == -1) { return (AttributeImpl) ownerType.getAttribute(attributeName); @@ -344,8 +354,10 @@ public JoinTable getJoinTable(EntityType ownerType, String attributeName) { return new JoinTable( tableName, + null, idColumnMapping, keyMapping, + null, targetIdColumnMapping ); } @@ -374,12 +386,22 @@ public boolean isOrphanRemoval(ManagedType ownerType, String attributeName) { return attribute != null && attribute.getMetadata().isCascadeRemoveOrphans(); } + @Override + public boolean isOrphanRemoval(ManagedType ownerType, String elementCollectionPath, String attributeName) { + return false; + } + @Override public boolean isDeleteCascaded(ManagedType ownerType, String attributeName) { AttributeImpl attribute = getAttribute(ownerType, attributeName); return attribute != null && attribute.getMetadata().isCascadeDelete(); } + @Override + public boolean isDeleteCascaded(ManagedType ownerType, String elementCollectionPath, String attributeName) { + return false; + } + @Override public boolean containsEntity(EntityManager em, Class entityClass, Object id) { ExecutionContext ec = em.unwrap(ExecutionContext.class); @@ -432,8 +454,13 @@ public boolean supportsJoinTableCleanupOnDelete() { } @Override - public boolean supportsJoinElementCollectionsOnCorrelatedInverseAssociations() { - return true; + public boolean needsCorrelationPredicateWhenCorrelatingWithWhereClause() { + return false; + } + + @Override + public boolean supportsSingleValuedAssociationNaturalIdExpressions() { + return false; } @Override @@ -462,6 +489,11 @@ public List getIdentifierOrUniqueKeyEmbeddedPropertyNames(EntityType return Collections.emptyList(); } + @Override + public List getIdentifierOrUniqueKeyEmbeddedPropertyNames(EntityType owner, String elementCollectionPath, String attributeName) { + return Collections.emptyList(); + } + @Override public Object getIdentifier(Object entity) { return persistenceUnitUtil.getIdentifier(entity); diff --git a/integration/eclipselink/src/main/java/com/blazebit/persistence/integration/eclipselink/EclipseLinkJpaProvider.java b/integration/eclipselink/src/main/java/com/blazebit/persistence/integration/eclipselink/EclipseLinkJpaProvider.java index 6da7de62c8..54d34492b4 100644 --- a/integration/eclipselink/src/main/java/com/blazebit/persistence/integration/eclipselink/EclipseLinkJpaProvider.java +++ b/integration/eclipselink/src/main/java/com/blazebit/persistence/integration/eclipselink/EclipseLinkJpaProvider.java @@ -243,11 +243,21 @@ public String[] getColumnNames(EntityType ownerType, String attributeName) { return EMPTY; } + @Override + public String[] getColumnNames(EntityType ownerType, String elementCollectionPath, String attributeName) { + return EMPTY; + } + @Override public String[] getColumnTypes(EntityType ownerType, String attributeName) { return EMPTY; } + @Override + public String[] getColumnTypes(EntityType ownerType, String elementCollectionPath, String attributeName) { + return EMPTY; + } + @Override public JoinTable getJoinTable(EntityType ownerType, String attributeName) { DatabaseMapping mapping = getAttribute(ownerType, attributeName).getMapping(); @@ -259,8 +269,10 @@ public JoinTable getJoinTable(EntityType ownerType, String attributeName) { Map targetIdColumnMapping = new HashMap<>(); return new JoinTable( oneToOneMapping.getRelationTable().getName(), + null, idColumnMapping, keyMapping, + null, targetIdColumnMapping ); @@ -286,8 +298,10 @@ public JoinTable getJoinTable(EntityType ownerType, String attributeName) { return new JoinTable( manyToManyMapping.getRelationTable().getName(), + null, idColumnMapping, keyMapping(manyToManyMapping.getContainerPolicy().getIdentityFieldsForMapKey()), + null, targetIdColumnMapping ); } else if (collectionMapping instanceof DirectCollectionMapping) { @@ -303,8 +317,10 @@ public JoinTable getJoinTable(EntityType ownerType, String attributeName) { } return new JoinTable( directCollectionMapping.getReferenceTableName(), + null, idColumnMapping, keyMapping(directCollectionMapping.getContainerPolicy().getIdentityFieldsForMapKey()), + null, targetIdColumnMapping ); } else if (collectionMapping instanceof DirectMapMapping) { @@ -320,8 +336,10 @@ public JoinTable getJoinTable(EntityType ownerType, String attributeName) { } return new JoinTable( directMapMapping.getReferenceTableName(), + null, idColumnMapping, keyMapping(directMapMapping.getContainerPolicy().getIdentityFieldsForMapKey()), + null, targetIdColumnMapping ); } else if (collectionMapping instanceof AggregateCollectionMapping) { @@ -339,8 +357,10 @@ public JoinTable getJoinTable(EntityType ownerType, String attributeName) { } return new JoinTable( tableName, + null, idColumnMapping, keyMapping(aggregateCollectionMapping.getContainerPolicy().getIdentityFieldsForMapKey()), + null, targetIdColumnMapping ); } @@ -388,6 +408,12 @@ public boolean isOrphanRemoval(ManagedType ownerType, String attributeName) { return false; } + @Override + public boolean isOrphanRemoval(ManagedType ownerType, String elementCollectionPath, String attributeName) { + // TODO: Not yet supported + return false; + } + @Override public boolean isDeleteCascaded(ManagedType ownerType, String attributeName) { AttributeImpl attribute = getAttribute(ownerType, attributeName); @@ -398,6 +424,12 @@ public boolean isDeleteCascaded(ManagedType ownerType, String attributeName) return false; } + @Override + public boolean isDeleteCascaded(ManagedType ownerType, String elementCollectionPath, String attributeName) { + // TODO: Not yet supported + return false; + } + @Override public boolean containsEntity(EntityManager em, Class entityClass, Object id) { return em.unwrap(JpaEntityManager.class).getActiveSession().getIdentityMapAccessor().getFromIdentityMap(id, entityClass) != null; @@ -477,8 +509,13 @@ public boolean supportsJoinTableCleanupOnDelete() { } @Override - public boolean supportsJoinElementCollectionsOnCorrelatedInverseAssociations() { - return true; + public boolean needsCorrelationPredicateWhenCorrelatingWithWhereClause() { + return false; + } + + @Override + public boolean supportsSingleValuedAssociationNaturalIdExpressions() { + return false; } @Override @@ -524,6 +561,12 @@ public List getIdentifierOrUniqueKeyEmbeddedPropertyNames(EntityType return Collections.emptyList(); } + @Override + public List getIdentifierOrUniqueKeyEmbeddedPropertyNames(EntityType owner, String elementCollectionPath, String attributeName) { + // TODO: Not yet supported + return Collections.emptyList(); + } + @Override public Object getIdentifier(Object entity) { return persistenceUnitUtil.getIdentifier(entity); diff --git a/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpa21Provider.java b/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpa21Provider.java index a16a8bbb8e..a2f52b47f7 100644 --- a/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpa21Provider.java +++ b/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpa21Provider.java @@ -21,6 +21,8 @@ import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.tuple.entity.EntityMetamodel; +import org.hibernate.type.ComponentType; +import org.hibernate.type.Type; import javax.persistence.PersistenceUnitUtil; import javax.persistence.metamodel.ManagedType; @@ -70,6 +72,37 @@ public boolean isOrphanRemoval(ManagedType ownerType, String attributeName) { return false; } + @Override + public boolean isOrphanRemoval(ManagedType ownerType, String elementCollectionPath, String attributeName) { + Type elementType = getCollectionPersister(ownerType, elementCollectionPath).getElementType(); + if (!(elementType instanceof ComponentType)) { + // This can only happen for collection/join table target attributes, where it is irrelevant + return false; + } + ComponentType componentType = (ComponentType) elementType; + String subAttribute = attributeName.substring(elementCollectionPath.length() + 1); + // Component types only store direct properties, so we have to go deeper + String[] propertyParts = subAttribute.split("\\."); + int propertyIndex = 0; + for (; propertyIndex < propertyParts.length - 1; propertyIndex++) { + int index = componentType.getPropertyIndex(propertyParts[propertyIndex]); + Type propertyType = componentType.getSubtypes()[index]; + if (propertyType instanceof ComponentType) { + componentType = (ComponentType) propertyType; + } else { + // The association property is just as good as the id property of the association for our purposes + // So we stop here and query the association property instead + break; + } + } + + try { + return (boolean) HAS_ORPHAN_DELETE_METHOD.invoke(componentType.getCascadeStyle(propertyIndex)); + } catch (Exception ex) { + throw new RuntimeException("Could not access orphan removal information. Please report your version of hibernate so we can provide support for it!", ex); + } + } + @Override public boolean isDeleteCascaded(ManagedType ownerType, String attributeName) { AbstractEntityPersister entityPersister = getEntityPersister(ownerType); @@ -88,6 +121,36 @@ public boolean isDeleteCascaded(ManagedType ownerType, String attributeName) return false; } + @Override + public boolean isDeleteCascaded(ManagedType ownerType, String elementCollectionPath, String attributeName) { + Type elementType = getCollectionPersister(ownerType, elementCollectionPath).getElementType(); + if (!(elementType instanceof ComponentType)) { + // This can only happen for collection/join table target attributes, where it is irrelevant + return false; + } + ComponentType componentType = (ComponentType) elementType; + String subAttribute = attributeName.substring(elementCollectionPath.length() + 1); + // Component types only store direct properties, so we have to go deeper + String[] propertyParts = subAttribute.split("\\."); + int propertyIndex = 0; + for (; propertyIndex < propertyParts.length - 1; propertyIndex++) { + int index = componentType.getPropertyIndex(propertyParts[propertyIndex]); + Type propertyType = componentType.getSubtypes()[index]; + if (propertyType instanceof ComponentType) { + componentType = (ComponentType) propertyType; + } else { + // The association property is just as good as the id property of the association for our purposes + // So we stop here and query the association property instead + break; + } + } + try { + return (boolean) DO_CASCADE_METHOD.invoke(componentType.getCascadeStyle(propertyIndex), DELETE_CASCADE); + } catch (Exception ex) { + throw new RuntimeException("Could not access orphan removal information. Please report your version of hibernate so we can provide support for it!", ex); + } + } + @Override public boolean supportsJpa21() { return true; diff --git a/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpaProvider.java b/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpaProvider.java index 7aa277d6a8..e06592c92c 100644 --- a/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpaProvider.java +++ b/integration/hibernate-base/src/main/java/com/blazebit/persistence/integration/hibernate/base/HibernateJpaProvider.java @@ -62,10 +62,12 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; +import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.logging.Logger; /** @@ -91,7 +93,7 @@ public class HibernateJpaProvider implements JpaProvider { private final boolean needsBrokenAssociationToIdRewriteInOnClause; private final boolean supportsCollectionTableCleanupOnDelete; private final boolean supportsJoinTableCleanupOnDelete; - private final boolean supportsJoinElementCollectionsOnCorrelatedInverseAssociations; + private final boolean needsCorrelationPredicateWhenCorrelatingWithWhereClause; static { Class typeClass = null; @@ -168,7 +170,7 @@ public HibernateJpaProvider(PersistenceUnitUtil persistenceUnitUtil, String dbms this.supportsCollectionTableCleanupOnDelete = false; this.supportsJoinTableCleanupOnDelete = true; // See https://hibernate.atlassian.net/browse/HHH-12942 for details - this.supportsJoinElementCollectionsOnCorrelatedInverseAssociations = false; + this.needsCorrelationPredicateWhenCorrelatingWithWhereClause = true; } catch (Exception e) { throw new RuntimeException(e); } @@ -210,8 +212,13 @@ public boolean supportsJoinTableCleanupOnDelete() { } @Override - public boolean supportsJoinElementCollectionsOnCorrelatedInverseAssociations() { - return supportsJoinElementCollectionsOnCorrelatedInverseAssociations; + public boolean needsCorrelationPredicateWhenCorrelatingWithWhereClause() { + return needsCorrelationPredicateWhenCorrelatingWithWhereClause; + } + + @Override + public boolean supportsSingleValuedAssociationNaturalIdExpressions() { + return false; } @Override @@ -403,6 +410,16 @@ protected final AbstractEntityPersister getEntityPersister(ManagedType ownerT return (AbstractEntityPersister) entityPersister; } + protected final QueryableCollection getCollectionPersister(ManagedType ownerType, String attributeName) { + String ownerTypeName = getTypeName(ownerType); + StringBuilder sb = new StringBuilder(ownerTypeName.length() + attributeName.length() + 1); + sb.append(ownerTypeName); + sb.append('.'); + sb.append(attributeName); + + return (QueryableCollection) collectionPersisters.get(sb.toString()); + } + @Override public boolean isForeignJoinColumn(EntityType ownerType, String attributeName) { AbstractEntityPersister persister = getEntityPersister(ownerType); @@ -516,13 +533,7 @@ private boolean isColumnShared(AbstractEntityPersister subclassPersister, String @Override public String getMappedBy(EntityType ownerType, String attributeName) { - String ownerTypeName = getTypeName(ownerType); - StringBuilder sb = new StringBuilder(ownerTypeName.length() + attributeName.length() + 1); - sb.append(ownerTypeName); - sb.append('.'); - sb.append(attributeName); - - CollectionPersister persister = collectionPersisters.get(sb.toString()); + CollectionPersister persister = getCollectionPersister(ownerType, attributeName); if (persister != null) { if (persister.isInverse()) { return getMappedBy(persister); @@ -607,6 +618,79 @@ public String[] getColumnNames(EntityType entityType, String attributeName) { } } + @Override + public String[] getColumnNames(EntityType ownerType, String elementCollectionPath, String attributeName) { + QueryableCollection persister = getCollectionPersister(ownerType, elementCollectionPath); + String subAttributeName = attributeName.substring(elementCollectionPath.length() + 1); + if (persister.getElementType() instanceof ComponentType) { + ComponentType elementType = (ComponentType) persister.getElementType(); + String[] propertyNames = elementType.getPropertyNames(); + Type[] subtypes = elementType.getSubtypes(); + int dotIndex = -1; + do { + String propertyName; + if (dotIndex == -1) { + propertyName = subAttributeName; + } else { + propertyName = subAttributeName.substring(0, dotIndex); + } + int offset = 0; + for (int i = 0; i < propertyNames.length; i++) { + int span = subtypes[i].getColumnSpan(persister.getFactory()); + if (propertyName.equals(propertyNames[i])) { + String[] columnNames = new String[span]; + String[] elementColumnNames = persister.getElementColumnNames(); + System.arraycopy(elementColumnNames, offset, columnNames, 0, span); + return columnNames; + } else { + offset += span; + } + } + // Component types do not store entries for the id properties of associations so we need to look for a sub-part of the attribute name + } while ((dotIndex = subAttributeName.indexOf('.', dotIndex + 1)) != -1); + } else if (persister.getElementType() instanceof org.hibernate.type.EntityType) { + AbstractEntityPersister elementPersister = (AbstractEntityPersister) entityPersisters.get(((org.hibernate.type.EntityType) persister.getElementType()).getAssociatedEntityName()); + Type identifierType = ((org.hibernate.type.EntityType) persister.getElementType()).getIdentifierOrUniqueKeyType(persister.getFactory()); + String identifierOrUniqueKeyPropertyName = ((org.hibernate.type.EntityType) persister.getElementType()).getIdentifierOrUniqueKeyPropertyName(persister.getFactory()); + String prefix; + if (identifierType instanceof EmbeddedComponentType) { + String[] propertyNames = ((EmbeddedComponentType) identifierType).getPropertyNames(); + String[] columnNames = columnNamesByPropertyName(elementPersister, propertyNames, subAttributeName, "", persister.getElementColumnNames(), persister.getFactory()); + if (columnNames != null) { + return columnNames; + } + } else if (subAttributeName.equals(identifierOrUniqueKeyPropertyName)) { + return persister.getElementColumnNames(); + } else if (identifierType instanceof ComponentType && subAttributeName.startsWith(prefix = identifierOrUniqueKeyPropertyName + ".")) { + String[] propertyNames = ((ComponentType) identifierType).getPropertyNames(); + String[] columnNames = columnNamesByPropertyName(elementPersister, propertyNames, subAttributeName.substring(identifierOrUniqueKeyPropertyName.length() + 1), prefix, persister.getElementColumnNames(), persister.getFactory()); + if (columnNames != null) { + return columnNames; + } + } + } + + throw new IllegalArgumentException("Couldn't find column names for " + getTypeName(ownerType) + "#" + attributeName); + } + + private String[] columnNamesByPropertyName(AbstractEntityPersister persister, String[] propertyNames, String subAttributeName, String prefix, String[] elementColumnNames, Mapping factory) { + int offset = 0; + for (int i = 0; i < propertyNames.length; i++) { + String propertyName = propertyNames[i]; + Type propertyType = persister.getPropertyType(prefix + propertyName); + int span = propertyType.getColumnSpan(factory); + if (subAttributeName.equals(propertyName)) { + String[] columnNames = new String[span]; + System.arraycopy(elementColumnNames, offset, columnNames, 0, span); + return columnNames; + } else { + offset += span; + } + } + + return null; + } + private String unquote(String name) { if (useQuoted || name == null || name.length() < 2) { return name; @@ -654,35 +738,13 @@ public String[] getColumnTypes(EntityType entityType, String attributeName) { if (isFormula || isSubselect) { Type propertyType = entityPersister.getPropertyType(attributeName); - - if (propertyType instanceof org.hibernate.type.EntityType) { - propertyType = ((org.hibernate.type.EntityType) propertyType).getIdentifierOrUniqueKeyType(sfi); - } - - long length = Column.DEFAULT_LENGTH; - int precision = Column.DEFAULT_PRECISION; - int scale = Column.DEFAULT_SCALE; - - try { - Method m = Type.class.getMethod("dictatedSizes", Mapping.class); - Object size = ((Object[]) m.invoke(propertyType, sfi))[0]; - length = (long) size.getClass().getMethod("getLength").invoke(size); - precision = (int) size.getClass().getMethod("getPrecision").invoke(size); - scale = (int) size.getClass().getMethod("getScale").invoke(size); - } catch (Exception ex) { - LOG.fine("Could not determine the column type of the attribute: " + attributeName + " of the entity: " + entityType.getName()); - } - - return new String[] { - sfi.getDialect().getTypeName( - propertyType.sqlTypes(sfi)[0], - length, - precision, - scale - ) - }; + return getColumnTypeForPropertyType(entityType, attributeName, sfi, propertyType); } + return getColumnTypesForColumnNames(entityType, columnNames, tables); + } + + private String[] getColumnTypesForColumnNames(EntityType entityType, String[] columnNames, Table[] tables) { String[] columnTypes = new String[columnNames.length]; for (int i = 0; i < columnNames.length; i++) { Column column = null; @@ -694,7 +756,7 @@ public String[] getColumnTypes(EntityType entityType, String attributeName) { } if (column == null) { - throw new IllegalArgumentException("Could not find column '" + columnNames[i] + "' in for entity: " + entityType.getName()); + throw new IllegalArgumentException("Could not find column '" + columnNames[i] + "' in entity: " + entityType.getName()); } columnTypes[i] = column.getSqlType(); @@ -703,15 +765,132 @@ public String[] getColumnTypes(EntityType entityType, String attributeName) { return columnTypes; } + private String[] getColumnTypeForPropertyType(EntityType entityType, String attributeName, SessionFactoryImplementor sfi, Type propertyType) { + if (propertyType instanceof org.hibernate.type.EntityType) { + propertyType = ((org.hibernate.type.EntityType) propertyType).getIdentifierOrUniqueKeyType(sfi); + } + + long length = Column.DEFAULT_LENGTH; + int precision = Column.DEFAULT_PRECISION; + int scale = Column.DEFAULT_SCALE; + + try { + Method m = Type.class.getMethod("dictatedSizes", Mapping.class); + Object size = ((Object[]) m.invoke(propertyType, sfi))[0]; + length = (long) size.getClass().getMethod("getLength").invoke(size); + precision = (int) size.getClass().getMethod("getPrecision").invoke(size); + scale = (int) size.getClass().getMethod("getScale").invoke(size); + } catch (Exception ex) { + LOG.fine("Could not determine the column type of the attribute: " + attributeName + " of the entity: " + entityType.getName()); + } + + return new String[] { + sfi.getDialect().getTypeName( + propertyType.sqlTypes(sfi)[0], + length, + precision, + scale + ) + }; + } + @Override - public JoinTable getJoinTable(EntityType ownerType, String attributeName) { - String ownerTypeName = getTypeName(ownerType); - StringBuilder sb = new StringBuilder(ownerTypeName.length() + attributeName.length() + 1); - sb.append(ownerTypeName); - sb.append('.'); - sb.append(attributeName); + public String[] getColumnTypes(EntityType ownerType, String elementCollectionPath, String attributeName) { + QueryableCollection persister = getCollectionPersister(ownerType, elementCollectionPath); + SessionFactoryImplementor sfi = persister.getFactory(); + String[] columnNames = null; + Type propertyType = null; + String subAttributeName = attributeName.substring(elementCollectionPath.length() + 1); + if (persister.getElementType() instanceof ComponentType) { + ComponentType elementType = (ComponentType) persister.getElementType(); + String[] propertyNames = elementType.getPropertyNames(); + Type[] subtypes = elementType.getSubtypes(); + int dotIndex = -1; + do { + String propertyName; + if (dotIndex == -1) { + propertyName = subAttributeName; + } else { + propertyName = subAttributeName.substring(0, dotIndex); + } + int offset = 0; + for (int i = 0; i < propertyNames.length; i++) { + int span = subtypes[i].getColumnSpan(persister.getFactory()); + if (propertyName.equals(propertyNames[i])) { + columnNames = new String[span]; + propertyType = subtypes[i]; + String[] elementColumnNames = persister.getElementColumnNames(); + System.arraycopy(elementColumnNames, offset, columnNames, 0, span); + break; + } else { + offset += span; + } + } + // Component type do not store entries for the id properties of associations so we need to look for a sub-part of the attribute name + } while (propertyType == null && (dotIndex = subAttributeName.indexOf('.', dotIndex + 1)) != -1); + } else if (persister.getElementType() instanceof org.hibernate.type.EntityType) { + Type identifierType = ((org.hibernate.type.EntityType) persister.getElementType()).getIdentifierOrUniqueKeyType(persister.getFactory()); + String identifierOrUniqueKeyPropertyName = ((org.hibernate.type.EntityType) persister.getElementType()).getIdentifierOrUniqueKeyPropertyName(persister.getFactory()); + String prefix; + if (identifierType instanceof EmbeddedComponentType) { + String[] propertyNames = ((EmbeddedComponentType) identifierType).getPropertyNames(); + Type[] subtypes = ((EmbeddedComponentType) identifierType).getSubtypes(); + int offset = 0; + for (int i = 0; i < propertyNames.length; i++) { + String propertyName = propertyNames[i]; + int span = subtypes[i].getColumnSpan(persister.getFactory()); + if (subAttributeName.equals(propertyName)) { + columnNames = new String[span]; + String[] elementColumnNames = persister.getElementColumnNames(); + System.arraycopy(elementColumnNames, offset, columnNames, 0, span); + propertyType = subtypes[i]; + break; + } else { + offset += span; + } + } + } else if (subAttributeName.equals(identifierOrUniqueKeyPropertyName)) { + columnNames = persister.getElementColumnNames(); + propertyType = identifierType; + } else if (identifierType instanceof ComponentType && subAttributeName.startsWith(prefix = identifierOrUniqueKeyPropertyName + ".")) { + String[] propertyNames = ((ComponentType) identifierType).getPropertyNames(); + Type[] subtypes = ((ComponentType) identifierType).getSubtypes(); + String subPropertyName = subAttributeName.substring(prefix.length()); + int offset = 0; + for (int i = 0; i < propertyNames.length; i++) { + String propertyName = propertyNames[i]; + int span = subtypes[i].getColumnSpan(persister.getFactory()); + if (subPropertyName.equals(propertyName)) { + columnNames = new String[span]; + String[] elementColumnNames = persister.getElementColumnNames(); + System.arraycopy(elementColumnNames, offset, columnNames, 0, span); + propertyType = subtypes[i]; + break; + } else { + offset += span; + } + } + } + } + + if (columnNames == null) { + throw new IllegalArgumentException("Couldn't find column names for " + getTypeName(ownerType) + "#" + attributeName); + } + boolean isFormula = columnNames.length == 1 && columnNames[0] == null; + + if (isFormula) { + return getColumnTypeForPropertyType(ownerType, attributeName, sfi, propertyType); + } + + Database database = sfi.getServiceRegistry().locateServiceBinding(Database.class).getService(); + Table[] tables = new Table[]{ database.getTable(unquote(persister.getTableName())) }; - CollectionPersister persister = collectionPersisters.get(sb.toString()); + return getColumnTypesForColumnNames(ownerType, columnNames, tables); + } + + @Override + public JoinTable getJoinTable(EntityType ownerType, String attributeName) { + CollectionPersister persister = getCollectionPersister(ownerType, attributeName); if (persister instanceof QueryableCollection) { QueryableCollection queryableCollection = (QueryableCollection) persister; @@ -722,25 +901,27 @@ public JoinTable getJoinTable(EntityType ownerType, String attributeName) { for (int i = 0; i < targetColumnMetaData.length; i++) { targetColumnMapping.put(targetColumnMetaData[i], targetColumnMetaData[i]); } - return createJoinTable(queryableCollection, targetColumnMapping); + return createJoinTable(queryableCollection, targetColumnMapping, null, attributeName); } else if (queryableCollection.getElementPersister() instanceof Joinable) { String elementTableName = ((Joinable) queryableCollection.getElementPersister()).getTableName(); if (!queryableCollection.getTableName().equals(elementTableName)) { String[] targetColumnMetaData = queryableCollection.getElementColumnNames(); - String[] targetPrimaryKeyColumnMetaData = ((AbstractEntityPersister) queryableCollection.getElementPersister()).getKeyColumnNames(); + AbstractEntityPersister elementPersister = (AbstractEntityPersister) queryableCollection.getElementPersister(); + String[] targetPrimaryKeyColumnMetaData = elementPersister.getKeyColumnNames(); Map targetIdColumnMapping = new HashMap<>(); for (int i = 0; i < targetColumnMetaData.length; i++) { targetIdColumnMapping.put(targetColumnMetaData[i], targetPrimaryKeyColumnMetaData[i]); } - return createJoinTable(queryableCollection, targetIdColumnMapping); + Set idAttributeNames = getColumnMatchingAttributeNames(elementPersister, Arrays.asList(targetPrimaryKeyColumnMetaData)); + return createJoinTable(queryableCollection, targetIdColumnMapping, idAttributeNames, attributeName); } } } return null; } - private JoinTable createJoinTable(QueryableCollection queryableCollection, Map targetColumnMapping) { + private JoinTable createJoinTable(QueryableCollection queryableCollection, Map targetColumnMapping, Set targetIdAttributeNames, String attributeName) { String[] indexColumnNames = queryableCollection.getIndexColumnNames(); Map keyColumnMapping = null; if (indexColumnNames != null) { @@ -751,21 +932,79 @@ private JoinTable createJoinTable(QueryableCollection queryableCollection, Map idColumnMapping = new HashMap<>(primaryKeyColumnMetaData.length); for (int i = 0; i < foreignKeyColumnMetaData.length; i++) { idColumnMapping.put(foreignKeyColumnMetaData[i], primaryKeyColumnMetaData[i]); } + Set idAttributeNames = getColumnMatchingAttributeNames(ownerEntityPersister, Arrays.asList(primaryKeyColumnMetaData)); + if (targetIdAttributeNames == null) { + Type elementType = queryableCollection.getElementType(); + if (elementType instanceof ComponentType) { + targetIdAttributeNames = new HashSet<>(); + collectPropertyNames(targetIdAttributeNames, null, elementType, queryableCollection.getFactory()); + } + } return new JoinTable( queryableCollection.getTableName(), + idAttributeNames, idColumnMapping, keyColumnMapping, + targetIdAttributeNames, targetColumnMapping ); } + private static Set getColumnMatchingAttributeNames(AbstractEntityPersister ownerEntityPersister, List idColumnNames) { + Set idAttributeNames = new HashSet<>(); + Type identifierType = ownerEntityPersister.getIdentifierType(); + if (identifierType instanceof ComponentType) { + String[] idPropertyNames = ((ComponentType) identifierType).getPropertyNames(); + for (String propertyName : idPropertyNames) { + String attributeName = ownerEntityPersister.getIdentifierPropertyName() + "." + propertyName; + String[] propertyColumnNames = ownerEntityPersister.getSubclassPropertyColumnNames(attributeName); + if (propertyColumnNames != null) { + for (int j = 0; j < propertyColumnNames.length; j++) { + String propertyColumnName = propertyColumnNames[j]; + if (idColumnNames.contains(propertyColumnName)) { + idAttributeNames.add(attributeName); + break; + } + } + } + } + // We assume that when a primary identifier attribute is part of the id column names, that we are done + if (!idAttributeNames.isEmpty()) { + return idAttributeNames; + } + } else { + for (String identifierColumnName : ownerEntityPersister.getIdentifierColumnNames()) { + if (idColumnNames.contains(identifierColumnName)) { + idAttributeNames.add(ownerEntityPersister.getIdentifierPropertyName()); + return idAttributeNames; + } + } + } + String[] propertyNames = ownerEntityPersister.getPropertyNames(); + for (int i = 0; i < propertyNames.length; i++) { + String propertyName = propertyNames[i]; + String[] propertyColumnNames = ownerEntityPersister.getSubclassPropertyColumnNames(propertyName); + if (propertyColumnNames != null) { + for (int j = 0; j < propertyColumnNames.length; j++) { + String propertyColumnName = propertyColumnNames[j]; + if (idColumnNames.contains(propertyColumnName)) { + idAttributeNames.add(propertyName); + break; + } + } + } + } + return idAttributeNames; + } + @Override public boolean isBag(EntityType ownerType, String attributeName) { CollectionPersister persister = null; @@ -798,6 +1037,32 @@ public boolean isOrphanRemoval(ManagedType ownerType, String attributeName) { return false; } + @Override + public boolean isOrphanRemoval(ManagedType ownerType, String elementCollectionPath, String attributeName) { + Type elementType = getCollectionPersister(ownerType, elementCollectionPath).getElementType(); + if (!(elementType instanceof ComponentType)) { + // This can only happen for collection/join table target attributes, where it is irrelevant + return false; + } + ComponentType componentType = (ComponentType) elementType; + String subAttribute = attributeName.substring(elementCollectionPath.length() + 1); + // Component types only store direct properties, so we have to go deeper + String[] propertyParts = subAttribute.split("\\."); + int propertyIndex = 0; + for (; propertyIndex < propertyParts.length - 1; propertyIndex++) { + int index = componentType.getPropertyIndex(propertyParts[propertyIndex]); + Type propertyType = componentType.getSubtypes()[index]; + if (propertyType instanceof ComponentType) { + componentType = (ComponentType) propertyType; + } else { + // The association property is just as good as the id property of the association for our purposes + // So we stop here and query the association property instead + break; + } + } + return componentType.getCascadeStyle(propertyIndex).hasOrphanDelete(); + } + @Override public boolean isDeleteCascaded(ManagedType ownerType, String attributeName) { AbstractEntityPersister entityPersister = getEntityPersister(ownerType); @@ -812,6 +1077,32 @@ public boolean isDeleteCascaded(ManagedType ownerType, String attributeName) return false; } + @Override + public boolean isDeleteCascaded(ManagedType ownerType, String elementCollectionPath, String attributeName) { + Type elementType = getCollectionPersister(ownerType, elementCollectionPath).getElementType(); + if (!(elementType instanceof ComponentType)) { + // This can only happen for collection/join table target attributes, where it is irrelevant + return false; + } + ComponentType componentType = (ComponentType) elementType; + String subAttribute = attributeName.substring(elementCollectionPath.length() + 1); + // Component types only store direct properties, so we have to go deeper + String[] propertyParts = subAttribute.split("\\."); + int propertyIndex = 0; + for (; propertyIndex < propertyParts.length - 1; propertyIndex++) { + int index = componentType.getPropertyIndex(propertyParts[propertyIndex]); + Type propertyType = componentType.getSubtypes()[index]; + if (propertyType instanceof ComponentType) { + componentType = (ComponentType) propertyType; + } else { + // The association property is just as good as the id property of the association for our purposes + // So we stop here and query the association property instead + break; + } + } + return componentType.getCascadeStyle(propertyIndex).doCascade(CascadingAction.DELETE); + } + @Override public boolean containsEntity(EntityManager em, Class entityClass, Object id) { SessionImplementor session = em.unwrap(SessionImplementor.class); @@ -873,47 +1164,76 @@ public void setCacheable(Query query) { @Override public List getIdentifierOrUniqueKeyEmbeddedPropertyNames(EntityType owner, String attributeName) { - // Hibernate does not properly optimize the access to the natural id but instead always creates a join for associations - boolean supportsNaturalIdAccessOptimization = false; AbstractEntityPersister entityPersister = getEntityPersister(owner); Type propertyType = entityPersister.getPropertyType(attributeName); + List identifierOrUniqueKeyPropertyNames = new ArrayList<>(); - // For nested embedded id's, the nested id is mapped as a component type - if (propertyType instanceof ComponentType) { - return Arrays.asList(((ComponentType) propertyType).getPropertyNames()); - } - - List identifierOrUniqueKeyPropertyNames; - org.hibernate.type.EntityType entityType = (org.hibernate.type.EntityType) propertyType; - Type identifierOrUniqueKeyType = entityType.getIdentifierOrUniqueKeyType(entityPersister.getFactory()); - - if (identifierOrUniqueKeyType instanceof EmbeddedComponentType) { - EmbeddedComponentType embeddedComponentType = (EmbeddedComponentType) identifierOrUniqueKeyType; - identifierOrUniqueKeyPropertyNames = Arrays.asList(embeddedComponentType.getPropertyNames()); + if (propertyType instanceof CollectionType) { + Type elementType = ((CollectionType) propertyType).getElementType(entityPersister.getFactory()); + Collection targetAttributeNames = getJoinTable(owner, attributeName).getTargetAttributeNames(); + if (targetAttributeNames == null) { + collectPropertyNames(identifierOrUniqueKeyPropertyNames, null, elementType, entityPersister.getFactory()); + } else { + AbstractEntityPersister elementPersister = (AbstractEntityPersister) entityPersisters.get(((org.hibernate.type.EntityType) elementType).getAssociatedEntityName()); + for (String targetAttributeName : targetAttributeNames) { + collectPropertyNames(identifierOrUniqueKeyPropertyNames, targetAttributeName, elementPersister.getPropertyType(targetAttributeName), entityPersister.getFactory()); + } + } } else { - String identifierOrUniqueKeyPropertyName = entityType.getIdentifierOrUniqueKeyPropertyName(entityPersister.getFactory()); - identifierOrUniqueKeyPropertyNames = Collections.singletonList(identifierOrUniqueKeyPropertyName); + collectPropertyNames(identifierOrUniqueKeyPropertyNames, null, propertyType, entityPersister.getFactory()); } - if (supportsNaturalIdAccessOptimization) { - return identifierOrUniqueKeyPropertyNames; + return identifierOrUniqueKeyPropertyNames; + } + + @Override + public List getIdentifierOrUniqueKeyEmbeddedPropertyNames(EntityType owner, String elementCollectionPath, String attributeName) { + QueryableCollection persister = getCollectionPersister(owner, elementCollectionPath); + ComponentType componentType = (ComponentType) persister.getElementType(); + String subAttribute = attributeName.substring(elementCollectionPath.length() + 1); + // Component types only store direct properties, so we have to go deeper + String[] propertyParts = subAttribute.split("\\."); + Type propertyType; + for (int i = 0; i < propertyParts.length - 1; i++) { + int index = componentType.getPropertyIndex(propertyParts[i]); + propertyType = componentType.getSubtypes()[index]; + if (propertyType instanceof ComponentType) { + componentType = (ComponentType) propertyType; + } else { + // A path expression shouldn't navigate over an association.. + throw new IllegalStateException("Can't get the id properties for: " + attributeName); + } } - EntityPersister attributeEntityPersister = entityPersisters.get(entityType.getAssociatedEntityName()); - Type identifierType = attributeEntityPersister.getIdentifierType(); + propertyType = componentType.getSubtypes()[componentType.getPropertyIndex(propertyParts[propertyParts.length - 1])]; + List identifierOrUniqueKeyPropertyNames = new ArrayList<>(); + collectPropertyNames(identifierOrUniqueKeyPropertyNames, null, propertyType, persister.getFactory()); + return identifierOrUniqueKeyPropertyNames; + } - if (identifierType instanceof EmbeddedComponentType) { - EmbeddedComponentType embeddedComponentType = (EmbeddedComponentType) identifierType; - if (!identifierOrUniqueKeyPropertyNames.containsAll(Arrays.asList(embeddedComponentType.getPropertyNames()))) { - return Collections.emptyList(); + private void collectPropertyNames(Collection propertyNames, String prefix, Type propertyType, Mapping factory) { + if (propertyType instanceof ComponentType) { + ComponentType componentType = (ComponentType) propertyType; + for (String propertyName : componentType.getPropertyNames()) { + Type subtype = componentType.getSubtypes()[componentType.getPropertyIndex(propertyName)]; + collectPropertyNames(propertyNames, prefix == null ? propertyName : prefix + "." + propertyName, subtype, factory); } - } else { - if (!identifierOrUniqueKeyPropertyNames.contains(attributeEntityPersister.getIdentifierPropertyName())) { - return Collections.emptyList(); + } else if (propertyType instanceof org.hibernate.type.EntityType) { + org.hibernate.type.EntityType entityType = (org.hibernate.type.EntityType) propertyType; + Type identifierOrUniqueKeyType = entityType.getIdentifierOrUniqueKeyType(factory); + + if (identifierOrUniqueKeyType instanceof EmbeddedComponentType) { + EmbeddedComponentType embeddedComponentType = (EmbeddedComponentType) identifierOrUniqueKeyType; + for (String propertyName : embeddedComponentType.getPropertyNames()) { + propertyNames.add(prefix == null ? propertyName : prefix + "." + propertyName); + } + } else { + String identifierOrUniqueKeyPropertyName = entityType.getIdentifierOrUniqueKeyPropertyName(factory); + propertyNames.add(prefix == null ? identifierOrUniqueKeyPropertyName : prefix + "." + identifierOrUniqueKeyPropertyName); } + } else if (!(propertyType instanceof CollectionType) && prefix != null) { + propertyNames.add(prefix); } - - return identifierOrUniqueKeyPropertyNames; } @Override diff --git a/integration/jpa-base/src/main/java/com/blazebit/persistence/integration/jpa/JpaMetamodelAccessorImpl.java b/integration/jpa-base/src/main/java/com/blazebit/persistence/integration/jpa/JpaMetamodelAccessorImpl.java index 4c7ab96fe2..b6348e7c71 100644 --- a/integration/jpa-base/src/main/java/com/blazebit/persistence/integration/jpa/JpaMetamodelAccessorImpl.java +++ b/integration/jpa-base/src/main/java/com/blazebit/persistence/integration/jpa/JpaMetamodelAccessorImpl.java @@ -45,7 +45,6 @@ public class JpaMetamodelAccessorImpl implements JpaMetamodelAccessor { protected JpaMetamodelAccessorImpl() { } - @Override public AttributePath getAttributePath(Metamodel metamodel, ManagedType type, String attributePath) { List> attrPath; @@ -61,7 +60,7 @@ public AttributePath getAttributePath(Metamodel metamodel, ManagedType type, attrPath.add(attribute); return new AttributePath(attrPath, com.blazebit.persistence.parser.util.JpaMetamodelUtils.resolveFieldClass(type.getJavaType(), attribute)); } else { - attrPath = new ArrayList>(); + attrPath = new ArrayList<>(); } String[] attributeParts = attributePath.split("\\."); diff --git a/integration/openjpa/src/main/java/com/blazebit/persistence/integration/openjpa/OpenJPAJpaProvider.java b/integration/openjpa/src/main/java/com/blazebit/persistence/integration/openjpa/OpenJPAJpaProvider.java index efcf8ea010..3b790cecc8 100644 --- a/integration/openjpa/src/main/java/com/blazebit/persistence/integration/openjpa/OpenJPAJpaProvider.java +++ b/integration/openjpa/src/main/java/com/blazebit/persistence/integration/openjpa/OpenJPAJpaProvider.java @@ -204,11 +204,21 @@ public String[] getColumnNames(EntityType ownerType, String attributeName) { return EMPTY; } + @Override + public String[] getColumnNames(EntityType ownerType, String elementCollectionPath, String attributeName) { + return EMPTY; + } + @Override public String[] getColumnTypes(EntityType ownerType, String attributeName) { return EMPTY; } + @Override + public String[] getColumnTypes(EntityType ownerType, String elementCollectionPath, String attributeName) { + return EMPTY; + } + @Override public JoinTable getJoinTable(EntityType ownerType, String attributeName) { // just return null since we don't need that for openjpa anyway @@ -225,11 +235,21 @@ public boolean isOrphanRemoval(ManagedType ownerType, String attributeName) { return false; } + @Override + public boolean isOrphanRemoval(ManagedType ownerType, String elementCollectionPath, String attributeName) { + return false; + } + @Override public boolean isDeleteCascaded(ManagedType ownerType, String attributeName) { return false; } + @Override + public boolean isDeleteCascaded(ManagedType ownerType, String elementCollectionPath, String attributeName) { + return false; + } + @Override public boolean containsEntity(EntityManager em, Class entityClass, Object id) { throw new UnsupportedOperationException("Not yet implemented!"); @@ -281,8 +301,13 @@ public boolean supportsJoinTableCleanupOnDelete() { } @Override - public boolean supportsJoinElementCollectionsOnCorrelatedInverseAssociations() { - return true; + public boolean needsCorrelationPredicateWhenCorrelatingWithWhereClause() { + return false; + } + + @Override + public boolean supportsSingleValuedAssociationNaturalIdExpressions() { + return false; } @Override @@ -332,6 +357,11 @@ public List getIdentifierOrUniqueKeyEmbeddedPropertyNames(EntityType return Collections.emptyList(); } + @Override + public List getIdentifierOrUniqueKeyEmbeddedPropertyNames(EntityType owner, String elementCollectionPath, String attributeName) { + return Collections.emptyList(); + } + @Override public Object getIdentifier(Object entity) { return persistenceUnitUtil.getIdentifier(entity); diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/AbstractJpaPersistenceTest.java b/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/AbstractJpaPersistenceTest.java index dd74996b43..59aad1aa9f 100644 --- a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/AbstractJpaPersistenceTest.java +++ b/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/AbstractJpaPersistenceTest.java @@ -582,12 +582,12 @@ public static void assertQueryCount(int count) { } } - public AssertStatementBuilder assertQuerySequence() { + public AssertStatementBuilder assertOrderedQuerySequence() { return new AssertStatementBuilder(getRelationalModelAccessor(), QueryInspectorListener.EXECUTED_QUERIES); } public AssertStatementBuilder assertUnorderedQuerySequence() { - return assertQuerySequence().unordered(); + return assertOrderedQuerySequence().unordered(); } protected RelationalModelAccessor getRelationalModelAccessor() { diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertInsertStatement.java b/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertInsertStatement.java index e9ae6bcf70..f29b98d531 100644 --- a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertInsertStatement.java +++ b/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertInsertStatement.java @@ -38,7 +38,7 @@ public void validate(String query) { if (!query.startsWith("with ") || (insertIndex = query.indexOf(")\ninsert ")) == -1) { query = stripReturningClause(query); insertIndex = -2; - if (!query.startsWith("delete ")) { + if (!query.startsWith("insert ")) { Assert.fail("Query is not an insert statement: " + query); return; } diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertStatementBuilder.java b/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertStatementBuilder.java index 4dcb8f5f5f..0f712a574a 100644 --- a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertStatementBuilder.java +++ b/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertStatementBuilder.java @@ -79,6 +79,12 @@ public AssertStatementBuilder select(Class entityClass) { .and(); } + public AssertStatementBuilder select(Class entityClass, String relationName) { + return assertSelect() + .forRelation(entityClass, relationName) + .and(); + } + public AssertSelectStatementBuilder assertSelect() { failIfValidated(); return new AssertSelectStatementBuilder(this, relationalModelAccessor); @@ -94,6 +100,12 @@ public AssertStatementBuilder insert(Class entityClass) { .and(); } + public AssertStatementBuilder insert(Class entityClass, String relationName) { + return assertInsert() + .forRelation(entityClass, relationName) + .and(); + } + public AssertInsertStatementBuilder assertInsert() { failIfValidated(); return new AssertInsertStatementBuilder(this, relationalModelAccessor); @@ -109,6 +121,12 @@ public AssertStatementBuilder update(Class entityClass) { .and(); } + public AssertStatementBuilder update(Class entityClass, String relationName) { + return assertUpdate() + .forRelation(entityClass, relationName) + .and(); + } + public AssertUpdateStatementBuilder assertUpdate() { failIfValidated(); return new AssertUpdateStatementBuilder(this, relationalModelAccessor); @@ -124,6 +142,12 @@ public AssertStatementBuilder delete(Class entityClass) { .and(); } + public AssertStatementBuilder delete(Class entityClass, String relationName) { + return assertDelete() + .forRelation(entityClass, relationName) + .and(); + } + public AssertDeleteStatementBuilder assertDelete() { failIfValidated(); return new AssertDeleteStatementBuilder(this, relationalModelAccessor); diff --git a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertUpdateStatement.java b/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertUpdateStatement.java index 8ce19d5446..737c4cba4a 100644 --- a/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertUpdateStatement.java +++ b/testsuite-base/jpa/src/main/java/com/blazebit/persistence/testsuite/base/jpa/assertion/AssertUpdateStatement.java @@ -38,7 +38,7 @@ public void validate(String query) { if (!query.startsWith("with ") || (updateIndex = query.indexOf(")\nupdate ")) == -1) { query = stripReturningClause(query); updateIndex = -2; - if (!query.startsWith("delete ")) { + if (!query.startsWith("update ")) { Assert.fail("Query is not an update statement: " + query); return; }