-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
Hi, specifications with sort creates additional join even though the entity was already fetched. The result is that the table is then left joined twice. Consider setup:
entities
@Entity
public class A {
@Id
Long id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "b_id")
B b;
}
@Entity
public class B {
@Id
Long id
String name
}
specifications
Specification<A> specification =
(root, query, builder) -> {
query.distinct(true);
root.fetch(A_.b, JoinType.LEFT);
return builder.equal(root.get(A_.id), 1L);
};
Sort sort = Sort.by(Sort.Direction.ASC, "b.name");
aRepository.findAll(specification, sort);
then
List<T> findAll(Specification<T> spec, Sort sort);
creates SQL query
select distinct A1.id, A1.b_id, B2.id, B2.name
from A A1
left join B B1 on A1.b_id = B1.id
left join B B2 on A1.b_id = B2.id
where A.id = 1
order by B1.name asc
B entity is joined twice. If the query is distinct, then it fails with SQL error
Expression #1 of ORDER BY clause is not in SELECT list, references column ... B1.name ... which is not in SELECT list; this is incompatible with DISTINCT
The SQL error in this case is correct, order by clause is not in select list. The sort should not create additional join if the entity was already fetched or joined.
The commit that introduced this behaviour is 43d1438
Previously, it checked whether there already is a fetched entity (isAlreadyFetched() method) if yes then it didn’t create additional join (getOrCreateJoin() method). The logic was changed to check whether the entity was already inner-joined. But even if I change the join type in specification to JoinType.INNER (root.fetch(A_.b, JoinType.INNER);) it still creates additional join.
Possible duplicate of #2206
Activity
Created a test reproducing the situation described by the OP.
schauder commentedon Jul 14, 2021
This behaviour is actually correct.
The real problem is that a
Specificationis really a (leaky) abstraction over a WHERE-clause.By using
fetchanddistinctyou are not only affecting the WHERE-clause though, but also the projection, i.e. the SELECT clause.This leads to confusion and ultimately errors.
The problem with a
JOIN FETCHis, it isn't really a join.From.getJoins()doesn't return it, which makes sense, since it must not be referenced anywhere in the rest of the statement.From the Java Persistence API, Version 2.2 Section 4.4.5.3 Fetch Joins
This talks about JPQL but I think it is save to assume the equivalent holds true for the Criteria-API used by a
Specification.This means the fetch-join must not and can not be used in a WHERE-clause or ORDER-BY-clause.
Therefore Spring Data JPA has to create an additional JOIN for that purpose.
This of course clashes on the SQL level with the
DISTINCTsince it basically ignores the additional JOIN and therefore it can't be used in the ORDER-BY-clause.There is a good Stack Overflow answer regarding this topic.
michalkrajcovic commentedon Jul 14, 2021
@schauder considering the entities described in this issue, if I create a specification
please note the equal predicate
then
then creates SQL
fetch-join is used in the WHERE clause. No additional join is created.
based on your statement
Are you sayin that observed behaviour is a bug and additional join should have been created?
schauder commentedon Jul 15, 2021
According to my understanding of the specification this is indeed wrong behaviour.
It results in an
Abeing loaded without theB.I might compromise on "inconsistent behaviour" since if the join is reused by a predicate it should be returned by
From.getJoins().I reopen the issue.
I still think it is not us to fix it, but we should create a proper JPA based reproducer so we can ask the JPA maintainers and implementors for their opinion.
1 remaining item
Added query fetches search for join existence when applying JPA order…
landsman commentedon Jul 12, 2022
Thanks for the issue.
This would help us a lot currently 🙏
bean-ben-heb commentedon Dec 22, 2023
+1
trying to use distinct with join fetch and order by a field in the join but the order by is wrong it that it isn't the table that was fetched but instead a duplicate join that's not in the select
Here is what is produced
but i want
ivan-lagunas commentedon Feb 6, 2024
Did you find any solution to this?
bean-ben-heb commentedon Feb 6, 2024
I had to right my own specification for this whole thing and override my Spring JPA repository handling of the query
without much context, here is the method i wrote
Gianpiero-Errigo commentedon May 19, 2025
I suspect my issue is related to this ticket:
basically I got error when using JOIN FETCH in specifications for a paginated result.
Exactly like in this post
https://stackoverflow.com/questions/61637545/how-to-solve-the-n1-problem-in-spring-data-jpa
It fails to issue the count query with
Is there any sane solution to get around this problem? It sounds quite basic to me, and I find strange it even shows up (maybe I'm doing something wrong?)
Wouldn't keeping out the fetch for count query as suggested in that SO bring to wrong count if the joined entities are used for some filtering?
This is my simple spec that fails when used for a paginated findAll from JpaSimpleRepository