Fix: Improve handling of contains() on JPA collections mapped with @Converter to basic types#1199
Merged
velo merged 4 commits intoOpenFeign:masterfrom Jun 9, 2025
Conversation
4db5be4 to
ecc95b0
Compare
Codecov ReportAttention: Patch coverage is
✅ All tests successful. No failed tests found.
Additional details and impacted files@@ Coverage Diff @@
## master #1199 +/- ##
============================================
+ Coverage 61.21% 71.03% +9.82%
+ Complexity 84 60 -24
============================================
Files 832 858 +26
Lines 32025 32389 +364
Branches 3590 3608 +18
============================================
+ Hits 19603 23008 +3405
+ Misses 11171 8150 -3021
+ Partials 1251 1231 -20 ☔ View full report in Codecov by Sentry. |
e67fefd to
2a7f839
Compare
2a7f839 to
189aa79
Compare
velo
approved these changes
Jun 9, 2025
Comment on lines
+637
to
+641
| type, | ||
| (operator == Ops.IN | ||
| ? com.querydsl.jpa.JPQLOps.MEMBER_OF | ||
| : com.querydsl.jpa.JPQLOps.NOT_MEMBER_OF), | ||
| args); |
Member
There was a problem hiding this comment.
can we avoid this code style change?
Contributor
Author
There was a problem hiding this comment.
Thank you for the feedback.
I’ve reverted the code style change to keep it consistent with the original formatting. 🙇♂️
34c7948 to
0b7cd58
Compare
velo
added a commit
that referenced
this pull request
Jun 9, 2025
* Add `TypeWrapperFactoryExpression` for Type-Safe Custom Number Mapping in Querydsl Aggregations (#1181) * Introduce TypeWrapper to wrap Expression results into custom types - Implement TypeWrapper<S, T> as a FactoryExpression - Allows converting a source Expression (e.g. BigDecimal) to a domain type (e.g. Money) - Prepares QueryDSL core to support custom aggregation projections without core API changes * Add JPAQueryCustomTypeWrapperTest covering success and failure scenarios - Verify IllegalArgumentException for unsupported custom types without wrapper - Verify sum-and-wrap to Money via TypeWrapper in both direct and DTO projection use cases - Ensure both positive and negative paths are exercised * Fix: Improve handling of `contains()` on JPA collections mapped with `@Converter` to basic types (#1199) * Fix: Early error for .contains() on @converter mapped JPA collections * Test : add test code * Add test cases to improve test coverage * style: revert formatting change based on review feedback for consistency * fix: correct escape & anchor handling in likeToRegex/regexToLike (#1174) * fix: correct escape & anchor handling in likeToRegex/regexToLike - treat \% \_ as literals, escape regex metachars, strip outer ^/$ only - add unit tests to guarantee accurate LIKE ↔ regex round-trips * Fix code format Signed-off-by: Marvin Froeder <[email protected]> --------- Signed-off-by: Marvin Froeder <[email protected]> Co-authored-by: Marvin Froeder <[email protected]> --------- Signed-off-by: Marvin Froeder <[email protected]> Co-authored-by: 차동민 <[email protected]> Co-authored-by: renechoi <[email protected]> Co-authored-by: Marvin Froeder <[email protected]>
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Related Issue(s): #173 querydsl/querydsl , #377 openfeign/querydsl
Problem Description
When using QueryDSL's
collection.contains(element)method on a JavaCollection-typed entity attribute that is mapped to a basic database type (e.g.,String,TEXT) via a JPA@Converter, QueryDSL currently generates a standard JPQLMEMBER OFclause (often via an internalOps.INtoJPQLOps.MEMBER_OFtranslation).Historically, some older Hibernate versions might have been less strict in validating this JPQL against such converted attributes, leading some users (like H-Lo in issue #3618, #377) to believe this pattern "worked." However, as pointed out by contributors like OrangeDog, and in line with JPA principles, an attribute converted to a basic type is not treated as a queryable "plural path" by the persistence provider for standard collection operators like
MEMBER OF.Consequently, modern JPA providers, especially Hibernate 6 and later with its stricter validation, now consistently reject this JPQL, resulting in a runtime
org.hibernate.query.SemanticException: Operand of 'member of' operator must be a plural path. This exception, often wrapped, originates глубоко from the ORM layer, making it less obvious to users why their QueryDSL code, which appears type-safe, is failing.Justification for This Change in QueryDSL
While some users (like H-Lo) might have previously found workarounds or experienced a more lenient behavior from older Hibernate versions, the current stricter validation by Hibernate is aligned with JPA's interpretation of how
@Converterto a basic type alters an attribute's nature for querying standard collection operations. The core issue is that QueryDSL currently generates JPQL that is predictably incompatible with how JPA providers handle such converted collections forMEMBER OFoperations.This PR proposes that QueryDSL should proactively address this known incompatibility for the following reasons:
Enhanced User Experience & Clearer Diagnostics (Fail-Fast):
SemanticExceptionat runtime, QueryDSL can provide an immediate, clear, and QueryDSL-specificQueryException.@Converteron a collection to a basic type) and guide the user towards understanding the limitation and considering alternatives (like native queries for DB-specific functions or re-evaluating their mapping if collection querying is crucial).Improved Abstraction and Predictability:
Alignment with Stricter ORM Validations:
Guidance Over Silent Failure or Unexpected Behavior:
MEMBER OFoperation on a TEXT column (without database-specific functions) ever produced the semantically correct SQL for a true collection membership check. It might have "not failed" but not worked as truly intended either.containscheck.This change is not about restoring a "broken" functionality that was once correctly supported, but rather about QueryDSL taking responsibility for generating valid and semantically appropriate queries for the given context, or clearly indicating when it cannot. This PR opts for the latter by providing explicit, early feedback.
Solution Implemented
This PR modifies the
JPQLSerializer(specifically thevisitAnyInPathmethod, which is involved whencollection.contains()is translated from anOps.INoperation on a collection path) in thequerydsl-jpamodule.The changes are as follows:
Detection of Problematic Mapping:
isCollectionPathWithConverterToBasicType(Path<?> path, jakarta.persistence.metamodel.Metamodel metamodel), uses the JPAMetamodelto determine if a given QueryDSLPath<?>corresponds to an entity attribute that is a JavaCollectionbut whosePersistentAttributeTypeisBASIC(indicating a JPA@Converterto a basic database type).Early Exception (Fail-Fast):
visitAnyInPath, before callingsuper.visitOperationto generate theMEMBER OFclause, this check is performed.com.querydsl.core.QueryExceptionwith an informative message.Normal Behavior for Valid Cases:
Benefits of the Change
How to Test
JPAQueryConverterCollectionContainsTest.java, has been added.Set<UserRoleInTest>attribute mapped using a custom@Converter(to a comma-delimited String).rolesContains_ShouldThrowQueryDSLException_WhenConverterIsUsedOnCollectionasserts that a.contains()query on this path now throws the newcom.querydsl.core.QueryExceptionwith the expected message.Possible Future Enhancements (Out of Scope for this PR)
While this PR focuses on better diagnostics, future work could explore support for querying such converted collections via database-specific functions, if a generic and maintainable approach within QueryDSL is feasible. This PR provides the immediate benefit of clear error reporting for currently unsupported standard operations.