1
1
package org .hibernate .bugs ;
2
2
3
- import org .assertj .core .api .Assertions ;
4
3
import org .hibernate .Hibernate ;
5
4
import org .junit .After ;
6
5
import org .junit .Before ;
6
+ import org .junit .Ignore ;
7
7
import org .junit .Test ;
8
8
9
9
import javax .persistence .EntityManager ;
10
10
import javax .persistence .EntityManagerFactory ;
11
11
import javax .persistence .Persistence ;
12
- import javax .persistence .Query ;
13
12
import javax .persistence .TypedQuery ;
14
13
import javax .persistence .criteria .CriteriaBuilder ;
15
14
import javax .persistence .criteria .CriteriaQuery ;
16
- import javax .persistence .criteria .Fetch ;
17
15
import javax .persistence .criteria .Join ;
18
16
import javax .persistence .criteria .JoinType ;
19
17
import javax .persistence .criteria .Root ;
20
18
import java .util .List ;
21
19
import java .util .function .Consumer ;
22
- import java .util .function .Function ;
23
- import java .util .function .Supplier ;
24
20
25
21
import static org .assertj .core .api .Assertions .*;
26
22
@@ -43,11 +39,13 @@ public void destroy() {
43
39
}
44
40
45
41
/**
46
- * A condition on a join limits the resulting parent entity, but not their referenced entities.
47
- * Since the children relation is lazy and no fetch join was specified the children property is not initialized.
42
+ * Simple Join does not fetch joined attribute.
43
+ * Children are complete.
44
+ * <p>
45
+ * JPQL.
48
46
*/
49
47
@ Test
50
- public void selectJoinWithSimpleCondition () {
48
+ public void simpleJoinDoesNotFetchAttributeJpql () {
51
49
inTransaction (em -> {
52
50
53
51
final TypedQuery <Parent > query = em .createQuery (
@@ -64,11 +62,13 @@ public void selectJoinWithSimpleCondition() {
64
62
}
65
63
66
64
/**
67
- * A condition on a join limits the resulting parent entity, but not their referenced entities.
68
- * Since the children relation is lazy and no fetch join was specified the children property is not initialized.
65
+ * Simple Join does not fetch joined attribute.
66
+ * Children are complete.
67
+ * <p>
68
+ * Criteria API.
69
69
*/
70
70
@ Test
71
- public void selectJoinWithSimpleConditionCriteria () {
71
+ public void selectJoinWithSimpleConditionCriteriaApi () {
72
72
inTransaction (em -> {
73
73
74
74
final CriteriaBuilder cb = em .getCriteriaBuilder ();
@@ -88,12 +88,14 @@ public void selectJoinWithSimpleConditionCriteria() {
88
88
89
89
/**
90
90
* This not a fetch join as described in the specification.
91
- *
91
+ * <p>
92
92
* It does limit the returned parent, but also limits the contained children.
93
93
* Since it is a fetch join the children list is initialized.
94
+ * <p>
95
+ * By this usage a FETCH JOIN is a JOIN.
94
96
*/
95
97
@ Test
96
- public void selectJoinFetchWithSimpleCondition () {
98
+ public void fetchJoinUsedInCoditionLimitsResultsJpql () {
97
99
98
100
inTransaction (em -> {
99
101
@@ -110,12 +112,15 @@ public void selectJoinFetchWithSimpleCondition() {
110
112
}
111
113
112
114
/**
113
- * This not a fetch join as described in the specification.
114
- *
115
- * And it is not expressable in Criteria API
115
+ * One actually can use a fetch join as a join in the Criteria API if one casts the {@link javax.persistence.criteria.Fetch} to {@link Join}.
116
+ * <p>
117
+ * This is highly brittle since it depends on implementation details.
118
+ * <p>
119
+ * It does limit the returned parent, but also limits the contained children.
120
+ * Since it is a fetch join the children list is initialized.
116
121
*/
117
122
@ Test
118
- public void selectJoinFetchWithSimpleConditionCriteria () {
123
+ public void fetchJoinUsedInCoditionHackedLimitsResultsCriteriaApi () {
119
124
120
125
inTransaction (em -> {
121
126
@@ -138,10 +143,35 @@ public void selectJoinFetchWithSimpleConditionCriteria() {
138
143
}
139
144
140
145
/**
146
+ * One cannot use a path expression to silently reuse a fetch with Criteria API. The test fails when executed.
147
+ */
148
+ @ Test
149
+ @ Ignore
150
+ public void fetchJoinUsedInCoditionFailsCriterApi () {
151
+
152
+ inTransaction (em -> {
153
+
154
+ final CriteriaBuilder cb = em .getCriteriaBuilder ();
155
+ final CriteriaQuery <Parent > query = cb .createQuery (Parent .class );
156
+ final Root <Parent > root = query .from (Parent .class );
157
+ root .fetch ("children" );
158
+ query .where (cb .equal (root .get ("children" ).get ("name" ), "Baby Smurf" ));
159
+
160
+ final Parent parent = em .createQuery (query ).getSingleResult ();
161
+
162
+ assertThat (parent ).isNotNull ();
163
+ assertThat (Hibernate .isInitialized (parent .children )).isFalse ();
164
+ assertThat (Hibernate .isInitialized (parent .favoriteChild )).isTrue ();
165
+ assertThat (parent .children ).hasSize (3 );
166
+ });
167
+ }
168
+
169
+ /**
170
+ * This is what the specification suggests to use if one wants to use a joined attribute in a condition AND wants to fetch that join as well:
141
171
* Join fetch PLUS a JOIN initializes the collection and loads the children.
142
172
*/
143
173
@ Test
144
- public void selectJoinFetchAndJoinWithSimpleConditionGet () {
174
+ public void joinFetchPlusJoinWithConditionFetchesAllChildrenAndFiltersParentByChildrenJpql () {
145
175
146
176
inTransaction (em -> {
147
177
@@ -157,11 +187,15 @@ public void selectJoinFetchAndJoinWithSimpleConditionGet() {
157
187
assertThat (parent .children ).hasSize (3 );
158
188
});
159
189
}
190
+
160
191
/**
192
+ * This is what the specification suggests to use if one wants to use a joined attribute in a condition AND wants to fetch that join as well:
161
193
* Join fetch PLUS a JOIN initializes the collection and loads the children.
194
+ * <p>
195
+ * But when queried with {@code getResultList()} duplicates are returned.
162
196
*/
163
197
@ Test
164
- public void selectJoinFetchAndJoinWithSimpleConditionResultList () {
198
+ public void joinFetchPlusJoinWithConditionFetchesAllChildrenAndFiltersParentByChildrenResultListJpql () {
165
199
166
200
inTransaction (em -> {
167
201
@@ -183,10 +217,11 @@ public void selectJoinFetchAndJoinWithSimpleConditionResultList() {
183
217
}
184
218
185
219
/**
220
+ * This is what the specification suggests to use if one wants to use a joined attribute in a condition AND wants to fetch that join as well:
186
221
* Join fetch PLUS a JOIN initializes the collection and loads the children.
187
222
*/
188
223
@ Test
189
- public void selectJoinFetchAndJoinWithSimpleConditionCriteria () {
224
+ public void joinFetchPlusJoinWithConditionFetchesAllChildrenAndFiltersParentByChildrenJpqlCriteriaApi () {
190
225
191
226
inTransaction (em -> {
192
227
@@ -206,33 +241,10 @@ public void selectJoinFetchAndJoinWithSimpleConditionCriteria() {
206
241
}
207
242
208
243
/**
209
- * Join fetch PLUS a JOIN initializes the collection and loads the children.
210
- */
211
- @ Test
212
- public void selectReusingFetchWithPathExpressionWithSimpleConditionCriteria () {
213
-
214
- inTransaction (em -> {
215
-
216
- final CriteriaBuilder cb = em .getCriteriaBuilder ();
217
- final CriteriaQuery <Parent > query = cb .createQuery (Parent .class );
218
- final Root <Parent > root = query .from (Parent .class );
219
- // root.fetch("children");
220
- query .where (cb .equal (root .get ("children" ).get ("name" ), "Baby Smurf" ));
221
-
222
- final Parent parent = em .createQuery (query ).getSingleResult ();
223
-
224
- assertThat (parent ).isNotNull ();
225
- assertThat (Hibernate .isInitialized (parent .children )).isFalse ();
226
- assertThat (Hibernate .isInitialized (parent .favoriteChild )).isTrue ();
227
- assertThat (parent .children ).hasSize (3 );
228
- });
229
- }
230
-
231
- /**
232
- * Join fetch PLUS a JOIN initializes the collection and loads the children.
244
+ * For * to One relations reusing a Fetch Join does not alter the result in a surprising way.
233
245
*/
234
246
@ Test
235
- public void selectReusingFetchWithPathExpressionWithSimpleConditionCriteriaSingle () {
247
+ public void fetchJoinReusedByPathExpressionForOneToOneJpqlMatch () {
236
248
237
249
inTransaction (em -> {
238
250
@@ -251,11 +263,12 @@ public void selectReusingFetchWithPathExpressionWithSimpleConditionCriteriaSingl
251
263
});
252
264
}
253
265
266
+
254
267
/**
255
- * Join fetch PLUS a JOIN initializes the collection and loads the children .
268
+ * For * to One relations reusing a Fetch Join does not alter the result in a surprising way .
256
269
*/
257
270
@ Test
258
- public void selectReusingLeftOuterFetchWithPathExpressionWithSimpleConditionCriteria () {
271
+ public void fetchJoinReusedByPathExpressionForOneToOneJpqlNoMatch () {
259
272
260
273
inTransaction (em -> {
261
274
0 commit comments