From 2ea226999d4936b12dfb190481feba5f126f3527 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 24 Oct 2024 08:03:18 -0500 Subject: [PATCH] HHH-18728 - Allow mixed discriminator-value mappings for ANY HHH-18729 - Allow custom strategy for implicit discriminator-value determination for ANY --- .../chapters/domain/associations.adoc | 182 +++++++-------- .../associations/associations-any-example.sql | 6 - .../associations-any-persist-example.sql | 11 - .../associations-any-query-example.sql | 12 - .../associations-many-to-any-example.sql | 11 +- ...sociations-many-to-any-persist-example.sql | 15 -- ...associations-many-to-any-query-example.sql | 15 -- .../annotations/AnyDiscriminator.java | 16 +- .../AnyDiscriminatorImplicitValues.java | 60 +++++ .../annotations/AnyDiscriminatorValue.java | 1 + .../boot/model/internal/AnyBinder.java | 41 +++- .../boot/model/internal/BinderHelper.java | 8 + .../boot/models/HibernateAnnotations.java | 4 + .../internal/AnyDiscriminatorAnnotation.java | 21 +- ...DiscriminatorImplicitValuesAnnotation.java | 69 ++++++ .../main/java/org/hibernate/mapping/Any.java | 60 ++--- .../org/hibernate/mapping/MappingHelper.java | 15 +- ...FullNameImplicitDiscriminatorStrategy.java | 39 ++++ ...hortNameImplicitDiscriminatorStrategy.java | 38 +++ .../mapping/DiscriminatorConverter.java | 10 +- .../EmbeddableDiscriminatorConverter.java | 13 +- .../metamodel/mapping/EntityMappingType.java | 4 + .../internal/AnyDiscriminatorPart.java | 50 +--- .../DiscriminatedAssociationMapping.java | 2 +- .../ExplicitDiscriminatorConverter.java | 144 ------------ .../ImplicitDiscriminatorConverter.java | 121 ---------- ... => UnifiedAnyDiscriminatorConverter.java} | 104 +++++---- .../internal/AnyMappingDomainTypeImpl.java | 2 +- .../spi/ImplicitDiscriminatorStrategy.java | 26 +++ .../entity/AbstractEntityPersister.java | 58 ++--- .../type/AnyDiscriminatorValueStrategy.java | 58 ----- .../main/java/org/hibernate/type/AnyType.java | 19 +- .../java/org/hibernate/type/MetaType.java | 22 +- .../{mixed => discriminator}/CardPayment.java | 10 +- .../{mixed => discriminator}/CashPayment.java | 9 +- .../CheckPayment.java | 7 +- .../orm/test/any/discriminator/Payment.java | 17 ++ .../explicit/ExplicitValueTests.java | 100 ++++++++ .../explicit}/Order.java | 28 +-- .../implicit/ImplicitValueTests.java | 104 +++++++++ .../any/discriminator/implicit/Order.java | 66 ++++++ .../orm/test/any/discriminator/many/Loan.java | 80 +++++++ .../discriminator/many/ManyToAnyTests.java | 68 ++++++ .../test/any/discriminator/meta/Order.java | 43 ++++ .../meta/PaymentDiscriminationDef.java | 33 +++ .../discriminator/mixed/MixedValueTests.java | 98 ++++++++ .../test/any/discriminator/mixed/Order.java | 65 ++++++ .../AnyDiscriminatorValueHandlingTests.java | 121 ---------- .../AnyDiscriminatorValueStrategyTests.java | 217 ------------------ .../hibernate/orm/test/any/mixed/Payment.java | 12 - 50 files changed, 1247 insertions(+), 1088 deletions(-) delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-any-example.sql delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-any-persist-example.sql delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-any-query-example.sql delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-many-to-any-persist-example.sql delete mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-many-to-any-query-example.sql create mode 100644 hibernate-core/src/main/java/org/hibernate/annotations/AnyDiscriminatorImplicitValues.java create mode 100644 hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/AnyDiscriminatorImplicitValuesAnnotation.java create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/internal/FullNameImplicitDiscriminatorStrategy.java create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/internal/ShortNameImplicitDiscriminatorStrategy.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ExplicitDiscriminatorConverter.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ImplicitDiscriminatorConverter.java rename hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/{MixedDiscriminatorConverter.java => UnifiedAnyDiscriminatorConverter.java} (52%) create mode 100644 hibernate-core/src/main/java/org/hibernate/metamodel/spi/ImplicitDiscriminatorStrategy.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/type/AnyDiscriminatorValueStrategy.java rename hibernate-core/src/test/java/org/hibernate/orm/test/any/{mixed => discriminator}/CardPayment.java (81%) rename hibernate-core/src/test/java/org/hibernate/orm/test/any/{mixed => discriminator}/CashPayment.java (75%) rename hibernate-core/src/test/java/org/hibernate/orm/test/any/{mixed => discriminator}/CheckPayment.java (80%) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/Payment.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/explicit/ExplicitValueTests.java rename hibernate-core/src/test/java/org/hibernate/orm/test/any/{mixed => discriminator/explicit}/Order.java (59%) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/implicit/ImplicitValueTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/implicit/Order.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/many/Loan.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/many/ManyToAnyTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/meta/Order.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/meta/PaymentDiscriminationDef.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/mixed/MixedValueTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/mixed/Order.java delete mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/AnyDiscriminatorValueHandlingTests.java delete mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/AnyDiscriminatorValueStrategyTests.java delete mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/Payment.java diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc index 4e2d2cebef..9bf942533c 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc @@ -3,6 +3,7 @@ :root-project-dir: ../../../../../../.. :core-project-dir: {root-project-dir}/hibernate-core :example-dir-association: {core-project-dir}/src/test/java/org/hibernate/orm/test/associations +:example-dir-any: {core-project-dir}/src/test/java/org/hibernate/orm/test/any :extrasdir: extras/associations Associations describe how two or more entities form a relationship based on a database joining semantics. @@ -559,7 +560,7 @@ The `@Any` mapping is useful to emulate a unidirectional `@ManyToOne` associatio Because the `@Any` mapping defines a polymorphic association to classes from multiple tables, this association type requires the FK column which provides the associated parent identifier and -a metadata information for the associated entity type. +a discriminator which identifies the associated entity type. [NOTE] ==== @@ -568,16 +569,33 @@ This is not the usual way of mapping polymorphic associations and you should use To map such an association, Hibernate needs to understand 3 things: -1. The column and mapping for the discriminator -2. The column and mapping for the key -3. The mapping between discriminator values and entity classes +1. The column and mapping for the <> +2. The column and mapping for the <> +3. The mapping between discriminator values and entity types which may be <> +<> or <>. + +For the rest of this discussion, consider the following model which will be the target types for the `@Any` associations: + +[[associations-any-target-example]] +.`Payment` class hierarchy +==== +[source, java, indent=0] +---- +include::{example-dir-any}/discriminator/Payment.java[tags=associations-any-example] + +include::{example-dir-any}/discriminator/CardPayment.java[tags=associations-any-example] + +include::{example-dir-any}/discriminator/CashPayment.java[tags=associations-any-example] + +include::{example-dir-any}/discriminator/CheckPayment.java[tags=associations-any-example] +---- +==== [[associations-any-discriminator]] ===== The discriminator -The discriminator of an any-style association holds the value that indicates which entity is -referred to by a row. +The discriminator is the value that indicates which entity is referred to by a row. Its "column" can be specified with either `@Column` or `@Formula`. The mapping type can be influenced by any of: @@ -601,114 +619,105 @@ type can be influenced by any of: 4. `@AnyKeyJdbcTypeCode` -[[associations-any-values]] -===== The discriminator value mappings -`@AnyDiscriminatorValue` is used to map the discriminator values to the corresponding entity classes +[[associations-any-explicit-discriminator]] +===== Explicit discriminator mappings + +Explicit discriminator mappings are defined using one-or-more `@AnyDiscriminatorValue` annotations. E.g. -[[associations-any-property]] -==== Example using @Any mapping - -For this example, consider the following `Property` class hierarchy: - -[[associations-any-property-example]] -.`Property` class hierarchy +[[associations-any-discriminator-explicit-example]] +.Explicit @AnyDiscriminatorValue annotations ==== [source, java, indent=0] ---- -include::{example-dir-association}/any/Property.java[tags=associations-any-property-example] - -include::{example-dir-association}/any/IntegerProperty.java[tags=associations-any-property-example] - -include::{example-dir-association}/any/StringProperty.java[tags=associations-any-property-example] +include::{example-dir-any}/discriminator/explicit/Order.java[tags=associations-any-explicit-discriminator-example] ---- ==== -A `PropertyHolder` entity defines an attribute of type `Property`: +Here, we map 2 explicit discriminator value mappings: -[[associations-any-example]] -.`@Any` mapping usage +1. `CARD` <-> `CardPayment` +2. `CHECK` <-> `CheckPayment` + +Notice that `CashPayment` is not explicitly mapped. An attempt to use `CashPayment` for this attribute will result +in an exception. + + +[[associations-any-implicit-discriminator]] +===== Implicit discriminator mappings + +Implicit discriminator mappings define no `@AnyDiscriminatorValue` annotations. E.g. + +[[associations-any-discriminator-implicit-example]] +.Implicit @Any discriminator mappings ==== [source, java, indent=0] ---- -include::{example-dir-association}/any/PropertyHolder.java[tags=associations-any-example] ----- - -[source, SQL, indent=0] ----- -include::{extrasdir}/associations-any-example.sql[] +include::{example-dir-any}/discriminator/implicit/Order.java[tags=associations-any-implicit-discriminator-example] ---- ==== -`PropertyHolder#property` can refer to either `StringProperty` or `IntegerProperty` references, as indicated -by the associated discriminator according to the `@DiscriminatorValue` annotations. +Here all `Payment` subtypes are allowed. By default Hibernate will use the entity's full-name (which is generally the class's FQN). -As you can see, there are two columns used to reference a `Property` instance: `property_id` and `property_type`. -The `property_id` is used to match the `id` column of either the `string_property` or `integer_property` tables, -while the `property_type` is used to match the `string_property` or the `integer_property` table. +Hibernate also offers a `@AnyDiscriminatorImplicitValues` annotation which allows configuration of how this implicit +mapping works. E.g., to use the entity's short-name instead of the full-name - -To see the `@Any` annotation in action, consider the next examples. -If we persist an `IntegerProperty` as well as a `StringProperty` entity, and associate -the `StringProperty` entity with a `PropertyHolder`, -Hibernate will generate the following SQL queries: - -[[associations-any-persist-example]] -.`@Any` mapping persist example +[[associations-any-discriminator-implicit-short-example]] +.Implicit @Any discriminator mappings (short name) ==== [source, java, indent=0] ---- -include::{example-dir-association}/any/AnyTest.java[tags=associations-any-persist-example] ----- - -[source, SQL, indent=0] ----- -include::{extrasdir}/associations-any-persist-example.sql[] +include::{example-dir-any}/discriminator/implicit/Order.java[tags=associations-any-implicit-discriminator-short-example] ---- ==== -When fetching the `PropertyHolder` entity and navigating its `property` association, -Hibernate will fetch the associated `StringProperty` entity like this: +[NOTE] +==== +`@AnyDiscriminatorImplicitValues` also offers the ability to define a custom strategy for determining the +discriminator-value <-> entity-type mapping, but its use is not covered here. +==== -[[associations-any-query-example]] -.`@Any` mapping query example + +[[associations-any-mixed-discriminator]] +===== Mixed discriminator mappings + +A mixed strategy combines `@AnyDiscriminatorValue` and `@AnyDiscriminatorImplicitValues`. Mappings +explicitly defined using `@AnyDiscriminatorValue` take precedence. E.g. + + +[[associations-any-discriminator-mixed-example]] +.Mixed @Any discriminator mappings (short name) ==== [source, java, indent=0] ---- -include::{example-dir-association}/any/AnyTest.java[tags=associations-any-query-example] ----- - -[source, SQL, indent=0] ----- -include::{extrasdir}/associations-any-query-example.sql[] +include::{example-dir-any}/discriminator/mixed/Order.java[tags=associations-any-mixed-discriminator-short-example] ---- ==== + [[associations-any-meta-annotations]] ===== Using meta-annotations As mentioned in <>, Hibernate's ANY-related annotations can be composed using meta-annotations to re-use ANY mapping details. -Looking back at <>, we can see how cumbersome it would be to duplicate that -information every time `Property` is mapped in the domain model. This description can also be moved +Given all the details needed to define an ANY mapping, we can see how cumbersome it would be to duplicate that +information every time `Payment` is mapped in the domain model. This description can also be moved into a single annotation that we can apply in each usage. [[associations-any-composed-example]] -.`@Any` mapping usage +.`@Any` mapping with meta-annotation ==== [source, java, indent=0] ---- -include::{example-dir-association}/any/PropertyHolder2.java[tags=associations-any-def-example] +include::{example-dir-any}/discriminator/meta/Order.java[tags=associations-any-discriminator-meta-example] ---- ==== -Though the mapping has been "simplified", the mapping works exactly as shown in <>. - - [[associations-many-to-any]] @@ -724,16 +733,16 @@ The mapping details are the same between `@Any` and `@ManyToAny` except for: of just `@JoinColumn` -In the following example, the `PropertyRepository` entity has a collection of `Property` entities. +In the following example, the `Loan` entity has a collection of `Payments` objects. -The `repository_properties` link table holds the associations between `PropertyRepository` and `Property` entities. +The `loan_payments` table holds the associations between `Loan` and `Payment` references. [[associations-many-to-any-example]] .`@ManyToAny` mapping usage ==== [source, java, indent=0] ---- -include::{example-dir-association}/any/PropertyRepository.java[tags=associations-many-to-any-example] +include::{example-dir-any}/discriminator/many/Loan.java[tags=associations-many-to-any-example] ---- [source, SQL, indent=0] @@ -742,43 +751,6 @@ include::{extrasdir}/associations-many-to-any-example.sql[] ---- ==== -To see the `@ManyToAny` annotation in action, consider the next examples. - -If we persist an `IntegerProperty` as well as a `StringProperty` entity, -and associate both of them with a `PropertyRepository` parent entity, -Hibernate will generate the following SQL queries: - -[[associations-many-to-any-persist-example]] -.`@ManyToAny` mapping persist example -==== -[source, java, indent=0] ----- -include::{example-dir-association}/any/ManyToAnyTest.java[tags=associations-many-to-any-persist-example] ----- - -[source, SQL, indent=0] ----- -include::{extrasdir}/associations-many-to-any-persist-example.sql[] ----- -==== - -When fetching the `PropertyRepository` entity and navigating its `properties` association, -Hibernate will fetch the associated `IntegerProperty` and `StringProperty` entities like this: - -[[associations-many-to-any-query-example]] -.`@ManyToAny` mapping query example -==== -[source, java, indent=0] ----- -include::{example-dir-association}/any/ManyToAnyTest.java[tags=associations-many-to-any-query-example] ----- - -[source, SQL, indent=0] ----- -include::{extrasdir}/associations-many-to-any-query-example.sql[] ----- -==== - [[associations-JoinFormula]] diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-any-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-any-example.sql deleted file mode 100644 index f25ee01e50..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-any-example.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE property_holder ( - id BIGINT NOT NULL, - property_type VARCHAR(255), - property_id BIGINT, - PRIMARY KEY ( id ) -) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-any-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-any-persist-example.sql deleted file mode 100644 index 87cf331e49..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-any-persist-example.sql +++ /dev/null @@ -1,11 +0,0 @@ -INSERT INTO integer_property - ( "name", "value", id ) -VALUES ( 'age', 23, 1 ) - -INSERT INTO string_property - ( "name", "value", id ) -VALUES ( 'name', 'John Doe', 1 ) - -INSERT INTO property_holder - ( property_type, property_id, id ) -VALUES ( 'S', 1, 1 ) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-any-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-any-query-example.sql deleted file mode 100644 index 1467518486..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-any-query-example.sql +++ /dev/null @@ -1,12 +0,0 @@ -SELECT ph.id AS id1_1_0_, - ph.property_type AS property2_1_0_, - ph.property_id AS property3_1_0_ -FROM property_holder ph -WHERE ph.id = 1 - - -SELECT sp.id AS id1_2_0_, - sp."name" AS name2_2_0_, - sp."value" AS value3_2_0_ -FROM string_property sp -WHERE sp.id = 1 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-many-to-any-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-many-to-any-example.sql index 08c8171611..4149a5c68b 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-many-to-any-example.sql +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-many-to-any-example.sql @@ -1,10 +1,11 @@ -CREATE TABLE property_repository ( +CREATE TABLE loans ( id BIGINT NOT NULL, + ..., PRIMARY KEY ( id ) ) -CREATE TABLE repository_properties ( - repository_id BIGINT NOT NULL, - property_type VARCHAR(255), - property_id BIGINT NOT NULL +CREATE TABLE loan_payments ( + loan_fk BIGINT NOT NULL, + payment_type VARCHAR(255), + payment_fk BIGINT NOT NULL ) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-many-to-any-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-many-to-any-persist-example.sql deleted file mode 100644 index 5bc5fd082e..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-many-to-any-persist-example.sql +++ /dev/null @@ -1,15 +0,0 @@ -INSERT INTO integer_property - ( "name", "value", id ) -VALUES ( 'age', 23, 1 ) - -INSERT INTO string_property - ( "name", "value", id ) -VALUES ( 'name', 'John Doe', 1 ) - -INSERT INTO property_repository ( id ) -VALUES ( 1 ) - -INSERT INTO repository_properties - ( repository_id , property_type , property_id ) -VALUES - ( 1 , 'I' , 1 ) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-many-to-any-query-example.sql b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-many-to-any-query-example.sql deleted file mode 100644 index 207fd60653..0000000000 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/associations/associations-many-to-any-query-example.sql +++ /dev/null @@ -1,15 +0,0 @@ -SELECT pr.id AS id1_1_0_ -FROM property_repository pr -WHERE pr.id = 1 - -SELECT ip.id AS id1_0_0_ , - ip."name" AS name2_0_0_ , - ip."value" AS value3_0_0_ -FROM integer_property ip -WHERE ip.id = 1 - -SELECT sp.id AS id1_3_0_ , - sp."name" AS name2_3_0_ , - sp."value" AS value3_3_0_ -FROM string_property sp -WHERE sp.id = 1 diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/AnyDiscriminator.java b/hibernate-core/src/main/java/org/hibernate/annotations/AnyDiscriminator.java index afc6b38c02..2ce6ddcba4 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/AnyDiscriminator.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/AnyDiscriminator.java @@ -4,17 +4,15 @@ */ package org.hibernate.annotations; +import jakarta.persistence.DiscriminatorType; + import java.lang.annotation.Retention; import java.lang.annotation.Target; -import jakarta.persistence.DiscriminatorType; -import org.hibernate.type.AnyDiscriminatorValueStrategy; - import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import static org.hibernate.type.AnyDiscriminatorValueStrategy.AUTO; /** * A simplified way to specify the type of the discriminator in an {@link Any} @@ -34,6 +32,8 @@ import static org.hibernate.type.AnyDiscriminatorValueStrategy.AUTO; * {@code @AnyDiscriminator}. * * @see Any + * @see AnyDiscriminatorValue + * @see AnyDiscriminatorImplicitValues * * @since 6.0 */ @@ -46,12 +46,4 @@ public @interface AnyDiscriminator { * or {@link JdbcTypeCode}. */ DiscriminatorType value() default DiscriminatorType.STRING; - - /** - * How the discriminator value should be handled in regard to explicit - * {@linkplain AnyDiscriminatorValue} mappings, if any. - * - * @since 7.0 - */ - AnyDiscriminatorValueStrategy valueStrategy() default AUTO; } diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/AnyDiscriminatorImplicitValues.java b/hibernate-core/src/main/java/org/hibernate/annotations/AnyDiscriminatorImplicitValues.java new file mode 100644 index 0000000000..04152f0828 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/annotations/AnyDiscriminatorImplicitValues.java @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.annotations; + +import org.hibernate.Incubating; +import org.hibernate.metamodel.internal.FullNameImplicitDiscriminatorStrategy; +import org.hibernate.metamodel.internal.ShortNameImplicitDiscriminatorStrategy; +import org.hibernate.metamodel.spi.ImplicitDiscriminatorStrategy; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Defines how to handle {@linkplain AnyDiscriminator discriminator} values which are not explicitly + * mapped with {@linkplain AnyDiscriminatorValue}. + * + * @author Steve Ebersole + * @since 7.0 + */ +@Target({METHOD, FIELD, ANNOTATION_TYPE}) +@Retention( RUNTIME ) +@Incubating +public @interface AnyDiscriminatorImplicitValues { + enum Strategy { + /** + * Use the {@link ImplicitDiscriminatorStrategy} implementation specified by {@link #implementation()} + */ + CUSTOM, + /** + * Use the entity's short-name. + * + * @see ShortNameImplicitDiscriminatorStrategy + */ + SHORT_NAME, + /** + * Use the entity's full-name. + * + * @see FullNameImplicitDiscriminatorStrategy + */ + FULL_NAME + } + + /** + * The type of strategy to use. This is {@link Strategy#CUSTOM} by default and + * the class named by {@link #implementation()} is used. + */ + Strategy value() default Strategy.CUSTOM; + + /** + * Specific strategy implementation to use, when combined with {@code value=CUSTOM} + */ + Class implementation() default ImplicitDiscriminatorStrategy.class; +} diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/AnyDiscriminatorValue.java b/hibernate-core/src/main/java/org/hibernate/annotations/AnyDiscriminatorValue.java index b3efe4584b..9bea511884 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/AnyDiscriminatorValue.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/AnyDiscriminatorValue.java @@ -27,6 +27,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; * * @see Any * @see AnyDiscriminator + * @see AnyDiscriminatorImplicitValues * * @since 6.0 */ diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnyBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnyBinder.java index 8199e10fe0..4c1ed0863d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnyBinder.java @@ -9,6 +9,7 @@ import java.util.Locale; import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; import org.hibernate.annotations.AnyDiscriminator; +import org.hibernate.annotations.AnyDiscriminatorImplicitValues; import org.hibernate.annotations.Cascade; import org.hibernate.annotations.Columns; import org.hibernate.annotations.Formula; @@ -19,6 +20,9 @@ import org.hibernate.boot.spi.PropertyData; import org.hibernate.mapping.Any; import org.hibernate.mapping.Join; import org.hibernate.mapping.Property; +import org.hibernate.metamodel.internal.FullNameImplicitDiscriminatorStrategy; +import org.hibernate.metamodel.internal.ShortNameImplicitDiscriminatorStrategy; +import org.hibernate.metamodel.spi.ImplicitDiscriminatorStrategy; import org.hibernate.models.spi.MemberDetails; import jakarta.persistence.Column; @@ -109,8 +113,9 @@ public class AnyBinder { ); final AnyDiscriminator anyDiscriminator = property.getDirectAnnotationUsage( AnyDiscriminator.class ); - if ( anyDiscriminator != null ) { - value.setDiscriminatorValueStrategy( anyDiscriminator.valueStrategy() ); + final AnyDiscriminatorImplicitValues anyDiscriminatorImplicitValues = property.getDirectAnnotationUsage( AnyDiscriminatorImplicitValues.class ); + if ( anyDiscriminatorImplicitValues != null ) { + value.setImplicitDiscriminatorValueStrategy( resolveImplicitDiscriminatorStrategy( anyDiscriminatorImplicitValues, context ) ); } final PropertyBinder binder = new PropertyBinder(); @@ -134,4 +139,36 @@ public class AnyBinder { propertyHolder.addProperty( prop, inferredData.getAttributeMember(), columns, inferredData.getDeclaringClass() ); binder.callAttributeBindersInSecondPass( prop ); } + + public static ImplicitDiscriminatorStrategy resolveImplicitDiscriminatorStrategy( + AnyDiscriminatorImplicitValues anyDiscriminatorImplicitValues, + MetadataBuildingContext context) { + final AnyDiscriminatorImplicitValues.Strategy strategy = anyDiscriminatorImplicitValues.value(); + + if ( strategy == AnyDiscriminatorImplicitValues.Strategy.FULL_NAME ) { + return FullNameImplicitDiscriminatorStrategy.FULL_NAME_STRATEGY; + } + + if ( strategy == AnyDiscriminatorImplicitValues.Strategy.SHORT_NAME ) { + return ShortNameImplicitDiscriminatorStrategy.SHORT_NAME_STRATEGY; + } + + assert strategy == AnyDiscriminatorImplicitValues.Strategy.CUSTOM; + + final Class customStrategy = anyDiscriminatorImplicitValues.implementation(); + + if ( ImplicitDiscriminatorStrategy.class.equals( customStrategy ) ) { + return null; + } + + if ( FullNameImplicitDiscriminatorStrategy.class.equals( customStrategy ) ) { + return FullNameImplicitDiscriminatorStrategy.FULL_NAME_STRATEGY; + } + + if ( ShortNameImplicitDiscriminatorStrategy.class.equals( customStrategy ) ) { + return ShortNameImplicitDiscriminatorStrategy.SHORT_NAME_STRATEGY; + } + + return context.getBootstrapContext().getCustomTypeProducer().produceBeanInstance( customStrategy ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java index b53be48ead..20bbffbb6d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java @@ -21,6 +21,7 @@ import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; import org.hibernate.FetchMode; import org.hibernate.MappingException; +import org.hibernate.annotations.AnyDiscriminatorImplicitValues; import org.hibernate.annotations.AnyDiscriminatorValue; import org.hibernate.annotations.AnyDiscriminatorValues; import org.hibernate.annotations.Cascade; @@ -68,6 +69,7 @@ import jakarta.persistence.OneToOne; import static jakarta.persistence.ConstraintMode.NO_CONSTRAINT; import static jakarta.persistence.ConstraintMode.PROVIDER_DEFAULT; import static org.hibernate.boot.model.internal.AnnotatedColumn.buildColumnOrFormulaFromAnnotation; +import static org.hibernate.boot.model.internal.AnyBinder.resolveImplicitDiscriminatorStrategy; import static org.hibernate.internal.util.StringHelper.isEmpty; import static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.internal.util.StringHelper.qualifier; @@ -806,6 +808,12 @@ public class BinderHelper { ); value.setDiscriminatorValueMappings( discriminatorValueMappings ); + + final AnyDiscriminatorImplicitValues anyDiscriminatorImplicitValues = property.getDirectAnnotationUsage( AnyDiscriminatorImplicitValues.class ); + if ( anyDiscriminatorImplicitValues != null ) { + value.setImplicitDiscriminatorValueStrategy( resolveImplicitDiscriminatorStrategy( anyDiscriminatorImplicitValues, context ) ); + } + final BasicValueBinder keyValueBinder = new BasicValueBinder( BasicValueBinder.Kind.ANY_KEY, context ); final List columns = keyColumns.getJoinColumns(); assert columns.size() == 1; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/HibernateAnnotations.java b/hibernate-core/src/main/java/org/hibernate/boot/models/HibernateAnnotations.java index 5c2ef4f566..63bf9170b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/HibernateAnnotations.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/HibernateAnnotations.java @@ -36,6 +36,10 @@ public interface HibernateAnnotations { AnyDiscriminator.class, AnyDiscriminatorAnnotation.class ); + OrmAnnotationDescriptor ANY_DISCRIMINATOR_IMPLICIT_VALUES = new OrmAnnotationDescriptor<>( + AnyDiscriminatorImplicitValues.class, + AnyDiscriminatorImplicitValuesAnnotation.class + ); OrmAnnotationDescriptor ANY_DISCRIMINATOR_VALUES = new OrmAnnotationDescriptor<>( AnyDiscriminatorValues.class, AnyDiscriminatorValuesAnnotation.class diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/AnyDiscriminatorAnnotation.java b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/AnyDiscriminatorAnnotation.java index 12404277da..27339375df 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/AnyDiscriminatorAnnotation.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/AnyDiscriminatorAnnotation.java @@ -4,25 +4,22 @@ */ package org.hibernate.boot.models.annotations.internal; -import java.lang.annotation.Annotation; -import java.util.Map; - import org.hibernate.annotations.AnyDiscriminator; import org.hibernate.models.spi.SourceModelBuildingContext; -import org.hibernate.type.AnyDiscriminatorValueStrategy; + +import java.lang.annotation.Annotation; +import java.util.Map; @SuppressWarnings({ "ClassExplicitlyAnnotation", "unused" }) @jakarta.annotation.Generated("org.hibernate.orm.build.annotations.ClassGeneratorProcessor") public class AnyDiscriminatorAnnotation implements AnyDiscriminator { private jakarta.persistence.DiscriminatorType value; - private AnyDiscriminatorValueStrategy valueStrategy; /** * Used in creating dynamic annotation instances (e.g. from XML) */ public AnyDiscriminatorAnnotation(SourceModelBuildingContext modelContext) { this.value = jakarta.persistence.DiscriminatorType.STRING; - this.valueStrategy = AnyDiscriminatorValueStrategy.AUTO; } /** @@ -30,7 +27,6 @@ public class AnyDiscriminatorAnnotation implements AnyDiscriminator { */ public AnyDiscriminatorAnnotation(AnyDiscriminator annotation, SourceModelBuildingContext modelContext) { this.value = annotation.value(); - this.valueStrategy = annotation.valueStrategy(); } /** @@ -53,15 +49,4 @@ public class AnyDiscriminatorAnnotation implements AnyDiscriminator { public void value(jakarta.persistence.DiscriminatorType value) { this.value = value; } - - @Override - public AnyDiscriminatorValueStrategy valueStrategy() { - return valueStrategy; - } - - public void valueStrategy(AnyDiscriminatorValueStrategy valueStrategy) { - this.valueStrategy = valueStrategy; - } - - } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/AnyDiscriminatorImplicitValuesAnnotation.java b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/AnyDiscriminatorImplicitValuesAnnotation.java new file mode 100644 index 0000000000..dd7df076b8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/AnyDiscriminatorImplicitValuesAnnotation.java @@ -0,0 +1,69 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.boot.models.annotations.internal; + +import org.hibernate.annotations.AnyDiscriminatorImplicitValues; +import org.hibernate.metamodel.spi.ImplicitDiscriminatorStrategy; +import org.hibernate.models.spi.SourceModelBuildingContext; + +import java.lang.annotation.Annotation; +import java.util.Map; + +@SuppressWarnings({ "ClassExplicitlyAnnotation", "unused" }) +@jakarta.annotation.Generated("org.hibernate.orm.build.annotations.ClassGeneratorProcessor") +public class AnyDiscriminatorImplicitValuesAnnotation implements AnyDiscriminatorImplicitValues { + private AnyDiscriminatorImplicitValues.Strategy value; + private Class implementation; + + /** + * Used in creating dynamic annotation instances (e.g. from XML) + */ + public AnyDiscriminatorImplicitValuesAnnotation(SourceModelBuildingContext modelContext) { + this.value = Strategy.CUSTOM; + this.implementation = ImplicitDiscriminatorStrategy.class; + } + + /** + * Used in creating annotation instances from JDK variant + */ + public AnyDiscriminatorImplicitValuesAnnotation(AnyDiscriminatorImplicitValues annotation, SourceModelBuildingContext modelContext) { + this.value = annotation.value(); + this.implementation = annotation.implementation(); + } + + /** + * Used in creating annotation instances from Jandex variant + */ + public AnyDiscriminatorImplicitValuesAnnotation(Map attributeValues, SourceModelBuildingContext modelContext) { + this.value = (Strategy) attributeValues.get( "value" ); + //noinspection unchecked + this.implementation = (Class) attributeValues.get( "implementation" ); + } + + @Override + public Class annotationType() { + return AnyDiscriminatorImplicitValues.class; + } + + + @Override + public Strategy value() { + return value; + } + + public void value(Strategy value) { + this.value = value; + } + + + @Override + public Class implementation() { + return implementation; + } + + public void implementation(Class implementation) { + this.implementation = implementation; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Any.java b/hibernate-core/src/main/java/org/hibernate/mapping/Any.java index 60d01794e7..bd6b4caed4 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Any.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Any.java @@ -4,19 +4,19 @@ */ package org.hibernate.mapping; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.function.Consumer; - import org.hibernate.Incubating; import org.hibernate.MappingException; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.type.AnyDiscriminatorValueStrategy; +import org.hibernate.metamodel.spi.ImplicitDiscriminatorStrategy; import org.hibernate.type.AnyType; -import org.hibernate.type.Type; import org.hibernate.type.MappingContext; +import org.hibernate.type.Type; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; /** * A mapping model object representing a {@linkplain org.hibernate.annotations.Any polymorphic association} @@ -35,7 +35,7 @@ public class Any extends SimpleValue { // common private Map metaValueToEntityNameMap; - private AnyDiscriminatorValueStrategy discriminatorValueStrategy = AnyDiscriminatorValueStrategy.AUTO; + private ImplicitDiscriminatorStrategy implicitValueStrategy; private boolean lazy = true; private AnyType resolvedType; @@ -75,7 +75,7 @@ public class Any extends SimpleValue { this.metaValueToEntityNameMap = original.metaValueToEntityNameMap == null ? null : new HashMap<>(original.metaValueToEntityNameMap); - this.discriminatorValueStrategy = original.discriminatorValueStrategy; + this.implicitValueStrategy = original.implicitValueStrategy; this.lazy = original.lazy; } @@ -131,28 +131,6 @@ public class Any extends SimpleValue { this.keyMapping.setTypeName( identifierType ); } - /** - * Current strategy for interpreting {@linkplain org.hibernate.annotations.AnyDiscriminatorValue} definitions, - * especially in terms of implicit, explicit and potentially missing values. - * - * @since 7.0 - */ - @Incubating - public AnyDiscriminatorValueStrategy getDiscriminatorValueStrategy() { - return discriminatorValueStrategy; - } - - /** - * Set the strategy - * - * @see #getDiscriminatorValueStrategy - * @since 7.0 - */ - @Incubating - public void setDiscriminatorValueStrategy(AnyDiscriminatorValueStrategy discriminatorValueStrategy) { - this.discriminatorValueStrategy = discriminatorValueStrategy; - } - @Override public AnyType getType() throws MappingException { if ( resolvedType == null ) { @@ -175,8 +153,8 @@ public class Any extends SimpleValue { resolvedType = MappingHelper.anyMapping( discriminatorType, identifierType, - discriminatorValueStrategy, metaValueToEntityNameMap, + implicitValueStrategy, isLazy(), getBuildingContext() ); @@ -242,6 +220,19 @@ public class Any extends SimpleValue { this.metaValueToEntityNameMap = metaValueToEntityNameMap; } + /** + * Set the strategy for dealing with discriminator mappings which are not explicitly defined by + * {@linkplain org.hibernate.annotations.AnyDiscriminatorValue}. + * + * @apiNote {@code null} indicates to not allow implicit mappings. + * + * @since 7.0 + */ + @Incubating + public void setImplicitDiscriminatorValueStrategy(ImplicitDiscriminatorStrategy implicitValueStrategy) { + this.implicitValueStrategy = implicitValueStrategy; + } + public boolean isLazy() { return lazy; } @@ -334,7 +325,6 @@ public class Any extends SimpleValue { public static class MetaValue extends SimpleValue { private String typeName; private String columnName; - private AnyDiscriminatorValueStrategy valueStrategy; private final Consumer selectableConsumer; @@ -427,10 +417,6 @@ public class Any extends SimpleValue { return columnName != null && getType().getColumnSpan( mappingContext ) == 1; } - - public AnyDiscriminatorValueStrategy getValueStrategy() { - return valueStrategy; - } } public static class KeyValue extends SimpleValue { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/MappingHelper.java b/hibernate-core/src/main/java/org/hibernate/mapping/MappingHelper.java index 77e5a0364f..5c265a47fd 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/MappingHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/MappingHelper.java @@ -18,11 +18,11 @@ import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.metamodel.spi.ImplicitDiscriminatorStrategy; import org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer; import org.hibernate.resource.beans.spi.ManagedBean; import org.hibernate.resource.beans.spi.ManagedBeanRegistry; import org.hibernate.resource.beans.spi.ProvidedInstanceManagedBeanImpl; -import org.hibernate.type.AnyDiscriminatorValueStrategy; import org.hibernate.type.AnyType; import org.hibernate.type.CollectionType; import org.hibernate.type.CustomCollectionType; @@ -129,17 +129,24 @@ public final class MappingHelper { Map explicitValeMappings, boolean lazy, MetadataBuildingContext buildingContext) { - return anyMapping( discriminatorType, identifierType, AnyDiscriminatorValueStrategy.AUTO, explicitValeMappings, lazy, buildingContext ); + return anyMapping( + discriminatorType, + identifierType, + explicitValeMappings, + null, + lazy, + buildingContext + ); } public static AnyType anyMapping( Type discriminatorType, Type identifierType, - AnyDiscriminatorValueStrategy discriminatorValueStrategy, Map explicitValeMappings, + ImplicitDiscriminatorStrategy implicitValueStrategy, boolean lazy, MetadataBuildingContext buildingContext) { - final MetaType metaType = new MetaType( discriminatorType, discriminatorValueStrategy, explicitValeMappings ); + final MetaType metaType = new MetaType( discriminatorType, implicitValueStrategy, explicitValeMappings ); return new AnyType( buildingContext.getBootstrapContext().getTypeConfiguration(), metaType, identifierType, lazy ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/FullNameImplicitDiscriminatorStrategy.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/FullNameImplicitDiscriminatorStrategy.java new file mode 100644 index 0000000000..7f3b26a669 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/FullNameImplicitDiscriminatorStrategy.java @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.metamodel.internal; + +import org.hibernate.HibernateException; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.metamodel.spi.ImplicitDiscriminatorStrategy; +import org.hibernate.metamodel.spi.MappingMetamodelImplementor; +import org.hibernate.persister.entity.EntityPersister; + +/** + * ImplicitDiscriminatorStrategy implementation using entity {@linkplain EntityMappingType#getEntityName() full-names}. + * + * @author Steve Ebersole + */ +public class FullNameImplicitDiscriminatorStrategy implements ImplicitDiscriminatorStrategy { + public static final FullNameImplicitDiscriminatorStrategy FULL_NAME_STRATEGY = new FullNameImplicitDiscriminatorStrategy(); + + @Override + public Object toDiscriminatorValue(EntityMappingType entityMapping, NavigableRole discriminatorRole, MappingMetamodelImplementor mappingModel) { + return entityMapping.getEntityName(); + } + + @Override + public EntityMappingType toEntityMapping(Object discriminatorValue, NavigableRole discriminatorRole, MappingMetamodelImplementor mappingModel) { + if ( discriminatorValue instanceof String assumedEntityName ) { + final EntityPersister persister = mappingModel.findEntityDescriptor( assumedEntityName ); + + if ( persister != null ) { + return persister; + } + } + + throw new HibernateException( "Cannot interpret discriminator value (" + discriminatorRole + ") : " + discriminatorValue ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/ShortNameImplicitDiscriminatorStrategy.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/ShortNameImplicitDiscriminatorStrategy.java new file mode 100644 index 0000000000..41566241f4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/ShortNameImplicitDiscriminatorStrategy.java @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.metamodel.internal; + +import org.hibernate.HibernateException; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.metamodel.spi.ImplicitDiscriminatorStrategy; +import org.hibernate.metamodel.spi.MappingMetamodelImplementor; + +/** + * ImplicitDiscriminatorStrategy implementation using entity {@linkplain EntityMappingType#getEntityName() full-names}. + * + * @author Steve Ebersole + */ +public class ShortNameImplicitDiscriminatorStrategy implements ImplicitDiscriminatorStrategy { + public static final ShortNameImplicitDiscriminatorStrategy SHORT_NAME_STRATEGY = new ShortNameImplicitDiscriminatorStrategy(); + + @Override + public Object toDiscriminatorValue(EntityMappingType entityMapping, NavigableRole discriminatorRole, MappingMetamodelImplementor mappingModel) { + return entityMapping.getImportedName(); + } + + @Override + public EntityMappingType toEntityMapping(Object discriminatorValue, NavigableRole discriminatorRole, MappingMetamodelImplementor mappingModel) { + if ( discriminatorValue instanceof String assumedEntityName ) { + final String importedName = mappingModel.getImportedName( assumedEntityName ); + final EntityMappingType entityMapping = mappingModel.findEntityDescriptor( importedName ); + if ( entityMapping != null ) { + return entityMapping; + } + } + + throw new HibernateException( "Cannot interpret discriminator value (" + discriminatorRole + ") : " + discriminatorValue ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/DiscriminatorConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/DiscriminatorConverter.java index 312c331eb9..db652d5307 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/DiscriminatorConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/DiscriminatorConverter.java @@ -4,9 +4,7 @@ */ package org.hibernate.metamodel.mapping; -import org.hibernate.Incubating; import org.hibernate.metamodel.RepresentationMode; -import org.hibernate.type.AnyDiscriminatorValueStrategy; import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.java.JavaType; @@ -31,9 +29,6 @@ public abstract class DiscriminatorConverter implements BasicValueConverter this.relationalJavaType = relationalJavaType; } - @Incubating - public abstract AnyDiscriminatorValueStrategy getValueStrategy(); - public String getDiscriminatorName() { return discriminatorName; } @@ -90,7 +85,7 @@ public abstract class DiscriminatorConverter implements BasicValueConverter return (R) discriminatorValueDetails.getValue(); } - public abstract DiscriminatorValueDetails getDetailsForDiscriminatorValue(Object relationalForm); + public abstract DiscriminatorValueDetails getDetailsForDiscriminatorValue(Object relationalValue); public abstract DiscriminatorValueDetails getDetailsForEntityName(String entityName); @@ -101,5 +96,8 @@ public abstract class DiscriminatorConverter implements BasicValueConverter public abstract void forEachValueDetail(Consumer consumer); + /** + * Find and return the first DiscriminatorValueDetails which matches the given {@code handler} + */ public abstract X fromValueDetails(Function handler); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableDiscriminatorConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableDiscriminatorConverter.java index d16a4be2a2..34a450781d 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableDiscriminatorConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableDiscriminatorConverter.java @@ -9,7 +9,6 @@ import org.hibernate.HibernateException; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.metamodel.mapping.internal.EmbeddableDiscriminatorValueDetailsImpl; import org.hibernate.service.ServiceRegistry; -import org.hibernate.type.AnyDiscriminatorValueStrategy; import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.java.JavaType; @@ -66,12 +65,6 @@ public class EmbeddableDiscriminatorConverter extends DiscriminatorConvert } ); } - @Override - public AnyDiscriminatorValueStrategy getValueStrategy() { - // discriminators for embeddables are always explicit - return AnyDiscriminatorValueStrategy.EXPLICIT; - } - @Override public O toDomainValue(R relationalForm) { assert relationalForm == null || getRelationalJavaType().isInstance( relationalForm ); @@ -86,13 +79,13 @@ public class EmbeddableDiscriminatorConverter extends DiscriminatorConvert } @Override - public EmbeddableDiscriminatorValueDetailsImpl getDetailsForDiscriminatorValue(Object value) { - final EmbeddableDiscriminatorValueDetailsImpl valueMatch = discriminatorValueToDetailsMap.get( value ); + public EmbeddableDiscriminatorValueDetailsImpl getDetailsForDiscriminatorValue(Object relationalValue) { + final EmbeddableDiscriminatorValueDetailsImpl valueMatch = discriminatorValueToDetailsMap.get( relationalValue ); if ( valueMatch != null ) { return valueMatch; } - throw new HibernateException( "Unrecognized discriminator value: " + value ); + throw new HibernateException( "Unrecognized discriminator value: " + relationalValue ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java index 9083b0565e..97d8b5554a 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java @@ -462,6 +462,10 @@ public interface EntityMappingType void visitConstraintOrderedTables(ConstraintOrderedTableConsumer consumer); + default String getImportedName() { + return getEntityPersister().getImportedName(); + } + interface ConstraintOrderedTableConsumer { void consume(String tableExpression, Supplier> tableKeyColumnVisitationSupplier); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java index 3556bbf835..f320d3e251 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java @@ -4,7 +4,6 @@ */ package org.hibernate.metamodel.mapping.internal; -import org.hibernate.AssertionFailure; import org.hibernate.cache.MutableCacheKeyBuilder; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; @@ -20,6 +19,7 @@ import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.metamodel.spi.ImplicitDiscriminatorStrategy; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.spi.FromClauseAccess; @@ -35,7 +35,6 @@ import org.hibernate.sql.results.graph.FetchOptions; import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.graph.basic.BasicResult; -import org.hibernate.type.AnyDiscriminatorValueStrategy; import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.java.ClassJavaType; import org.hibernate.type.descriptor.java.JavaType; @@ -84,7 +83,7 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions boolean partitioned, BasicType underlyingJdbcMapping, Map valueToEntityNameMap, - AnyDiscriminatorValueStrategy valueStrategy, + ImplicitDiscriminatorStrategy implicitValueStrategy, MappingMetamodelImplementor mappingMetamodel) { this.navigableRole = partRole; this.declaringType = declaringType; @@ -105,7 +104,7 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions partRole, underlyingJdbcMapping, valueToEntityNameMap, - valueStrategy, + implicitValueStrategy, mappingMetamodel ); } @@ -114,41 +113,16 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions NavigableRole partRole, BasicType underlyingJdbcMapping, Map valueToEntityNameMap, - AnyDiscriminatorValueStrategy valueStrategy, + ImplicitDiscriminatorStrategy implicitValueStrategy, MappingMetamodelImplementor mappingMetamodel) { - if ( valueStrategy == AnyDiscriminatorValueStrategy.AUTO ) { - if ( valueToEntityNameMap == null || valueToEntityNameMap.isEmpty() ) { - valueStrategy = AnyDiscriminatorValueStrategy.IMPLICIT; - } - else { - valueStrategy = AnyDiscriminatorValueStrategy.EXPLICIT; - } - } - - return switch ( valueStrategy ) { - case AUTO -> throw new AssertionFailure( "Not expecting AUTO" ); - case MIXED -> new MixedDiscriminatorConverter<>( - partRole, - ClassJavaType.INSTANCE, - underlyingJdbcMapping.getJavaTypeDescriptor(), - valueToEntityNameMap, - mappingMetamodel - ); - case EXPLICIT -> new ExplicitDiscriminatorConverter<>( - partRole, - ClassJavaType.INSTANCE, - underlyingJdbcMapping.getJavaTypeDescriptor(), - valueToEntityNameMap, - mappingMetamodel - ); - case IMPLICIT -> new ImplicitDiscriminatorConverter<>( - partRole, - ClassJavaType.INSTANCE, - underlyingJdbcMapping.getJavaTypeDescriptor(), - valueToEntityNameMap, - mappingMetamodel - ); - }; + return new UnifiedAnyDiscriminatorConverter<>( + partRole, + ClassJavaType.INSTANCE, + underlyingJdbcMapping.getJavaTypeDescriptor(), + valueToEntityNameMap, + implicitValueStrategy, + mappingMetamodel + ); } public DiscriminatorConverter getValueConverter() { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java index 1b788e0db9..014e9ddd5e 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java @@ -89,7 +89,7 @@ public class DiscriminatedAssociationMapping implements MappingType, FetchOption bootValueMapping.isPartitionKey(), (BasicType) metaType.getBaseType(), metaType.getDiscriminatorValuesToEntityNameMap(), - metaType.getValueStrategy(), + metaType.getImplicitValueStrategy(), creationProcess.getCreationContext().getSessionFactory().getMappingMetamodel() ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ExplicitDiscriminatorConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ExplicitDiscriminatorConverter.java deleted file mode 100644 index 7cd3ab5f04..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ExplicitDiscriminatorConverter.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.metamodel.mapping.internal; - -import org.hibernate.HibernateException; -import org.hibernate.MappingException; -import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.metamodel.mapping.DiscriminatorConverter; -import org.hibernate.metamodel.mapping.DiscriminatorValueDetails; -import org.hibernate.metamodel.model.domain.NavigableRole; -import org.hibernate.metamodel.spi.MappingMetamodelImplementor; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.type.AnyDiscriminatorValueStrategy; -import org.hibernate.type.descriptor.java.CharacterJavaType; -import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.java.StringJavaType; - -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; - -import static java.util.Locale.ROOT; -import static org.hibernate.persister.entity.DiscriminatorHelper.NOT_NULL_DISCRIMINATOR; -import static org.hibernate.persister.entity.DiscriminatorHelper.NULL_DISCRIMINATOR; - -/** - * @author Steve Ebersole - */ -public class ExplicitDiscriminatorConverter extends DiscriminatorConverter { - private final NavigableRole discriminatorRole; - private final Map detailsByValue; - private final Map detailsByEntityName; - - public ExplicitDiscriminatorConverter( - NavigableRole discriminatorRole, - JavaType domainJavaType, - JavaType relationalJavaType, - Map explicitValueMappings, - MappingMetamodelImplementor mappingMetamodel) { - super( discriminatorRole.getFullPath(), domainJavaType, relationalJavaType ); - this.discriminatorRole = discriminatorRole; - - if ( CollectionHelper.isEmpty( explicitValueMappings ) ) { - throw new MappingException( String.format( - ROOT, - "No explicit ANY discriminator mappings (%s)", - discriminatorRole.getFullPath() - ) ); - } - - this.detailsByValue = CollectionHelper.concurrentMap( explicitValueMappings.size() ); - this.detailsByEntityName = CollectionHelper.concurrentMap( explicitValueMappings.size() ); - - explicitValueMappings.forEach( (value, entityName) -> { - final EntityPersister entityDescriptor = mappingMetamodel.getEntityDescriptor( entityName ); - final DiscriminatorValueDetails details = new DiscriminatorValueDetailsImpl( value, entityDescriptor ); - detailsByValue.put( value, details ); - detailsByEntityName.put( entityDescriptor.getEntityName(), details ); - } ); - } - - @Override - public AnyDiscriminatorValueStrategy getValueStrategy() { - return AnyDiscriminatorValueStrategy.EXPLICIT; - } - - public Map getDetailsByValue() { - return detailsByValue; - } - - public Map getDetailsByEntityName() { - return detailsByEntityName; - } - - @Override - public DiscriminatorValueDetails getDetailsForDiscriminatorValue(Object relationalForm) { - if ( relationalForm == null ) { - return detailsByValue.get( NULL_DISCRIMINATOR ); - } - - final DiscriminatorValueDetails existing = detailsByValue.get( relationalForm ); - if ( existing != null ) { - // an explicit or previously-resolved mapping - return existing; - } - - final DiscriminatorValueDetails notNullMatch = detailsByValue.get( NOT_NULL_DISCRIMINATOR ); - if ( notNullMatch != null ) { - return notNullMatch; - } - - if ( relationalForm.getClass().isEnum() ) { - final Object enumValue; - if ( getRelationalJavaType() instanceof StringJavaType ) { - enumValue = ( (Enum) relationalForm ).name(); - } - else if ( getRelationalJavaType() instanceof CharacterJavaType ) { - enumValue = ( (Enum) relationalForm ).name().charAt( 0 ); - } - else { - enumValue = ( (Enum) relationalForm ).ordinal(); - } - final DiscriminatorValueDetails enumMatch = detailsByValue.get( enumValue ); - if ( enumMatch != null ) { - return enumMatch; - } - } - - throw new HibernateException( String.format( - ROOT, - "Unknown discriminator value (%s) : %s", - discriminatorRole, - relationalForm - ) ); - } - - @Override - public DiscriminatorValueDetails getDetailsForEntityName(String entityName) { - final DiscriminatorValueDetails valueDetails = detailsByEntityName.get( entityName ); - if ( valueDetails != null) { - return valueDetails; - } - throw new HibernateException( "Entity not explicitly mapped for ANY discriminator (" + discriminatorRole + ") : " + entityName ); - } - - - @Override - public void forEachValueDetail(Consumer consumer) { - detailsByEntityName.forEach( (value, detail) -> consumer.accept( detail ) ); - } - - @Override - public X fromValueDetails(Function handler) { - for ( DiscriminatorValueDetails detail : detailsByEntityName.values() ) { - final X result = handler.apply( detail ); - if ( result != null ) { - return result; - } - } - return null; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ImplicitDiscriminatorConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ImplicitDiscriminatorConverter.java deleted file mode 100644 index cf0c5ee0f0..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ImplicitDiscriminatorConverter.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.metamodel.mapping.internal; - -import org.hibernate.HibernateException; -import org.hibernate.MappingException; -import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.metamodel.mapping.DiscriminatorConverter; -import org.hibernate.metamodel.mapping.DiscriminatorValueDetails; -import org.hibernate.metamodel.model.domain.NavigableRole; -import org.hibernate.metamodel.spi.MappingMetamodelImplementor; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.type.AnyDiscriminatorValueStrategy; -import org.hibernate.type.descriptor.java.JavaType; - -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; - -import static java.util.Locale.ROOT; - -/** - * @author Steve Ebersole - */ -public class ImplicitDiscriminatorConverter extends DiscriminatorConverter { - private final NavigableRole discriminatorRole; - private final MappingMetamodelImplementor mappingMetamodel; - private final Map detailsByValue; - private final Map detailsByEntityName; - - public ImplicitDiscriminatorConverter( - NavigableRole discriminatorRole, - JavaType domainJavaType, - JavaType relationalJavaType, - Map explicitValueMappings, - MappingMetamodelImplementor mappingMetamodel) { - super( discriminatorRole.getFullPath(), domainJavaType, relationalJavaType ); - this.discriminatorRole = discriminatorRole; - this.mappingMetamodel = mappingMetamodel; - - if ( CollectionHelper.isNotEmpty( explicitValueMappings ) ) { - throw new MappingException( String.format( - ROOT, - "Encountered explicit ANY discriminator mappings (%s)", - discriminatorRole.getFullPath() - ) ); - } - - this.detailsByValue = CollectionHelper.concurrentMap( 8 ); - this.detailsByEntityName = CollectionHelper.concurrentMap( 8 ); - } - - @Override - public AnyDiscriminatorValueStrategy getValueStrategy() { - return AnyDiscriminatorValueStrategy.IMPLICIT; - } - - public Map getDetailsByValue() { - return detailsByValue; - } - - public Map getDetailsByEntityName() { - return detailsByEntityName; - } - - @Override - public DiscriminatorValueDetails getDetailsForDiscriminatorValue(Object value) { - if ( value instanceof String incoming ) { - final DiscriminatorValueDetails existingDetails = detailsByValue.get( incoming ); - if ( existingDetails != null ) { - return existingDetails; - } - final EntityPersister persister = mappingMetamodel.findEntityDescriptor( incoming ); - if ( persister != null ) { - return register( incoming, persister ); - } - } - throw new HibernateException( String.format( - ROOT, - "Unrecognized discriminator value (%s): %s", - discriminatorRole.getFullPath(), - value - ) ); - } - - private DiscriminatorValueDetails register(Object value, EntityPersister entityDescriptor) { - final DiscriminatorValueDetails details = new DiscriminatorValueDetailsImpl( value, entityDescriptor ); - detailsByValue.put( value, details ); - detailsByEntityName.put( entityDescriptor.getEntityName(), details ); - return details; - } - - @Override - public DiscriminatorValueDetails getDetailsForEntityName(String entityName) { - final DiscriminatorValueDetails existingDetails = detailsByEntityName.get( entityName ); - if ( existingDetails != null ) { - return existingDetails; - } - final EntityPersister persister = mappingMetamodel.findEntityDescriptor( entityName ); - if ( persister!= null ) { - return register( persister.getEntityName(), persister ); - } - throw new HibernateException( String.format( - ROOT, - "Unrecognized entity name (%s): %s", - discriminatorRole.getFullPath(), - entityName - ) ); - } - - @Override - public void forEachValueDetail(Consumer consumer) { - } - - @Override - public X fromValueDetails(Function handler) { - return null; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MixedDiscriminatorConverter.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/UnifiedAnyDiscriminatorConverter.java similarity index 52% rename from hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MixedDiscriminatorConverter.java rename to hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/UnifiedAnyDiscriminatorConverter.java index c4c9d99c8f..e8b41bd2e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MixedDiscriminatorConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/UnifiedAnyDiscriminatorConverter.java @@ -6,12 +6,14 @@ package org.hibernate.metamodel.mapping.internal; import org.hibernate.HibernateException; import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.metamodel.internal.FullNameImplicitDiscriminatorStrategy; import org.hibernate.metamodel.mapping.DiscriminatorConverter; import org.hibernate.metamodel.mapping.DiscriminatorValueDetails; +import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.metamodel.spi.ImplicitDiscriminatorStrategy; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.type.AnyDiscriminatorValueStrategy; import org.hibernate.type.descriptor.java.CharacterJavaType; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.StringJavaType; @@ -24,45 +26,59 @@ import static org.hibernate.persister.entity.DiscriminatorHelper.NOT_NULL_DISCRI import static org.hibernate.persister.entity.DiscriminatorHelper.NULL_DISCRIMINATOR; /** - * DiscriminatorConverter for use with {@linkplain org.hibernate.type.AnyDiscriminatorValueStrategy#MIXED} - * * @author Steve Ebersole */ -public class MixedDiscriminatorConverter extends DiscriminatorConverter { +public class UnifiedAnyDiscriminatorConverter extends DiscriminatorConverter { private final NavigableRole discriminatorRole; private final Map detailsByValue; private final Map detailsByEntityName; + private final ImplicitDiscriminatorStrategy implicitValueStrategy; private final MappingMetamodelImplementor mappingMetamodel; - public MixedDiscriminatorConverter( + public UnifiedAnyDiscriminatorConverter( NavigableRole discriminatorRole, JavaType domainJavaType, JavaType relationalJavaType, Map explicitValueMappings, + ImplicitDiscriminatorStrategy implicitValueStrategy, MappingMetamodelImplementor mappingMetamodel) { super( discriminatorRole.getFullPath(), domainJavaType, relationalJavaType ); this.discriminatorRole = discriminatorRole; this.mappingMetamodel = mappingMetamodel; + this.implicitValueStrategy = resolveImplicitValueStrategy( implicitValueStrategy, explicitValueMappings ); + this.detailsByValue = CollectionHelper.concurrentMap( explicitValueMappings.size() ); this.detailsByEntityName = CollectionHelper.concurrentMap( explicitValueMappings.size() ); explicitValueMappings.forEach( (value,entityName) -> { - String importedEntityName = mappingMetamodel.getImportedName( entityName ); - final EntityPersister entityDescriptor = mappingMetamodel.getEntityDescriptor( importedEntityName ); - register( value, entityDescriptor ); + final String importedEntityName = mappingMetamodel.getImportedName( entityName ); + final EntityPersister entityMapping = mappingMetamodel.getEntityDescriptor( importedEntityName ); + register( value, entityMapping ); } ); } - private DiscriminatorValueDetails register(Object value, EntityPersister entityDescriptor) { - final DiscriminatorValueDetails details = new DiscriminatorValueDetailsImpl( value, entityDescriptor ); - detailsByValue.put( value, details ); - detailsByEntityName.put( entityDescriptor.getEntityName(), details ); - return details; + private ImplicitDiscriminatorStrategy resolveImplicitValueStrategy(ImplicitDiscriminatorStrategy implicitValueStrategy, Map explicitValueMappings) { + if ( explicitValueMappings.isEmpty() ) { + if ( implicitValueStrategy == null ) { + return FullNameImplicitDiscriminatorStrategy.FULL_NAME_STRATEGY; + } + } + else { + if ( explicitValueMappings.containsKey( NOT_NULL_DISCRIMINATOR ) ) { + if ( implicitValueStrategy != null ) { + // we will ultimately not know how to handle "implicit" values which are non-null + throw new HibernateException( "Illegal use of ImplicitDiscriminatorStrategy with explicit non-null discriminator mapping: " + discriminatorRole.getFullPath() ); + } + } + } + return implicitValueStrategy; } - @Override - public AnyDiscriminatorValueStrategy getValueStrategy() { - return AnyDiscriminatorValueStrategy.MIXED; + private DiscriminatorValueDetails register(Object value, EntityMappingType entityMapping) { + final DiscriminatorValueDetails details = new DiscriminatorValueDetailsImpl( value, entityMapping ); + detailsByValue.put( value, details ); + detailsByEntityName.put( entityMapping.getEntityName(), details ); + return details; } public Map getDetailsByValue() { @@ -74,32 +90,26 @@ public class MixedDiscriminatorConverter extends DiscriminatorConverter) relationalForm ).name(); + enumValue = ( (Enum) relationalValue ).name(); } else if ( getRelationalJavaType() instanceof CharacterJavaType ) { - enumValue = ( (Enum) relationalForm ).name().charAt( 0 ); + enumValue = ( (Enum) relationalValue ).name().charAt( 0 ); } else { - enumValue = ( (Enum) relationalForm ).ordinal(); + enumValue = ( (Enum) relationalValue ).ordinal(); } final DiscriminatorValueDetails enumMatch = detailsByValue.get( enumValue ); if ( enumMatch != null ) { @@ -107,15 +117,19 @@ public class MixedDiscriminatorConverter extends DiscriminatorConverter extends DiscriminatorConverter consumer) { - detailsByEntityName.forEach( (value, detail) -> consumer.accept( detail ) ); + detailsByEntityName.values().forEach( consumer ); } @Override public X fromValueDetails(Function handler) { - for ( DiscriminatorValueDetails detail : detailsByEntityName.values() ) { - final X result = handler.apply( detail ); + for ( DiscriminatorValueDetails valueDetails : detailsByEntityName.values() ) { + final X result = handler.apply( valueDetails ); if ( result != null ) { return result; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingDomainTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingDomainTypeImpl.java index 417236612f..6e8b36a427 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingDomainTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/AnyMappingDomainTypeImpl.java @@ -48,7 +48,7 @@ public class AnyMappingDomainTypeImpl implements AnyMappingDomainType { navigableRole, discriminatorBaseType, bootAnyMapping.getMetaValues(), - discriminatorType.getValueStrategy(), + discriminatorType.getImplicitValueStrategy(), mappingMetamodel ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/spi/ImplicitDiscriminatorStrategy.java b/hibernate-core/src/main/java/org/hibernate/metamodel/spi/ImplicitDiscriminatorStrategy.java new file mode 100644 index 0000000000..70bf846f78 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/spi/ImplicitDiscriminatorStrategy.java @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.metamodel.spi; + +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.model.domain.NavigableRole; + +/** + * Used in cases where we have no explicit {@linkplain org.hibernate.annotations.AnyDiscriminatorValue} + * mapping which matches. + * + * @author Steve Ebersole + */ +public interface ImplicitDiscriminatorStrategy { + /** + * Determine the discriminator value to use for the given {@code entityMapping}. + */ + Object toDiscriminatorValue(EntityMappingType entityMapping, NavigableRole discriminatorRole, MappingMetamodelImplementor mappingModel); + + /** + * Determine the entity-mapping which matches the given {@code discriminatorValue}. + */ + EntityMappingType toEntityMapping(Object discriminatorValue, NavigableRole discriminatorRole, MappingMetamodelImplementor mappingModel); +} diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 8a33cffd45..92d5857c00 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -4,30 +4,7 @@ */ package org.hibernate.persister.entity; -import java.io.Serializable; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.BitSet; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Supplier; - +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.AssertionFailure; import org.hibernate.FetchMode; import org.hibernate.Filter; @@ -101,8 +78,8 @@ import org.hibernate.generator.Generator; import org.hibernate.generator.OnExecutionGenerator; import org.hibernate.generator.internal.VersionGeneration; import org.hibernate.generator.values.GeneratedValues; -import org.hibernate.generator.values.internal.GeneratedValuesHelper; import org.hibernate.generator.values.GeneratedValuesMutationDelegate; +import org.hibernate.generator.values.internal.GeneratedValuesHelper; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.id.BulkInsertionCapableIdentifierGenerator; import org.hibernate.id.IdentifierGenerator; @@ -120,6 +97,7 @@ import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.internal.util.collections.LockModeEnumMap; import org.hibernate.jdbc.Expectation; import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper; +import org.hibernate.loader.ast.internal.EntityConcreteTypeLoader; import org.hibernate.loader.ast.internal.LoaderSelectBuilder; import org.hibernate.loader.ast.internal.LoaderSqlAstCreationState; import org.hibernate.loader.ast.internal.MultiIdEntityLoaderArrayParam; @@ -180,11 +158,9 @@ import org.hibernate.metamodel.mapping.internal.CompoundNaturalIdMapping; import org.hibernate.metamodel.mapping.internal.DiscriminatedAssociationAttributeMapping; import org.hibernate.metamodel.mapping.internal.DiscriminatorTypeImpl; import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; -import org.hibernate.loader.ast.internal.EntityConcreteTypeLoader; import org.hibernate.metamodel.mapping.internal.EntityRowIdMappingImpl; import org.hibernate.metamodel.mapping.internal.EntityVersionMappingImpl; import org.hibernate.metamodel.mapping.internal.ExplicitColumnDiscriminatorMappingImpl; -import org.hibernate.metamodel.mapping.internal.ExplicitDiscriminatorConverter; import org.hibernate.metamodel.mapping.internal.GeneratedValuesProcessor; import org.hibernate.metamodel.mapping.internal.ImmutableAttributeMappingList; import org.hibernate.metamodel.mapping.internal.InFlightEntityMappingType; @@ -192,6 +168,7 @@ import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.metamodel.mapping.internal.SimpleAttributeMetadata; import org.hibernate.metamodel.mapping.internal.SimpleNaturalIdMapping; +import org.hibernate.metamodel.mapping.internal.UnifiedAnyDiscriminatorConverter; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.spi.EntityInstantiator; import org.hibernate.metamodel.spi.EntityRepresentationStrategy; @@ -284,7 +261,29 @@ import org.hibernate.type.descriptor.java.MutabilityPlan; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.spi.TypeConfiguration; -import org.checkerframework.checker.nullness.qual.Nullable; +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; @@ -2278,11 +2277,12 @@ public abstract class AbstractEntityPersister } //noinspection rawtypes - final DiscriminatorConverter converter = new ExplicitDiscriminatorConverter( + final DiscriminatorConverter converter = new UnifiedAnyDiscriminatorConverter<>( getNavigableRole().append( EntityDiscriminatorMapping.DISCRIMINATOR_ROLE_NAME ), domainJavaType, underlingJdbcMapping.getRelationalJavaType(), getSubclassByDiscriminatorValue(), + null, factory.getMappingMetamodel() ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/AnyDiscriminatorValueStrategy.java b/hibernate-core/src/main/java/org/hibernate/type/AnyDiscriminatorValueStrategy.java deleted file mode 100644 index b11dcbeee5..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/type/AnyDiscriminatorValueStrategy.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.type; - -import org.hibernate.Incubating; -import org.hibernate.annotations.AnyDiscriminatorValue; - -/** - * Describes how to deal with discriminator values in regard to - * a {@linkplain org.hibernate.metamodel.mapping.DiscriminatedAssociationModelPart ANY mapping} - * - * @see AnyDiscriminatorValue - * - * @since 7.0 - * @author Steve Ebersole - */ -@Incubating -public enum AnyDiscriminatorValueStrategy { - /** - * Pick between {@link #IMPLICIT} and {@link #EXPLICIT} based on - * presence or not of {@link AnyDiscriminatorValue}. The default - * (and legacy) behavior. - */ - AUTO, - - /** - * Expect explicit, complete mapping of discriminator values using - * one-or-more {@link AnyDiscriminatorValue}. - * - * @implNote With this option, it is considered an error if, at runtime, - * we encounter an entity type not explicitly mapped with a - * {@link AnyDiscriminatorValue}. - */ - EXPLICIT, - - /** - * Expect no {@link AnyDiscriminatorValue}. The entity name of the associated - * entity is used as the discriminator value. - * - * @implNote With this option, it is considered illegal to specify - * any {@link AnyDiscriminatorValue} mappings. - */ - IMPLICIT, - - /** - * Allows a combination of {@linkplain #EXPLICIT explicit} and {@linkplain #IMPLICIT implicit} - * discriminator value mappings. If an entity is mapped using an explicit - * {@link AnyDiscriminatorValue} mapping, the associated discriminator value is used. - * Otherwise, the entity name is used. - * - * @implNote This option is roughly the same as {@link #EXPLICIT} except - * that an implicit mapping using the entity name is used when a matching - * explicit {@link AnyDiscriminatorValue} mapping is not found. - */ - MIXED -} diff --git a/hibernate-core/src/main/java/org/hibernate/type/AnyType.java b/hibernate-core/src/main/java/org/hibernate/type/AnyType.java index cee22ced08..7a4393ab66 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AnyType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AnyType.java @@ -4,14 +4,6 @@ */ package org.hibernate.type; -import java.io.Serializable; -import java.lang.reflect.Method; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.Arrays; -import java.util.Map; -import java.util.Objects; - import org.hibernate.EntityNameResolver; import org.hibernate.FetchMode; import org.hibernate.Hibernate; @@ -30,8 +22,17 @@ import org.hibernate.persister.entity.Joinable; import org.hibernate.proxy.LazyInitializer; import org.hibernate.type.spi.TypeConfiguration; +import java.io.Serializable; +import java.lang.reflect.Method; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + import static org.hibernate.engine.internal.ForeignKeys.getEntityIdentifierIfNotUnsaved; import static org.hibernate.internal.util.collections.ArrayHelper.join; +import static org.hibernate.metamodel.internal.FullNameImplicitDiscriminatorStrategy.FULL_NAME_STRATEGY; import static org.hibernate.pretty.MessageHelper.infoString; import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; @@ -71,7 +72,7 @@ public class AnyType extends AbstractType implements CompositeType, AssociationT return metaType; } - return new MetaType( discriminatorType, AnyDiscriminatorValueStrategy.AUTO, null ); + return new MetaType( discriminatorType, FULL_NAME_STRATEGY, null ); } public Type getIdentifierType() { diff --git a/hibernate-core/src/main/java/org/hibernate/type/MetaType.java b/hibernate-core/src/main/java/org/hibernate/type/MetaType.java index 45b0095a27..763b96c758 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/MetaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/MetaType.java @@ -11,6 +11,7 @@ import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.metamodel.spi.ImplicitDiscriminatorStrategy; import java.sql.PreparedStatement; import java.sql.SQLException; @@ -25,47 +26,36 @@ public class MetaType extends AbstractType { public static final String[] REGISTRATION_KEYS = ArrayHelper.EMPTY_STRING_ARRAY; private final Type valueType; - private final AnyDiscriminatorValueStrategy valueStrategy; + private final ImplicitDiscriminatorStrategy implicitValueStrategy; private final Map discriminatorValuesToEntityNameMap; private final Map entityNameToDiscriminatorValueMap; public MetaType( Type valueType, - AnyDiscriminatorValueStrategy valueStrategy, + ImplicitDiscriminatorStrategy implicitValueStrategy, Map explicitValueMappings) { this.valueType = valueType; + this.implicitValueStrategy = implicitValueStrategy; if ( explicitValueMappings == null || explicitValueMappings.isEmpty() ) { - if ( valueStrategy == AnyDiscriminatorValueStrategy.AUTO ) { - valueStrategy = AnyDiscriminatorValueStrategy.IMPLICIT; - } this.discriminatorValuesToEntityNameMap = new HashMap<>(); this.entityNameToDiscriminatorValueMap = new HashMap<>(); } else { - if ( valueStrategy == AnyDiscriminatorValueStrategy.AUTO ) { - valueStrategy = AnyDiscriminatorValueStrategy.EXPLICIT; - } this.discriminatorValuesToEntityNameMap = explicitValueMappings; this.entityNameToDiscriminatorValueMap = new HashMap<>(); for ( Map.Entry entry : discriminatorValuesToEntityNameMap.entrySet() ) { entityNameToDiscriminatorValueMap.put( entry.getValue(), entry.getKey() ); } } - - this.valueStrategy = valueStrategy; - } - - public MetaType(Map discriminatorValuesToEntityNameMap, Type baseType) { - this( baseType, AnyDiscriminatorValueStrategy.AUTO, discriminatorValuesToEntityNameMap ); } public Type getBaseType() { return valueType; } - public AnyDiscriminatorValueStrategy getValueStrategy() { - return valueStrategy; + public ImplicitDiscriminatorStrategy getImplicitValueStrategy() { + return implicitValueStrategy; } public String[] getRegistrationKeys() { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/CardPayment.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/CardPayment.java similarity index 81% rename from hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/CardPayment.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/CardPayment.java index e7726006ce..0cd8e43920 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/CardPayment.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/CardPayment.java @@ -2,7 +2,7 @@ * SPDX-License-Identifier: LGPL-2.1-or-later * Copyright Red Hat Inc. and Hibernate Authors */ -package org.hibernate.orm.test.any.mixed; +package org.hibernate.orm.test.any.discriminator; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -11,8 +11,12 @@ import jakarta.persistence.Id; * @author Steve Ebersole */ @SuppressWarnings("unused") -@Entity(name = "CardPayment") +//tag::associations-any-example[] +@Entity public class CardPayment implements Payment { + // ... +//end::associations-any-example[] + @Id private Integer id; private Double amount; @@ -47,4 +51,6 @@ public class CardPayment implements Payment { public void setAuthorizationCode(String authorizationCode) { this.authorizationCode = authorizationCode; } +//tag::associations-any-example[] } +//end::associations-any-example[] diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/CashPayment.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/CashPayment.java similarity index 75% rename from hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/CashPayment.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/CashPayment.java index 7d338a22f4..6a5ae03eba 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/CashPayment.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/CashPayment.java @@ -2,7 +2,7 @@ * SPDX-License-Identifier: LGPL-2.1-or-later * Copyright Red Hat Inc. and Hibernate Authors */ -package org.hibernate.orm.test.any.mixed; +package org.hibernate.orm.test.any.discriminator; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -11,8 +11,11 @@ import jakarta.persistence.Id; * @author Steve Ebersole */ @SuppressWarnings("unused") -@Entity(name = "CashPayment") +//tag::associations-any-example[] +@Entity public class CashPayment implements Payment { + // ... +//end::associations-any-example[] @Id private Integer id; private Double amount; @@ -37,4 +40,6 @@ public class CashPayment implements Payment { public void setAmount(Double amount) { this.amount = amount; } +//tag::associations-any-example[] } +//end::associations-any-example[] diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/CheckPayment.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/CheckPayment.java similarity index 80% rename from hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/CheckPayment.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/CheckPayment.java index b0aabb7fc2..0c7cde5439 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/CheckPayment.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/CheckPayment.java @@ -2,7 +2,7 @@ * SPDX-License-Identifier: LGPL-2.1-or-later * Copyright Red Hat Inc. and Hibernate Authors */ -package org.hibernate.orm.test.any.mixed; +package org.hibernate.orm.test.any.discriminator; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -11,8 +11,11 @@ import jakarta.persistence.Id; * @author Steve Ebersole */ @SuppressWarnings("unused") +//tag::associations-any-example[] @Entity public class CheckPayment implements Payment { + // ... +//end::associations-any-example[] @Id public Integer id; public Double amount; @@ -35,4 +38,6 @@ public class CheckPayment implements Payment { public Double getAmount() { return amount; } +//tag::associations-any-example[] } +//end::associations-any-example[] diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/Payment.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/Payment.java new file mode 100644 index 0000000000..b48c4de1b3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/Payment.java @@ -0,0 +1,17 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.any.discriminator; + +/** + * @author Steve Ebersole + */ +//tag::associations-any-example[] +public interface Payment { + // ... +//end::associations-any-example[] + Double getAmount(); +//tag::associations-any-example[] +} +//end::associations-any-example[] diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/explicit/ExplicitValueTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/explicit/ExplicitValueTests.java new file mode 100644 index 0000000000..ebe335f77f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/explicit/ExplicitValueTests.java @@ -0,0 +1,100 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.any.discriminator.explicit; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.orm.test.any.discriminator.CardPayment; +import org.hibernate.orm.test.any.discriminator.CashPayment; +import org.hibernate.orm.test.any.discriminator.CheckPayment; +import org.hibernate.orm.test.any.discriminator.Payment; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.sql.ResultSet; +import java.sql.Statement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("JUnitMalformedDeclaration") +@DomainModel(annotatedClasses = {Payment.class, CashPayment.class, CardPayment.class, CheckPayment.class, Order.class}) +@SessionFactory +public class ExplicitValueTests { + + @Test + void verifyExplicitMappingHandling(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + final Order order = session.find( Order.class, 1 ); + final CashPayment cashPayment = session.find( CashPayment.class, 1 ); + final CardPayment cardPayment = session.find( CardPayment.class, 1 ); + final CheckPayment checkPayment = session.find( CheckPayment.class, 1 ); + + order.paymentExplicit = cardPayment; + session.flush(); + verifyDiscriminatorValue( "explicit_type", "CARD", session ); + + order.paymentExplicit = checkPayment; + session.flush(); + verifyDiscriminatorValue( "explicit_type", "CHECK", session ); + + // NOTE : cash is not explicitly mapped and implicit mappings are not enabled, so this should be an error + try { + order.paymentExplicit = cashPayment; + session.flush(); + fail( "Expecting an error" ); + } + catch (HibernateException expected) { + assertThat( expected ).hasMessageContaining( "Cannot determine discriminator value from entity-name" ); + } + } ); + } + + private void verifyDiscriminatorValue(String columnName, String expectedValue, SessionImplementor session) { + final String qry = String.format( "select %s from orders", columnName ); + session.doWork( (connection) -> { + try (final Statement stmnt = connection.createStatement() ) { + try (ResultSet resultSet = stmnt.executeQuery( qry )) { + assertThat( resultSet.next() ).isTrue(); + final String discriminatorValue = resultSet.getString( columnName ); + assertThat( resultSet.next() ).isFalse(); + assertThat( discriminatorValue ).isEqualTo( expectedValue ); + } + } + } ); + } + + @BeforeEach + void prepareTestData(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + final Order order = new Order( 1, "1" ); + final CashPayment cashPayment = new CashPayment( 1, 50.00 ); + final CardPayment cardPayment = new CardPayment( 1, 150.00, "123-456-789" ); + final CheckPayment checkPayment = new CheckPayment( 1, 250.00, 1001, "123", "987" ); + session.persist( order ); + session.persist( cashPayment ); + session.persist( cardPayment ); + session.persist( checkPayment ); + } ); + } + + @AfterEach + void dropTestData(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + session.createMutationQuery( "delete Order" ).executeUpdate(); + session.createMutationQuery( "delete CashPayment" ).executeUpdate(); + session.createMutationQuery( "delete CardPayment" ).executeUpdate(); + session.createMutationQuery( "delete CheckPayment" ).executeUpdate(); + } ); + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/Order.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/explicit/Order.java similarity index 59% rename from hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/Order.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/explicit/Order.java index 741375fa78..ad654616bc 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/Order.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/explicit/Order.java @@ -2,18 +2,20 @@ * SPDX-License-Identifier: LGPL-2.1-or-later * Copyright Red Hat Inc. and Hibernate Authors */ -package org.hibernate.orm.test.any.mixed; +package org.hibernate.orm.test.any.discriminator.explicit; +import jakarta.persistence.Basic; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; -import jakarta.persistence.Basic; import jakarta.persistence.JoinColumn; import jakarta.persistence.Table; import org.hibernate.annotations.Any; -import org.hibernate.annotations.AnyDiscriminator; import org.hibernate.annotations.AnyDiscriminatorValue; import org.hibernate.annotations.AnyKeyJavaClass; -import org.hibernate.type.AnyDiscriminatorValueStrategy; +import org.hibernate.orm.test.any.discriminator.CardPayment; +import org.hibernate.orm.test.any.discriminator.CheckPayment; +import org.hibernate.orm.test.any.discriminator.Payment; /** * @author Steve Ebersole @@ -26,25 +28,15 @@ public class Order { @Basic public String name; + //tag::associations-any-explicit-discriminator-example[] @Any @AnyKeyJavaClass( Integer.class ) @JoinColumn(name = "explicit_fk") + @Column( name="explicit_type" ) @AnyDiscriminatorValue( discriminator = "CARD", entity = CardPayment.class ) @AnyDiscriminatorValue( discriminator = "CHECK", entity = CheckPayment.class ) - public Payment explicitPayment; - - @Any - @AnyKeyJavaClass( Integer.class ) - @JoinColumn(name = "implicit_fk") - public Payment implicitPayment; - - @Any - @AnyKeyJavaClass( Integer.class ) - @JoinColumn(name = "mixed_fk") - @AnyDiscriminator(valueStrategy = AnyDiscriminatorValueStrategy.MIXED) - @AnyDiscriminatorValue( discriminator = "CARD", entity = CardPayment.class ) - @AnyDiscriminatorValue( discriminator = "CHECK", entity = CheckPayment.class ) - public Payment mixedPayment; + public Payment paymentExplicit; + //end::associations-any-explicit-discriminator-example[] protected Order() { // for Hibernate use diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/implicit/ImplicitValueTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/implicit/ImplicitValueTests.java new file mode 100644 index 0000000000..c135250d8c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/implicit/ImplicitValueTests.java @@ -0,0 +1,104 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.any.discriminator.implicit; + +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.orm.test.any.discriminator.CardPayment; +import org.hibernate.orm.test.any.discriminator.CashPayment; +import org.hibernate.orm.test.any.discriminator.CheckPayment; +import org.hibernate.orm.test.any.discriminator.Payment; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.sql.ResultSet; +import java.sql.Statement; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("JUnitMalformedDeclaration") +@DomainModel(annotatedClasses = {Payment.class, CashPayment.class, CardPayment.class, CheckPayment.class, Order.class}) +@SessionFactory +public class ImplicitValueTests { + + @Test + void verifyImplicitMappingHandling(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + final Order order = session.find( Order.class, 1 ); + final CashPayment cashPayment = session.find( CashPayment.class, 1 ); + final CardPayment cardPayment = session.find( CardPayment.class, 1 ); + final CheckPayment checkPayment = session.find( CheckPayment.class, 1 ); + + order.paymentImplicit = cardPayment; + order.paymentImplicitFullName = cardPayment; + order.paymentImplicitShortName = cardPayment; + session.flush(); + verifyDiscriminatorValue( "implicit_type", CardPayment.class.getName(), session ); + verifyDiscriminatorValue( "implicit_full_type", CardPayment.class.getName(), session ); + verifyDiscriminatorValue( "implicit_short_type", CardPayment.class.getSimpleName(), session ); + + order.paymentImplicit = checkPayment; + order.paymentImplicitFullName = checkPayment; + order.paymentImplicitShortName = checkPayment; + session.flush(); + verifyDiscriminatorValue( "implicit_type", CheckPayment.class.getName(), session ); + verifyDiscriminatorValue( "implicit_full_type", CheckPayment.class.getName(), session ); + verifyDiscriminatorValue( "implicit_short_type", CheckPayment.class.getSimpleName(), session ); + + order.paymentImplicit = cashPayment; + order.paymentImplicitFullName = cashPayment; + order.paymentImplicitShortName = cashPayment; + session.flush(); + verifyDiscriminatorValue( "implicit_type", CashPayment.class.getName(), session ); + verifyDiscriminatorValue( "implicit_full_type", CashPayment.class.getName(), session ); + verifyDiscriminatorValue( "implicit_short_type", CashPayment.class.getSimpleName(), session ); + } ); + } + + private void verifyDiscriminatorValue(String columnName, String expectedValue, SessionImplementor session) { + final String qry = String.format( "select %s from orders", columnName ); + session.doWork( (connection) -> { + try (final Statement stmnt = connection.createStatement() ) { + try (ResultSet resultSet = stmnt.executeQuery( qry )) { + assertThat( resultSet.next() ).isTrue(); + final String discriminatorValue = resultSet.getString( columnName ); + assertThat( resultSet.next() ).isFalse(); + assertThat( discriminatorValue ).isEqualTo( expectedValue ); + } + } + } ); + } + + @BeforeEach + void prepareTestData(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + final Order order = new Order( 1, "1" ); + final CashPayment cashPayment = new CashPayment( 1, 50.00 ); + final CardPayment cardPayment = new CardPayment( 1, 150.00, "123-456-789" ); + final CheckPayment checkPayment = new CheckPayment( 1, 250.00, 1001, "123", "987" ); + session.persist( order ); + session.persist( cashPayment ); + session.persist( cardPayment ); + session.persist( checkPayment ); + } ); + } + + @AfterEach + void dropTestData(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + session.createMutationQuery( "delete Order" ).executeUpdate(); + session.createMutationQuery( "delete CashPayment" ).executeUpdate(); + session.createMutationQuery( "delete CardPayment" ).executeUpdate(); + session.createMutationQuery( "delete CheckPayment" ).executeUpdate(); + } ); + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/implicit/Order.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/implicit/Order.java new file mode 100644 index 0000000000..89b90d9dff --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/implicit/Order.java @@ -0,0 +1,66 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.any.discriminator.implicit; + +import jakarta.persistence.Basic; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.Table; +import org.hibernate.annotations.Any; +import org.hibernate.annotations.AnyDiscriminatorImplicitValues; +import org.hibernate.annotations.AnyKeyJavaClass; +import org.hibernate.orm.test.any.discriminator.Payment; + +import static org.hibernate.annotations.AnyDiscriminatorImplicitValues.Strategy.FULL_NAME; +import static org.hibernate.annotations.AnyDiscriminatorImplicitValues.Strategy.SHORT_NAME; + +/** + * @author Steve Ebersole + */ +@Entity +@Table(name = "orders") +public class Order { + @Id + public Integer id; + @Basic + public String name; + + //tag::associations-any-implicit-discriminator-example[] + @Any + @AnyKeyJavaClass( Integer.class ) + @JoinColumn(name = "implicit_fk") + @Column(name = "implicit_type") + public Payment paymentImplicit; + //end::associations-any-implicit-discriminator-example[] + + //tag::associations-any-implicit-discriminator-full-example[] + @Any + @AnyKeyJavaClass( Integer.class ) + @JoinColumn(name = "implicit_full_fk") + @Column(name = "implicit_full_type") + @AnyDiscriminatorImplicitValues(FULL_NAME) + public Payment paymentImplicitFullName; + //end::associations-any-implicit-discriminator-full-example[] + + //tag::associations-any-implicit-discriminator-short-example[] + @Any + @AnyKeyJavaClass( Integer.class ) + @JoinColumn(name = "implicit_short_fk") + @Column(name = "implicit_short_type") + @AnyDiscriminatorImplicitValues(SHORT_NAME) + public Payment paymentImplicitShortName; + //end::associations-any-implicit-discriminator-short-example[] + + protected Order() { + // for Hibernate use + } + + public Order(Integer id, String name) { + this.id = id; + this.name = name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/many/Loan.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/many/Loan.java new file mode 100644 index 0000000000..910bfeaf88 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/many/Loan.java @@ -0,0 +1,80 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.any.discriminator.many; + +import jakarta.persistence.Basic; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import org.hibernate.annotations.AnyDiscriminator; +import org.hibernate.annotations.AnyDiscriminatorImplicitValues; +import org.hibernate.annotations.AnyDiscriminatorValue; +import org.hibernate.annotations.AnyKeyJavaClass; +import org.hibernate.annotations.ManyToAny; +import org.hibernate.orm.test.any.discriminator.CardPayment; +import org.hibernate.orm.test.any.discriminator.CheckPayment; +import org.hibernate.orm.test.any.discriminator.Payment; + +import java.util.Set; + +import static org.hibernate.annotations.AnyDiscriminatorImplicitValues.Strategy.SHORT_NAME; + +/** + * @author Steve Ebersole + */ +@Entity +public class Loan { + @Id + private Integer id; + @Basic + private String name; + + //tag::associations-many-to-any-example[] + @ManyToAny + @AnyDiscriminator(DiscriminatorType.STRING) + @Column(name = "payment_type") + @AnyKeyJavaClass(Integer.class) + @AnyDiscriminatorValue( discriminator = "CARD", entity = CardPayment.class ) + @AnyDiscriminatorValue( discriminator = "CHECK", entity = CheckPayment.class ) + @AnyDiscriminatorImplicitValues(SHORT_NAME) + @JoinTable(name = "loan_payments", + joinColumns = @JoinColumn(name = "loan_fk"), + inverseJoinColumns = @JoinColumn(name = "payment_fk") + ) + private Set payments; + //end::associations-many-to-any-example[] + + protected Loan() { + // for Hibernate use + } + + public Loan(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getPayments() { + return payments; + } + + public void setPayments(Set payments) { + this.payments = payments; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/many/ManyToAnyTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/many/ManyToAnyTests.java new file mode 100644 index 0000000000..b37ba752c3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/many/ManyToAnyTests.java @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.any.discriminator.many; + +import org.hibernate.orm.test.any.discriminator.CardPayment; +import org.hibernate.orm.test.any.discriminator.CashPayment; +import org.hibernate.orm.test.any.discriminator.CheckPayment; +import org.hibernate.orm.test.any.discriminator.Payment; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("JUnitMalformedDeclaration") +@DomainModel(annotatedClasses = {Payment.class, CashPayment.class, CardPayment.class, CheckPayment.class, Loan.class}) +@SessionFactory +public class ManyToAnyTests { + @Test + public void testManyToAnyUsage(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + final Loan loan = session.find( Loan.class, 1 ); + final CashPayment cashPayment = session.find( CashPayment.class, 1 ); + final CardPayment cardPayment = session.find( CardPayment.class, 1 ); + final CheckPayment checkPayment = session.find( CheckPayment.class, 1 ); + + loan.getPayments().add( cardPayment ); + session.flush(); + + loan.getPayments().add( checkPayment ); + session.flush(); + + loan.getPayments().add( cashPayment ); + session.flush(); + } ); + } + + @BeforeEach + void prepareTestData(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + final Loan loan = new Loan( 1, "1" ); + final CashPayment cashPayment = new CashPayment( 1, 50.00 ); + final CardPayment cardPayment = new CardPayment( 1, 150.00, "123-456-789" ); + final CheckPayment checkPayment = new CheckPayment( 1, 250.00, 1001, "123", "987" ); + session.persist( loan ); + session.persist( cashPayment ); + session.persist( cardPayment ); + session.persist( checkPayment ); + } ); + } + + @AfterEach + void dropTestData(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + session.createMutationQuery( "delete Loan" ).executeUpdate(); + session.createMutationQuery( "delete CashPayment" ).executeUpdate(); + session.createMutationQuery( "delete CardPayment" ).executeUpdate(); + session.createMutationQuery( "delete CheckPayment" ).executeUpdate(); + } ); + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/meta/Order.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/meta/Order.java new file mode 100644 index 0000000000..b66eb713f1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/meta/Order.java @@ -0,0 +1,43 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.any.discriminator.meta; + +import jakarta.persistence.Basic; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.Table; +import org.hibernate.annotations.Any; +import org.hibernate.orm.test.any.discriminator.Payment; + +/** + * @author Steve Ebersole + */ +@Entity +@Table(name = "orders") +public class Order { + @Id + public Integer id; + @Basic + public String name; + + //tag::associations-any-discriminator-meta-example[] + @Any + @PaymentDiscriminationDef + @Column(name = "payment_type") + @JoinColumn(name = "payment_fk") + public Payment payment; + //end::associations-any-discriminator-meta-example[] + + protected Order() { + // for Hibernate use + } + + public Order(Integer id, String name) { + this.id = id; + this.name = name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/meta/PaymentDiscriminationDef.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/meta/PaymentDiscriminationDef.java new file mode 100644 index 0000000000..fff709374f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/meta/PaymentDiscriminationDef.java @@ -0,0 +1,33 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.any.discriminator.meta; + +import jakarta.persistence.DiscriminatorType; +import org.hibernate.annotations.AnyDiscriminator; +import org.hibernate.annotations.AnyDiscriminatorImplicitValues; +import org.hibernate.annotations.AnyDiscriminatorValue; +import org.hibernate.annotations.AnyKeyJavaClass; +import org.hibernate.orm.test.any.discriminator.CardPayment; +import org.hibernate.orm.test.any.discriminator.CheckPayment; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static org.hibernate.annotations.AnyDiscriminatorImplicitValues.Strategy.SHORT_NAME; + + +@Target({ElementType.TYPE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) + +@AnyDiscriminator(DiscriminatorType.STRING) +@AnyKeyJavaClass(Long.class) + +@AnyDiscriminatorValue(discriminator = "CARD", entity = CardPayment.class) +@AnyDiscriminatorValue(discriminator = "CHECK", entity = CheckPayment.class) +@AnyDiscriminatorImplicitValues(SHORT_NAME) +public @interface PaymentDiscriminationDef { +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/mixed/MixedValueTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/mixed/MixedValueTests.java new file mode 100644 index 0000000000..977c1418f6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/mixed/MixedValueTests.java @@ -0,0 +1,98 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.any.discriminator.mixed; + +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.orm.test.any.discriminator.CardPayment; +import org.hibernate.orm.test.any.discriminator.CashPayment; +import org.hibernate.orm.test.any.discriminator.CheckPayment; +import org.hibernate.orm.test.any.discriminator.Payment; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.sql.ResultSet; +import java.sql.Statement; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("JUnitMalformedDeclaration") +@DomainModel(annotatedClasses = {Payment.class, CashPayment.class, CardPayment.class, CheckPayment.class, Order.class}) +@SessionFactory +public class MixedValueTests { + + @Test + void verifyImplicitMappingHandling(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + final Order order = session.find( Order.class, 1 ); + final CashPayment cashPayment = session.find( CashPayment.class, 1 ); + final CardPayment cardPayment = session.find( CardPayment.class, 1 ); + final CheckPayment checkPayment = session.find( CheckPayment.class, 1 ); + + order.paymentMixedFullName = cardPayment; + order.paymentMixedShortName = cardPayment; + session.flush(); + verifyDiscriminatorValue( "full_mixed_type", "CARD", session ); + verifyDiscriminatorValue( "short_mixed_type", "CARD", session ); + + order.paymentMixedFullName = checkPayment; + order.paymentMixedShortName = checkPayment; + session.flush(); + verifyDiscriminatorValue( "full_mixed_type", "CHECK", session ); + verifyDiscriminatorValue( "short_mixed_type", "CHECK", session ); + + order.paymentMixedFullName = cashPayment; + order.paymentMixedShortName = cashPayment; + session.flush(); + verifyDiscriminatorValue( "full_mixed_type", CashPayment.class.getName(), session ); + verifyDiscriminatorValue( "short_mixed_type", CashPayment.class.getSimpleName(), session ); + } ); + } + + private void verifyDiscriminatorValue(String columnName, String expectedValue, SessionImplementor session) { + final String qry = String.format( "select %s from orders", columnName ); + session.doWork( (connection) -> { + try (final Statement stmnt = connection.createStatement() ) { + try (ResultSet resultSet = stmnt.executeQuery( qry )) { + assertThat( resultSet.next() ).isTrue(); + final String discriminatorValue = resultSet.getString( columnName ); + assertThat( resultSet.next() ).isFalse(); + assertThat( discriminatorValue ).isEqualTo( expectedValue ); + } + } + } ); + } + + @BeforeEach + void prepareTestData(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + final Order order = new Order( 1, "1" ); + final CashPayment cashPayment = new CashPayment( 1, 50.00 ); + final CardPayment cardPayment = new CardPayment( 1, 150.00, "123-456-789" ); + final CheckPayment checkPayment = new CheckPayment( 1, 250.00, 1001, "123", "987" ); + session.persist( order ); + session.persist( cashPayment ); + session.persist( cardPayment ); + session.persist( checkPayment ); + } ); + } + + @AfterEach + void dropTestData(SessionFactoryScope sessions) { + sessions.inTransaction( (session) -> { + session.createMutationQuery( "delete Order" ).executeUpdate(); + session.createMutationQuery( "delete CashPayment" ).executeUpdate(); + session.createMutationQuery( "delete CardPayment" ).executeUpdate(); + session.createMutationQuery( "delete CheckPayment" ).executeUpdate(); + } ); + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/mixed/Order.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/mixed/Order.java new file mode 100644 index 0000000000..2c442841a0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/any/discriminator/mixed/Order.java @@ -0,0 +1,65 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.any.discriminator.mixed; + +import jakarta.persistence.Basic; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.Table; +import org.hibernate.annotations.Any; +import org.hibernate.annotations.AnyDiscriminatorImplicitValues; +import org.hibernate.annotations.AnyDiscriminatorValue; +import org.hibernate.annotations.AnyKeyJavaClass; +import org.hibernate.orm.test.any.discriminator.CardPayment; +import org.hibernate.orm.test.any.discriminator.CheckPayment; +import org.hibernate.orm.test.any.discriminator.Payment; + +import static org.hibernate.annotations.AnyDiscriminatorImplicitValues.Strategy.FULL_NAME; +import static org.hibernate.annotations.AnyDiscriminatorImplicitValues.Strategy.SHORT_NAME; + +/** + * @author Steve Ebersole + */ +@Entity +@Table(name = "orders") +public class Order { + @Id + public Integer id; + @Basic + public String name; + + //tag::associations-any-mixed-discriminator-full-example[] + @Any + @AnyKeyJavaClass( Integer.class ) + @JoinColumn(name = "full_mixed_fk") + @Column(name = "full_mixed_type") + @AnyDiscriminatorImplicitValues(FULL_NAME) + @AnyDiscriminatorValue( discriminator = "CARD", entity = CardPayment.class ) + @AnyDiscriminatorValue( discriminator = "CHECK", entity = CheckPayment.class ) + public Payment paymentMixedFullName; + //end::associations-any-mixed-discriminator-full-example[] + + //tag::associations-any-mixed-discriminator-short-example[] + @Any + @AnyKeyJavaClass( Integer.class ) + @JoinColumn(name = "short_mixed_fk") + @Column(name = "short_mixed_type") + @AnyDiscriminatorImplicitValues(SHORT_NAME) + @AnyDiscriminatorValue( discriminator = "CARD", entity = CardPayment.class ) + @AnyDiscriminatorValue( discriminator = "CHECK", entity = CheckPayment.class ) + public Payment paymentMixedShortName; + //end::associations-any-mixed-discriminator-short-example[] + + protected Order() { + // for Hibernate use + } + + public Order(Integer id, String name) { + this.id = id; + this.name = name; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/AnyDiscriminatorValueHandlingTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/AnyDiscriminatorValueHandlingTests.java deleted file mode 100644 index a713f80205..0000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/AnyDiscriminatorValueHandlingTests.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.orm.test.any.mixed; - -import org.hibernate.HibernateException; -import org.hibernate.testing.orm.junit.DomainModel; -import org.hibernate.testing.orm.junit.SessionFactory; -import org.hibernate.testing.orm.junit.SessionFactoryScope; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -/** - * Tests for {@link org.hibernate.type.AnyDiscriminatorValueStrategy} - * - * @author Steve Ebersole - */ -@SuppressWarnings("JUnitMalformedDeclaration") -public class AnyDiscriminatorValueHandlingTests { - @Test - @DomainModel(annotatedClasses = {Payment.class, CashPayment.class, CardPayment.class, CheckPayment.class, Order.class}) - @SessionFactory - void verifyImplicitMappingHandling(SessionFactoryScope sessions) { - sessions.inTransaction( (session) -> { - final Order order = new Order( 1, "1" ); - final CashPayment cashPayment = new CashPayment( 1, 50.00 ); - final CardPayment cardPayment = new CardPayment( 1, 150.00, "123-456-789" ); - final CheckPayment checkPayment = new CheckPayment( 1, 250.00, 1001, "123", "987" ); - session.persist( order ); - session.persist( cashPayment ); - session.persist( cardPayment ); - session.persist( checkPayment ); - session.flush(); - - order.implicitPayment = cardPayment; - session.flush(); - - order.implicitPayment = checkPayment; - session.flush(); - - order.implicitPayment = cashPayment; - session.flush(); - } ); - } - - @Test - @DomainModel(annotatedClasses = {Payment.class, CashPayment.class, CardPayment.class, CheckPayment.class, Order.class}) - @SessionFactory - void verifyExplicitMappingHandling(SessionFactoryScope sessions) { - sessions.inTransaction( (session) -> { - final Order order = new Order( 1, "1" ); - final CashPayment cashPayment = new CashPayment( 1, 50.00 ); - final CardPayment cardPayment = new CardPayment( 1, 150.00, "123-456-789" ); - final CheckPayment checkPayment = new CheckPayment( 1, 250.00, 1001, "123", "987" ); - session.persist( order ); - session.persist( cashPayment ); - session.persist( cardPayment ); - session.persist( checkPayment ); - session.flush(); - - order.explicitPayment = cardPayment; - session.flush(); - - order.explicitPayment = checkPayment; - session.flush(); - - // NOTE : cash is not explicitly mapped - try { - order.explicitPayment = cashPayment; - session.flush(); - fail( "Expecting an error" ); - } - catch (HibernateException expected) { - assertThat( expected ).hasMessageContaining( "Entity not explicitly mapped for ANY discriminator" ); - } - } ); - } - - @Test - @DomainModel(annotatedClasses = {Payment.class, CashPayment.class, CardPayment.class, CheckPayment.class, Order.class}) - @SessionFactory - void verifyMixedMappingHandling(SessionFactoryScope sessions) { - sessions.inTransaction( (session) -> { - final Order order = new Order( 1, "1" ); - final CashPayment cashPayment = new CashPayment( 1, 50.00 ); - final CardPayment cardPayment = new CardPayment( 1, 150.00, "123-456-789" ); - final CheckPayment checkPayment = new CheckPayment( 1, 250.00, 1001, "123", "987" ); - session.persist( order ); - session.persist( cashPayment ); - session.persist( cardPayment ); - session.persist( checkPayment ); - session.flush(); - - order.mixedPayment = cardPayment; - session.flush(); - - order.mixedPayment = checkPayment; - session.flush(); - - order.mixedPayment = cashPayment; - session.flush(); - } ); - } - - @AfterEach - @DomainModel(annotatedClasses = {Payment.class, CashPayment.class, CardPayment.class, CheckPayment.class, Order.class}) - @SessionFactory - void dropTestData(SessionFactoryScope sessions) { - sessions.inTransaction( (session) -> { - session.createMutationQuery( "delete Order" ).executeUpdate(); - session.createMutationQuery( "delete CashPayment" ).executeUpdate(); - session.createMutationQuery( "delete CardPayment" ).executeUpdate(); - session.createMutationQuery( "delete CheckPayment" ).executeUpdate(); - } ); - - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/AnyDiscriminatorValueStrategyTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/AnyDiscriminatorValueStrategyTests.java deleted file mode 100644 index aff913e793..0000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/AnyDiscriminatorValueStrategyTests.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.orm.test.any.mixed; - - -import org.hibernate.HibernateException; -import org.hibernate.metamodel.mapping.DiscriminatorConverter; -import org.hibernate.metamodel.mapping.DiscriminatorMapping; -import org.hibernate.metamodel.mapping.DiscriminatorValueDetails; -import org.hibernate.metamodel.mapping.internal.DiscriminatedAssociationAttributeMapping; -import org.hibernate.metamodel.mapping.internal.ExplicitDiscriminatorConverter; -import org.hibernate.metamodel.mapping.internal.ImplicitDiscriminatorConverter; -import org.hibernate.metamodel.mapping.internal.MixedDiscriminatorConverter; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.testing.orm.junit.DomainModel; -import org.hibernate.testing.orm.junit.SessionFactory; -import org.hibernate.testing.orm.junit.SessionFactoryScope; -import org.hibernate.type.AnyDiscriminatorValueStrategy; -import org.junit.jupiter.api.Test; - -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -/** - * Tests for {@link org.hibernate.type.AnyDiscriminatorValueStrategy} - * - * @author Steve Ebersole - */ -@SuppressWarnings("JUnitMalformedDeclaration") -public class AnyDiscriminatorValueStrategyTests { - @Test - @DomainModel(annotatedClasses = {Payment.class, CashPayment.class, CardPayment.class, CheckPayment.class, Order.class}) - @SessionFactory - void verifyImplicitMappingModel(SessionFactoryScope sessions) { - sessions.withSessionFactory( (factory) -> { - final EntityPersister entityDescriptor = factory.getMappingMetamodel().getEntityDescriptor( Order.class ); - final DiscriminatedAssociationAttributeMapping implicitMapping = (DiscriminatedAssociationAttributeMapping) entityDescriptor.findAttributeMapping( "implicitPayment" ); - final DiscriminatorMapping discriminatorMapping = implicitMapping.getDiscriminatorMapping(); - final DiscriminatorConverter discriminatorConverter = discriminatorMapping.getValueConverter(); - assertThat( discriminatorConverter.getValueStrategy() ).isEqualTo( AnyDiscriminatorValueStrategy.IMPLICIT ); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // check discriminator -> entity - - final DiscriminatorValueDetails cash = discriminatorConverter.getDetailsForDiscriminatorValue( CashPayment.class.getName() ); - assertThat( cash.getIndicatedEntity().getEntityName() ).isEqualTo( CashPayment.class.getName() ); - assertThat( cash.getIndicatedEntityName() ).isEqualTo( CashPayment.class.getName() ); - - final DiscriminatorValueDetails card = discriminatorConverter.getDetailsForDiscriminatorValue( CardPayment.class.getName() ); - assertThat( card.getIndicatedEntity().getEntityName() ).isEqualTo( CardPayment.class.getName() ); - assertThat( card.getIndicatedEntityName() ).isEqualTo( CardPayment.class.getName() ); - - final DiscriminatorValueDetails check = discriminatorConverter.getDetailsForDiscriminatorValue( CheckPayment.class.getName() ); - assertThat( check.getIndicatedEntity().getEntityName() ).isEqualTo( CheckPayment.class.getName() ); - assertThat( check.getIndicatedEntityName() ).isEqualTo( CheckPayment.class.getName() ); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // check entity -> discriminator - - final DiscriminatorValueDetails cashDiscriminatorValue = discriminatorConverter.getDetailsForEntityName( CashPayment.class.getName() ); - assertThat( cashDiscriminatorValue.getValue() ).isEqualTo( CashPayment.class.getName() ); - - final DiscriminatorValueDetails cardDiscriminatorValue = discriminatorConverter.getDetailsForEntityName( CardPayment.class.getName() ); - assertThat( cardDiscriminatorValue.getValue() ).isEqualTo( CardPayment.class.getName() ); - - final DiscriminatorValueDetails checkDiscriminatorValue = discriminatorConverter.getDetailsForEntityName( CheckPayment.class.getName() ); - assertThat( checkDiscriminatorValue.getValue() ).isEqualTo( CheckPayment.class.getName() ); - - final Map detailsByEntityName = ((ImplicitDiscriminatorConverter) discriminatorConverter).getDetailsByEntityName(); - assertThat( detailsByEntityName.keySet() ).containsOnly( - CashPayment.class.getName(), - CardPayment.class.getName(), - CheckPayment.class.getName() - ); - - final Map detailsByValue = ((ImplicitDiscriminatorConverter) discriminatorConverter).getDetailsByValue(); - assertThat( detailsByValue.keySet() ).containsOnly( - CashPayment.class.getName(), - CardPayment.class.getName(), - CheckPayment.class.getName() - ); - } ); - } - - @Test - @DomainModel(annotatedClasses = {Payment.class, CashPayment.class, CardPayment.class, CheckPayment.class, Order.class}) - @SessionFactory - void verifyExplicitMappingModel(SessionFactoryScope sessions) { - sessions.withSessionFactory( (factory) -> { - final EntityPersister entityDescriptor = factory.getMappingMetamodel().getEntityDescriptor( Order.class ); - final DiscriminatedAssociationAttributeMapping explicitMapping = (DiscriminatedAssociationAttributeMapping) entityDescriptor.findAttributeMapping( "explicitPayment" ); - final DiscriminatorMapping discriminatorMapping = explicitMapping.getDiscriminatorMapping(); - final DiscriminatorConverter discriminatorConverter = discriminatorMapping.getValueConverter(); - assertThat( discriminatorConverter.getValueStrategy() ).isEqualTo( AnyDiscriminatorValueStrategy.EXPLICIT ); - - // NOTE : cash is NOT mapped - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // check discriminator -> entity - - try { - discriminatorConverter.getDetailsForDiscriminatorValue( "CASH" ); - fail( "Expecting an error" ); - } - catch (HibernateException expected) { - assertThat( expected ).hasMessageContaining( "Unknown discriminator value" ); - } - - try { - discriminatorConverter.getDetailsForDiscriminatorValue( CashPayment.class.getName() ); - fail( "Expecting an error" ); - } - catch (HibernateException expected) { - assertThat( expected ).hasMessageContaining( "Unknown discriminator value" ); - } - - final DiscriminatorValueDetails card = discriminatorConverter.getDetailsForDiscriminatorValue( "CARD" ); - assertThat( card.getIndicatedEntity().getEntityName() ).isEqualTo( CardPayment.class.getName() ); - assertThat( card.getIndicatedEntityName() ).isEqualTo( CardPayment.class.getName() ); - - final DiscriminatorValueDetails check = discriminatorConverter.getDetailsForDiscriminatorValue( "CHECK" ); - assertThat( check.getIndicatedEntity().getEntityName() ).isEqualTo( CheckPayment.class.getName() ); - assertThat( check.getIndicatedEntityName() ).isEqualTo( CheckPayment.class.getName() ); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // check entity -> discriminator - - try { - discriminatorConverter.getDetailsForDiscriminatorValue( CashPayment.class.getName() ); - fail( "Expecting an error" ); - } - catch (HibernateException expected) { - assertThat( expected ).hasMessageContaining( "Unknown discriminator value" ); - } - - final DiscriminatorValueDetails cardDiscriminatorValue = discriminatorConverter.getDetailsForEntityName( CardPayment.class.getName() ); - assertThat( cardDiscriminatorValue.getValue() ).isEqualTo( "CARD" ); - - final DiscriminatorValueDetails checkDiscriminatorValue = discriminatorConverter.getDetailsForEntityName( CheckPayment.class.getName() ); - assertThat( checkDiscriminatorValue.getValue() ).isEqualTo( "CHECK" ); - - - final Map detailsByEntityName = ((ExplicitDiscriminatorConverter) discriminatorConverter).getDetailsByEntityName(); - assertThat( detailsByEntityName.keySet() ).containsOnly( - CardPayment.class.getName(), - CheckPayment.class.getName() - ); - - final Map detailsByValue = ((ExplicitDiscriminatorConverter) discriminatorConverter).getDetailsByValue(); - assertThat( detailsByValue.keySet() ).containsOnly( - "CARD", - "CHECK" - ); - } ); - } - - @Test - @DomainModel(annotatedClasses = {Payment.class, CashPayment.class, CardPayment.class, CheckPayment.class, Order.class}) - @SessionFactory - void verifyMixedMappingModel(SessionFactoryScope sessions) { - sessions.withSessionFactory( (factory) -> { - final EntityPersister entityDescriptor = factory.getMappingMetamodel().getEntityDescriptor( Order.class ); - final DiscriminatedAssociationAttributeMapping mixedMapping = (DiscriminatedAssociationAttributeMapping) entityDescriptor.findAttributeMapping( "mixedPayment" ); - final DiscriminatorMapping discriminatorMapping = mixedMapping.getDiscriminatorMapping(); - final DiscriminatorConverter discriminatorConverter = discriminatorMapping.getValueConverter(); - // historically this operated as if EXPLICIT - assertThat( discriminatorConverter.getValueStrategy() ).isEqualTo( AnyDiscriminatorValueStrategy.MIXED ); - - // NOTE : cash is NOT mapped - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // check discriminator -> entity - - final DiscriminatorValueDetails cash = discriminatorConverter.getDetailsForDiscriminatorValue( CashPayment.class.getName() ); - assertThat( cash.getIndicatedEntity().getEntityName() ).isEqualTo( CashPayment.class.getName() ); - assertThat( cash.getIndicatedEntityName() ).isEqualTo( CashPayment.class.getName() ); - - final DiscriminatorValueDetails card = discriminatorConverter.getDetailsForDiscriminatorValue( "CARD" ); - assertThat( card.getIndicatedEntity().getEntityName() ).isEqualTo( CardPayment.class.getName() ); - assertThat( card.getIndicatedEntityName() ).isEqualTo( CardPayment.class.getName() ); - - final DiscriminatorValueDetails check = discriminatorConverter.getDetailsForDiscriminatorValue( "CHECK" ); - assertThat( check.getIndicatedEntity().getEntityName() ).isEqualTo( CheckPayment.class.getName() ); - assertThat( check.getIndicatedEntityName() ).isEqualTo( CheckPayment.class.getName() ); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // check entity -> discriminator - - final DiscriminatorValueDetails cashDiscriminatorValue = discriminatorConverter.getDetailsForEntityName( CashPayment.class.getName() ); - assertThat( cashDiscriminatorValue.getValue() ).isEqualTo( CashPayment.class.getName() ); - - final DiscriminatorValueDetails cardDiscriminatorValue = discriminatorConverter.getDetailsForEntityName( CardPayment.class.getName() ); - assertThat( cardDiscriminatorValue.getValue() ).isEqualTo( "CARD" ); - - final DiscriminatorValueDetails checkDiscriminatorValue = discriminatorConverter.getDetailsForEntityName( CheckPayment.class.getName() ); - assertThat( checkDiscriminatorValue.getValue() ).isEqualTo( "CHECK" ); - - final Map detailsByEntityName = ((MixedDiscriminatorConverter) discriminatorConverter).getDetailsByEntityName(); - assertThat( detailsByEntityName.keySet() ).containsOnly( - CashPayment.class.getName(), - CardPayment.class.getName(), - CheckPayment.class.getName() - ); - - final Map detailsByValue = ((MixedDiscriminatorConverter) discriminatorConverter).getDetailsByValue(); - assertThat( detailsByValue.keySet() ).containsOnly( - CashPayment.class.getName(), - "CARD", - "CHECK" - ); - } ); - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/Payment.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/Payment.java deleted file mode 100644 index 76d47ae334..0000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/Payment.java +++ /dev/null @@ -1,12 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.orm.test.any.mixed; - -/** - * @author Steve Ebersole - */ -public interface Payment { - Double getAmount(); -}