Skip to content

Commit 9537995

Browse files
committed
HHH-4396 - Ability to patternize embedded column names
1 parent bddd49c commit 9537995

File tree

7 files changed

+144
-3
lines changed

7 files changed

+144
-3
lines changed

documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,68 @@ include::{extrasdir}/embeddable/embeddable-type-override-mapping-example.sql[]
134134
----
135135
====
136136

137+
[[embeddable-column-naming]]
138+
==== @EmbeddedColumnNaming
139+
140+
The most common use case for `@AttributeOverride` in relation to an embeddable is to rename the associated columns.
141+
142+
Consider a typical embeddable mapping -
143+
144+
[[embeddable-column-naming-example-model]]
145+
.Typical embeddable mapping
146+
====
147+
[source,java]
148+
----
149+
@Embeddable
150+
class Address {
151+
String street;
152+
String city;
153+
// ...
154+
}
155+
156+
@Entity
157+
class Person {
158+
// ...
159+
@Embedded
160+
Address homeAddress;
161+
@Embedded
162+
Address workAddress;
163+
}
164+
----
165+
====
166+
167+
In strict Jakarta Persistence sense, this will lead to a bootstrapping error because
168+
Jakarta Persistence requires that the implicit names for all of the columns for these
169+
both of the embedded `Address` mappings to be based on the attribute names from the embeddable -
170+
here, `street`, `city`, etc. However, that will lead to duplicate column names here.
171+
172+
The strict compliance way to accomplish this would be a tedious use of the `@AttributeOverride` annotation
173+
as <<embeddable-override,discussed previously>>.
174+
175+
Since this is such a common pattern, Hibernate offers a much simpler solution through its `@EmbeddedColumnNaming` annotation
176+
which allows to "patternize" the column naming -
177+
178+
[[embeddable-column-naming-example-basic]]
179+
.@EmbeddedColumnNaming example
180+
====
181+
[source,java]
182+
----
183+
@Entity
184+
class Person {
185+
// ...
186+
@Embedded
187+
@EmbeddedColumnNaming("home_%s")
188+
Address homeAddress;
189+
@Embedded
190+
@EmbeddedColumnNaming("work_%s")
191+
Address workAddress;
192+
}
193+
----
194+
====
195+
196+
Here we'd end up with implicit column names `home_street`, `home_city`, `work_street`, `work_city`, etc.
197+
198+
137199

138200
[[embeddable-collections]]
139201
==== Collections of embeddable types

documentation/src/main/asciidoc/userguide/chapters/domain/naming.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ to specify the ImplicitNamingStrategy to use. See
6565
<<chapters/bootstrap/Bootstrap.adoc#bootstrap,Bootstrap>> for additional details on bootstrapping.
6666

6767

68+
[NOTE]
69+
.@EmbeddedColumnNaming
70+
====
71+
A related topic is the use of `@EmbeddedColumnNaming` to help with the implicit naming of columns associated with mapping an embeddable.
72+
See <<bnb,the disucussion>>
73+
====
6874

6975
[[PhysicalNamingStrategy]]
7076
==== PhysicalNamingStrategy

hibernate-core/src/main/java/org/hibernate/annotations/EmbeddedColumnNaming.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
/**
3131
* The naming pattern. It is expected to contain a single pattern marker ({@code %})
3232
* into which the "raw" column name will be injected.
33+
* <p/>
34+
* The {@code value} may be omitted which will indicate to use the pattern
35+
* {@code "{ATTRIBUTE_NAME}_%s"} where {@code {ATTRIBUTE_NAME}} is the name of the attribute
36+
* where the annotation is placed.
3337
*/
34-
String value();
38+
String value() default "";
3539
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.hibernate.boot.spi.PropertyData;
3333
import org.hibernate.internal.CoreMessageLogger;
3434
import org.hibernate.internal.util.MutableInteger;
35+
import org.hibernate.internal.util.NullnessHelper;
3536
import org.hibernate.internal.util.StringHelper;
3637
import org.hibernate.mapping.BasicValue;
3738
import org.hibernate.mapping.Component;
@@ -1019,7 +1020,11 @@ private static void applyColumnNamingPattern(Component component, PropertyData i
10191020
return;
10201021
}
10211022

1022-
final String columnNamingPattern = columnNaming.value();
1023+
final String columnNamingPattern = NullnessHelper.coalesce(
1024+
columnNaming.value(),
1025+
inferredData.getPropertyName() + "_%s"
1026+
);
1027+
10231028
final int markerCount = StringHelper.count( columnNamingPattern, '%' );
10241029
if ( markerCount != 1 ) {
10251030
throw new MappingException( String.format(

hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/EmbeddedColumnNamingAnnotation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-License-Identifier: Apache-2.0
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
33
* Copyright Red Hat Inc. and Hibernate Authors
44
*/
55
package org.hibernate.boot.models.annotations.internal;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.embeddable;
6+
7+
import jakarta.persistence.Embeddable;
8+
import jakarta.persistence.Embedded;
9+
import jakarta.persistence.Entity;
10+
import jakarta.persistence.Id;
11+
import jakarta.persistence.Table;
12+
import org.hibernate.annotations.EmbeddedColumnNaming;
13+
import org.hibernate.engine.spi.SessionFactoryImplementor;
14+
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
15+
import org.hibernate.persister.entity.EntityPersister;
16+
import org.hibernate.testing.orm.junit.DomainModel;
17+
import org.hibernate.testing.orm.junit.SessionFactory;
18+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
19+
import org.junit.jupiter.api.Test;
20+
21+
import static org.hibernate.orm.test.embeddable.EmbeddedColumnNamingTests.verifyColumnNames;
22+
23+
/**
24+
* @author Steve Ebersole
25+
*/
26+
@SuppressWarnings("JUnitMalformedDeclaration")
27+
public class EmbeddedColumnNamingImplicitTests {
28+
@Test
29+
@DomainModel( annotatedClasses = {Person.class, Address.class} )
30+
@SessionFactory(exportSchema = false)
31+
void testNaming(SessionFactoryScope factoryScope) {
32+
final SessionFactoryImplementor sessionFactory = factoryScope.getSessionFactory();
33+
final MappingMetamodelImplementor mappingMetamodel = sessionFactory.getMappingMetamodel();
34+
final EntityPersister persister = mappingMetamodel.getEntityDescriptor( Person.class );
35+
verifyColumnNames( persister.findAttributeMapping( "homeAddress" ), "homeAddress_" );
36+
verifyColumnNames( persister.findAttributeMapping( "workAddress" ), "workAddress_" );
37+
}
38+
39+
@Entity(name="Person")
40+
@Table(name="person")
41+
public static class Person {
42+
@Id
43+
private Integer id;
44+
private String name;
45+
46+
@Embedded
47+
@EmbeddedColumnNaming
48+
private Address homeAddress;
49+
50+
@Embedded
51+
@EmbeddedColumnNaming
52+
private Address workAddress;
53+
}
54+
55+
@Embeddable
56+
public static class Address {
57+
private String street;
58+
private String city;
59+
private String state;
60+
private String zip;
61+
}
62+
}

hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/EmbeddedColumnNamingMixedTests.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import static org.junit.jupiter.api.Assertions.fail;
2828

2929
/**
30+
* Tests how {@code @EmbeddedColumnNaming} and {@code @Column} work together.
31+
*
3032
* @author Steve Ebersole
3133
*/
3234
@SuppressWarnings("JUnitMalformedDeclaration")

0 commit comments

Comments
 (0)