1818import static org .assertj .core .api .Assertions .*;
1919import static org .springframework .data .jpa .repository .query .JpqlQueryBuilder .*;
2020
21+ import jakarta .persistence .Embeddable ;
22+ import jakarta .persistence .EmbeddedId ;
2123import jakarta .persistence .Id ;
2224import jakarta .persistence .ManyToOne ;
2325import jakarta .persistence .OneToMany ;
26+ import jakarta .persistence .metamodel .EntityType ;
2427
2528import java .util .Date ;
29+ import java .util .HashSet ;
2630import java .util .LinkedHashMap ;
2731import java .util .List ;
2832import java .util .Map ;
33+ import java .util .Set ;
2934
3035import org .assertj .core .api .AbstractStringAssert ;
3136import org .assertj .core .api .Assertions ;
3237import org .junit .jupiter .api .Test ;
3338
39+ import org .springframework .data .core .PropertyPath ;
40+ import org .springframework .data .jpa .util .TestMetaModel ;
41+
3442/**
3543 * Unit tests for {@link JpqlQueryBuilder}.
3644 *
@@ -200,7 +208,7 @@ void joins() {
200208 String fragment = JpqlQueryBuilder .where (productName ).eq (literal ("ex30" ))
201209 .and (JpqlQueryBuilder .where (personName ).eq (literal ("ex40" ))).render (ctx (entity ));
202210
203- assertThat (fragment ).isEqualTo ("p.name = 'ex30' AND join_0 .name = 'ex40'" );
211+ assertThat (fragment ).isEqualTo ("p.name = 'ex30' AND p_0 .name = 'ex40'" );
204212 }
205213
206214 @ Test // GH-3588
@@ -219,6 +227,27 @@ void joinOnPaths() {
219227 assertThat (fragment ).isEqualTo ("p.name = 'ex30' AND join_0.name = 'cstrobl'" );
220228 }
221229
230+ @ Test // GH-3989
231+ void shouldRenderJoinsWithSamePathSegmentCorrectly () {
232+
233+ TestMetaModel model = TestMetaModel .hibernateModel (WithCompositeId .class , CompositeId .class , Groups .class ,
234+ Group .class , Person .class );
235+ Entity entity = entity (WithCompositeId .class );
236+
237+ EntityType <WithCompositeId > entityType = model .entity (WithCompositeId .class );
238+ JpqlQueryBuilder .PathExpression pas = JpqlUtils .toExpressionRecursively (model , entity , entityType ,
239+ PropertyPath .from ("id.groups.groups.id.person.id" , WithCompositeId .class ));
240+ String jpql = JpqlQueryBuilder .selectFrom (entity ).entity ().where (JpqlQueryBuilder .where (pas ).isNotNull ()).render ();
241+
242+ assertThat (jpql ).isEqualTo ("SELECT w FROM JpqlQueryBuilderUnitTests$WithCompositeId w " //
243+ + "INNER JOIN w.id i_0 " //
244+ + "LEFT JOIN i_0.groups g " //
245+ + "LEFT JOIN g.groups g_0 " //
246+ + "INNER JOIN g_0.id i " //
247+ + "WHERE i.person.id IS NOT NULL" );
248+
249+ }
250+
222251 static ContextualAssert contextual (RenderContext context ) {
223252 return new ContextualAssert (context );
224253 }
@@ -294,4 +323,41 @@ static class Product {
294323 String productType ;
295324
296325 }
326+
327+ @ jakarta .persistence .Entity
328+ static class WithCompositeId {
329+
330+ @ EmbeddedId CompositeId id ;
331+
332+ }
333+
334+ @ Embeddable
335+ static class CompositeId {
336+
337+ @ ManyToOne Groups groups ;
338+
339+ }
340+
341+ @ jakarta .persistence .Entity
342+ static class Groups {
343+
344+ @ Id long id ;
345+ @ OneToMany Set <Group > groups = new HashSet <>();
346+
347+ }
348+
349+ @ jakarta .persistence .Entity
350+ static class Group {
351+
352+ @ EmbeddedId GroupId id ;
353+
354+ }
355+
356+ @ Embeddable
357+ static class GroupId {
358+
359+ @ ManyToOne Person person ;
360+
361+ }
362+
297363}
0 commit comments