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.
This commit is contained in:
Mark Paluch
2016-06-28 14:55:26 +02:00
committed by Oliver Gierke
parent 6afdaa3a15
commit e07e53e556
12 changed files with 192 additions and 9 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2015 the original author or authors.
* Copyright 2008-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
*/
package org.springframework.data.jpa.repository.query;
import static org.springframework.data.jpa.domain.AbstractPersistable_.*;
import static org.springframework.data.jpa.repository.query.QueryUtils.*;
import static org.springframework.data.repository.query.parser.Part.Type.*;
@@ -22,6 +23,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
@@ -30,6 +32,7 @@ import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import javax.persistence.metamodel.SingularAttribute;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
@@ -45,6 +48,7 @@ import org.springframework.util.Assert;
* Query creator to create a {@link CriteriaQuery} from a {@link PartTree}.
*
* @author Oliver Gierke
* @author Mark Paluch
*/
public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extends Object>, Predicate> {
@@ -53,6 +57,7 @@ public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extend
private final CriteriaQuery<? extends Object> query;
private final ParameterMetadataProvider provider;
private final ReturnedType returnedType;
private final PartTree tree;
/**
* Create a new {@link JpaQueryCreator}.
@@ -66,6 +71,7 @@ public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extend
ParameterMetadataProvider provider) {
super(tree);
this.tree = tree;
CriteriaQuery<? extends Object> criteriaQuery = createCriteriaQuery(builder, type);
@@ -87,7 +93,7 @@ public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extend
Class<?> typeToRead = type.getTypeToRead();
return typeToRead == null ? builder.createTupleQuery() : builder.createQuery(typeToRead);
return (typeToRead == null || tree.isExistsProjection()) ? builder.createTupleQuery() : builder.createQuery(typeToRead);
}
/**
@@ -162,6 +168,24 @@ public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extend
}
query = query.multiselect(selections);
} else if (tree.isExistsProjection()) {
if (root.getModel().hasSingleIdAttribute()) {
SingularAttribute<?, ?> id = root.getModel().getId(root.getModel().getIdType().getJavaType());
query = query.multiselect(root.get((SingularAttribute) id).alias(id.getName()));
} else {
List<Selection<?>> selections = new ArrayList<Selection<?>>();
Set<SingularAttribute<?, ?>> idClassAttributes = (Set<SingularAttribute<?, ?>>) root.getModel().getIdClassAttributes();
for (SingularAttribute<?, ?> attribute : idClassAttributes) {
selections.add(root.get((SingularAttribute) attribute).alias(attribute.getName()));
}
selections.add(root.get((SingularAttribute) id).alias(id.getName()));
query = query.multiselect(selections);
}
} else {
query = query.select((Root) root);
}
@@ -172,7 +196,7 @@ public class JpaQueryCreator extends AbstractQueryCreator<CriteriaQuery<? extend
/**
* Creates a {@link Predicate} from the given {@link Part}.
*
*
* @param part
* @param root
* @param iterator

View File

@@ -276,6 +276,20 @@ public abstract class JpaQueryExecution {
}
}
/**
* {@link Execution} performing an exists check on the query.
*
* @author Mark Paluch
* @since 1.11
*/
static class ExistsExecution extends JpaQueryExecution {
@Override
protected Object doExecute(AbstractJpaQuery query, Object[] values) {
return !query.createQuery(values).getResultList().isEmpty();
}
}
/**
* {@link Execution} executing a stored procedure.
*

View File

@@ -26,6 +26,7 @@ import javax.persistence.criteria.CriteriaQuery;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.provider.PersistenceProvider;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.DeleteExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.ExistsExecution;
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.ResultProcessor;
@@ -94,7 +95,14 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
*/
@Override
protected JpaQueryExecution getExecution() {
return this.tree.isDelete() ? new DeleteExecution(em) : super.getExecution();
if(this.tree.isDelete()) {
return new DeleteExecution(em);
} else if(this.tree.isExistsProjection()) {
return new ExistsExecution();
}
return super.getExecution();
}
/**
@@ -168,6 +176,10 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
query.setMaxResults(tree.getMaxResults());
}
if(tree.isExistsProjection()) {
query.setMaxResults(1);
}
return query;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2104 the original author or authors.
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import javax.persistence.MapsId;
/**
* @author Thomas Darimont
* @author Mark Paluch
*/
@Entity
public class EmbeddedIdExampleEmployee {
@@ -33,6 +34,8 @@ public class EmbeddedIdExampleEmployee {
@ManyToOne(cascade = CascadeType.ALL)//
EmbeddedIdExampleDepartment department;
String name;
public EmbeddedIdExampleEmployeePK getEmployeePk() {
return employeePk;
}
@@ -48,4 +51,12 @@ public class EmbeddedIdExampleEmployee {
public void setDepartment(EmbeddedIdExampleDepartment department) {
this.department = department;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -18,6 +18,9 @@ package org.springframework.data.jpa.domain.sample;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* @author Thomas Darimont
*/
@Entity
public class IdClassExampleDepartment {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,10 @@ import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.ManyToOne;
/**
* @author Thomas Darimont
* @author Mark Paluch
*/
@IdClass(IdClassExampleEmployeePK.class)
@Entity
public class IdClassExampleEmployee {
@@ -27,6 +31,8 @@ public class IdClassExampleEmployee {
@Id long empId;
@Id @ManyToOne IdClassExampleDepartment department;
String name;
public long getEmpId() {
return empId;
}
@@ -42,4 +48,12 @@ public class IdClassExampleEmployee {
public void setDepartment(IdClassExampleDepartment department) {
this.department = department;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -46,7 +46,7 @@ import org.springframework.transaction.annotation.Transactional;
/**
* Tests some usage variants of composite keys with spring data jpa.
*
*
* @author Thomas Darimont
* @author Mark Paluch
*/
@@ -303,4 +303,49 @@ public class RepositoryWithCompositeKeyTests {
assertThat(result, hasSize(2));
}
/**
* @see DATAJPA-920
*/
@Test
public void shouldExecuteExistsQueryForEntitiesWithEmbeddedId() {
EmbeddedIdExampleDepartment dep1 = new EmbeddedIdExampleDepartment();
dep1.setDepartmentId(1L);
dep1.setName("Dep1");
EmbeddedIdExampleEmployeePK key = new EmbeddedIdExampleEmployeePK();
key.setDepartmentId(1L);
key.setEmployeeId(1L);
EmbeddedIdExampleEmployee emp = new EmbeddedIdExampleEmployee();
emp.setDepartment(dep1);
emp.setEmployeePk(key);
emp.setName("White");
employeeRepositoryWithEmbeddedId.save(emp);
assertThat(employeeRepositoryWithEmbeddedId.existsByName(emp.getName()), is(true));
}
/**
* @see DATAJPA-920
*/
@Test
public void shouldExecuteExistsQueryForEntitiesWithCompoundIdClassKeys() {
IdClassExampleDepartment dep2 = new IdClassExampleDepartment();
dep2.setDepartmentId(2L);
dep2.setName("Dep2");
IdClassExampleEmployee emp1 = new IdClassExampleEmployee();
emp1.setEmpId(3L);
emp1.setDepartment(dep2);
emp1.setName("White");
employeeRepositoryWithIdClass.save(emp1);
assertThat(employeeRepositoryWithIdClass.existsByName(emp1.getName()), is(true));
assertThat(employeeRepositoryWithIdClass.existsByName("Walter"), is(false));
}
}

View File

@@ -1096,6 +1096,18 @@ public class UserRepositoryTests {
assertThat(repository.countUsersByFirstname("Dave"), is(1));
}
/**
* @see DATAJPA-231
*/
@Test
public void executesDerivedExistsQuery() {
flushTestUsers();
assertThat(repository.existsByLastname("Matthews"), is(true));
assertThat(repository.existsByLastname("Hans Peter"), is(false));
}
/**
* @see DATAJPA-332
*/

View File

@@ -56,6 +56,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
* Integration tests for {@link PartTreeJpaQuery}.
*
* @author Oliver Gierke
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:infrastructure.xml")
@@ -120,6 +121,34 @@ public class PartTreeJpaQueryIntegrationTests {
assertThat(HibernateUtils.getHibernateQuery(getValue(query, PROPERTY)), endsWith("firstname is null"));
}
/**
* @see DATAJPA-920
*/
@Test
public void shouldLimitExistsProjectionQueries() throws Exception {
JpaQueryMethod queryMethod = getQueryMethod("existsByFirstname", String.class);
PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager, provider);
Query query = jpaQuery.createQuery(new Object[]{"Matthews"});
assertThat(query.getMaxResults(), is(1));
}
/**
* @see DATAJPA-920
*/
@Test
public void shouldSelectAliasedIdForExistsProjectionQueries() throws Exception {
JpaQueryMethod queryMethod = getQueryMethod("existsByFirstname", String.class);
PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager, provider);
Query query = jpaQuery.createQuery(new Object[]{"Matthews"});
assertThat(HibernateUtils.getHibernateQuery(getValue(query, PROPERTY)), containsString(".id from User as"));
}
private void testIgnoreCase(String methodName, Object... values) throws Exception {
Class<?>[] parameterTypes = new Class[values.length];
@@ -173,6 +202,8 @@ public class PartTreeJpaQueryIntegrationTests {
User findByIdAllIgnoringCase(Integer id);
boolean existsByFirstname(String firstname);
List<User> findByCreatedAtAfter(@Temporal(TemporalType.TIMESTAMP) @Param("refDate") Date refDate);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@ import com.querydsl.core.types.Predicate;
* Demonstrates the support for composite primary keys with {@code @EmbeddedId}.
*
* @author Thomas Darimont
* @author Mark Paluch
*/
@Lazy
public interface EmployeeRepositoryWithEmbeddedId
@@ -37,4 +38,9 @@ public interface EmployeeRepositoryWithEmbeddedId
QueryDslPredicateExecutor<EmbeddedIdExampleEmployee> {
List<EmbeddedIdExampleEmployee> findAll(Predicate predicate, OrderSpecifier<?>... orders);
/**
* @see DATAJPA-920
*/
boolean existsByName(String name);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,10 +30,16 @@ import com.querydsl.core.types.Predicate;
* Demonstrates the support for composite primary keys with {@code @IdClass}.
*
* @author Thomas Darimont
* @author Mark Paluch
*/
@Lazy
public interface EmployeeRepositoryWithIdClass extends JpaRepository<IdClassExampleEmployee, IdClassExampleEmployeePK>,
QueryDslPredicateExecutor<IdClassExampleEmployee> {
List<IdClassExampleEmployee> findAll(Predicate predicate, OrderSpecifier<?>... orders);
/**
* @see DATAJPA-920
*/
boolean existsByName(String name);
}

View File

@@ -282,6 +282,11 @@ public interface UserRepository
*/
int countUsersByFirstname(String firstname);
/**
* @see DATAJPA-920
*/
boolean existsByLastname(String lastname);
/**
* @see DATAJPA-391
*/