Skip to content

Commit e1370d7

Browse files
committed
HHH-19542: Embeddable property order for SecondaryTable is no longer causing issue
1 parent f0792cc commit e1370d7

File tree

6 files changed

+194
-21
lines changed

6 files changed

+194
-21
lines changed

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

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44
*/
55
package org.hibernate.boot.model.internal;
66

7+
import java.util.ArrayList;
78
import java.util.HashMap;
9+
import java.util.List;
810
import java.util.Map;
11+
import java.util.Objects;
912

1013
import org.hibernate.AnnotationException;
1114
import org.hibernate.boot.spi.MetadataBuildingContext;
1215
import org.hibernate.boot.spi.PropertyData;
16+
import org.hibernate.internal.util.StringHelper;
1317
import org.hibernate.mapping.AggregateColumn;
1418
import org.hibernate.mapping.Component;
1519
import org.hibernate.mapping.Join;
@@ -68,6 +72,7 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder {
6872

6973
private final String embeddedAttributeName;
7074
private final Map<String,AttributeConversionInfo> attributeConversionInfoMap;
75+
private final List<AnnotatedColumn> annotatedColumns;
7176

7277
public ComponentPropertyHolder(
7378
Component component,
@@ -94,6 +99,12 @@ public ComponentPropertyHolder(
9499
this.embeddedAttributeName = "";
95100
this.attributeConversionInfoMap = processAttributeConversions( inferredData.getClassOrElementType() );
96101
}
102+
103+
if ( parent instanceof ComponentPropertyHolder parentHolder ) {
104+
this.annotatedColumns = parentHolder.annotatedColumns;
105+
} else {
106+
this.annotatedColumns = new ArrayList<>();
107+
}
97108
}
98109

99110
/**
@@ -221,26 +232,45 @@ public String getEntityName() {
221232

222233
@Override
223234
public void addProperty(Property property, MemberDetails attributeMemberDetails, AnnotatedColumns columns, ClassDetails declaringClass) {
224-
//Ejb3Column.checkPropertyConsistency( ); //already called earlier
235+
//AnnotatedColumns.checkPropertyConsistency( ); //already called earlier
225236
// Check table matches between the component and the columns
226237
// if not, change the component table if no properties are set
227238
// if a property is set already the core cannot support that
239+
final Table table = property.getValue().getTable();
240+
if ( !table.equals( getTable() ) ) {
241+
if ( component.getPropertySpan() == 0 ) {
242+
component.setTable( table );
243+
}
244+
else {
245+
throw new AnnotationException(
246+
"Embeddable class '" + component.getComponentClassName()
247+
+ "' has properties mapped to two different tables"
248+
+ " (all properties of the embeddable class must map to the same table)"
249+
);
250+
}
251+
}
228252
if ( columns != null ) {
229-
final Table table = columns.getTable();
230-
if ( !table.equals( getTable() ) ) {
231-
if ( component.getPropertySpan() == 0 ) {
232-
component.setTable( table );
233-
}
234-
else {
253+
annotatedColumns.addAll( columns.getColumns() );
254+
}
255+
addProperty( property, attributeMemberDetails, declaringClass );
256+
}
257+
258+
public void checkPropertyConsistency() {
259+
if ( annotatedColumns.size() > 1 ) {
260+
for ( int currentIndex = 1; currentIndex < annotatedColumns.size(); currentIndex++ ) {
261+
final AnnotatedColumn current = annotatedColumns.get( currentIndex );
262+
final AnnotatedColumn previous = annotatedColumns.get( currentIndex - 1 );
263+
if ( !Objects.equals(
264+
StringHelper.nullIfEmpty( current.getExplicitTableName() ),
265+
StringHelper.nullIfEmpty( previous.getExplicitTableName() ) ) ) {
235266
throw new AnnotationException(
236267
"Embeddable class '" + component.getComponentClassName()
237-
+ "' has properties mapped to two different tables"
238-
+ " (all properties of the embeddable class must map to the same table)"
268+
+ "' has properties mapped to two different tables"
269+
+ " (all properties of the embeddable class must map to the same table)"
239270
);
240271
}
241272
}
242273
}
243-
addProperty( property, attributeMemberDetails, declaringClass );
244274
}
245275

246276
@Override

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -352,15 +352,15 @@ private static PropertyBinder createEmbeddedProperty(
352352
final PropertyBinder binder = new PropertyBinder();
353353
binder.setDeclaringClass( inferredData.getDeclaringClass() );
354354
binder.setName( inferredData.getPropertyName() );
355-
binder.setValue(component);
355+
binder.setValue( component );
356356
binder.setMemberDetails( inferredData.getAttributeMember() );
357357
binder.setAccessType( inferredData.getDefaultAccess() );
358-
binder.setEmbedded(isComponentEmbedded);
359-
binder.setHolder(propertyHolder);
360-
binder.setId(isId);
361-
binder.setEntityBinder(entityBinder);
362-
binder.setInheritanceStatePerClass(inheritanceStatePerClass);
363-
binder.setBuildingContext(context);
358+
binder.setEmbedded( isComponentEmbedded );
359+
binder.setHolder( propertyHolder );
360+
binder.setId( isId );
361+
binder.setEntityBinder( entityBinder );
362+
binder.setInheritanceStatePerClass( inheritanceStatePerClass );
363+
binder.setBuildingContext( context );
364364
binder.makePropertyAndBind();
365365
return binder;
366366
}
@@ -455,7 +455,7 @@ static Component fillEmbeddable(
455455
if ( LOG.isDebugEnabled() ) {
456456
LOG.debug( "Binding component with path: " + subpath );
457457
}
458-
final PropertyHolder subholder = buildPropertyHolder(
458+
final ComponentPropertyHolder subholder = buildPropertyHolder(
459459
component,
460460
subpath,
461461
inferredData,
@@ -579,6 +579,8 @@ else if ( member.hasDirectAnnotationUsage( GeneratedValue.class ) ) {
579579
}
580580
}
581581

582+
subholder.checkPropertyConsistency();
583+
582584
if ( compositeUserType != null ) {
583585
processCompositeUserType( component, compositeUserType );
584586
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public static PropertyHolder buildPropertyHolder(
4747
*
4848
* @return PropertyHolder
4949
*/
50-
public static PropertyHolder buildPropertyHolder(
50+
public static ComponentPropertyHolder buildPropertyHolder(
5151
Component component,
5252
String path,
5353
PropertyData inferredData,

hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/binding/annotations/embedded/EmbeddableA.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class EmbeddableA {
2020
@AttributeOverrides({@AttributeOverride(name = "embedAttrB" , column = @Column(table = "TableB"))})
2121
private EmbeddableB embedB;
2222

23+
@Column(table = "TableB")
2324
private String embedAttrA;
2425

2526
public EmbeddableB getEmbedB() {

hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedEmbeddedObjectWithASecondaryTableTest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import jakarta.persistence.Table;
1616
import org.hibernate.boot.MetadataSources;
1717
import org.hibernate.testing.orm.junit.JiraKey;
18-
import org.hibernate.testing.orm.junit.NotImplementedYet;
1918
import org.hibernate.testing.orm.junit.ServiceRegistry;
2019
import org.hibernate.testing.orm.junit.ServiceRegistryScope;
2120
import org.junit.jupiter.api.Test;
@@ -32,7 +31,6 @@
3231
class NestedEmbeddedObjectWithASecondaryTableTest {
3332

3433
@Test
35-
@NotImplementedYet
3634
void testNestedEmbeddedAndSecondaryTables(ServiceRegistryScope registryScope) {
3735
final MetadataSources metadataSources = new MetadataSources( registryScope.getRegistry() )
3836
.addAnnotatedClasses( Book.class, Author.class, House.class );
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.records;
6+
7+
import jakarta.persistence.Column;
8+
import jakarta.persistence.Embeddable;
9+
import jakarta.persistence.Entity;
10+
import jakarta.persistence.GeneratedValue;
11+
import jakarta.persistence.Id;
12+
import jakarta.persistence.SecondaryTable;
13+
import jakarta.persistence.Table;
14+
import org.hibernate.AnnotationException;
15+
import org.hibernate.boot.MetadataSources;
16+
import org.hibernate.boot.registry.StandardServiceRegistry;
17+
import org.hibernate.testing.orm.junit.DomainModel;
18+
import org.hibernate.testing.orm.junit.JiraKey;
19+
import org.hibernate.testing.orm.junit.ServiceRegistryScope;
20+
import org.hibernate.testing.orm.junit.SessionFactory;
21+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
22+
import org.junit.jupiter.api.BeforeAll;
23+
import org.junit.jupiter.api.Test;
24+
25+
import static org.assertj.core.api.Assertions.assertThat;
26+
import static org.assertj.core.api.Assertions.fail;
27+
28+
@JiraKey("HHH-19542")
29+
@DomainModel(annotatedClasses = {
30+
RecordNestedEmbeddedWithASecondaryTableTest.UserEntity.class
31+
})
32+
@SessionFactory
33+
class RecordNestedEmbeddedWithASecondaryTableTest {
34+
35+
private UserEntity user;
36+
37+
@BeforeAll
38+
void prepare(SessionFactoryScope scope) {
39+
scope.inTransaction( session -> {
40+
Person person = new Person( new FullName( "Sylvain", "Lecoy" ), 38 );
41+
user = new UserEntity( person );
42+
session.persist( user );
43+
} );
44+
}
45+
46+
@Test
47+
void test(SessionFactoryScope scope) {
48+
scope.inTransaction(session -> {
49+
UserEntity entity = session.find( UserEntity.class, user.id );
50+
assertThat( entity ).isNotNull();
51+
assertThat( entity.id ).isEqualTo( user.id );
52+
assertThat( entity.person ).isNotNull();
53+
assertThat( entity.person.age ).isEqualTo( 38 );
54+
assertThat( entity.person.fullName.firstName ).isEqualTo( "Sylvain" );
55+
assertThat( entity.person.fullName.lastName ).isEqualTo( "Lecoy" );
56+
});
57+
}
58+
59+
@Test
60+
void test(ServiceRegistryScope scope) {
61+
final StandardServiceRegistry registry = scope.getRegistry();
62+
final MetadataSources sources = new MetadataSources( registry ).addAnnotatedClass( UserEntity1.class );
63+
64+
try {
65+
sources.buildMetadata();
66+
fail( "Expecting to fail" );
67+
} catch (AnnotationException expected) {
68+
assertThat( expected ).hasMessageContaining( "all properties of the embeddable class must map to the same table" );
69+
}
70+
}
71+
72+
@Entity
73+
@Table(name = "UserEntity")
74+
@SecondaryTable(name = "Person")
75+
static class UserEntity {
76+
@Id
77+
@GeneratedValue
78+
private Integer id;
79+
private Person person;
80+
81+
public UserEntity(
82+
final Person person) {
83+
this.person = person;
84+
}
85+
86+
protected UserEntity() {
87+
88+
}
89+
}
90+
91+
@Embeddable
92+
record Person(
93+
FullName fullName,
94+
@Column(table = "Person")
95+
Integer age) {
96+
97+
}
98+
99+
@Embeddable
100+
record FullName(
101+
@Column(table = "Person")
102+
String firstName,
103+
@Column(table = "Person")
104+
String lastName) {
105+
106+
}
107+
108+
@Entity
109+
@Table(name = "UserEntity")
110+
@SecondaryTable(name = "Person")
111+
public static class UserEntity1 {
112+
@Id
113+
@GeneratedValue
114+
private Integer id;
115+
private Person1 person;
116+
117+
public UserEntity1(
118+
final Person1 person) {
119+
this.person = person;
120+
}
121+
122+
protected UserEntity1() {
123+
124+
}
125+
}
126+
127+
@Embeddable
128+
public record Person1(
129+
FullName1 fullName,
130+
@Column(table = "Person")
131+
Integer age) {
132+
133+
}
134+
135+
@Embeddable
136+
public record FullName1(
137+
@Column(table = "Person")
138+
String firstName,
139+
String lastName) {
140+
141+
}
142+
}

0 commit comments

Comments
 (0)