Skip to content

Commit ae27e23

Browse files
committed
Provide more expressive exception for projections without properties.
Closes #1813
1 parent 89093cf commit ae27e23

File tree

4 files changed

+67
-13
lines changed

4 files changed

+67
-13
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
3434
import org.springframework.data.relational.core.query.Criteria;
3535
import org.springframework.data.relational.core.sql.Column;
36+
import org.springframework.data.relational.core.sql.EmptySelectListException;
3637
import org.springframework.data.relational.core.sql.Expression;
3738
import org.springframework.data.relational.core.sql.Expressions;
3839
import org.springframework.data.relational.core.sql.Functions;
@@ -177,13 +178,23 @@ protected ParametrizedQuery complete(@Nullable Criteria criteria, Sort sort) {
177178
completedBuildSelect = selectOrderBuilder.lock(this.lockMode.get().value());
178179
}
179180

180-
Select select = completedBuildSelect.build();
181+
Select select = getSelect(completedBuildSelect);
181182

182183
String sql = SqlRenderer.create(renderContextFactory.createRenderContext()).render(select);
183184

184185
return new ParametrizedQuery(sql, parameterSource);
185186
}
186187

188+
private Select getSelect(SelectBuilder.BuildSelect completedBuildSelect) {
189+
190+
try {
191+
return completedBuildSelect.build();
192+
} catch (EmptySelectListException cause) {
193+
throw new IllegalStateException(
194+
returnedType.getReturnedType().getName() + " does not define any properties to select", cause);
195+
}
196+
}
197+
187198
SelectBuilder.SelectOrdered applyOrderBy(Sort sort, RelationalPersistentEntity<?> entity, Table table,
188199
SelectBuilder.SelectOrdered selectOrdered) {
189200

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
import org.junit.jupiter.params.ParameterizedTest;
4242
import org.junit.jupiter.params.provider.Arguments;
4343
import org.junit.jupiter.params.provider.MethodSource;
44-
4544
import org.springframework.beans.factory.annotation.Autowired;
4645
import org.springframework.beans.factory.config.PropertiesFactoryBean;
4746
import org.springframework.context.ApplicationListener;
@@ -51,16 +50,7 @@
5150
import org.springframework.core.io.ClassPathResource;
5251
import org.springframework.dao.IncorrectResultSizeDataAccessException;
5352
import org.springframework.data.annotation.Id;
54-
import org.springframework.data.domain.Example;
55-
import org.springframework.data.domain.ExampleMatcher;
56-
import org.springframework.data.domain.Limit;
57-
import org.springframework.data.domain.Page;
58-
import org.springframework.data.domain.PageRequest;
59-
import org.springframework.data.domain.Pageable;
60-
import org.springframework.data.domain.ScrollPosition;
61-
import org.springframework.data.domain.Slice;
62-
import org.springframework.data.domain.Sort;
63-
import org.springframework.data.domain.Window;
53+
import org.springframework.data.domain.*;
6454
import org.springframework.data.jdbc.core.mapping.AggregateReference;
6555
import org.springframework.data.jdbc.repository.query.Modifying;
6656
import org.springframework.data.jdbc.repository.query.Query;
@@ -572,6 +562,24 @@ public void partTreeQueryProjectionShouldReturnProjectedEntities() {
572562
assertThat(result.get(0).getName()).isEqualTo("Entity Name");
573563
}
574564

565+
@Test // GH-1813
566+
public void partTreeQueryDynamicProjectionShouldReturnProjectedEntities() {
567+
568+
repository.save(createDummyEntity());
569+
570+
List<DummyProjection> result = repository.findDynamicProjectedByName("Entity Name", DummyProjection.class);
571+
572+
assertThat(result).hasSize(1);
573+
assertThat(result.get(0).getName()).isEqualTo("Entity Name");
574+
}
575+
576+
@Test // GH-1813
577+
public void partTreeQueryDynamicProjectionWithBrokenProjectionShouldError() {
578+
579+
assertThatThrownBy(() -> repository.findDynamicProjectedByName("Entity Name", BrokenProjection.class))
580+
.hasMessageContaining("BrokenProjection does not define any properties to select");
581+
}
582+
575583
@Test // GH-971
576584
public void pageQueryProjectionShouldReturnProjectedEntities() {
577585

@@ -1428,6 +1436,8 @@ interface DummyEntityRepository extends CrudRepository<DummyEntity, Long>, Query
14281436

14291437
List<DummyProjection> findProjectedByName(String name);
14301438

1439+
<T> List<T> findDynamicProjectedByName(String name, Class<T> projection);
1440+
14311441
@Query(value = "SELECT * FROM DUMMY_ENTITY", rowMapperClass = CustomRowMapper.class)
14321442
List<DummyEntity> findAllWithCustomMapper();
14331443

@@ -1941,6 +1951,10 @@ interface DummyProjection {
19411951
String getName();
19421952
}
19431953

1954+
interface BrokenProjection {
1955+
String name();
1956+
}
1957+
19441958
static final class DtoProjection {
19451959
private final String name;
19461960

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.data.relational.core.sql;
18+
19+
/**
20+
* Exception denoting the absence of a select list from a query.
21+
*
22+
* @author Jens Schauder
23+
* @since 3.4
24+
*/
25+
public class EmptySelectListException extends IllegalStateException {
26+
public EmptySelectListException() {
27+
super("SELECT does not declare a select list");
28+
}
29+
}

spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ private void doValidate(Select select) {
5454
select.visit(this);
5555

5656
if (selectFieldCount == 0) {
57-
throw new IllegalStateException("SELECT does not declare a select list");
57+
throw new EmptySelectListException();
5858
}
5959

6060
for (TableLike table : requiredBySelect) {

0 commit comments

Comments
 (0)