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:
committed by
Oliver Gierke
parent
6afdaa3a15
commit
e07e53e556
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -282,6 +282,11 @@ public interface UserRepository
|
||||
*/
|
||||
int countUsersByFirstname(String firstname);
|
||||
|
||||
/**
|
||||
* @see DATAJPA-920
|
||||
*/
|
||||
boolean existsByLastname(String lastname);
|
||||
|
||||
/**
|
||||
* @see DATAJPA-391
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user