Skip to content

Commit 5febfc5

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

File tree

8 files changed

+90
-50
lines changed

8 files changed

+90
-50
lines changed

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

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ Since this is such a common pattern, Hibernate offers a much simpler solution th
176176
which allows to "patternize" the column naming -
177177

178178
[[embeddable-column-naming-example-basic]]
179-
.@EmbeddedColumnNaming example
179+
.Simple `@EmbeddedColumnNaming` example
180180
====
181181
[source,java]
182182
----
@@ -193,8 +193,47 @@ class Person {
193193
----
194194
====
195195

196-
Here we'd end up with implicit column names `home_street`, `home_city`, `work_street`, `work_city`, etc.
196+
This mapping produces implicit column names `home_street`, `home_city`, `work_street`, `work_city`, etc.
197197

198+
`@EmbeddedColumnNaming` also works in nested usages and plays nicely with explicit column names.
199+
200+
201+
[[embeddable-column-naming-example-advanced]]
202+
.More advanced `@EmbeddedColumnNaming` example
203+
====
204+
[source,java]
205+
----
206+
@Embeddable
207+
class Address {
208+
String street;
209+
String city;
210+
@Embedded
211+
private ZipPlus zip;
212+
// ...
213+
}
214+
215+
@Embeddable
216+
public static class ZipPlus {
217+
@Column(name="zip_code")
218+
private String code;
219+
@Column(name="zip_plus4")
220+
private String plus4;
221+
}
222+
223+
@Entity
224+
class Person {
225+
// ...
226+
@Embedded
227+
@EmbeddedColumnNaming("home_%s")
228+
Address homeAddress;
229+
@Embedded
230+
@EmbeddedColumnNaming("work_%s")
231+
Address workAddress;
232+
}
233+
----
234+
====
235+
236+
This will produce implicit column names `home_street`, `home_city`, `home_zip_code`, `zip_plus4`, ...
198237

199238

200239
[[embeddable-collections]]

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

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -342,33 +342,38 @@ public boolean isNameDeferred() {
342342
}
343343

344344
public void redefineColumnName(String columnName, String propertyName, boolean applyNamingStrategy) {
345-
if ( isNotEmpty( columnName ) ) {
346-
mappingColumn.setName( processColumnName( columnName, applyNamingStrategy ) );
345+
if ( StringHelper.isEmpty( columnName ) && StringHelper.isEmpty( propertyName ) ) {
346+
// nothing to do
347+
return;
348+
}
349+
final String logicalColumnName = resolveLogicalColumnName( columnName, propertyName );
350+
mappingColumn.setName( processColumnName( logicalColumnName, applyNamingStrategy ) );
351+
}
352+
353+
private String resolveLogicalColumnName(String columnName, String propertyName) {
354+
final String baseColumnName = StringHelper.isNotEmpty( columnName )
355+
? columnName
356+
: inferColumnName( propertyName );
357+
358+
if ( parent.getPropertyHolder() != null && parent.getPropertyHolder().isComponent() ) {
359+
// see if we need to apply one-or-more @EmbeddedColumnNaming patterns
360+
return applyEmbeddedColumnNaming( baseColumnName, (ComponentPropertyHolder) parent.getPropertyHolder() );
347361
}
348362
else {
349-
if ( propertyName != null && applyNamingStrategy ) {
350-
// the inferred name would be, e.g., `city`
351-
final String inferredColumnName = inferColumnName( propertyName );
352-
String realColumnName = inferredColumnName;
353-
if ( parent.getPropertyHolder().isComponent() ) {
354-
realColumnName = applyEmbeddedColumnNaming( realColumnName, (ComponentPropertyHolder) parent.getPropertyHolder() );
355-
// we need to adjust the logical name to be picked up in `#addColumnBinding`
356-
logicalColumnName = realColumnName;
357-
}
358-
mappingColumn.setName( realColumnName );
359-
}
360-
//Do nothing otherwise
363+
return baseColumnName;
361364
}
362365
}
363366

364367
private String applyEmbeddedColumnNaming(String inferredColumnName, ComponentPropertyHolder propertyHolder) {
365368
// code
366369
String result = inferredColumnName;
370+
boolean appliedAnyPatterns = false;
367371

368372
final String columnNamingPattern = propertyHolder.getComponent().getColumnNamingPattern();
369373
if ( StringHelper.isNotEmpty( columnNamingPattern ) ) {
370374
// zip_code
371375
result = String.format( columnNamingPattern, result );
376+
appliedAnyPatterns = true;
372377
}
373378

374379
ComponentPropertyHolder tester = propertyHolder;
@@ -378,14 +383,20 @@ private String applyEmbeddedColumnNaming(String inferredColumnName, ComponentPro
378383
if ( StringHelper.isNotEmpty( parentColumnNamingPattern ) ) {
379384
// home_zip_code
380385
result = String.format( parentColumnNamingPattern, result );
386+
appliedAnyPatterns = true;
381387
}
382388
tester = parentHolder;
383389
}
384390

391+
if ( appliedAnyPatterns ) {
392+
// we need to adjust the logical name to be picked up in `#addColumnBinding`
393+
this.logicalColumnName = result;
394+
}
395+
385396
return result;
386397
}
387398

388-
private String processColumnName(String columnName, boolean applyNamingStrategy) {
399+
protected String processColumnName(String columnName, boolean applyNamingStrategy) {
389400
if ( applyNamingStrategy ) {
390401
final Database database = getBuildingContext().getMetadataCollector().getDatabase();
391402
return getBuildingContext().getBuildingOptions().getPhysicalNamingStrategy()
@@ -398,7 +409,7 @@ private String processColumnName(String columnName, boolean applyNamingStrategy)
398409

399410
}
400411

401-
private String inferColumnName(String propertyName) {
412+
protected String inferColumnName(String propertyName) {
402413
final Database database = getBuildingContext().getMetadataCollector().getDatabase();
403414
final ObjectNameNormalizer normalizer = getBuildingContext().getObjectNameNormalizer();
404415
final ImplicitNamingStrategy implicitNamingStrategy = getBuildingContext().getBuildingOptions().getImplicitNamingStrategy();

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.hibernate.boot.spi.InFlightMetadataCollector;
1414
import org.hibernate.boot.spi.MetadataBuildingContext;
1515
import org.hibernate.boot.spi.PropertyData;
16+
import org.hibernate.internal.util.StringHelper;
1617
import org.hibernate.mapping.Column;
1718
import org.hibernate.mapping.PersistentClass;
1819
import org.hibernate.mapping.SimpleValue;
@@ -452,7 +453,9 @@ public void overrideFromReferencedColumnIfNecessary(Column column) {
452453

453454
@Override
454455
public void redefineColumnName(String columnName, String propertyName, boolean applyNamingStrategy) {
455-
super.redefineColumnName( columnName, null, applyNamingStrategy );
456+
if ( StringHelper.isNotEmpty( columnName ) ) {
457+
getMappingColumn().setName( processColumnName( columnName, applyNamingStrategy ) );
458+
}
456459
}
457460

458461
static AnnotatedJoinColumn buildImplicitJoinTableJoinColumn(
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* SPDX-License-Identifier: LGPL-2.1-or-later
33
* Copyright Red Hat Inc. and Hibernate Authors
44
*/
5-
package org.hibernate.orm.test.embeddable;
5+
package org.hibernate.orm.test.embeddable.naming;
66

77
import jakarta.persistence.Embeddable;
88
import jakarta.persistence.Embedded;
@@ -18,7 +18,7 @@
1818
import org.hibernate.testing.orm.junit.SessionFactoryScope;
1919
import org.junit.jupiter.api.Test;
2020

21-
import static org.hibernate.orm.test.embeddable.EmbeddedColumnNamingTests.verifyColumnNames;
21+
import static org.hibernate.orm.test.embeddable.naming.EmbeddedColumnNamingTests.verifyColumnNames;
2222

2323
/**
2424
* @author Steve Ebersole
Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,25 @@
22
* SPDX-License-Identifier: LGPL-2.1-or-later
33
* Copyright Red Hat Inc. and Hibernate Authors
44
*/
5-
package org.hibernate.orm.test.embeddable;
5+
package org.hibernate.orm.test.embeddable.naming;
66

77
import jakarta.persistence.Column;
88
import jakarta.persistence.Embeddable;
99
import jakarta.persistence.Embedded;
1010
import jakarta.persistence.Entity;
1111
import jakarta.persistence.Id;
1212
import jakarta.persistence.Table;
13-
import org.hibernate.MappingException;
1413
import org.hibernate.annotations.EmbeddedColumnNaming;
1514
import org.hibernate.engine.spi.SessionFactoryImplementor;
1615
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
1716
import org.hibernate.persister.entity.EntityPersister;
1817
import org.hibernate.testing.orm.junit.DomainModel;
19-
import org.hibernate.testing.orm.junit.NotImplementedYet;
2018
import org.hibernate.testing.orm.junit.ServiceRegistry;
2119
import org.hibernate.testing.orm.junit.SessionFactory;
2220
import org.hibernate.testing.orm.junit.SessionFactoryScope;
2321
import org.junit.jupiter.api.Test;
2422

25-
import static org.assertj.core.api.Assertions.assertThat;
26-
import static org.hibernate.orm.test.embeddable.EmbeddedColumnNamingTests.verifyColumnNames;
27-
import static org.junit.jupiter.api.Assertions.fail;
23+
import static org.hibernate.orm.test.embeddable.naming.EmbeddedColumnNamingTests.verifyColumnNames;
2824

2925
/**
3026
* Tests how {@code @EmbeddedColumnNaming} and {@code @Column} work together.
@@ -42,26 +38,6 @@ public class EmbeddedColumnNamingMixedTests {
4238
@ServiceRegistry
4339
@DomainModel(annotatedClasses = {Address.class, Person.class})
4440
@SessionFactory
45-
void smokeTest(SessionFactoryScope sfScope) {
46-
// atm this mixing is not supported / implemented - tbd
47-
try (SessionFactoryImplementor sessionFactory = sfScope.getSessionFactory()) {
48-
fail( "Expecting an exception" );
49-
}
50-
catch (RuntimeException e) {
51-
assertThat( e ).hasCauseExactlyInstanceOf( MappingException.class );
52-
assertThat( e ).cause().hasMessageStartingWith( "Column 'zip_code' is duplicated in mapping for entity" );
53-
}
54-
}
55-
56-
/**
57-
* Test use of {@code @EmbeddedColumnNaming} one 2 separate embedded attributes with
58-
* a nested embeddable using explicit column names
59-
*/
60-
@Test
61-
@ServiceRegistry
62-
@DomainModel(annotatedClasses = {Address.class, Person.class})
63-
@SessionFactory
64-
@NotImplementedYet( reason = "this mixing is not supported / implemented - tbd" )
6541
void testMixedNamingPattern(SessionFactoryScope sfScope) {
6642
final SessionFactoryImplementor sessionFactory = sfScope.getSessionFactory();
6743
final MappingMetamodelImplementor mappingMetamodel = sessionFactory.getMappingMetamodel();
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* SPDX-License-Identifier: LGPL-2.1-or-later
33
* Copyright Red Hat Inc. and Hibernate Authors
44
*/
5-
package org.hibernate.orm.test.embeddable;
5+
package org.hibernate.orm.test.embeddable.naming;
66

77
import jakarta.persistence.Embeddable;
88
import jakarta.persistence.Embedded;
@@ -20,7 +20,7 @@
2020
import org.hibernate.testing.orm.junit.SessionFactoryScope;
2121
import org.junit.jupiter.api.Test;
2222

23-
import static org.hibernate.orm.test.embeddable.EmbeddedColumnNamingTests.verifyColumnNames;
23+
import static org.hibernate.orm.test.embeddable.naming.EmbeddedColumnNamingTests.verifyColumnNames;
2424

2525
/**
2626
* @author Steve Ebersole

hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/EmbeddedColumnNamingTests.java renamed to hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/naming/EmbeddedColumnNamingTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* SPDX-License-Identifier: LGPL-2.1-or-later
33
* Copyright Red Hat Inc. and Hibernate Authors
44
*/
5-
package org.hibernate.orm.test.embeddable;
5+
package org.hibernate.orm.test.embeddable.naming;
66

77
import org.hibernate.MappingException;
88
import org.hibernate.annotations.EmbeddedColumnNaming;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
6+
/**
7+
* Tests for {@linkplain org.hibernate.annotations.EmbeddedColumnNaming @EmbeddedColumnNaming}
8+
*
9+
* @author Steve Ebersole
10+
*/
11+
package org.hibernate.orm.test.embeddable.naming;

0 commit comments

Comments
 (0)