Skip to content

Commit 58b87dd

Browse files
author
Hà Trung Kiên
committed
HHH-19203 Unexpected association fetch triggered by Bean Validation integration
1 parent e61b5f0 commit 58b87dd

File tree

2 files changed

+126
-1
lines changed

2 files changed

+126
-1
lines changed

hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/HibernateTraversableResolver.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.hibernate.type.EntityType;
2020
import org.hibernate.type.Type;
2121

22+
import jakarta.persistence.PersistenceUnitUtil;
2223
import jakarta.validation.Path;
2324
import jakarta.validation.TraversableResolver;
2425

@@ -31,6 +32,7 @@
3132
*/
3233
public class HibernateTraversableResolver implements TraversableResolver {
3334
private Set<String> associations;
35+
private final PersistenceUnitUtil persistenceUnitUtil;
3436

3537
public HibernateTraversableResolver(
3638
EntityPersister persister,
@@ -42,6 +44,7 @@ public HibernateTraversableResolver(
4244
addAssociationsToTheSetForAllProperties( persister.getPropertyNames(), persister.getPropertyTypes(), "", factory );
4345
associationsPerEntityPersister.put( persister, associations );
4446
}
47+
persistenceUnitUtil = factory.getPersistenceUnitUtil();
4548
}
4649

4750
private void addAssociationsToTheSetForAllProperties(
@@ -94,7 +97,7 @@ public boolean isReachable(Object traversableObject,
9497
ElementType elementType) {
9598
//lazy, don't load
9699
return Hibernate.isInitialized( traversableObject )
97-
&& Hibernate.isPropertyInitialized( traversableObject, traversableProperty.getName() );
100+
&& persistenceUnitUtil.isLoaded( traversableObject, traversableProperty.getName() );
98101
}
99102

100103
public boolean isCascadable(Object traversableObject,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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.annotations.beanvalidation;
6+
7+
import java.util.List;
8+
9+
import org.hibernate.cfg.AvailableSettings;
10+
11+
import org.hibernate.testing.jdbc.SQLStatementInspector;
12+
import org.hibernate.testing.orm.junit.DomainModel;
13+
import org.hibernate.testing.orm.junit.JiraKey;
14+
import org.hibernate.testing.orm.junit.ServiceRegistry;
15+
import org.hibernate.testing.orm.junit.SessionFactory;
16+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
17+
import org.hibernate.testing.orm.junit.Setting;
18+
import org.junit.jupiter.api.AfterAll;
19+
import org.junit.jupiter.api.Test;
20+
21+
import jakarta.persistence.CascadeType;
22+
import jakarta.persistence.ElementCollection;
23+
import jakarta.persistence.Entity;
24+
import jakarta.persistence.Id;
25+
import jakarta.persistence.JoinTable;
26+
import jakarta.persistence.OneToMany;
27+
import jakarta.validation.constraints.Size;
28+
29+
@DomainModel(annotatedClasses = {
30+
LazyPropertiesFetchTest.Association.class,
31+
LazyPropertiesFetchTest.MutableEntity.class
32+
})
33+
@ServiceRegistry(settings = @Setting(name = AvailableSettings.JAKARTA_VALIDATION_MODE, value = "CALLBACK"))
34+
@SessionFactory(useCollectingStatementInspector = true)
35+
@JiraKey("HHH-19203")
36+
class LazyPropertiesFetchTest {
37+
38+
@AfterAll
39+
static void cleanup(SessionFactoryScope scope) {
40+
scope.dropData();
41+
}
42+
43+
@Test
44+
void testLazyCollectionNotFetched(SessionFactoryScope scope) {
45+
scope.inTransaction( session -> {
46+
MutableEntity mutableEntity = new MutableEntity();
47+
mutableEntity.id = 1L;
48+
mutableEntity.lazyCollection = List.of( 1, 2 );
49+
session.persist( mutableEntity );
50+
} );
51+
52+
SQLStatementInspector inspector = scope.getCollectingStatementInspector();
53+
inspector.clear();
54+
55+
scope.inTransaction( session -> {
56+
MutableEntity fetched = session.find( MutableEntity.class, 1L );
57+
inspector.assertExecutedCount( 1 );
58+
fetched.mutableField = 1;
59+
} );
60+
61+
inspector.assertExecutedCount( 2 );
62+
}
63+
64+
@Test
65+
void testLazyCollectionFetchDoesntDependOnEachOther(SessionFactoryScope scope) {
66+
scope.inTransaction( session -> {
67+
MutableEntity mutableEntity = new MutableEntity();
68+
mutableEntity.id = 2L;
69+
mutableEntity.lazyCollection = List.of( 1, 2 );
70+
71+
Association asso = new Association();
72+
asso.id = 1L;
73+
asso.lazyCollection = List.of( 2, 3 );
74+
75+
mutableEntity.lazyAssociation = List.of( asso );
76+
77+
session.persist( mutableEntity );
78+
} );
79+
80+
SQLStatementInspector inspector = scope.getCollectingStatementInspector();
81+
inspector.clear();
82+
83+
scope.inTransaction( session -> {
84+
MutableEntity fetched = session.find( MutableEntity.class, 2L );
85+
inspector.assertExecutedCount( 1 );
86+
87+
Association asso = fetched.lazyAssociation.get( 0 );
88+
inspector.assertExecutedCount( 2 );
89+
90+
asso.mutableField = 5;
91+
} );
92+
inspector.assertExecutedCount( 3 );
93+
}
94+
95+
@Entity
96+
static class MutableEntity {
97+
@Id
98+
private Long id;
99+
100+
private int mutableField = 0;
101+
102+
@Size(max = 10)
103+
@ElementCollection
104+
private List<Integer> lazyCollection;
105+
106+
@OneToMany(cascade = CascadeType.PERSIST)
107+
@JoinTable
108+
private List<Association> lazyAssociation;
109+
}
110+
111+
@Entity
112+
static class Association {
113+
@Id
114+
private Long id;
115+
116+
private int mutableField = 0;
117+
118+
@Size(max = 10)
119+
@ElementCollection
120+
private List<Integer> lazyCollection;
121+
}
122+
}

0 commit comments

Comments
 (0)