Skip to content

Commit

Permalink
HHH-1152 Discriminator based inheritance for embeddable types
Browse files Browse the repository at this point in the history
  • Loading branch information
mbladel committed May 14, 2024
1 parent 8b5cdba commit 4766753
Show file tree
Hide file tree
Showing 80 changed files with 3,507 additions and 562 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
create table TestEntity (
id bigint not null,
embeddable_type varchar(31) not null,
parentProp varchar(255),
childOneProp integer,
subChildOneProp float(53),
primary key (id)
)
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,57 @@ include::{example-dir-inheritance}/polymorphism/ExplicitPolymorphismTest.java[ta
Therefore, only the `Book` was fetched since the `Blog` entity was marked with the
`@Polymorphism(type = PolymorphismType.EXPLICIT)` annotation, which instructs Hibernate
to skip it when executing a polymorphic query against a non-mapped base class.

[[embeddable-inheritance]]
==== Embeddable inheritance

Hibernate also supports discriminator-based inheritance for *embeddable types*. This works similarly to
<<entity-inheritance-single-table, Single Table Entity inheritance>>: an `@Embeddable` class may be
extended by other `@Embeddable` classes, in which case the `@Embedded` properties using that type will
rely on an additional discriminator column to store information about the composite value's subtype.

When retrieving the inherited property, Hibernate will read the discriminator value and instantiate the
correct `@Embeddable` subtype with its corresponding properties.

By default, the discriminator column will be `STRING` typed and named like `<property_name>_DTYPE`,
where `property_name` is the name of the `@Embedded` property in the respective entity mapping.
It's possible to customize the discriminator column mapping:

* For the whole `@Embeddable` type, by using `@DiscriminatorColumn` or `@DiscriminatorFormula` on the *root* class of the inheritance hierarchy
(NOTE: if using the same inheritance-enabled embeddable type for two different properties in the same entity mapping,
this will cause a column name conflict);
* For a specific `@Embedded` property, by using the `@AttributeOverride` annotation with the special name `{discriminator}`.

Finally, to specify custom discriminator values for each subtype one can annotate the inheritance hierarchy's
classes with `@DiscriminatorValue`.

[IMPORTANT]
====
Embeddable inheritance *IS* also supported for components used in an `@ElementCollection`.
Embeddable inheritance is *NOT* supported for `@EmbeddedId`, embeddable types used as `@IdClass`
and embedded properties using a custom `@CompositeType`.
====

[[embeddable-inheritance-example]]
.Example mapping of an embeddable inheritance hierarchy
[source,java]
----
include::{example-dir-inheritance}/embeddable/ParentEmbeddable.java[tags=embeddable-inheritance-parent-example,indent=0]
----
[source,java]
----
include::{example-dir-inheritance}/embeddable/ChildOneEmbeddable.java[tags=embeddable-inheritance-child-one-example,indent=0]
----
[source,java]
----
include::{example-dir-inheritance}/embeddable/SubChildOneEmbeddable.java[tags=embeddable-inheritance-sub-child-one-example,indent=0]
----
[source,java]
----
include::{example-dir-inheritance}/embeddable/BasicEmbeddableInheritanceTest.java[tags=embeddable-inheritance-entity-example,indent=0]
----
This is the resulting table structure:
[source,sql]
----
include::{extrasdir}/embeddable-inheritance-create-table-example.sql[]
----
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.hibernate.event.spi.EventSource;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.AttributeMappingsList;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping;
Expand Down Expand Up @@ -176,22 +177,25 @@ private void visitEmbeddedAttributeMapping(
Object object,
PersistenceContext persistenceContext) {
if ( object != null ) {
final AttributeMappingsList attributeMappings = attributeMapping.getEmbeddableTypeDescriptor().getAttributeMappings();
final EmbeddableMappingType descriptor = attributeMapping.getEmbeddableTypeDescriptor();
final AttributeMappingsList attributeMappings = descriptor.getAttributeMappings();
for ( int i = 0; i < attributeMappings.size(); i++ ) {
final AttributeMapping attribute = attributeMappings.get( i );
if ( attribute.isPluralAttributeMapping() ) {
addCollectionKey(
attribute.asPluralAttributeMapping(),
attribute.getPropertyAccess().getGetter().get( object ),
persistenceContext
);
}
else if ( attribute.isEmbeddedAttributeMapping() ) {
visitEmbeddedAttributeMapping(
attribute.asEmbeddedAttributeMapping(),
attribute.getPropertyAccess().getGetter().get( object ),
persistenceContext
);
if ( descriptor.declaresAttribute( object.getClass().getName(), attributeMapping ) ) {
if ( attribute.isPluralAttributeMapping() ) {
addCollectionKey(
attribute.asPluralAttributeMapping(),
attribute.getPropertyAccess().getGetter().get( object ),
persistenceContext
);
}
else if ( attribute.isEmbeddedAttributeMapping() ) {
visitEmbeddedAttributeMapping(
attribute.asEmbeddedAttributeMapping(),
attribute.getPropertyAccess().getGetter().get( object ),
persistenceContext
);
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
private final Map<String,PersistentClass> entityBindingMap = new HashMap<>();
private final List<Component> composites = new ArrayList<>();
private final Map<Class<?>, Component> genericComponentsMap = new HashMap<>();
private final Map<XClass, List<XClass>> embeddableSubtypes = new HashMap<>();
private final Map<String,Collection> collectionBindingMap = new HashMap<>();

private final Map<String, FilterDefinition> filterDefinitionMap = new HashMap<>();
Expand Down Expand Up @@ -284,6 +285,17 @@ public Component getGenericComponent(Class<?> componentClass) {
return genericComponentsMap.get( componentClass );
}

@Override
public void registerEmbeddableSubclass(XClass superclass, XClass subclass) {
embeddableSubtypes.computeIfAbsent( superclass, c -> new ArrayList<>() ).add( subclass );
}

@Override
public List<XClass> getEmbeddableSubclasses(XClass superclass) {
final List<XClass> subclasses = embeddableSubtypes.get( superclass );
return subclasses != null ? subclasses : List.of();
}

@Override
public SessionFactoryBuilder getSessionFactoryBuilder() {
throw new UnsupportedOperationException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws
final Dialect dialect = database.getDialect();
final AggregateSupport aggregateSupport = dialect.getAggregateSupport();

// Sort the component properties early to ensure the aggregated
// columns respect the same order as the component's properties
final int[] originalOrder = component.sortProperties();
// Compute aggregated columns since we have to replace them in the table with the aggregate column
final List<Column> aggregatedColumns = component.getAggregatedColumns();
final AggregateColumn aggregateColumn = component.getAggregateColumn();
Expand Down Expand Up @@ -97,7 +100,7 @@ public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws
);
if ( registeredUdt == udt ) {
addAuxiliaryObjects = true;
orderColumns( registeredUdt );
orderColumns( registeredUdt, originalOrder );
}
else {
addAuxiliaryObjects = false;
Expand Down Expand Up @@ -184,9 +187,8 @@ public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws
propertyHolder.getTable().getColumns().removeAll( aggregatedColumns );
}

private void orderColumns(UserDefinedObjectType userDefinedType) {
private void orderColumns(UserDefinedObjectType userDefinedType, int[] originalOrder) {
final Class<?> componentClass = component.getComponentClass();
final int[] originalOrder = component.sortProperties();
final String[] structColumnNames = component.getStructColumnNames();
if ( structColumnNames == null || structColumnNames.length == 0 ) {
final int[] propertyMappingIndex;
Expand All @@ -211,23 +213,27 @@ else if ( component.getInstantiatorPropertyNames() != null ) {
else {
propertyMappingIndex = null;
}
final ArrayList<Column> orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() );
if ( propertyMappingIndex == null ) {
// If there is default ordering possible, assume alphabetical ordering
final ArrayList<Column> orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() );
final List<Property> properties = component.getProperties();
for ( Property property : properties ) {
addColumns( orderedColumns, property.getValue() );
}
userDefinedType.reorderColumns( orderedColumns );
if ( component.isPolymorphic() ) {
addColumns( orderedColumns, component.getDiscriminator() );
}
}
else {
final ArrayList<Column> orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() );
final List<Property> properties = component.getProperties();
for ( final int propertyIndex : propertyMappingIndex ) {
addColumns( orderedColumns, properties.get( propertyIndex ).getValue() );
}
userDefinedType.reorderColumns( orderedColumns );
}
final List<Column> reorderedColumn = context.getBuildingOptions()
.getColumnOrderingStrategy()
.orderUserDefinedTypeColumns( userDefinedType, context.getMetadataCollector() );
userDefinedType.reorderColumns( reorderedColumn != null ? reorderedColumn : orderedColumns );
}
else {
final ArrayList<Column> orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() );
Expand Down Expand Up @@ -281,6 +287,13 @@ else if ( structColumnName.equals( subComponent.getAggregateColumn().getName() )
}
}
}
if ( component.isPolymorphic() ) {
final Column column = component.getDiscriminator().getColumns().get( 0 );
if ( structColumnName.equals( column.getName() ) ) {
orderedColumns.add( column );
return true;
}
}
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import org.hibernate.annotations.DiscriminatorFormula;
import org.hibernate.boot.spi.MetadataBuildingContext;

import jakarta.persistence.AttributeOverride;
import jakarta.persistence.Column;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.DiscriminatorType;

Expand All @@ -25,10 +27,10 @@ public class AnnotatedDiscriminatorColumn extends AnnotatedColumn {

private String discriminatorTypeName;

public AnnotatedDiscriminatorColumn() {
public AnnotatedDiscriminatorColumn(String defaultColumnName) {
//discriminator default value
super();
setLogicalColumnName( DEFAULT_DISCRIMINATOR_COLUMN_NAME );
setLogicalColumnName( defaultColumnName );
setNullable( false );
setDiscriminatorTypeName( DEFAULT_DISCRIMINATOR_TYPE );
setLength( DEFAULT_DISCRIMINATOR_LENGTH );
Expand All @@ -45,10 +47,12 @@ public void setDiscriminatorTypeName(String discriminatorTypeName) {
public static AnnotatedDiscriminatorColumn buildDiscriminatorColumn(
DiscriminatorColumn discriminatorColumn,
DiscriminatorFormula discriminatorFormula,
Column columnOverride,
String defaultColumnName,
MetadataBuildingContext context) {
final AnnotatedColumns parent = new AnnotatedColumns();
parent.setBuildingContext( context );
final AnnotatedDiscriminatorColumn column = new AnnotatedDiscriminatorColumn();
final AnnotatedDiscriminatorColumn column = new AnnotatedDiscriminatorColumn( defaultColumnName );
final DiscriminatorType discriminatorType;
if ( discriminatorFormula != null ) {
final DiscriminatorType type = discriminatorFormula.discriminatorType();
Expand Down Expand Up @@ -76,7 +80,13 @@ else if ( discriminatorColumn != null ) {
discriminatorType = DiscriminatorType.STRING;
column.setImplicit( true );
}
setDiscriminatorType( discriminatorType, discriminatorColumn, column );
if ( columnOverride != null ) {
column.setLogicalColumnName( columnOverride.name() );
if ( !columnOverride.columnDefinition().isEmpty() ) {
column.setSqlType( columnOverride.columnDefinition() );
}
}
setDiscriminatorType( discriminatorType, discriminatorColumn, columnOverride, column );
column.setParent( parent );
column.bind();
return column;
Expand All @@ -85,6 +95,7 @@ else if ( discriminatorColumn != null ) {
private static void setDiscriminatorType(
DiscriminatorType type,
DiscriminatorColumn discriminatorColumn,
Column columnOverride,
AnnotatedDiscriminatorColumn column) {
if ( type == null ) {
column.setDiscriminatorTypeName( "string" );
Expand All @@ -102,7 +113,10 @@ private static void setDiscriminatorType(
break;
case STRING:
column.setDiscriminatorTypeName( "string" );
if ( discriminatorColumn != null ) {
if ( columnOverride != null ) {
column.setLength( (long) columnOverride.length() );
}
else if ( discriminatorColumn != null ) {
column.setLength( (long) discriminatorColumn.length() );
}
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
import jakarta.persistence.TableGenerator;
import jakarta.persistence.TableGenerators;

import static org.hibernate.boot.model.internal.AnnotatedClassType.EMBEDDABLE;
import static org.hibernate.boot.model.internal.AnnotatedClassType.ENTITY;
import static org.hibernate.boot.model.internal.FilterDefBinder.bindFilterDefs;
import static org.hibernate.boot.model.internal.GeneratorBinder.buildGenerators;
Expand Down Expand Up @@ -697,7 +698,15 @@ public static Map<XClass, InheritanceState> buildInheritanceStates(
superclassState.setHasSiblings( true );
final InheritanceState superEntityState =
getInheritanceStateOfSuperEntity( clazz, inheritanceStatePerClass );
state.setHasParents( superEntityState != null );
if ( superEntityState != null ) {
state.setHasParents( true );
if ( buildingContext.getMetadataCollector().getClassType( clazz ) == EMBEDDABLE ) {
buildingContext.getMetadataCollector().registerEmbeddableSubclass(
superEntityState.getClazz(),
clazz
);
}
}
logMixedInheritance( clazz, superclassState, state );
if ( superclassState.getType() != null ) {
state.setType( superclassState.getType() );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ public Table getTable() {

@Override
public void addProperty(Property prop, XClass declaringClass) {
component.addProperty( prop );
component.addProperty( prop, declaringClass );
}

@Override
Expand Down
Loading

0 comments on commit 4766753

Please sign in to comment.