diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc index 4e2d2cebef50..9bf942533c1c 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: + +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-example]] -.`@Any` mapping usage +[[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] +include::{example-dir-any}/discriminator/implicit/Order.java[tags=associations-any-implicit-discriminator-short-example] ---- +==== -[source, SQL, indent=0] ----- -include::{extrasdir}/associations-any-persist-example.sql[] ----- +[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-mixed-discriminator]] +===== Mixed discriminator mappings -When fetching the `PropertyHolder` entity and navigating its `property` association, -Hibernate will fetch the associated `StringProperty` entity like this: +A mixed strategy combines `@AnyDiscriminatorValue` and `@AnyDiscriminatorImplicitValues`. Mappings +explicitly defined using `@AnyDiscriminatorValue` take precedence. E.g. -[[associations-any-query-example]] -.`@Any` mapping query example + +[[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 f25ee01e504d..000000000000 --- 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 87cf331e49de..000000000000 --- 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 1467518486f1..000000000000 --- 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 08c8171611c2..4149a5c68b95 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 5bc5fd082e4e..000000000000 --- 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 207fd6065302..000000000000 --- 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 e491df2b2d86..2ce6ddcba4e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/AnyDiscriminator.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/AnyDiscriminator.java @@ -4,12 +4,11 @@ */ 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.metamodel.spi.ImplicitDiscriminatorStrategy; - import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; @@ -33,6 +32,8 @@ * {@code @AnyDiscriminator}. * * @see Any + * @see AnyDiscriminatorValue + * @see AnyDiscriminatorImplicitValues * * @since 6.0 */ @@ -45,10 +46,4 @@ * or {@link JdbcTypeCode}. */ DiscriminatorType value() default DiscriminatorType.STRING; - - /** - * Determines how to handle mappings which are not explicitly defined by - * any associated {@linkplain AnyDiscriminatorValue} annotations - */ - Class implicitValueStrategy() default ImplicitDiscriminatorStrategy.class; } 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 000000000000..04152f082836 --- /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 b3efe4584b3a..9bea511884bd 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 @@ * * @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 a772cf3e53f8..4c1ed0863d44 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 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; @@ -112,8 +113,9 @@ private static void bindAny( ); final AnyDiscriminator anyDiscriminator = property.getDirectAnnotationUsage( AnyDiscriminator.class ); - if ( anyDiscriminator != null ) { - value.setImplicitDiscriminatorValueStrategy( resolveImplicitDiscriminatorStrategy( anyDiscriminator, context ) ); + final AnyDiscriminatorImplicitValues anyDiscriminatorImplicitValues = property.getDirectAnnotationUsage( AnyDiscriminatorImplicitValues.class ); + if ( anyDiscriminatorImplicitValues != null ) { + value.setImplicitDiscriminatorValueStrategy( resolveImplicitDiscriminatorStrategy( anyDiscriminatorImplicitValues, context ) ); } final PropertyBinder binder = new PropertyBinder(); @@ -138,22 +140,35 @@ private static void bindAny( binder.callAttributeBindersInSecondPass( prop ); } - private static ImplicitDiscriminatorStrategy resolveImplicitDiscriminatorStrategy( - AnyDiscriminator anyDiscriminator, + public static ImplicitDiscriminatorStrategy resolveImplicitDiscriminatorStrategy( + AnyDiscriminatorImplicitValues anyDiscriminatorImplicitValues, MetadataBuildingContext context) { - final Class implicitValueStrategy = anyDiscriminator.implicitValueStrategy(); - if ( ImplicitDiscriminatorStrategy.class.equals( implicitValueStrategy ) ) { + 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( implicitValueStrategy ) ) { + if ( FullNameImplicitDiscriminatorStrategy.class.equals( customStrategy ) ) { return FullNameImplicitDiscriminatorStrategy.FULL_NAME_STRATEGY; } - if ( ShortNameImplicitDiscriminatorStrategy.class.equals( implicitValueStrategy ) ) { + if ( ShortNameImplicitDiscriminatorStrategy.class.equals( customStrategy ) ) { return ShortNameImplicitDiscriminatorStrategy.SHORT_NAME_STRATEGY; } - return context.getBootstrapContext().getCustomTypeProducer().produceBeanInstance( implicitValueStrategy ); + 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 b53be48ead83..20bbffbb6d5b 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.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 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 static Any buildAnyValue( ); 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 5c2ef4f56664..63bf9170b3df 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 25caaeff5d86..27339375df86 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.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 AnyDiscriminatorAnnotation implements AnyDiscriminator { private jakarta.persistence.DiscriminatorType value; - private Class implicitValueStrategy; /** * Used in creating dynamic annotation instances (e.g. from XML) */ public AnyDiscriminatorAnnotation(SourceModelBuildingContext modelContext) { this.value = jakarta.persistence.DiscriminatorType.STRING; - this.implicitValueStrategy = ImplicitDiscriminatorStrategy.class; } /** @@ -30,7 +27,6 @@ public AnyDiscriminatorAnnotation(SourceModelBuildingContext modelContext) { */ public AnyDiscriminatorAnnotation(AnyDiscriminator annotation, SourceModelBuildingContext modelContext) { this.value = annotation.value(); - this.implicitValueStrategy = annotation.implicitValueStrategy(); } /** @@ -38,8 +34,6 @@ public AnyDiscriminatorAnnotation(AnyDiscriminator annotation, SourceModelBuildi */ public AnyDiscriminatorAnnotation(Map attributeValues, SourceModelBuildingContext modelContext) { this.value = (jakarta.persistence.DiscriminatorType) attributeValues.get( "value" ); - //noinspection unchecked - this.implicitValueStrategy = (Class) attributeValues.get( "implicitValueStrategy" ); } @Override @@ -55,13 +49,4 @@ public jakarta.persistence.DiscriminatorType value() { public void value(jakarta.persistence.DiscriminatorType value) { this.value = value; } - - @Override - public Class implicitValueStrategy() { - return implicitValueStrategy; - } - - public void implicitValueStrategy(Class implicitValueStrategy) { - this.implicitValueStrategy = implicitValueStrategy; - } } 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 000000000000..dd7df076b85e --- /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 747e29a6a23f..bd6b4caed4a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Any.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Any.java @@ -131,19 +131,6 @@ public void setIdentifierType(String identifierType) { this.keyMapping.setTypeName( identifierType ); } - /** - * 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; - } - @Override public AnyType getType() throws MappingException { if ( resolvedType == null ) { @@ -233,6 +220,19 @@ public void setMetaValues(Map metaValueToEntityNameMap) { 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; } 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 index ef4986bdb3ee..7f3b26a6691e 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/FullNameImplicitDiscriminatorStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/FullNameImplicitDiscriminatorStrategy.java @@ -12,6 +12,8 @@ import org.hibernate.persister.entity.EntityPersister; /** + * ImplicitDiscriminatorStrategy implementation using entity {@linkplain EntityMappingType#getEntityName() full-names}. + * * @author Steve Ebersole */ public class FullNameImplicitDiscriminatorStrategy implements ImplicitDiscriminatorStrategy { 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 index 929858cc6e88..41566241f4d6 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/ShortNameImplicitDiscriminatorStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/ShortNameImplicitDiscriminatorStrategy.java @@ -11,6 +11,8 @@ import org.hibernate.metamodel.spi.MappingMetamodelImplementor; /** + * ImplicitDiscriminatorStrategy implementation using entity {@linkplain EntityMappingType#getEntityName() full-names}. + * * @author Steve Ebersole */ public class ShortNameImplicitDiscriminatorStrategy implements ImplicitDiscriminatorStrategy { 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 e7726006ced4..0cd8e439203d 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 @@ * @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 String getAuthorizationCode() { 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 7d338a22f40a..6a5ae03eba72 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 @@ * @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 Double getAmount() { 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 b0aabb7fc206..0c7cde543934 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 @@ * @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 CheckPayment(Integer id, Double amount, int checkNumber, String routingNu 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 000000000000..b48c4de1b3bb --- /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 000000000000..ebe335f77f77 --- /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 58% 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 1d1b6d2d4391..ad654616bccb 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.metamodel.internal.FullNameImplicitDiscriminatorStrategy; +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(implicitValueStrategy = FullNameImplicitDiscriminatorStrategy.class) - @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 000000000000..c135250d8c95 --- /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 000000000000..89b90d9dff7e --- /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 000000000000..910bfeaf889f --- /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 000000000000..b37ba752c373 --- /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 000000000000..b66eb713f121 --- /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 000000000000..fff709374f9c --- /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 000000000000..977c1418f67c --- /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 000000000000..2c442841a0dc --- /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 cb9dff6e16b1..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/AnyDiscriminatorValueHandlingTests.java +++ /dev/null @@ -1,119 +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; - -/** - * @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( "Cannot determine discriminator value from entity-name" ); - } - } ); - } - - @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 4f16cb689536..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/AnyDiscriminatorValueStrategyTests.java +++ /dev/null @@ -1,208 +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.UnifiedAnyDiscriminatorConverter; -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.junit.jupiter.api.Test; - -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -/** - * @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(); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // 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 = ((UnifiedAnyDiscriminatorConverter) discriminatorConverter).getDetailsByEntityName(); - assertThat( detailsByEntityName.keySet() ).containsOnly( - CashPayment.class.getName(), - CardPayment.class.getName(), - CheckPayment.class.getName() - ); - - final Map detailsByValue = ((UnifiedAnyDiscriminatorConverter) 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(); - - // 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 = ((UnifiedAnyDiscriminatorConverter) discriminatorConverter).getDetailsByEntityName(); - assertThat( detailsByEntityName.keySet() ).containsOnly( - CardPayment.class.getName(), - CheckPayment.class.getName() - ); - - final Map detailsByValue = ((UnifiedAnyDiscriminatorConverter) 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(); - - // 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 = ((UnifiedAnyDiscriminatorConverter) discriminatorConverter).getDetailsByEntityName(); - assertThat( detailsByEntityName.keySet() ).containsOnly( - CashPayment.class.getName(), - CardPayment.class.getName(), - CheckPayment.class.getName() - ); - - final Map detailsByValue = ((UnifiedAnyDiscriminatorConverter) 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/ImplicitStrategyWithShortNameTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/ImplicitStrategyWithShortNameTest.java deleted file mode 100644 index ced66e5c8abd..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/any/mixed/ImplicitStrategyWithShortNameTest.java +++ /dev/null @@ -1,106 +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 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.AnyDiscriminator; -import org.hibernate.annotations.AnyDiscriminatorValue; -import org.hibernate.annotations.AnyKeyJavaClass; -import org.hibernate.metamodel.internal.ShortNameImplicitDiscriminatorStrategy; -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 java.sql.ResultSet; -import java.sql.Statement; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -/** - * @author Steve Ebersole - */ -@SuppressWarnings("JUnitMalformedDeclaration") -public class ImplicitStrategyWithShortNameTest { - @Test - @DomainModel(annotatedClasses = {Payment.class, CashPayment.class, CardPayment.class, CheckPayment.class, Bill.class}) - @SessionFactory - void testIt(SessionFactoryScope sessions) { - sessions.inTransaction( (session) -> { - final Bill bill = new Bill( 1, "Water" ); - session.persist( bill ); - - final CashPayment cashPayment = new CashPayment( 1, 123.45 ); - session.persist( cashPayment ); - - final CardPayment cardPayment = new CardPayment( 1, 987.50, "12345" ); - session.persist( cardPayment ); - - bill.implicitPayment = cashPayment; - bill.mixedPayment = cardPayment; - } ); - - sessions.inTransaction( (session) -> session.doWork( (conn) -> { - final Statement statement = conn.createStatement(); - final ResultSet resultSet = statement.executeQuery( "select implicit_kind, mixed_kind from billz" ); - assertThat( resultSet.next() ).isTrue(); - assertThat( resultSet.getString( 1 ) ).isEqualTo( "CashPayment" ); - assertThat( resultSet.getString( 2 ) ).isEqualTo( "CardPayment" ); - } ) ); - - sessions.inTransaction( (session) -> { - final Bill bill = session.find( Bill.class, 1 ); - assertThat( bill.implicitPayment ).isInstanceOf( CashPayment.class ); - assertThat( bill.mixedPayment ).isInstanceOf( CardPayment.class ); - } ); - } - - @AfterEach - @SessionFactory - void dropTestData(SessionFactoryScope sessions) { - sessions.inTransaction( session -> { - session.createMutationQuery( "delete Bill" ).executeUpdate(); - session.createMutationQuery( "delete CashPayment" ).executeUpdate(); - session.createMutationQuery( "delete CardPayment" ).executeUpdate(); - } ); - } - - @Entity(name="Bill") - @Table(name="billz") - public static class Bill { - @Id - private Integer id; - private String name; - - @Any - @AnyDiscriminator(implicitValueStrategy = ShortNameImplicitDiscriminatorStrategy.class) - @Column(name = "implicit_kind") - @AnyKeyJavaClass( Integer.class ) - @JoinColumn( name = "implicit_fk" ) - private Payment implicitPayment; - - @Any - @AnyDiscriminator(implicitValueStrategy = ShortNameImplicitDiscriminatorStrategy.class) - @AnyDiscriminatorValue( discriminator = "cash", entity = CashPayment.class ) - @Column(name = "mixed_kind") - @AnyKeyJavaClass( Integer.class ) - @JoinColumn( name = "mixed_fk" ) - private Payment mixedPayment; - - public Bill() { - } - - public Bill(Integer id, String name) { - this.id = id; - this.name = name; - } - } -} 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 76d47ae3344e..000000000000 --- 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(); -}