Skip to content

Projection fails when using subquery [DATAJPA-1713] #2008

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

Closed
spring-projects-issues opened this issue Apr 15, 2020 · 7 comments
Closed

Projection fails when using subquery [DATAJPA-1713] #2008

spring-projects-issues opened this issue Apr 15, 2020 · 7 comments
Assignees
Labels
in: query-parser Everything related to parsing JPQL or SQL type: enhancement A general enhancement

Comments

@spring-projects-issues
Copy link

Manu VIDAL opened DATAJPA-1713 and commented

When using a subquery in query defined with @Query, JPA projection fails.
Return type is not of projection class but

org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap**

Example :

@Entity
public class Employee {
   @Id
   @GeneratedValue(strategy=GenerationType.AUTO)
   private Long id;
   private String firstName;
   private String lastName;
   private Integer salary;

   // Constructors, getters and setters
}

Projection class :

public interface EmployeeProjection {

    public Long getId();

    public String getFirstName();
 
    //@Value("#{target.firstName + ' ' + target.lastName}") 
    //String getFullName();
}

Repo :

public interface EmployeeRepository extends CrudRepository<Employee, Long> {
   @Query("select c from Employee c where c.salary >= "
          //+ "500"
          + "(select avg(salary) from Employee )"
   )
   List<EmployeeProjection> findProjections();
}

When using condition "c.salary >= 500", method returns a correct result, i.e. a List<EmployeeProjection>.

But when using a subquery "c.salary >= (select avg(salary) from Employee)", method returns a list of AbstractJpaQuery$TupleConverter$TupleBackedMap.

On this object, calling any getter returns null.
Using @Value (open projection) throws an exception.

Example sources are attached.
Thanks in advance.

 


Affects: 2.2.6 (Moore SR6)

Attachments:

Referenced from: pull request #420

@spring-projects-issues
Copy link
Author

Jens Schauder commented

Thanks for raising the issue. Based on your reproducer I was able to add a test demonstrating the issue.

I haven't checked, but I assume this is due to the parser not being able to properly handle the subselect and therefore thinking the select returns single fields.

This might actually be a valid workaround: instead of returning an entity: return the fields of the entity required for the projection.

@spring-projects-issues
Copy link
Author

Manu VIDAL commented

Ok, thanks Jens Schauder for your quick response.

I'v tested this Query : @Query("select c.id as id, c.firstName as firstName from Employee c [...]") and it works.
But i have to alias all the properties corresponding to the projection.

I've however another problem : if I have a nested projection, i.e. a @onetomany Set property on class Employee, and a List getDepartments() in EmployeeProjection, how can I write my Query ?

I point out that it works if @Query is "select c from Employee c"

Thanks

@spring-projects-issues
Copy link
Author

Jens Schauder commented

If you want to create a complex object from a SQL query with JPA, you have to use ResultSet mapping. https://thoughts-on-java.org/result-set-mapping-basics/

@spring-projects-issues
Copy link
Author

Manu VIDAL commented

Sorry Jens Schauder I think I expressed myself poorly. I've added a second project demo2.zip to illustrate my test case.

First method of repository works fine and retrieves 1 employee and its 2 jobs.

Second method retrieves bad type and wrong numbers of results.
In debug, we can see that tree representation of result is quite strange : data is present but duplicated and hard to extract from TupleBackedMap.

So ... is there a workaround for this precise case ?

Thanks again for your help

@jamesdh
Copy link

jamesdh commented Dec 9, 2022

Likewise, seeing something similar when using a Postgres jsonb function to try and render a jsonb array of objects as a list of entity types, e,g...

@Query(
    nativeQuery = true,
    value = """
        SELECT
            x.path,
            x.base64
        FROM
            claim c,
            jsonb_to_recordset(c.files) as x(path varchar, base64 varchar)
        WHERE c.id = :id
    """)
List<SupportFile> findFiles(@RequestParam UUID id);

@gregturn
Copy link
Contributor

If this involves native queries and JSONB functions on the database provider, this may have gone into something too complex for Spring Data JPA to support. Your best best moving forward may be to implement a custom implementation. Check out https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations for more details on how to hook such a solution into your repository.

@gregturn gregturn closed this as not planned Won't fix, can't repro, duplicate, stale Apr 28, 2023
@gregturn gregturn assigned schauder and gregturn and unassigned schauder Apr 28, 2023
@gregturn gregturn added status: declined A suggestion or change that we don't feel we should currently apply type: enhancement A general enhancement and removed type: bug A general bug labels Apr 28, 2023
@gregturn gregturn reopened this May 31, 2023
@gregturn gregturn added in: query-parser Everything related to parsing JPQL or SQL and removed status: declined A suggestion or change that we don't feel we should currently apply labels May 31, 2023
@gregturn
Copy link
Contributor

The original reporter on this ticket had a problem with:

@Query("select c from Employee c where c.salary >= (select avg(salary) from Employee )")
List<EmployeeProjection> findProjections();

This is non-native query that involves a subselect and projections. @schauder coded a simple reproducer of the same issue that fits into our existing test machinery:

// DATAJPA-1713
@Query("select u from User u where firstname >= (select Min(u0.firstname) from User u0 )")
List<NameOnly> findProjectionBySubselect();


@Test // DATAJPA-1713
public void selectProjectionWithSubselect() {

	List<UserRepository.NameOnly> list = userRepository.findProjectionBySubselect();
	assertThat(list).isNotEmpty() //
			.allSatisfy(no -> {

				assertThat(no.getFirstname()).describedAs("firstname").isNotNull();
				assertThat(no.getLastname()).describedAs("lastname").isNotNull();
			});
}

This test scenario is overcome by the new query parser and passes.

NOTE: This ticket was closed due to an unrelated native query, for which the query parser does NOT get applied, so that's why it's reopened.

@gregturn gregturn linked a pull request May 31, 2023 that will close this issue
gregturn added a commit that referenced this issue May 31, 2023
Interface-based projections used to NOT work when the query had a subquery in it. But with the new query parser, it already handles it. So this simply captures the test providing that corner case.

See #2008
Original Pull Request: #420
gregturn added a commit that referenced this issue May 31, 2023
Interface-based projections used to NOT work when the query had a subquery in it. But with the new query parser, it already handles it. So this simply captures the test providing that corner case.

See #2008
Original Pull Request: #420
gregturn added a commit that referenced this issue Jun 1, 2023
Interface-based projections used to NOT work when the query had a subquery in it. But with the new query parser, it already handles it. So this simply captures the test providing that corner case.

See #2008
Original Pull Request: #420
gregturn added a commit that referenced this issue Jun 1, 2023
Interface-based projections used to NOT work when the query had a subquery in it. But with the new query parser, it already handles it. So this simply captures the test providing that corner case.

See #2008
Original Pull Request: #420
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: query-parser Everything related to parsing JPQL or SQL type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants