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

This commit is contained in:
Steve Ebersole 2024-10-19 10:21:34 -05:00
parent f82c581990
commit b1135b537c
17 changed files with 665 additions and 21 deletions

View File

@ -8,11 +8,13 @@ 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}
@ -44,4 +46,12 @@ 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

@ -8,6 +8,7 @@ import java.util.Locale;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.annotations.AnyDiscriminator;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Formula;
@ -107,6 +108,11 @@ public class AnyBinder {
context
);
final AnyDiscriminator anyDiscriminator = property.getDirectAnnotationUsage( AnyDiscriminator.class );
if ( anyDiscriminator != null ) {
value.setDiscriminatorValueStrategy( anyDiscriminator.valueStrategy() );
}
final PropertyBinder binder = new PropertyBinder();
binder.setName( inferredData.getPropertyName() );
binder.setValue( value );

View File

@ -9,17 +9,20 @@ import java.util.Map;
import org.hibernate.annotations.AnyDiscriminator;
import org.hibernate.models.spi.SourceModelBuildingContext;
import org.hibernate.type.AnyDiscriminatorValueStrategy;
@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;
}
/**
@ -27,6 +30,7 @@ public class AnyDiscriminatorAnnotation implements AnyDiscriminator {
*/
public AnyDiscriminatorAnnotation(AnyDiscriminator annotation, SourceModelBuildingContext modelContext) {
this.value = annotation.value();
this.valueStrategy = annotation.valueStrategy();
}
/**
@ -50,5 +54,14 @@ public class AnyDiscriminatorAnnotation implements AnyDiscriminator {
this.value = value;
}
@Override
public AnyDiscriminatorValueStrategy valueStrategy() {
return valueStrategy;
}
public void valueStrategy(AnyDiscriminatorValueStrategy valueStrategy) {
this.valueStrategy = valueStrategy;
}
}

View File

@ -9,6 +9,7 @@ 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;
@ -34,6 +35,7 @@ public class Any extends SimpleValue {
// common
private Map<Object,String> metaValueToEntityNameMap;
private AnyDiscriminatorValueStrategy discriminatorValueStrategy = AnyDiscriminatorValueStrategy.AUTO;
private boolean lazy = true;
private AnyType resolvedType;
@ -73,6 +75,7 @@ public class Any extends SimpleValue {
this.metaValueToEntityNameMap = original.metaValueToEntityNameMap == null
? null
: new HashMap<>(original.metaValueToEntityNameMap);
this.discriminatorValueStrategy = original.discriminatorValueStrategy;
this.lazy = original.lazy;
}
@ -128,6 +131,28 @@ 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 ) {
@ -150,6 +175,7 @@ public class Any extends SimpleValue {
resolvedType = MappingHelper.anyMapping(
discriminatorType,
identifierType,
discriminatorValueStrategy,
metaValueToEntityNameMap,
isLazy(),
getBuildingContext()

View File

@ -4,7 +4,9 @@
*/
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;
@ -16,7 +18,6 @@ import java.util.function.Function;
* @author Gavin King
*/
public abstract class DiscriminatorConverter<O,R> implements BasicValueConverter<O,R> {
private final String discriminatorName;
private final JavaType<O> domainJavaType;
private final JavaType<R> relationalJavaType;
@ -30,6 +31,9 @@ public abstract class DiscriminatorConverter<O,R> implements BasicValueConverter
this.relationalJavaType = relationalJavaType;
}
@Incubating
public abstract AnyDiscriminatorValueStrategy getValueStrategy();
public String getDiscriminatorName() {
return discriminatorName;
}

View File

@ -4,6 +4,15 @@
*/
package org.hibernate.metamodel.mapping;
import org.hibernate.AssertionFailure;
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;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -11,14 +20,6 @@ import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import org.hibernate.AssertionFailure;
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.BasicType;
import org.hibernate.type.descriptor.java.JavaType;
/**
* Handles conversion of discriminator values for embeddable subtype classes
* to their domain typed form.
@ -65,6 +66,12 @@ 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 );

View File

@ -12,6 +12,7 @@ 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;
@ -60,6 +61,19 @@ public class ExplicitDiscriminatorConverter<O,R> extends DiscriminatorConverter<
} );
}
@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 ) {
@ -80,13 +94,13 @@ public class ExplicitDiscriminatorConverter<O,R> extends DiscriminatorConverter<
if ( relationalForm.getClass().isEnum() ) {
final Object enumValue;
if ( getRelationalJavaType() instanceof StringJavaType ) {
enumValue = ( (Enum) relationalForm ).name();
enumValue = ( (Enum<?>) relationalForm ).name();
}
else if ( getRelationalJavaType() instanceof CharacterJavaType ) {
enumValue = ( (Enum) relationalForm ).name().charAt( 0 );
enumValue = ( (Enum<?>) relationalForm ).name().charAt( 0 );
}
else {
enumValue = ( (Enum) relationalForm ).ordinal();
enumValue = ( (Enum<?>) relationalForm ).ordinal();
}
final DiscriminatorValueDetails enumMatch = detailsByValue.get( enumValue );
if ( enumMatch != null ) {
@ -108,7 +122,7 @@ public class ExplicitDiscriminatorConverter<O,R> extends DiscriminatorConverter<
if ( valueDetails != null) {
return valueDetails;
}
throw new HibernateException( "Unknown entity name (" + discriminatorRole + ") : " + entityName );
throw new HibernateException( "Entity not explicitly mapped for ANY discriminator (" + discriminatorRole + ") : " + entityName );
}

View File

@ -12,6 +12,7 @@ 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;
@ -51,6 +52,19 @@ public class ImplicitDiscriminatorConverter<O,R> extends DiscriminatorConverter<
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 ) {
@ -58,10 +72,8 @@ public class ImplicitDiscriminatorConverter<O,R> extends DiscriminatorConverter<
if ( existingDetails != null ) {
return existingDetails;
}
final String entityName = mappingMetamodel.getImportedName( incoming );
final EntityPersister persister = mappingMetamodel.findEntityDescriptor( entityName );
final EntityPersister persister = mappingMetamodel.findEntityDescriptor( incoming );
if ( persister != null ) {
assert persister.getImportedName().equals( incoming );
return register( incoming, persister );
}
}
@ -76,7 +88,7 @@ public class ImplicitDiscriminatorConverter<O,R> extends DiscriminatorConverter<
private DiscriminatorValueDetails register(Object value, EntityPersister entityDescriptor) {
final DiscriminatorValueDetails details = new DiscriminatorValueDetailsImpl( value, entityDescriptor );
detailsByValue.put( value, details );
detailsByEntityName.put( entityDescriptor.getImportedName(), details );
detailsByEntityName.put( entityDescriptor.getEntityName(), details );
return details;
}
@ -88,7 +100,7 @@ public class ImplicitDiscriminatorConverter<O,R> extends DiscriminatorConverter<
}
final EntityPersister persister = mappingMetamodel.findEntityDescriptor( entityName );
if ( persister!= null ) {
return register( persister.getImportedName(), persister );
return register( persister.getEntityName(), persister );
}
throw new HibernateException( String.format(
ROOT,

View File

@ -11,6 +11,7 @@ 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;
@ -59,6 +60,19 @@ public class MixedDiscriminatorConverter<O,R> extends DiscriminatorConverter<O,R
return details;
}
@Override
public AnyDiscriminatorValueStrategy getValueStrategy() {
return AnyDiscriminatorValueStrategy.MIXED;
}
public Map<Object, DiscriminatorValueDetails> getDetailsByValue() {
return detailsByValue;
}
public Map<String, DiscriminatorValueDetails> getDetailsByEntityName() {
return detailsByEntityName;
}
@Override
public DiscriminatorValueDetails getDetailsForDiscriminatorValue(Object relationalForm) {
if ( relationalForm == null ) {
@ -79,13 +93,13 @@ public class MixedDiscriminatorConverter<O,R> extends DiscriminatorConverter<O,R
if ( relationalForm.getClass().isEnum() ) {
final Object enumValue;
if ( getRelationalJavaType() instanceof StringJavaType ) {
enumValue = ( (Enum) relationalForm ).name();
enumValue = ( (Enum<?>) relationalForm ).name();
}
else if ( getRelationalJavaType() instanceof CharacterJavaType ) {
enumValue = ( (Enum) relationalForm ).name().charAt( 0 );
enumValue = ( (Enum<?>) relationalForm ).name().charAt( 0 );
}
else {
enumValue = ( (Enum) relationalForm ).ordinal();
enumValue = ( (Enum<?>) relationalForm ).ordinal();
}
final DiscriminatorValueDetails enumMatch = detailsByValue.get( enumValue );
if ( enumMatch != null ) {

View File

@ -4,6 +4,7 @@
*/
package org.hibernate.type;
import org.hibernate.Incubating;
import org.hibernate.annotations.AnyDiscriminatorValue;
/**
@ -12,8 +13,10 @@ import org.hibernate.annotations.AnyDiscriminatorValue;
*
* @see AnyDiscriminatorValue
*
* @since 7.0
* @author Steve Ebersole
*/
@Incubating
public enum AnyDiscriminatorValueStrategy {
/**
* Pick between {@link #IMPLICIT} and {@link #EXPLICIT} based on

View File

@ -0,0 +1,121 @@
/*
* 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

@ -0,0 +1,217 @@
/*
* 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

@ -0,0 +1,50 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.any.mixed;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
/**
* @author Steve Ebersole
*/
@SuppressWarnings("unused")
@Entity(name = "CardPayment")
public class CardPayment implements Payment {
@Id
private Integer id;
private Double amount;
private String authorizationCode;
public CardPayment() {
}
public CardPayment(Integer id, Double amount, String authorizationCode) {
this.id = id;
this.amount = amount;
this.authorizationCode = authorizationCode;
}
public Integer getId() {
return id;
}
@Override
public Double getAmount() {
return amount;
}
public void setAmount(Double amount) {
this.amount = amount;
}
public String getAuthorizationCode() {
return authorizationCode;
}
public void setAuthorizationCode(String authorizationCode) {
this.authorizationCode = authorizationCode;
}
}

View File

@ -0,0 +1,40 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.any.mixed;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
/**
* @author Steve Ebersole
*/
@SuppressWarnings("unused")
@Entity(name = "CashPayment")
public class CashPayment implements Payment {
@Id
private Integer id;
private Double amount;
public CashPayment() {
}
public CashPayment(Integer id, Double amount) {
this.id = id;
this.amount = amount;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public Double getAmount() {
return amount;
}
public void setAmount(Double amount) {
this.amount = amount;
}
}

View File

@ -0,0 +1,38 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.any.mixed;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
/**
* @author Steve Ebersole
*/
@SuppressWarnings("unused")
@Entity
public class CheckPayment implements Payment {
@Id
public Integer id;
public Double amount;
public int checkNumber;
public String routingNumber;
public String accountNumber;
public CheckPayment() {
}
public CheckPayment(Integer id, Double amount, int checkNumber, String routingNumber, String accountNumber) {
this.id = id;
this.amount = amount;
this.checkNumber = checkNumber;
this.routingNumber = routingNumber;
this.accountNumber = accountNumber;
}
@Override
public Double getAmount() {
return amount;
}
}

View File

@ -0,0 +1,57 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.any.mixed;
import jakarta.persistence.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;
/**
* @author Steve Ebersole
*/
@Entity
@Table(name = "orders")
public class Order {
@Id
public Integer id;
@Basic
public String name;
@Any
@AnyKeyJavaClass( Integer.class )
@JoinColumn(name = "explicit_fk")
@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;
protected Order() {
// for Hibernate use
}
public Order(Integer id, String name) {
this.id = id;
this.name = name;
}
}

View File

@ -0,0 +1,12 @@
/*
* 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();
}