HHH-18728 - Allow mixed discriminator-value mappings for ANY

HHH-18729 - Allow custom strategy for implicit discriminator-value determination for ANY
This commit is contained in:
Steve Ebersole 2024-10-24 08:03:18 -05:00
parent 046557fe15
commit 2ea226999d
50 changed files with 1247 additions and 1088 deletions

View File

@ -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 <<associations-any-discriminator,discriminator>>
2. The column and mapping for the <<associations-any-key,key>>
3. The mapping between discriminator values and entity types which may be <<associations-any-explicit-discriminator,explicit>>
<<associations-any-implicit-discriminator,implicit>> or <<associations-any-mixed-discriminator,mixed>>.
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 <<basic-mapping>>, Hibernate's ANY-related annotations can be composed using meta-annotations
to re-use ANY mapping details.
Looking back at <<associations-any-example>>, 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-any-example>>.
[[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]]

View File

@ -1,6 +0,0 @@
CREATE TABLE property_holder (
id BIGINT NOT NULL,
property_type VARCHAR(255),
property_id BIGINT,
PRIMARY KEY ( id )
)

View File

@ -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 )

View File

@ -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

View File

@ -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
)

View File

@ -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 )

View File

@ -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

View File

@ -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;
}

View File

@ -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<? extends ImplicitDiscriminatorStrategy> implementation() default ImplicitDiscriminatorStrategy.class;
}

View File

@ -27,6 +27,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
*
* @see Any
* @see AnyDiscriminator
* @see AnyDiscriminatorImplicitValues
*
* @since 6.0
*/

View File

@ -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<? extends ImplicitDiscriminatorStrategy> 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 );
}
}

View File

@ -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<AnnotatedJoinColumn> columns = keyColumns.getJoinColumns();
assert columns.size() == 1;

View File

@ -36,6 +36,10 @@ public interface HibernateAnnotations {
AnyDiscriminator.class,
AnyDiscriminatorAnnotation.class
);
OrmAnnotationDescriptor<AnyDiscriminatorImplicitValues,AnyDiscriminatorImplicitValuesAnnotation> ANY_DISCRIMINATOR_IMPLICIT_VALUES = new OrmAnnotationDescriptor<>(
AnyDiscriminatorImplicitValues.class,
AnyDiscriminatorImplicitValuesAnnotation.class
);
OrmAnnotationDescriptor<AnyDiscriminatorValues,AnyDiscriminatorValuesAnnotation> ANY_DISCRIMINATOR_VALUES = new OrmAnnotationDescriptor<>(
AnyDiscriminatorValues.class,
AnyDiscriminatorValuesAnnotation.class

View File

@ -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;
}
}

View File

@ -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<? extends ImplicitDiscriminatorStrategy> 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<String, Object> attributeValues, SourceModelBuildingContext modelContext) {
this.value = (Strategy) attributeValues.get( "value" );
//noinspection unchecked
this.implementation = (Class<? extends ImplicitDiscriminatorStrategy>) attributeValues.get( "implementation" );
}
@Override
public Class<? extends Annotation> annotationType() {
return AnyDiscriminatorImplicitValues.class;
}
@Override
public Strategy value() {
return value;
}
public void value(Strategy value) {
this.value = value;
}
@Override
public Class<? extends ImplicitDiscriminatorStrategy> implementation() {
return implementation;
}
public void implementation(Class<? extends ImplicitDiscriminatorStrategy> implementation) {
this.implementation = implementation;
}
}

View File

@ -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<Object,String> 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<Selectable> 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 {

View File

@ -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<Object, String> 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<Object, String> 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 );
}

View File

@ -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 );
}
}

View File

@ -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 );
}
}

View File

@ -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<O,R> implements BasicValueConverter
this.relationalJavaType = relationalJavaType;
}
@Incubating
public abstract AnyDiscriminatorValueStrategy getValueStrategy();
public String getDiscriminatorName() {
return discriminatorName;
}
@ -90,7 +85,7 @@ public abstract class DiscriminatorConverter<O,R> 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<O,R> implements BasicValueConverter
public abstract void forEachValueDetail(Consumer<DiscriminatorValueDetails> consumer);
/**
* Find and return the first DiscriminatorValueDetails which matches the given {@code handler}
*/
public abstract <X> X fromValueDetails(Function<DiscriminatorValueDetails,X> handler);
}

View File

@ -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<O, R> 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<O, R> 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

View File

@ -462,6 +462,10 @@ public interface EntityMappingType
void visitConstraintOrderedTables(ConstraintOrderedTableConsumer consumer);
default String getImportedName() {
return getEntityPersister().getImportedName();
}
interface ConstraintOrderedTableConsumer {
void consume(String tableExpression, Supplier<Consumer<SelectableConsumer>> tableKeyColumnVisitationSupplier);
}

View File

@ -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<Object,String> 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<Object, String> 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<>(
return new UnifiedAnyDiscriminatorConverter<>(
partRole,
ClassJavaType.INSTANCE,
underlyingJdbcMapping.getJavaTypeDescriptor(),
valueToEntityNameMap,
implicitValueStrategy,
mappingMetamodel
);
case EXPLICIT -> new ExplicitDiscriminatorConverter<>(
partRole,
ClassJavaType.INSTANCE,
underlyingJdbcMapping.getJavaTypeDescriptor(),
valueToEntityNameMap,
mappingMetamodel
);
case IMPLICIT -> new ImplicitDiscriminatorConverter<>(
partRole,
ClassJavaType.INSTANCE,
underlyingJdbcMapping.getJavaTypeDescriptor(),
valueToEntityNameMap,
mappingMetamodel
);
};
}
public DiscriminatorConverter<?,?> getValueConverter() {

View File

@ -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()
);

View File

@ -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<O,R> extends DiscriminatorConverter<O, R> {
private final NavigableRole discriminatorRole;
private final Map<Object, DiscriminatorValueDetails> detailsByValue;
private final Map<String,DiscriminatorValueDetails> detailsByEntityName;
public ExplicitDiscriminatorConverter(
NavigableRole discriminatorRole,
JavaType<O> domainJavaType,
JavaType<R> relationalJavaType,
Map<Object, String> 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<Object, DiscriminatorValueDetails> getDetailsByValue() {
return detailsByValue;
}
public Map<String, DiscriminatorValueDetails> 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<DiscriminatorValueDetails> consumer) {
detailsByEntityName.forEach( (value, detail) -> consumer.accept( detail ) );
}
@Override
public <X> X fromValueDetails(Function<DiscriminatorValueDetails, X> handler) {
for ( DiscriminatorValueDetails detail : detailsByEntityName.values() ) {
final X result = handler.apply( detail );
if ( result != null ) {
return result;
}
}
return null;
}
}

View File

@ -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<O,R> extends DiscriminatorConverter<O,R> {
private final NavigableRole discriminatorRole;
private final MappingMetamodelImplementor mappingMetamodel;
private final Map<Object, DiscriminatorValueDetails> detailsByValue;
private final Map<String,DiscriminatorValueDetails> detailsByEntityName;
public ImplicitDiscriminatorConverter(
NavigableRole discriminatorRole,
JavaType<O> domainJavaType,
JavaType<R> relationalJavaType,
Map<Object,String> 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<Object, DiscriminatorValueDetails> getDetailsByValue() {
return detailsByValue;
}
public Map<String, DiscriminatorValueDetails> 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<DiscriminatorValueDetails> consumer) {
}
@Override
public <X> X fromValueDetails(Function<DiscriminatorValueDetails,X> handler) {
return null;
}
}

View File

@ -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<O,R> extends DiscriminatorConverter<O,R> {
public class UnifiedAnyDiscriminatorConverter<O,R> extends DiscriminatorConverter<O,R> {
private final NavigableRole discriminatorRole;
private final Map<Object, DiscriminatorValueDetails> detailsByValue;
private final Map<String,DiscriminatorValueDetails> detailsByEntityName;
private final ImplicitDiscriminatorStrategy implicitValueStrategy;
private final MappingMetamodelImplementor mappingMetamodel;
public MixedDiscriminatorConverter(
public UnifiedAnyDiscriminatorConverter(
NavigableRole discriminatorRole,
JavaType<O> domainJavaType,
JavaType<R> relationalJavaType,
Map<Object,String> 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<Object, String> 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<Object, DiscriminatorValueDetails> getDetailsByValue() {
@ -74,32 +90,26 @@ public class MixedDiscriminatorConverter<O,R> extends DiscriminatorConverter<O,R
}
@Override
public DiscriminatorValueDetails getDetailsForDiscriminatorValue(Object relationalForm) {
if ( relationalForm == null ) {
public DiscriminatorValueDetails getDetailsForDiscriminatorValue(Object relationalValue) {
if ( relationalValue == null ) {
return detailsByValue.get( NULL_DISCRIMINATOR );
}
final DiscriminatorValueDetails existing = detailsByValue.get( relationalForm );
final DiscriminatorValueDetails existing = detailsByValue.get( relationalValue );
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() ) {
if ( relationalValue.getClass().isEnum() ) {
final Object enumValue;
if ( getRelationalJavaType() instanceof StringJavaType ) {
enumValue = ( (Enum<?>) 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<O,R> extends DiscriminatorConverter<O,R
}
}
if ( relationalForm instanceof String assumedEntityName ) {
// Assume the relational form is the entity name
final EntityPersister persister = mappingMetamodel.findEntityDescriptor( assumedEntityName );
if ( persister != null ) {
return register( assumedEntityName, persister );
if ( implicitValueStrategy != null ) {
final EntityMappingType entityMapping = implicitValueStrategy.toEntityMapping( relationalValue, discriminatorRole, mappingMetamodel );
if ( entityMapping != null ) {
return register( relationalValue, entityMapping );
}
}
throw new HibernateException( "Cannot interpret discriminator value (" + discriminatorRole + ") : " + relationalForm );
final DiscriminatorValueDetails nonNullMatch = detailsByValue.get( NOT_NULL_DISCRIMINATOR );
if ( nonNullMatch != null ) {
return nonNullMatch;
}
throw new HibernateException( "Unknown discriminator value (" + discriminatorRole.getFullPath() + ") : " + relationalValue );
}
@Override
@ -125,19 +139,29 @@ public class MixedDiscriminatorConverter<O,R> extends DiscriminatorConverter<O,R
return existing;
}
final EntityPersister entityDescriptor = mappingMetamodel.getEntityDescriptor( entityName );
return register( entityName, entityDescriptor );
if ( implicitValueStrategy != null ) {
final EntityMappingType entityMapping = mappingMetamodel.getEntityDescriptor( entityName );
assert entityMapping != null;
final Object discriminatorValue = implicitValueStrategy.toDiscriminatorValue(
entityMapping,
discriminatorRole,
mappingMetamodel
);
return register( discriminatorValue, entityMapping );
}
throw new HibernateException( "Cannot determine discriminator value from entity-name (" + discriminatorRole.getFullPath() + ") : " + entityName );
}
@Override
public void forEachValueDetail(Consumer<DiscriminatorValueDetails> consumer) {
detailsByEntityName.forEach( (value, detail) -> consumer.accept( detail ) );
detailsByEntityName.values().forEach( consumer );
}
@Override
public <X> X fromValueDetails(Function<DiscriminatorValueDetails, X> 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;
}

View File

@ -48,7 +48,7 @@ public class AnyMappingDomainTypeImpl<T> implements AnyMappingDomainType<T> {
navigableRole,
discriminatorBaseType,
bootAnyMapping.getMetaValues(),
discriminatorType.getValueStrategy(),
discriminatorType.getImplicitValueStrategy(),
mappingMetamodel
)
);

View File

@ -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);
}

View File

@ -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()
);

View File

@ -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
}

View File

@ -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() {

View File

@ -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<Object,String> discriminatorValuesToEntityNameMap;
private final Map<String,Object> entityNameToDiscriminatorValueMap;
public MetaType(
Type valueType,
AnyDiscriminatorValueStrategy valueStrategy,
ImplicitDiscriminatorStrategy implicitValueStrategy,
Map<Object,String> 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<Object,String> entry : discriminatorValuesToEntityNameMap.entrySet() ) {
entityNameToDiscriminatorValueMap.put( entry.getValue(), entry.getKey() );
}
}
this.valueStrategy = valueStrategy;
}
public MetaType(Map<Object,String> 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() {

View File

@ -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[]

View File

@ -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[]

View File

@ -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[]

View File

@ -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[]

View File

@ -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();
} );
}
}

View File

@ -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

View File

@ -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();
} );
}
}

View File

@ -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;
}
}

View File

@ -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<Payment> 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<Payment> getPayments() {
return payments;
}
public void setPayments(Set<Payment> payments) {
this.payments = payments;
}
}

View File

@ -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();
} );
}
}

View File

@ -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;
}
}

View File

@ -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 {
}

View File

@ -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();
} );
}
}

View File

@ -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;
}
}

View File

@ -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();
} );
}
}

View File

@ -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<String,?> detailsByEntityName = ((ImplicitDiscriminatorConverter<?,?>) discriminatorConverter).getDetailsByEntityName();
assertThat( detailsByEntityName.keySet() ).containsOnly(
CashPayment.class.getName(),
CardPayment.class.getName(),
CheckPayment.class.getName()
);
final Map<Object,?> 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<String,?> detailsByEntityName = ((ExplicitDiscriminatorConverter<?,?>) discriminatorConverter).getDetailsByEntityName();
assertThat( detailsByEntityName.keySet() ).containsOnly(
CardPayment.class.getName(),
CheckPayment.class.getName()
);
final Map<Object,?> 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<String,?> detailsByEntityName = ((MixedDiscriminatorConverter<?,?>) discriminatorConverter).getDetailsByEntityName();
assertThat( detailsByEntityName.keySet() ).containsOnly(
CashPayment.class.getName(),
CardPayment.class.getName(),
CheckPayment.class.getName()
);
final Map<Object,?> detailsByValue = ((MixedDiscriminatorConverter<?,?>) discriminatorConverter).getDetailsByValue();
assertThat( detailsByValue.keySet() ).containsOnly(
CashPayment.class.getName(),
"CARD",
"CHECK"
);
} );
}
}

View File

@ -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();
}