-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
Description
When using @Query with nativeQuery = true and Pageable on queries containing derived tables (subqueries in the FROM clause), Spring Data JPA generates incorrect count queries for pagination, resulting in SQL syntax errors.
Versions
- Spring Boot: 3.5.7
- Spring Data JPA: 3.5.5
- Hibernate: 6.6.33.Final
- Database: MySQL
Current Behavior
The COUNT_MATCH regex pattern in QueryUtils.createCountQueryFor() incorrectly parses native queries with derived tables by matching the FROM keyword inside the subquery instead of the outer query's FROM clause.
Example Query:
select result.order_id, result.latest_activity
from (
select orders.order_id,
orders.item_id,
max(activities.activity_date) as latest_activity
from order_members
inner join activities on order_members.order_id = activities.order_id
where order_members.member_id = ?1
group by orders.id
) result
where result.latest_activity < ?2Repository Method:
@Query(value = "...", nativeQuery = true)
Page<Object[]> findOrderActivities(long memberId, Date activityDate, Pageable pageable);Error:
org.springframework.dao.InvalidDataAccessResourceUsageException:
JDBC exception executing SQL [select result.order_id, result.latest_activity from (
select orders.order_id from order_members
group by orders.id]
[You have an error in your SQL syntax; check the manual that
corresponds to your MySQL server version for the right syntax
to use near '' at line 7]
The generated query is truncated, missing:
- The closing parenthesis of the subquery
- The outer FROM alias (result)
- The outer WHERE clause
- Any ORDER BY clauses
Expected Behavior
The count query should be correctly generated, either by:
- Properly parsing the entire query structure, or
- Wrapping the entire query in a subquery:
SELECT COUNT(*) FROM (original_query) subquery_aliasRoot Cause Analysis
In QueryUtils.java, line 626:
Matcher matcher = COUNT_MATCH.matcher(originalQuery);The COUNT_MATCH pattern does not account for derived tables and matches the first FROM keyword it encounters, which is inside the subquery. This causes:
- Incorrect alias detection (e.g., detecting group as the alias instead of result)
- Malformed count query generation
- Query string truncation
Steps to Reproduce
- Create a repository method with a native query containing a derived table
- Use Pageable parameter
- Execute the query
- Observe SQL syntax error
Minimal Reproducible Example:
@Repository
public interface TestRepository extends JpaRepository<Entity, Long> {
@Query(value = """
select u.id, u.name
from (
select id, name from users where active = 1
) u
""", nativeQuery = true)
Page<Object[]> findActiveUsers(Pageable pageable);
}Additional Context
This issue did not occur in Spring Boot 2.7.x but started appearing after upgrading to Spring Boot 3.x.
The workaround is to manually specify a countQuery:
@Query(value = "...",
countQuery = "SELECT COUNT(*) FROM (original_query) subquery_alias",
nativeQuery = true)However, this is verbose and error-prone when the query changes.