Skip to content
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

Use tuple queries for projections using QueryDSL and Query by Example #2600

Open
kekhuai opened this issue Jul 18, 2022 · 3 comments
Open

Use tuple queries for projections using QueryDSL and Query by Example #2600

kekhuai opened this issue Jul 18, 2022 · 3 comments
Assignees
Labels
in: repository Repositories abstraction type: enhancement A general enhancement

Comments

@kekhuai
Copy link

kekhuai commented Jul 18, 2022

I'm implementing a dynamic query with following code.

userRepository.findBy(QUser.user.id.isNotNull(), q -> q.project("username").as(UserWithUsernameOnly.class).all());

But the generated query still select all of the columns.

select user0_.id as id1_0_, user0_.create_date as create_d2_0_, user0_.created_by as created_3_0_, user0_.last_modified_by as last_mod4_0_, user0_.last_modified_date as last_mod5_0_, user0_.password as password6_0_, user0_.username as username7_0_, user0_.version as version8_0_ from users user0_ where user0_.id is not null

I expect the generated query should only select the specified column.

Here is the reproducible repo.
https://github.com/kekhuay/special-palm-tree

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jul 18, 2022
@mp911de mp911de transferred this issue from spring-projects/spring-data-commons Jul 19, 2022
@gregturn
Copy link
Contributor

gregturn commented Mar 28, 2023

I have certainly reproduced this issue. To be clear, the query works. It simply doesn't slim things down to the subset of columns expected.

I tested against both H2 as well as Postgres with the following test case:

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = FetchableFluentQueryByPredicateIntegrationTests.Config.class)
@Transactional
public class FetchableFluentQueryByPredicateIntegrationTests {

	@Autowired FetchableFluentRepository repository;

	@BeforeEach
	void setUp() {
		repository.saveAndFlush(new User("Bilbo", "Baggins", "[email protected]"));
	}

	@Test
	void projectionsOnDtoClassesShouldHaveAReducedProjectionInTheQuery() {

		List<UserDto> users = repository.findBy(QUser.user.firstname.eq("Bilbo"), p -> p //
				.project("firstname") //
				.as(UserDto.class) //
				.all());

		assertThat(users).extracting(UserDto::getFirstname).containsExactly("Bilbo");
	}

	@Test
	void projectionsOnEntitiesShouldHaveAReducedProjectionInTheQuery() {

		List<User> users = repository.findBy(QUser.user.firstname.eq("Bilbo"), p -> p //
				.project("firstname") //
				.all());

		assertThat(users).extracting(User::getFirstname).containsExactly("Bilbo");
	}

	public interface FetchableFluentRepository extends JpaRepository<User, Long>, QuerydslPredicateExecutor<User> {

	}

	public interface UserDto {

		String getFirstname();
	}

	@EnableJpaRepositories(considerNestedRepositories = true, basePackageClasses = FetchableFluentRepository.class, //
			includeFilters = @ComponentScan.Filter(value = { FetchableFluentRepository.class },
					type = FilterType.ASSIGNABLE_TYPE))
	@EnableTransactionManagement
	static class Config {

		@Bean(initMethod = "start", destroyMethod = "stop")
		public PostgreSQLContainer<?> container() {

			return new PostgreSQLContainer<>("postgres:9.6.12") //
					.withUsername("postgres");
		}

		@Bean
		public DataSource dataSource(PostgreSQLContainer<?> container) {

			PGSimpleDataSource dataSource = new PGSimpleDataSource();
			dataSource.setUrl(container.getJdbcUrl());
			dataSource.setUser(container.getUsername());
			dataSource.setPassword(container.getPassword());
			return dataSource;
		}

		@Bean
		public AbstractEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {

			LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
			factoryBean.setDataSource(dataSource);
			factoryBean.setPersistenceUnitRootLocation("simple-persistence");
			factoryBean.setPersistenceUnitName("spring-data-jpa");
			factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());

			Properties properties = new Properties();
			properties.setProperty("hibernate.hbm2ddl.auto", "create");
			properties.setProperty("hibernate.dialect", PostgreSQL91Dialect.class.getCanonicalName());
			factoryBean.setJpaProperties(properties);

			return factoryBean;
		}

		@Bean
		PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
			return new JpaTransactionManager(entityManagerFactory);
		}
	}
}

In both scenarios, first on H2 (before adding the Testcontainers chunk now shown) as well as on Postgres (see above), I saw this query:

2023-03-28 09:05:55,297 DEBUG                        org.hibernate.SQL: 128 - select u1_0.id,u1_0.DTYPE,u1_0.active,u1_0.city,u1_0.country,u1_0.streetName,u1_0.streetNo,u1_0.age,u1_0.binaryData,u1_0.createdAt,u1_0.dateOfBirth,u1_0.emailAddress,u1_0.firstname,u1_0.lastname,u1_0.manager_id from SD_User u1_0 where u1_0.firstname=?
2023-03-28 09:05:55,297 TRACE              org.hibernate.orm.jdbc.bind:  28 - binding parameter [1] as [VARCHAR] - [Bilbo]

This while seeing the hint applied (by design) of:

Screen Shot 2023-03-28 at 9 03 13 AM

All that being said, this is a JPA hint, which means it's not a hard requirement that the persistence provider support this hint. I'm trying to dig in and see if we're doing the hint wrong, or if in reality, these persistence providers simply don't honor it.

@gregturn gregturn added for: team-attention An issue we need to discuss as a team to make progress in: repository Repositories abstraction type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Mar 28, 2023
@gregturn
Copy link
Contributor

I also flipped to MySQLContainer, and got the same results.

2023-03-28 09:19:12,231 DEBUG                        org.hibernate.SQL: 128 - select u1_0.id,u1_0.DTYPE,u1_0.active,u1_0.city,u1_0.country,u1_0.streetName,u1_0.streetNo,u1_0.age,u1_0.binaryData,u1_0.createdAt,u1_0.dateOfBirth,u1_0.emailAddress,u1_0.firstname,u1_0.lastname,u1_0.manager_id from SD_User u1_0 where u1_0.firstname=?
2023-03-28 09:19:12,232 TRACE              org.hibernate.orm.jdbc.bind:  28 - binding parameter [1] as [VARCHAR] - [Bilbo]

@mp911de
Copy link
Member

mp911de commented Jul 13, 2023

Query by Example uses specifications that select the Root. For projections we need to resort to tuple queries, including a proper selection:

CriteriaQuery<Tuple> query = criteriaBuilder.createTupleQuery()

query.select(criteriaBuilder.tuple(employee.get(Employee_.name), employee.get(Employee_.salary)));

Related: #487

@mp911de mp911de removed the for: team-attention An issue we need to discuss as a team to make progress label Jul 13, 2023
@mp911de mp911de changed the title Projection with QueryDSL does not work Use tuple queries for projections using QueryDSL and Query by Example Aug 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: repository Repositories abstraction type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

4 participants