Skip to content

Commit e1b1b2d

Browse files
committed
HHH-19246 Traverse entity graph also for explicit join fetches
1 parent 7ae0973 commit e1b1b2d

File tree

2 files changed

+218
-0
lines changed

2 files changed

+218
-0
lines changed

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

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

0 commit comments

Comments
 (0)