Skip to content

DATAJPA-920 - Add support for exists projection in repository query derivation. #176

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.11.0.BUILD-SNAPSHOT</version>
<version>1.11.0.DATAJPA-920-SNAPSHOT</version>

<name>Spring Data JPA</name>
<description>Spring Data module for JPA repositories.</description>
Expand All @@ -27,7 +27,7 @@
<hsqldb1>1.8.0.10</hsqldb1>
<jpa>2.0.0</jpa>
<openjpa>2.4.1</openjpa>
<springdata.commons>1.13.0.BUILD-SNAPSHOT</springdata.commons>
<springdata.commons>1.13.0.DATACMNS-875-SNAPSHOT</springdata.commons>

<sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -15,13 +15,15 @@
*/
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.*;

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;
Expand All @@ -30,6 +32,7 @@
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;
Expand All @@ -45,6 +48,7 @@
* 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> {

Expand All @@ -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}.
Expand All @@ -66,6 +71,7 @@ public JpaQueryCreator(PartTree tree, ReturnedType type, CriteriaBuilder builder
ParameterMetadataProvider provider) {

super(tree);
this.tree = tree;

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

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

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

return typeToRead == null ? builder.createTupleQuery() : builder.createQuery(typeToRead);
return (typeToRead == null || tree.isExistsProjection()) ? builder.createTupleQuery() : builder.createQuery(typeToRead);
}

/**
Expand Down Expand Up @@ -160,6 +166,24 @@ protected CriteriaQuery<? extends Object> complete(Predicate predicate, Sort sor
}

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);
}
Expand All @@ -170,7 +194,7 @@ protected CriteriaQuery<? extends Object> complete(Predicate predicate, Sort sor

/**
* Creates a {@link Predicate} from the given {@link Part}.
*
*
* @param part
* @param root
* @param iterator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,20 @@ protected Object doExecute(AbstractJpaQuery jpaQuery, Object[] values) {
}
}

/**
* {@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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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;
Expand Down Expand Up @@ -94,7 +95,14 @@ public TypedQuery<Long> doCreateCountQuery(Object[] values) {
*/
@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();
}

/**
Expand Down Expand Up @@ -168,6 +176,10 @@ private Query restrictMaxResultsIfNecessary(Query query) {
query.setMaxResults(tree.getMaxResults());
}

if(tree.isExistsProjection()) {
query.setMaxResults(1);
}

return query;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -23,6 +23,7 @@

/**
* @author Thomas Darimont
* @author Mark Paluch
*/
@Entity
public class EmbeddedIdExampleEmployee {
Expand All @@ -33,6 +34,8 @@ public class EmbeddedIdExampleEmployee {
@ManyToOne(cascade = CascadeType.ALL)//
EmbeddedIdExampleDepartment department;

String name;

public EmbeddedIdExampleEmployeePK getEmployeePk() {
return employeePk;
}
Expand All @@ -48,4 +51,12 @@ public EmbeddedIdExampleDepartment getDepartment() {
public void setDepartment(EmbeddedIdExampleDepartment department) {
this.department = department;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
import javax.persistence.Entity;
import javax.persistence.Id;

/**
* @author Thomas Darimont
*/
@Entity
public class IdClassExampleDepartment {

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -20,13 +20,19 @@
import javax.persistence.IdClass;
import javax.persistence.ManyToOne;

/**
* @author Thomas Darimont
* @author Mark Paluch
*/
@IdClass(IdClassExampleEmployeePK.class)
@Entity
public class IdClassExampleEmployee {

@Id long empId;
@Id @ManyToOne IdClassExampleDepartment department;

String name;

public long getEmpId() {
return empId;
}
Expand All @@ -42,4 +48,12 @@ public IdClassExampleDepartment getDepartment() {
public void setDepartment(IdClassExampleDepartment department) {
this.department = department;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 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.
Expand Down Expand Up @@ -46,8 +46,9 @@

/**
* Tests some usage variants of composite keys with spring data jpa.
*
*
* @author Thomas Darimont
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SampleConfig.class)
Expand Down Expand Up @@ -301,4 +302,49 @@ public void shouldAllowFindAllWithIdsForEntitiesWithCompoundIdClassKeys() {

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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,18 @@ public void executesDerivedCountQueryToInt() {
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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
* Integration tests for {@link PartTreeJpaQuery}.
*
* @author Oliver Gierke
* @author Mark Paluch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:infrastructure.xml")
Expand Down Expand Up @@ -120,6 +121,34 @@ public void recreatesQueryIfNullValueIsGiven() throws Exception {
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];
Expand Down Expand Up @@ -173,6 +202,8 @@ interface UserRepository extends Repository<User, Long> {

User findByIdAllIgnoringCase(Integer id);

boolean existsByFirstname(String firstname);

List<User> findByCreatedAtAfter(@Temporal(TemporalType.TIMESTAMP) @Param("refDate") Date refDate);
}
}
Loading