Skip to content

Commit a5dc413

Browse files
committed
HHH-19257 - Introduce @EmbeddedTable
1 parent b7bd345 commit a5dc413

File tree

12 files changed

+570
-7
lines changed

12 files changed

+570
-7
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.annotations;
6+
7+
import org.hibernate.Incubating;
8+
9+
import java.lang.annotation.Target;
10+
import java.lang.annotation.Retention;
11+
12+
import static java.lang.annotation.ElementType.FIELD;
13+
import static java.lang.annotation.ElementType.METHOD;
14+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
15+
16+
/**
17+
* An easier mechanism to declare the table to which an embedded value
18+
* maps compared to the Jakarta Persistence compliant mechanism requiring
19+
* multiple {@link jakarta.persistence.AttributeOverride}
20+
* and {@link jakarta.persistence.AssociationOverride} annotations.
21+
* <pre>
22+
* &#64;Entity
23+
* &#64;Table(name="primary")
24+
* &#64;SecondaryTable(name="secondary")
25+
* class Person {
26+
* ...
27+
* &#64;Embedded
28+
* &#64;EmbeddedTable("secondary")
29+
* Address address;
30+
* }
31+
* </pre>
32+
*
33+
* @apiNote Only supported for an embedded declared on an entity or mapped-superclass; all other (mis)uses
34+
* will lead to a {@linkplain org.hibernate.boot.models.AnnotationPlacementException}.
35+
*
36+
* @see EmbeddedColumnNaming
37+
*
38+
* @since 7.2
39+
* @author Steve Ebersole
40+
*/
41+
@Target({METHOD, FIELD})
42+
@Retention(RUNTIME)
43+
@Incubating
44+
public @interface EmbeddedTable {
45+
/**
46+
* The name of the table in which the embedded value is stored.
47+
*/
48+
String value();
49+
}

hibernate-core/src/main/java/org/hibernate/boot/model/internal/AbstractPropertyHolder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
5353

5454
private final String path;
5555
protected final AbstractPropertyHolder parent;
56-
private final MetadataBuildingContext context;
56+
protected final MetadataBuildingContext context;
5757

5858
private Boolean isInIdClass;
5959

hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.hibernate.MappingException;
2121
import org.hibernate.annotations.*;
2222
import org.hibernate.boot.model.IdentifierGeneratorDefinition;
23+
import org.hibernate.boot.models.AnnotationPlacementException;
2324
import org.hibernate.boot.models.JpaAnnotations;
2425
import org.hibernate.boot.models.annotations.internal.MapKeyColumnJpaAnnotation;
2526
import org.hibernate.boot.spi.AccessType;
@@ -1052,10 +1053,17 @@ private void setDeclaringClass(ClassDetails declaringClass) {
10521053
}
10531054

10541055
private void bind() {
1056+
if ( property != null ) {
1057+
final EmbeddedTable misplaced = property.getDirectAnnotationUsage( EmbeddedTable.class );
1058+
if ( misplaced != null ) {
1059+
// not allowed
1060+
throw new AnnotationPlacementException( "@EmbeddedTable only supported for use on entity or mapped-superclass" );
1061+
}
1062+
}
10551063
collection = createCollection( propertyHolder.getPersistentClass() );
10561064
final String role = qualify( propertyHolder.getPath(), propertyName );
10571065
if ( BOOT_LOGGER.isTraceEnabled() ) {
1058-
BOOT_LOGGER.bindingCollectionRole( role );
1066+
BOOT_LOGGER.bindingCollectionRole( role );
10591067
}
10601068
collection.setRole( role );
10611069
collection.setMappedByProperty( mappedBy );

hibernate-core/src/main/java/org/hibernate/boot/model/internal/ComponentPropertyHolder.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

1010
import org.checkerframework.checker.nullness.qual.Nullable;
1111
import org.hibernate.AnnotationException;
12+
import org.hibernate.annotations.EmbeddedTable;
13+
import org.hibernate.boot.model.naming.Identifier;
14+
import org.hibernate.boot.models.AnnotationPlacementException;
15+
import org.hibernate.boot.spi.InFlightMetadataCollector;
1216
import org.hibernate.boot.spi.MetadataBuildingContext;
1317
import org.hibernate.boot.spi.PropertyData;
1418
import org.hibernate.mapping.AggregateColumn;
@@ -83,6 +87,8 @@ public ComponentPropertyHolder(
8387
this.component = component;
8488
this.inheritanceStatePerClass = inheritanceStatePerClass;
8589

90+
applyExplicitTableName( component, inferredData, parent, context );
91+
8692
isOrWithinEmbeddedId = parent.isOrWithinEmbeddedId()
8793
|| embeddedMemberDetails != null && hasIdAnnotation( embeddedMemberDetails );
8894
isWithinElementCollection = parent.isWithinElementCollection()
@@ -98,6 +104,61 @@ public ComponentPropertyHolder(
98104
}
99105
}
100106

107+
/**
108+
* Apply the explicit {@link EmbeddedTable} if there is one and if its
109+
* appropriate for the context (the type of {@code container}).
110+
*
111+
* @param component The (in-flight) component mapping details.
112+
* @param propertyData Details about the property defining this component.
113+
* @param container The container for this component.
114+
*/
115+
public static void applyExplicitTableName(
116+
Component component,
117+
PropertyData propertyData,
118+
PropertyHolder container,
119+
MetadataBuildingContext buildingContext) {
120+
Table tableToUse = container.getTable();
121+
boolean wasExplicit = false;
122+
if ( container instanceof ComponentPropertyHolder componentPropertyHolder ) {
123+
wasExplicit = componentPropertyHolder.getComponent().wasTableExplicitlyDefined();
124+
}
125+
126+
if ( propertyData.getAttributeMember() != null ) {
127+
final EmbeddedTable embeddedTableAnn = propertyData.getAttributeMember()
128+
.getDirectAnnotationUsage( EmbeddedTable.class );
129+
// we only allow this when done for an embedded on an entity or mapped-superclass
130+
if ( container instanceof ClassPropertyHolder ) {
131+
if ( embeddedTableAnn != null ) {
132+
final Identifier tableNameIdentifier = buildingContext.getObjectNameNormalizer().normalizeIdentifierQuoting( embeddedTableAnn.value() );
133+
final InFlightMetadataCollector.EntityTableXref entityTableXref = buildingContext
134+
.getMetadataCollector()
135+
.getEntityTableXref( container.getEntityName() );
136+
tableToUse = entityTableXref.resolveTable( tableNameIdentifier );
137+
wasExplicit = true;
138+
}
139+
}
140+
else {
141+
if ( embeddedTableAnn != null ) {
142+
// not allowed
143+
throw new AnnotationPlacementException( "@EmbeddedTable only supported for use on entity or mapped-superclass" );
144+
}
145+
}
146+
}
147+
if ( propertyData.getAttributeMember() != null && container instanceof ClassPropertyHolder ) {
148+
final EmbeddedTable embeddedTableAnn = propertyData.getAttributeMember().getDirectAnnotationUsage( EmbeddedTable.class );
149+
if ( embeddedTableAnn != null ) {
150+
final Identifier tableNameIdentifier = buildingContext.getObjectNameNormalizer().normalizeIdentifierQuoting( embeddedTableAnn.value() );
151+
final InFlightMetadataCollector.EntityTableXref entityTableXref = buildingContext
152+
.getMetadataCollector()
153+
.getEntityTableXref( container.getEntityName() );
154+
tableToUse = entityTableXref.resolveTable( tableNameIdentifier );
155+
wasExplicit = true;
156+
}
157+
}
158+
159+
component.setTable( tableToUse, wasExplicit );
160+
}
161+
101162
/**
102163
* Access to the underlying component
103164
*/

hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -951,16 +951,16 @@ private static String canonicalize(String typeName) {
951951
static Component createEmbeddable(
952952
PropertyHolder propertyHolder,
953953
PropertyData inferredData,
954-
boolean isComponentEmbedded,
954+
boolean isNonAggregated,
955955
boolean isIdentifierMapper,
956956
Class<? extends EmbeddableInstantiator> customInstantiatorImpl,
957957
MetadataBuildingContext context) {
958958
final var embeddable = new Component( context, propertyHolder.getPersistentClass() );
959-
embeddable.setEmbedded( isComponentEmbedded );
960-
//yuk
961-
embeddable.setTable( propertyHolder.getTable() );
959+
embeddable.setEmbedded( isNonAggregated );
960+
ComponentPropertyHolder.applyExplicitTableName( embeddable, inferredData, propertyHolder, context );
961+
962962
if ( isIdentifierMapper
963-
|| isComponentEmbedded && inferredData.getPropertyName() == null ) {
963+
|| isNonAggregated && inferredData.getPropertyName() == null ) {
964964
embeddable.setComponentClassName( embeddable.getOwner().getClassName() );
965965
}
966966
else {

hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,13 @@ private Property makePropertyAndValue() {
269269
basicValueBinder.setReferencedEntityName( referencedEntityName );
270270
basicValueBinder.setAccessType( accessType );
271271

272+
if ( holder instanceof ComponentPropertyHolder embeddableTypedContainer ) {
273+
final Component component = embeddableTypedContainer.getComponent();
274+
if ( component.wasTableExplicitlyDefined() ) {
275+
basicValueBinder.setTable( component.getTable() );
276+
}
277+
}
278+
272279
value = basicValueBinder.make();
273280

274281
return makeProperty();

hibernate-core/src/main/java/org/hibernate/boot/models/HibernateAnnotations.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,10 @@ public interface HibernateAnnotations {
248248
EmbeddedColumnNaming.class,
249249
EmbeddedColumnNamingAnnotation.class
250250
);
251+
OrmAnnotationDescriptor<EmbeddedTable,EmbeddedTableAnnotation> EMBEDDED_TABLE = new OrmAnnotationDescriptor<>(
252+
EmbeddedTable.class,
253+
EmbeddedTableAnnotation.class
254+
);
251255
OrmAnnotationDescriptor<Fetch,FetchAnnotation> FETCH = new OrmAnnotationDescriptor<>(
252256
Fetch.class,
253257
FetchAnnotation.class
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.boot.models.annotations.internal;
6+
7+
import org.hibernate.annotations.EmbeddedTable;
8+
import org.hibernate.models.spi.ModelsContext;
9+
10+
import java.lang.annotation.Annotation;
11+
import java.util.Map;
12+
13+
/**
14+
* @author Steve Ebersole
15+
*/
16+
@SuppressWarnings({ "ClassExplicitlyAnnotation", "unused" })
17+
public class EmbeddedTableAnnotation implements EmbeddedTable {
18+
private String value;
19+
20+
/**
21+
* Used in creating dynamic annotation instances (e.g. from XML)
22+
*/
23+
public EmbeddedTableAnnotation(ModelsContext modelContext) {
24+
}
25+
26+
/**
27+
* Used in creating annotation instances from JDK variant
28+
*/
29+
public EmbeddedTableAnnotation(
30+
EmbeddedTable annotation,
31+
ModelsContext modelContext) {
32+
this.value = annotation.value();
33+
}
34+
35+
/**
36+
* Used in creating annotation instances from Jandex variant
37+
*/
38+
public EmbeddedTableAnnotation(
39+
Map<String, Object> attributeValues,
40+
ModelsContext modelContext) {
41+
this.value = (String) attributeValues.get( "value" );
42+
}
43+
44+
@Override
45+
public Class<? extends Annotation> annotationType() {
46+
return EmbeddedTable.class;
47+
}
48+
49+
@Override
50+
public String value() {
51+
return value;
52+
}
53+
54+
public void value(String value) {
55+
this.value = value;
56+
}
57+
}

hibernate-core/src/main/java/org/hibernate/mapping/Component.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ public class Component extends SimpleValue implements AttributeContainer, MetaAt
100100
private transient Boolean simpleRecord;
101101
private String columnNamingPattern;
102102

103+
private boolean tableWasExplicit;
104+
103105
public Component(MetadataBuildingContext metadata, PersistentClass owner) throws MappingException {
104106
this( metadata, owner.getTable(), owner );
105107
}
@@ -158,6 +160,23 @@ public List<Property> getProperties() {
158160
return properties;
159161
}
160162

163+
public void setTable(Table table) {
164+
if ( !tableWasExplicit ) {
165+
super.setTable( table );
166+
}
167+
168+
// otherwise, ignore it...
169+
}
170+
171+
public void setTable(Table table, boolean wasExplicit) {
172+
super.setTable( table );
173+
tableWasExplicit = wasExplicit;
174+
}
175+
176+
public boolean wasTableExplicitlyDefined() {
177+
return tableWasExplicit;
178+
}
179+
161180
public void addProperty(Property p, ClassDetails declaringClass) {
162181
properties.add( p );
163182
if ( isPolymorphic() && declaringClass != null ) {

0 commit comments

Comments
 (0)