-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HHH-19203 Unexpected association fetch triggered by Bean Validation #9804
Open
Kamii0909
wants to merge
1
commit into
hibernate:main
Choose a base branch
from
Kamii0909:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
.../test/java/org/hibernate/orm/test/annotations/beanvalidation/LazyPropertiesFetchTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/* | ||
* SPDX-License-Identifier: LGPL-2.1-or-later | ||
* Copyright Red Hat Inc. and Hibernate Authors | ||
*/ | ||
package org.hibernate.orm.test.annotations.beanvalidation; | ||
|
||
import java.util.List; | ||
|
||
import org.hibernate.cfg.AvailableSettings; | ||
|
||
import org.hibernate.testing.jdbc.SQLStatementInspector; | ||
import org.hibernate.testing.orm.junit.DomainModel; | ||
import org.hibernate.testing.orm.junit.JiraKey; | ||
import org.hibernate.testing.orm.junit.ServiceRegistry; | ||
import org.hibernate.testing.orm.junit.SessionFactory; | ||
import org.hibernate.testing.orm.junit.SessionFactoryScope; | ||
import org.hibernate.testing.orm.junit.Setting; | ||
import org.junit.jupiter.api.AfterAll; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import jakarta.persistence.CascadeType; | ||
import jakarta.persistence.CollectionTable; | ||
import jakarta.persistence.ElementCollection; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.OneToMany; | ||
import jakarta.validation.constraints.Size; | ||
|
||
@DomainModel(annotatedClasses = { | ||
LazyPropertiesFetchTest.Association.class, | ||
LazyPropertiesFetchTest.MutableEntity.class | ||
}) | ||
@ServiceRegistry(settings = @Setting(name = AvailableSettings.JAKARTA_VALIDATION_MODE, value = "CALLBACK")) | ||
@SessionFactory(useCollectingStatementInspector = true) | ||
@JiraKey("HHH-19203") | ||
class LazyPropertiesFetchTest { | ||
|
||
@AfterAll | ||
static void cleanup(SessionFactoryScope scope) { | ||
scope.dropData(); | ||
} | ||
|
||
@Test | ||
void testLazyCollectionNotFetched(SessionFactoryScope scope) { | ||
scope.inTransaction( session -> { | ||
MutableEntity mutableEntity = new MutableEntity(); | ||
mutableEntity.id = 1L; | ||
mutableEntity.lazyCollection = List.of( 1, 2 ); | ||
session.persist( mutableEntity ); | ||
} ); | ||
|
||
SQLStatementInspector inspector = scope.getCollectingStatementInspector(); | ||
inspector.clear(); | ||
|
||
scope.inTransaction( session -> { | ||
MutableEntity fetched = session.find( MutableEntity.class, 1L ); | ||
inspector.assertExecutedCount( 1 ); | ||
fetched.mutableField = 1; | ||
} ); | ||
|
||
inspector.assertExecutedCount( 2 ); | ||
} | ||
|
||
@Test | ||
void testLazyCollectionFetchDoesntDependOnEachOther(SessionFactoryScope scope) { | ||
scope.inTransaction( session -> { | ||
MutableEntity mutableEntity = new MutableEntity(); | ||
mutableEntity.id = 2L; | ||
mutableEntity.lazyCollection = List.of( 1, 2 ); | ||
|
||
Association asso = new Association(); | ||
asso.id = 1L; | ||
asso.lazyCollection = List.of( 2, 3 ); | ||
|
||
mutableEntity.lazyAssociation = List.of( asso ); | ||
|
||
session.persist( mutableEntity ); | ||
} ); | ||
|
||
SQLStatementInspector inspector = scope.getCollectingStatementInspector(); | ||
inspector.clear(); | ||
|
||
scope.inTransaction( session -> { | ||
MutableEntity fetched = session.find( MutableEntity.class, 2L ); | ||
inspector.assertExecutedCount( 1 ); | ||
|
||
Association asso = fetched.lazyAssociation.get( 0 ); | ||
inspector.assertExecutedCount( 2 ); | ||
|
||
asso.mutableField = 5; | ||
} ); | ||
inspector.assertExecutedCount( 3 ); | ||
} | ||
|
||
@Entity(name = "MutableEntity") | ||
static class MutableEntity { | ||
@Id | ||
private Long id; | ||
|
||
private int mutableField = 0; | ||
|
||
@Size(max = 10) | ||
@ElementCollection | ||
@CollectionTable(name = "LazyPropertiesCollection1") | ||
private List<Integer> lazyCollection; | ||
|
||
@OneToMany(cascade = CascadeType.PERSIST) | ||
private List<Association> lazyAssociation; | ||
} | ||
|
||
@Entity(name = "Association") | ||
static class Association { | ||
@Id | ||
private Long id; | ||
|
||
private int mutableField = 0; | ||
|
||
@Size(max = 10) | ||
@ElementCollection | ||
@CollectionTable(name = "LazyPropertiesCollection2") | ||
private List<Integer> lazyCollection; | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hibernate.isPropertyInitialized()
is documented to be a synonym forPersistenceUtil.isLoaded()
, so this should in principle not have fixed anything.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly, the issue is around uninitialized collections. So what we're trying to do here is add an additional check equivalent to
Hibernate.isInitialized(collection)
. Apparently there's an undocumented difference betweenHibernate.isPropertyInitialized()
andPersistenceUtil.isLoaded()
for that collections. At minimum we should document that properly/.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then we should maybe also change the documentation, because it's not a synonym.
The
PersistenceUnitUtil
implementation is aware of theSessionFactory
and can hence also check laziness of and initialize managed objects that are not enhanced, whereas theHibernate
class can only deal with bytecode enhanced classes.This is mostly due to the fact that checking if an attribute is actually initialized, we'd have to be able to access the value of the attribute, which we can't without the
SessionFactory
, because we don't have aPropertyAccess
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
btw, there's a built-in one that relies on the persistence utils:
https://github.com/hibernate/hibernate-validator/blob/b383125e81fa88ec5a1b4283c51edd46a1b2adfd/engine/src/main/java/org/hibernate/validator/internal/engine/resolver/JPATraversableResolver.java#L28-L53
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, with all bytecode enhancement settings currently deprecated, I don't see any uses for
Hibernate.isInitialized
anymore. I think at least a documentation update is in order, and would go as far as deprecating the method. I don't think it's possible to reimplementHibernate.isPropertyInitialized
to alignPersistenceUnitUtil.isLoaded
without significant side effects.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hibernate does not require the use of the bytecode enhancer, and most people don't use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh sorry I meant
Hibernate.isPropertyInitialized
notHibernate.isInitialized
. Without bytecode enhancement, the only time it returns unintialized (false
) is when the passed inObject entity
itself is aHibernateProxy
returned byemf.getReferenceById
, which is completely covered byHibernate.isInitialized
.