12
12
import jakarta .persistence .Embedded ;
13
13
import jakarta .persistence .Entity ;
14
14
import jakarta .persistence .Id ;
15
+ import jakarta .persistence .JoinColumn ;
16
+ import jakarta .persistence .PrimaryKeyJoinColumn ;
15
17
import jakarta .persistence .SecondaryTable ;
16
18
import jakarta .persistence .Table ;
17
19
import org .hibernate .annotations .EmbeddedTable ;
20
+ import org .hibernate .boot .model .naming .Identifier ;
21
+ import org .hibernate .boot .model .relational .Namespace ;
22
+ import org .hibernate .boot .models .AnnotationPlacementException ;
23
+ import org .hibernate .boot .spi .MetadataImplementor ;
24
+ import org .hibernate .dialect .H2Dialect ;
25
+ import org .hibernate .jpa .HibernatePersistenceConfiguration ;
18
26
import org .hibernate .mapping .Collection ;
19
27
import org .hibernate .mapping .Component ;
20
28
import org .hibernate .mapping .PersistentClass ;
21
29
import org .hibernate .mapping .Property ;
30
+ import org .hibernate .testing .jdbc .SQLStatementInspector ;
22
31
import org .hibernate .testing .orm .junit .DomainModel ;
23
32
import org .hibernate .testing .orm .junit .DomainModelScope ;
33
+ import org .hibernate .testing .orm .junit .RequiresDialect ;
24
34
import org .hibernate .testing .orm .junit .ServiceRegistry ;
35
+ import org .hibernate .testing .orm .junit .ServiceRegistryScope ;
36
+ import org .hibernate .testing .orm .junit .SessionFactory ;
37
+ import org .hibernate .testing .orm .junit .SessionFactoryScope ;
25
38
import org .junit .jupiter .api .Test ;
26
39
27
40
import java .time .Instant ;
28
41
import java .util .Set ;
29
42
30
43
import static org .assertj .core .api .Assertions .assertThat ;
44
+ import static org .assertj .core .api .Assertions .fail ;
31
45
32
46
/**
33
47
* @author Steve Ebersole
34
48
*/
35
49
@ SuppressWarnings ("JUnitMalformedDeclaration" )
36
50
@ ServiceRegistry
51
+ @ RequiresDialect (value = H2Dialect .class , comment = "The underlying database has no effect on this, so just run on the default" )
37
52
public class EmbeddedTableTests {
38
53
@ Test
39
54
@ ServiceRegistry
40
55
@ DomainModel (annotatedClasses = {EmbeddedTableTests .Tag .class , EmbeddedTableTests .PostCompliant .class })
41
56
void testCompliantApproach (DomainModelScope modelScope ) {
42
57
verifyModel ( modelScope .getEntityBinding ( PostCompliant .class ),
43
58
"posts_compliant" ,
44
- "posts_compliant_secondary" );
59
+ "posts_compliant_secondary" ,
60
+ modelScope .getDomainModel () );
45
61
}
46
62
47
63
@ Test
@@ -50,15 +66,36 @@ void testCompliantApproach(DomainModelScope modelScope) {
50
66
void testTableNaming (DomainModelScope modelScope ) {
51
67
verifyModel ( modelScope .getEntityBinding ( Post .class ),
52
68
"posts" ,
53
- "posts_secondary" );
69
+ "posts_secondary" ,
70
+ modelScope .getDomainModel () );
54
71
}
55
72
56
- void verifyModel (PersistentClass entityBinding , String primaryTable , String secondaryTable ) {
73
+ void verifyModel (
74
+ PersistentClass entityBinding ,
75
+ String primaryTableName ,
76
+ String secondaryTableName ,
77
+ MetadataImplementor domainModel ) {
57
78
final Property nameProperty = entityBinding .getProperty ( "name" );
58
- assertThat ( nameProperty .getValue ().getTable ().getName () ).isEqualTo ( primaryTable );
79
+ assertThat ( nameProperty .getValue ().getTable ().getName () ).isEqualTo ( primaryTableName );
59
80
60
81
final Property primaryTagProperty = entityBinding .getProperty ( "tag" );
61
- assertThat ( primaryTagProperty .getValue ().getTable ().getName () ).isEqualTo ( secondaryTable );
82
+ assertThat ( primaryTagProperty .getValue ().getTable ().getName () ).isEqualTo ( secondaryTableName );
83
+
84
+ final Namespace dbNamespace = domainModel .getDatabase ().getDefaultNamespace ();
85
+
86
+ // id, name
87
+ final org .hibernate .mapping .Table primaryTable = dbNamespace .locateTable (
88
+ Identifier .toIdentifier ( primaryTableName ) );
89
+ assertThat ( primaryTable .getColumns () ).hasSize ( 2 );
90
+ assertThat ( primaryTable .getColumns ().stream ().map ( org .hibernate .mapping .Column ::getName ) )
91
+ .containsExactlyInAnyOrder ( "id" , "name" );
92
+
93
+ // text, added
94
+ final org .hibernate .mapping .Table secondaryTable = dbNamespace .locateTable (
95
+ Identifier .toIdentifier ( secondaryTableName ) );
96
+ assertThat ( secondaryTable .getColumns () ).hasSize ( 3 );
97
+ assertThat ( secondaryTable .getColumns ().stream ().map ( org .hibernate .mapping .Column ::getName ) )
98
+ .containsExactlyInAnyOrder ( "text" , "added" , "post_fk" );
62
99
}
63
100
64
101
@ Test
@@ -68,7 +105,7 @@ void verifyModel(PersistentClass entityBinding, String primaryTable, String seco
68
105
EmbeddedTableTests .Container .class ,
69
106
EmbeddedTableTests .TopContainer .class
70
107
})
71
- void testNestedUsage (DomainModelScope modelScope ) {
108
+ void testNestedModel (DomainModelScope modelScope ) {
72
109
final PersistentClass entityBinding = modelScope .getEntityBinding ( TopContainer .class );
73
110
74
111
final Property subContainerProp = entityBinding .getProperty ( "subContainer" );
@@ -77,6 +114,29 @@ void testNestedUsage(DomainModelScope modelScope) {
77
114
final Property subContainersProp = entityBinding .getProperty ( "subContainers" );
78
115
final Collection containersPropValue = (Collection ) subContainersProp .getValue ();
79
116
checkContainerComponent ( (Component ) containersPropValue .getElement (), "sub_containers" );
117
+
118
+ final Namespace dbNamespace = modelScope .getDomainModel ().getDatabase ().getDefaultNamespace ();
119
+
120
+ // id, name
121
+ final org .hibernate .mapping .Table primaryTable = dbNamespace .locateTable (
122
+ Identifier .toIdentifier ( "top" ) );
123
+ assertThat ( primaryTable .getColumns () ).hasSize ( 2 );
124
+ assertThat ( primaryTable .getColumns ().stream ().map ( org .hibernate .mapping .Column ::getName ) )
125
+ .containsExactlyInAnyOrder ( "id" , "name" );
126
+
127
+ // thing1, thing2, top_fk
128
+ final org .hibernate .mapping .Table secondaryTable = dbNamespace .locateTable (
129
+ Identifier .toIdentifier ( "supp" ) );
130
+ assertThat ( secondaryTable .getColumns () ).hasSize ( 3 );
131
+ assertThat ( secondaryTable .getColumns ().stream ().map ( org .hibernate .mapping .Column ::getName ) )
132
+ .containsExactlyInAnyOrder ( "thing1" , "thing2" , "top_fk" );
133
+
134
+ // thing1, thing2, top_fk
135
+ final org .hibernate .mapping .Table collectionTable = dbNamespace .locateTable (
136
+ Identifier .toIdentifier ( "sub_containers" ) );
137
+ assertThat ( collectionTable .getColumns () ).hasSize ( 3 );
138
+ assertThat ( collectionTable .getColumns ().stream ().map ( org .hibernate .mapping .Column ::getName ) )
139
+ .containsExactlyInAnyOrder ( "thing1" , "thing2" , "top_fk" );
80
140
}
81
141
82
142
private void checkContainerComponent (Component containerComponent , String tableName ) {
@@ -89,6 +149,49 @@ private void checkContainerComponent(Component containerComponent, String tableN
89
149
} );
90
150
}
91
151
152
+ @ Test
153
+ @ ServiceRegistry
154
+ @ DomainModel (annotatedClasses = {EmbeddedTableTests .Tag .class , EmbeddedTableTests .Post .class })
155
+ @ SessionFactory (useCollectingStatementInspector = true )
156
+ void testDatabase (SessionFactoryScope factoryScope ) {
157
+ final SQLStatementInspector sqlCollector = factoryScope .getCollectingStatementInspector ();
158
+ sqlCollector .clear ();
159
+
160
+ factoryScope .inTransaction ( (session ) -> {
161
+ // NOTE: ... from posts p1_0 left join posts_secondary p1_1 ...
162
+ session .createSelectionQuery ( "from Post" , Post .class ).list ();
163
+ assertThat ( sqlCollector .getSqlQueries () ).hasSize ( 1 );
164
+ assertThat ( sqlCollector .getSqlQueries ().get ( 0 ) )
165
+ .contains ( "p1_0.id" , "p1_0.name" , "p1_1.added" , "p1_1.text" );
166
+ } );
167
+ }
168
+
169
+ @ Test
170
+ @ ServiceRegistry
171
+ void testBadNestedPlacement (ServiceRegistryScope registryScope ) {
172
+ final HibernatePersistenceConfiguration persistenceConfiguration = registryScope
173
+ .createPersistenceConfiguration ( "bad-nested" )
174
+ .managedClasses ( Bottom .class , BadMiddle .class , BadNesterEntity .class );
175
+ try ( var sf = persistenceConfiguration .createEntityManagerFactory () ) {
176
+ fail ( "Should have failed with AnnotationPlacementException" );
177
+ }
178
+ catch (AnnotationPlacementException expected ) {
179
+ }
180
+ }
181
+
182
+ @ Test
183
+ @ ServiceRegistry
184
+ void testBadCollectionPlacement (ServiceRegistryScope registryScope ) {
185
+ final HibernatePersistenceConfiguration persistenceConfiguration = registryScope
186
+ .createPersistenceConfiguration ( "bad-nested" )
187
+ .managedClasses ( Bottom .class , Middle .class , BadCollectionEntity .class );
188
+ try ( var sf = persistenceConfiguration .createEntityManagerFactory () ) {
189
+ fail ( "Should have failed with AnnotationPlacementException" );
190
+ }
191
+ catch (AnnotationPlacementException expected ) {
192
+ }
193
+ }
194
+
92
195
@ Embeddable
93
196
public static class Tag {
94
197
String text ;
@@ -97,7 +200,7 @@ public static class Tag {
97
200
98
201
@ Entity (name ="Post" )
99
202
@ Table (name ="posts" )
100
- @ SecondaryTable (name ="posts_secondary" )
203
+ @ SecondaryTable (name ="posts_secondary" , pkJoinColumns = @ PrimaryKeyJoinColumn ( name = "post_fk" ) )
101
204
public static class Post {
102
205
@ Id
103
206
private Integer id ;
@@ -109,7 +212,7 @@ public static class Post {
109
212
110
213
@ Entity (name ="PostCompliant" )
111
214
@ Table (name ="posts_compliant" )
112
- @ SecondaryTable (name ="posts_compliant_secondary" )
215
+ @ SecondaryTable (name ="posts_compliant_secondary" , pkJoinColumns = @ PrimaryKeyJoinColumn ( name = "post_fk" ) )
113
216
public static class PostCompliant {
114
217
@ Id
115
218
private Integer id ;
@@ -129,13 +232,12 @@ public static class Nested {
129
232
@ Embeddable
130
233
public static class Container {
131
234
@ Embedded
132
- @ EmbeddedTable ("should_be_ignored" ) // or maybe this should be an error? same for element-collections of embeddables
133
235
Nested nested ;
134
236
}
135
237
136
238
@ Entity (name ="TopContainer" )
137
239
@ Table (name ="top" )
138
- @ SecondaryTable (name ="supp" )
240
+ @ SecondaryTable (name ="supp" , pkJoinColumns = @ PrimaryKeyJoinColumn ( name = "top_fk" ) )
139
241
public static class TopContainer {
140
242
@ Id
141
243
private Integer id ;
@@ -145,8 +247,50 @@ public static class TopContainer {
145
247
private Container subContainer ;
146
248
147
249
@ ElementCollection
148
- @ CollectionTable (name = "sub_containers" )
149
- @ EmbeddedTable ("supp" )
250
+ @ CollectionTable (name = "sub_containers" , joinColumns = @ JoinColumn (name = "top_fk" ))
150
251
private Set <Container > subContainers ;
151
252
}
253
+
254
+ @ Embeddable
255
+ public static class Bottom {
256
+ private String kind ;
257
+ private Instant whenReached ;
258
+ }
259
+
260
+ @ Embeddable
261
+ public static class BadMiddle {
262
+ @ Embedded
263
+ @ EmbeddedTable ("secondary" )
264
+ private Bottom bottom ;
265
+ }
266
+
267
+ @ Embeddable
268
+ public static class Middle {
269
+ @ Embedded
270
+ private Bottom bottom ;
271
+ }
272
+
273
+ @ Entity (name ="BadNesterEntity" )
274
+ @ Table (name ="primary" )
275
+ @ SecondaryTable (name ="secondary" , pkJoinColumns = @ PrimaryKeyJoinColumn (name = "primary_fk" ))
276
+ public static class BadNesterEntity {
277
+ @ Id
278
+ private Integer id ;
279
+ private String name ;
280
+ @ Embedded
281
+ @ EmbeddedTable ("secondary" )
282
+ BadMiddle badMiddle ;
283
+ }
284
+
285
+ @ Entity (name ="BadNesterEntity" )
286
+ @ Table (name ="primary" )
287
+ @ SecondaryTable (name ="secondary" , pkJoinColumns = @ PrimaryKeyJoinColumn (name = "primary_fk" ))
288
+ public static class BadCollectionEntity {
289
+ @ Id
290
+ private Integer id ;
291
+ private String name ;
292
+ @ ElementCollection
293
+ @ EmbeddedTable ("secondary" )
294
+ Set <Middle > middles ;
295
+ }
152
296
}
0 commit comments