Skip to content

Commit e07e53e

Browse files
mp911deodrotbohm
authored andcommitted
DATAJPA-920 - Add support for exists projection in repository query derivation.
We now support exists projections in derived queries. Queries select the primary key using tuple queries limiting the result to the first row. interface UserRepository extends Repository<User, Long> { boolean existsByFirstname(String firstname); } Original pull request: #176.
1 parent 6afdaa3 commit e07e53e

12 files changed

+192
-9
lines changed

src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2008-2015 the original author or authors.
2+
* Copyright 2008-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,13 +15,15 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18+
import static org.springframework.data.jpa.domain.AbstractPersistable_.*;
1819
import static org.springframework.data.jpa.repository.query.QueryUtils.*;
1920
import static org.springframework.data.repository.query.parser.Part.Type.*;
2021

2122
import java.util.ArrayList;
2223
import java.util.Collection;
2324
import java.util.Iterator;
2425
import java.util.List;
26+
import java.util.Set;
2527

2628
import javax.persistence.criteria.CriteriaBuilder;
2729
import javax.persistence.criteria.CriteriaQuery;
@@ -30,6 +32,7 @@
3032
import javax.persistence.criteria.Predicate;
3133
import javax.persistence.criteria.Root;
3234
import javax.persistence.criteria.Selection;
35+
import javax.persistence.metamodel.SingularAttribute;
3336

3437
import org.springframework.data.domain.Sort;
3538
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
@@ -45,6 +48,7 @@
4548
* Query creator to create a {@link CriteriaQuery} from a {@link PartTree}.
4649
*
4750
* @author Oliver Gierke
51+
* @author Mark Paluch
4852
*/
4953
public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extends Object>, Predicate> {
5054

@@ -53,6 +57,7 @@ public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extend
5357
private final CriteriaQuery<? extends Object> query;
5458
private final ParameterMetadataProvider provider;
5559
private final ReturnedType returnedType;
60+
private final PartTree tree;
5661

5762
/**
5863
* Create a new {@link JpaQueryCreator}.
@@ -66,6 +71,7 @@ public JpaQueryCreator(PartTree tree, ReturnedType type, CriteriaBuilder builder
6671
ParameterMetadataProvider provider) {
6772

6873
super(tree);
74+
this.tree = tree;
6975

7076
CriteriaQuery<? extends Object> criteriaQuery = createCriteriaQuery(builder, type);
7177

@@ -87,7 +93,7 @@ protected CriteriaQuery<? extends Object> createCriteriaQuery(CriteriaBuilder bu
8793

8894
Class<?> typeToRead = type.getTypeToRead();
8995

90-
return typeToRead == null ? builder.createTupleQuery() : builder.createQuery(typeToRead);
96+
return (typeToRead == null || tree.isExistsProjection()) ? builder.createTupleQuery() : builder.createQuery(typeToRead);
9197
}
9298

9399
/**
@@ -162,6 +168,24 @@ protected CriteriaQuery<? extends Object> complete(Predicate predicate, Sort sor
162168
}
163169

164170
query = query.multiselect(selections);
171+
} else if (tree.isExistsProjection()) {
172+
173+
if (root.getModel().hasSingleIdAttribute()) {
174+
175+
SingularAttribute<?, ?> id = root.getModel().getId(root.getModel().getIdType().getJavaType());
176+
query = query.multiselect(root.get((SingularAttribute) id).alias(id.getName()));
177+
} else {
178+
179+
List<Selection<?>> selections = new ArrayList<Selection<?>>();
180+
181+
Set<SingularAttribute<?, ?>> idClassAttributes = (Set<SingularAttribute<?, ?>>) root.getModel().getIdClassAttributes();
182+
183+
for (SingularAttribute<?, ?> attribute : idClassAttributes) {
184+
selections.add(root.get((SingularAttribute) attribute).alias(attribute.getName()));
185+
}
186+
selections.add(root.get((SingularAttribute) id).alias(id.getName()));
187+
query = query.multiselect(selections);
188+
}
165189
} else {
166190
query = query.select((Root) root);
167191
}
@@ -172,7 +196,7 @@ protected CriteriaQuery<? extends Object> complete(Predicate predicate, Sort sor
172196

173197
/**
174198
* Creates a {@link Predicate} from the given {@link Part}.
175-
*
199+
*
176200
* @param part
177201
* @param root
178202
* @param iterator

src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,20 @@ protected Object doExecute(AbstractJpaQuery jpaQuery, Object[] values) {
276276
}
277277
}
278278

279+
/**
280+
* {@link Execution} performing an exists check on the query.
281+
*
282+
* @author Mark Paluch
283+
* @since 1.11
284+
*/
285+
static class ExistsExecution extends JpaQueryExecution {
286+
287+
@Override
288+
protected Object doExecute(AbstractJpaQuery query, Object[] values) {
289+
return !query.createQuery(values).getResultList().isEmpty();
290+
}
291+
}
292+
279293
/**
280294
* {@link Execution} executing a stored procedure.
281295
*

src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.data.domain.Sort;
2727
import org.springframework.data.jpa.provider.PersistenceProvider;
2828
import org.springframework.data.jpa.repository.query.JpaQueryExecution.DeleteExecution;
29+
import org.springframework.data.jpa.repository.query.JpaQueryExecution.ExistsExecution;
2930
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
3031
import org.springframework.data.repository.query.ParametersParameterAccessor;
3132
import org.springframework.data.repository.query.ResultProcessor;
@@ -94,7 +95,14 @@ public TypedQuery<Long> doCreateCountQuery(Object[] values) {
9495
*/
9596
@Override
9697
protected JpaQueryExecution getExecution() {
97-
return this.tree.isDelete() ? new DeleteExecution(em) : super.getExecution();
98+
99+
if(this.tree.isDelete()) {
100+
return new DeleteExecution(em);
101+
} else if(this.tree.isExistsProjection()) {
102+
return new ExistsExecution();
103+
}
104+
105+
return super.getExecution();
98106
}
99107

100108
/**
@@ -168,6 +176,10 @@ private Query restrictMaxResultsIfNecessary(Query query) {
168176
query.setMaxResults(tree.getMaxResults());
169177
}
170178

179+
if(tree.isExistsProjection()) {
180+
query.setMaxResults(1);
181+
}
182+
171183
return query;
172184
}
173185

src/test/java/org/springframework/data/jpa/domain/sample/EmbeddedIdExampleEmployee.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2104 the original author or authors.
2+
* Copyright 2013-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323

2424
/**
2525
* @author Thomas Darimont
26+
* @author Mark Paluch
2627
*/
2728
@Entity
2829
public class EmbeddedIdExampleEmployee {
@@ -33,6 +34,8 @@ public class EmbeddedIdExampleEmployee {
3334
@ManyToOne(cascade = CascadeType.ALL)//
3435
EmbeddedIdExampleDepartment department;
3536

37+
String name;
38+
3639
public EmbeddedIdExampleEmployeePK getEmployeePk() {
3740
return employeePk;
3841
}
@@ -48,4 +51,12 @@ public EmbeddedIdExampleDepartment getDepartment() {
4851
public void setDepartment(EmbeddedIdExampleDepartment department) {
4952
this.department = department;
5053
}
54+
55+
public String getName() {
56+
return name;
57+
}
58+
59+
public void setName(String name) {
60+
this.name = name;
61+
}
5162
}

src/test/java/org/springframework/data/jpa/domain/sample/IdClassExampleDepartment.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
import javax.persistence.Entity;
1919
import javax.persistence.Id;
2020

21+
/**
22+
* @author Thomas Darimont
23+
*/
2124
@Entity
2225
public class IdClassExampleDepartment {
2326

src/test/java/org/springframework/data/jpa/domain/sample/IdClassExampleEmployee.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013 the original author or authors.
2+
* Copyright 2013-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,13 +20,19 @@
2020
import javax.persistence.IdClass;
2121
import javax.persistence.ManyToOne;
2222

23+
/**
24+
* @author Thomas Darimont
25+
* @author Mark Paluch
26+
*/
2327
@IdClass(IdClassExampleEmployeePK.class)
2428
@Entity
2529
public class IdClassExampleEmployee {
2630

2731
@Id long empId;
2832
@Id @ManyToOne IdClassExampleDepartment department;
2933

34+
String name;
35+
3036
public long getEmpId() {
3137
return empId;
3238
}
@@ -42,4 +48,12 @@ public IdClassExampleDepartment getDepartment() {
4248
public void setDepartment(IdClassExampleDepartment department) {
4349
this.department = department;
4450
}
51+
52+
public String getName() {
53+
return name;
54+
}
55+
56+
public void setName(String name) {
57+
this.name = name;
58+
}
4559
}

src/test/java/org/springframework/data/jpa/repository/RepositoryWithCompositeKeyTests.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646

4747
/**
4848
* Tests some usage variants of composite keys with spring data jpa.
49-
*
49+
*
5050
* @author Thomas Darimont
5151
* @author Mark Paluch
5252
*/
@@ -303,4 +303,49 @@ public void shouldAllowFindAllWithIdsForEntitiesWithCompoundIdClassKeys() {
303303

304304
assertThat(result, hasSize(2));
305305
}
306+
307+
/**
308+
* @see DATAJPA-920
309+
*/
310+
@Test
311+
public void shouldExecuteExistsQueryForEntitiesWithEmbeddedId() {
312+
313+
EmbeddedIdExampleDepartment dep1 = new EmbeddedIdExampleDepartment();
314+
dep1.setDepartmentId(1L);
315+
dep1.setName("Dep1");
316+
317+
EmbeddedIdExampleEmployeePK key = new EmbeddedIdExampleEmployeePK();
318+
key.setDepartmentId(1L);
319+
key.setEmployeeId(1L);
320+
321+
EmbeddedIdExampleEmployee emp = new EmbeddedIdExampleEmployee();
322+
emp.setDepartment(dep1);
323+
emp.setEmployeePk(key);
324+
emp.setName("White");
325+
326+
employeeRepositoryWithEmbeddedId.save(emp);
327+
328+
assertThat(employeeRepositoryWithEmbeddedId.existsByName(emp.getName()), is(true));
329+
}
330+
331+
/**
332+
* @see DATAJPA-920
333+
*/
334+
@Test
335+
public void shouldExecuteExistsQueryForEntitiesWithCompoundIdClassKeys() {
336+
337+
IdClassExampleDepartment dep2 = new IdClassExampleDepartment();
338+
dep2.setDepartmentId(2L);
339+
dep2.setName("Dep2");
340+
341+
IdClassExampleEmployee emp1 = new IdClassExampleEmployee();
342+
emp1.setEmpId(3L);
343+
emp1.setDepartment(dep2);
344+
emp1.setName("White");
345+
346+
employeeRepositoryWithIdClass.save(emp1);
347+
348+
assertThat(employeeRepositoryWithIdClass.existsByName(emp1.getName()), is(true));
349+
assertThat(employeeRepositoryWithIdClass.existsByName("Walter"), is(false));
350+
}
306351
}

src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,18 @@ public void executesDerivedCountQueryToInt() {
10961096
assertThat(repository.countUsersByFirstname("Dave"), is(1));
10971097
}
10981098

1099+
/**
1100+
* @see DATAJPA-231
1101+
*/
1102+
@Test
1103+
public void executesDerivedExistsQuery() {
1104+
1105+
flushTestUsers();
1106+
1107+
assertThat(repository.existsByLastname("Matthews"), is(true));
1108+
assertThat(repository.existsByLastname("Hans Peter"), is(false));
1109+
}
1110+
10991111
/**
11001112
* @see DATAJPA-332
11011113
*/

src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
* Integration tests for {@link PartTreeJpaQuery}.
5757
*
5858
* @author Oliver Gierke
59+
* @author Mark Paluch
5960
*/
6061
@RunWith(SpringJUnit4ClassRunner.class)
6162
@ContextConfiguration("classpath:infrastructure.xml")
@@ -120,6 +121,34 @@ public void recreatesQueryIfNullValueIsGiven() throws Exception {
120121
assertThat(HibernateUtils.getHibernateQuery(getValue(query, PROPERTY)), endsWith("firstname is null"));
121122
}
122123

124+
/**
125+
* @see DATAJPA-920
126+
*/
127+
@Test
128+
public void shouldLimitExistsProjectionQueries() throws Exception {
129+
130+
JpaQueryMethod queryMethod = getQueryMethod("existsByFirstname", String.class);
131+
PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager, provider);
132+
133+
Query query = jpaQuery.createQuery(new Object[]{"Matthews"});
134+
135+
assertThat(query.getMaxResults(), is(1));
136+
}
137+
138+
/**
139+
* @see DATAJPA-920
140+
*/
141+
@Test
142+
public void shouldSelectAliasedIdForExistsProjectionQueries() throws Exception {
143+
144+
JpaQueryMethod queryMethod = getQueryMethod("existsByFirstname", String.class);
145+
PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager, provider);
146+
147+
Query query = jpaQuery.createQuery(new Object[]{"Matthews"});
148+
149+
assertThat(HibernateUtils.getHibernateQuery(getValue(query, PROPERTY)), containsString(".id from User as"));
150+
}
151+
123152
private void testIgnoreCase(String methodName, Object... values) throws Exception {
124153

125154
Class<?>[] parameterTypes = new Class[values.length];
@@ -173,6 +202,8 @@ interface UserRepository extends Repository<User, Long> {
173202

174203
User findByIdAllIgnoringCase(Integer id);
175204

205+
boolean existsByFirstname(String firstname);
206+
176207
List<User> findByCreatedAtAfter(@Temporal(TemporalType.TIMESTAMP) @Param("refDate") Date refDate);
177208
}
178209
}

src/test/java/org/springframework/data/jpa/repository/sample/EmployeeRepositoryWithEmbeddedId.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2015 the original author or authors.
2+
* Copyright 2013-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,11 +30,17 @@
3030
* Demonstrates the support for composite primary keys with {@code @EmbeddedId}.
3131
*
3232
* @author Thomas Darimont
33+
* @author Mark Paluch
3334
*/
3435
@Lazy
3536
public interface EmployeeRepositoryWithEmbeddedId
3637
extends JpaRepository<EmbeddedIdExampleEmployee, EmbeddedIdExampleEmployeePK>,
3738
QueryDslPredicateExecutor<EmbeddedIdExampleEmployee> {
3839

3940
List<EmbeddedIdExampleEmployee> findAll(Predicate predicate, OrderSpecifier<?>... orders);
41+
42+
/**
43+
* @see DATAJPA-920
44+
*/
45+
boolean existsByName(String name);
4046
}

0 commit comments

Comments
 (0)