Skip to content

Commit acb1398

Browse files
committed
HHH-19246 Traverse entity graph also for explicit join fetches
1 parent 3c1636f commit acb1398

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed

hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8223,6 +8223,15 @@ private Fetch createFetch(FetchParent fetchParent, Fetchable fetchable, Boolean
82238223
joined = true;
82248224
alias = fetchedJoin.getExplicitAlias();
82258225
explicitFetch = true;
8226+
8227+
if ( entityGraphTraversalState != null ) {
8228+
// Still do traverse the entity graph even if we encounter a fetch join
8229+
traversalResult = entityGraphTraversalState.traverse(
8230+
fetchParent,
8231+
fetchable,
8232+
isKeyFetchable
8233+
);
8234+
}
82268235
}
82278236
else {
82288237
fetchablePath = resolvedNavigablePath;
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.entitygraph;
6+
7+
import jakarta.persistence.Entity;
8+
import jakarta.persistence.FetchType;
9+
import jakarta.persistence.Id;
10+
import jakarta.persistence.IdClass;
11+
import jakarta.persistence.JoinColumn;
12+
import jakarta.persistence.ManyToOne;
13+
import jakarta.persistence.NamedAttributeNode;
14+
import jakarta.persistence.NamedEntityGraph;
15+
import jakarta.persistence.NamedSubgraph;
16+
import jakarta.persistence.OneToMany;
17+
import jakarta.persistence.Table;
18+
import org.hibernate.Hibernate;
19+
import org.hibernate.jpa.SpecHints;
20+
import org.hibernate.testing.orm.junit.DomainModel;
21+
import org.hibernate.testing.orm.junit.JiraKey;
22+
import org.hibernate.testing.orm.junit.SessionFactory;
23+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
24+
import org.junit.jupiter.api.BeforeAll;
25+
import org.junit.jupiter.api.Test;
26+
27+
import java.io.Serializable;
28+
import java.math.BigDecimal;
29+
import java.util.HashSet;
30+
import java.util.Objects;
31+
import java.util.Set;
32+
33+
import static org.junit.jupiter.api.Assertions.assertEquals;
34+
import static org.junit.jupiter.api.Assertions.assertTrue;
35+
36+
@DomainModel(
37+
annotatedClasses = {
38+
EntityGraphAndJoinFetchTest.OrderItem.class,
39+
EntityGraphAndJoinFetchTest.Order.class,
40+
EntityGraphAndJoinFetchTest.Product.class
41+
}
42+
)
43+
@SessionFactory
44+
@JiraKey( value = "HHH-19246")
45+
public class EntityGraphAndJoinFetchTest {
46+
47+
private static final Long ORDER_ID = 1l;
48+
private static final Long PRODUCT_ID = 2l;
49+
50+
private static final String NAMED_GRAPH_NAME = "Order.fetchAll";
51+
private static final String NAMED_SUBGRAPH_NAME = "OrderItem.fetchAll";
52+
53+
54+
@BeforeAll
55+
public void setUp(SessionFactoryScope scope) {
56+
scope.inTransaction(
57+
session -> {
58+
Order order = new Order( ORDER_ID, new BigDecimal( 1000 ) );
59+
Product product = new Product( PRODUCT_ID, "laptop" );
60+
OrderItem orderItem = new OrderItem( product, order );
61+
order.getItems().add( orderItem );
62+
63+
session.persist( order );
64+
session.persist( product );
65+
session.persist( orderItem );
66+
}
67+
);
68+
}
69+
70+
@Test
71+
public void testJoinFetchBeingSubsetOfGraph(SessionFactoryScope scope) {
72+
scope.inTransaction(
73+
session -> {
74+
Order order = session.createQuery( "FROM Order e LEFT JOIN FETCH e.items", Order.class )
75+
.setHint(
76+
SpecHints.HINT_SPEC_LOAD_GRAPH,
77+
scope.getSessionFactory().createEntityManager().getEntityGraph( NAMED_GRAPH_NAME )
78+
)
79+
.getSingleResult();
80+
assertTrue( Hibernate.isInitialized( order.getItems() ), "OrderItems have not been fetched" );
81+
assertEquals( 1, order.getItems().size(), "OrderItems have not been fetched" );
82+
assertTrue( Hibernate.isInitialized( order.getItems().iterator().next().getProduct() ), "Product has not been fetched" );
83+
}
84+
);
85+
}
86+
87+
@Entity(name = "OrderItem")
88+
@IdClass(OrderItem.PK.class)
89+
public static class OrderItem {
90+
91+
@Id
92+
@ManyToOne(fetch = FetchType.LAZY)
93+
@JoinColumn(name = "product_id")
94+
private Product product;
95+
96+
@Id
97+
@ManyToOne(fetch = FetchType.LAZY)
98+
@JoinColumn(name = "order_id")
99+
private Order order;
100+
101+
public OrderItem() {
102+
}
103+
104+
public OrderItem(Product product, Order order) {
105+
this.product = product;
106+
this.order = order;
107+
}
108+
109+
public Product getProduct() {
110+
return product;
111+
}
112+
113+
public Order getOrder() {
114+
return order;
115+
}
116+
117+
public static class PK implements Serializable {
118+
private Long product;
119+
private Long order;
120+
121+
@Override
122+
public boolean equals(Object o) {
123+
if ( this == o ) {
124+
return true;
125+
}
126+
if ( o == null || getClass() != o.getClass() ) {
127+
return false;
128+
}
129+
PK pk = (PK) o;
130+
return Objects.equals( product, pk.product ) && Objects.equals( order, pk.order );
131+
}
132+
133+
@Override
134+
public int hashCode() {
135+
return Objects.hash( product, order );
136+
}
137+
}
138+
}
139+
140+
141+
@Entity(name = "Order")
142+
@Table(name = "ORDER_TABLE")
143+
@NamedEntityGraph(
144+
name = NAMED_GRAPH_NAME,
145+
attributeNodes = {
146+
@NamedAttributeNode(value = "items", subgraph = NAMED_SUBGRAPH_NAME)
147+
},
148+
subgraphs = {
149+
@NamedSubgraph(name = NAMED_SUBGRAPH_NAME, attributeNodes = {@NamedAttributeNode("product")})
150+
}
151+
)
152+
public static class Order {
153+
154+
@Id
155+
private Long id;
156+
157+
private BigDecimal total;
158+
@OneToMany(mappedBy = "order")
159+
private Set<OrderItem> items = new HashSet<>();
160+
161+
public Order() {
162+
}
163+
164+
public Order(Long id, BigDecimal total) {
165+
this.id = id;
166+
this.total = total;
167+
}
168+
169+
public Long getId() {
170+
return id;
171+
}
172+
173+
public BigDecimal getTotal() {
174+
return total;
175+
}
176+
177+
public Set<OrderItem> getItems() {
178+
return items;
179+
}
180+
}
181+
182+
@Entity(name = "Product")
183+
public static class Product {
184+
185+
@Id
186+
private Long id;
187+
188+
private String name;
189+
190+
public Product() {
191+
}
192+
193+
public Product(Long id, String name) {
194+
this.id = id;
195+
this.name = name;
196+
}
197+
198+
public Long getId() {
199+
return id;
200+
}
201+
202+
public String getName() {
203+
return name;
204+
}
205+
206+
}
207+
}

0 commit comments

Comments
 (0)