DATAJPA-460 - Support query creation for deleteBy / removeBy prefix.

Added implementation of deleteBy / removeBy support for JPA backed repositories. We delete entities by looking them up with the appropriate query and delete them afterwards via entityManager.remove(...). This is rather inefficient but provides the benefit of being able to use the query derivation mechanism for entity deletion as well.

Original pull request: #66.
This commit is contained in:
Thomas Darimont
2014-03-12 18:59:22 +01:00
committed by Oliver Gierke
parent dd64fe21ea
commit 99ae3c4567
7 changed files with 198 additions and 2 deletions

View File

@@ -64,4 +64,12 @@ public @interface Query {
* @return
*/
String countName() default "";
/**
* Returns whether the query should delete matching entities.
*
* @since 1.6
* @return
*/
boolean delete() default false;
}

View File

@@ -23,6 +23,7 @@ import javax.persistence.TypedQuery;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.CollectionExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.DeleteExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.ModifyingExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.PagedExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.SingleEntityExecution;
@@ -100,7 +101,9 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
protected JpaQueryExecution getExecution() {
if (method.isCollectionQuery()) {
if (method.isDeleteQuery()) {
return new DeleteExecution(em);
} else if (method.isCollectionQuery()) {
return new CollectionExecution();
} else if (method.isSliceQuery()) {
return new SlicedExecution(method.getParameters());

View File

@@ -40,6 +40,7 @@ import org.springframework.util.Assert;
* in various flavours.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
public abstract class JpaQueryExecution {
@@ -223,4 +224,43 @@ public abstract class JpaQueryExecution {
return result;
}
}
/**
* {@link Execution} removing entities matching the query.
*
* @author Thomas Darimont
* @since 1.6
*/
static class DeleteExecution extends JpaQueryExecution {
private final EntityManager em;
public DeleteExecution(EntityManager em) {
this.em = em;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.JpaQueryExecution#doExecute(org.springframework.data.jpa.repository.query.AbstractJpaQuery, java.lang.Object[])
*/
@Override
protected Object doExecute(AbstractJpaQuery query, Object[] values) {
Query qry = query.createQuery(values);
List<?> resultList = qry.getResultList();
for (Object o : resultList) {
em.remove(o);
}
Object result = null;
if (query.getQueryMethod().isCollectionQuery()) {
result = resultList;
} else {
result = resultList.size();
}
return result;
}
}
}

View File

@@ -288,4 +288,15 @@ public class JpaQueryMethod extends QueryMethod {
public JpaParameters getParameters() {
return (JpaParameters) super.getParameters();
}
/**
* Return {@literal true} if this backing method is a query method with the {@link Query#delete()} attribute set to
* {@literal true} else {@literal false}.
*
* @return
* @since 1.6
*/
public boolean isDeleteQuery() {
return getAnnotationValue("delete", Boolean.class);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2013 the original author or authors.
* Copyright 2008-2014 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.
@@ -24,6 +24,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.DeleteExecution;
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.parser.PartTree;
@@ -42,6 +43,7 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
private final QueryPreparer query;
private final QueryPreparer countQuery;
private EntityManager em;
/**
* Creates a new {@link PartTreeJpaQuery}.
@@ -52,6 +54,7 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
public PartTreeJpaQuery(JpaQueryMethod method, EntityManager em) {
super(method, em);
this.em = em;
this.domainClass = method.getEntityInformation().getJavaType();
this.tree = new PartTree(method.getName(), domainClass);
@@ -82,6 +85,20 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
return (TypedQuery<Long>) countQuery.createQuery(values);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#getExecution()
*/
@Override
protected JpaQueryExecution getExecution() {
if (this.tree.isDelete()) {
return new DeleteExecution(em);
}
return super.getExecution();
}
/**
* Query preparer to create {@link CriteriaQuery} instances and potentially cache them.
*

View File

@@ -1298,6 +1298,101 @@ public class UserRepositoryTests {
assertThat(result, hasItems(firstUser, secondUser));
}
/**
* @see DATAJPA-460
*/
@Test
public void deleteByShouldReturnListOfDeletedElementsWhenRetunTypeIsCollectionLike() {
flushTestUsers();
List<User> result = repository.deleteByLastname(firstUser.getLastname());
assertThat(result, hasItem(firstUser));
assertThat(result, hasSize(1));
}
/**
* @see DATAJPA-460
*/
@Test
public void deleteByShouldRemoveElementsMatchingDerivedQuery() {
flushTestUsers();
repository.deleteByLastname(firstUser.getLastname());
assertThat(repository.countByLastname(firstUser.getLastname()), is(0L));
}
/**
* @see DATAJPA-460
*/
@Test
public void deleteByShouldReturnNumberOfEntitiesRemovedIfReturnTypeIsLong() {
flushTestUsers();
assertThat(repository.removeByLastname(firstUser.getLastname()), is(1L));
}
/**
* @see DATAJPA-460
*/
@Test
public void deleteByShouldReturnZeroInCaseNoEntityHasBeenRemovedAndReturnTypeIsNumber() {
flushTestUsers();
assertThat(repository.removeByLastname("bubu"), is(0L));
}
/**
* @see DATAJPA-460
*/
@Test
public void deleteByShouldReturnEmptyListInCaseNoEntityHasBeenRemovedAndReturnTypeIsCollectionLike() {
flushTestUsers();
assertThat(repository.deleteByLastname("dorfuaeB"), empty());
}
/**
* @see DATAJPA-460
*/
@Test
public void deleteByUsingAnnotatedQueryShouldReturnListOfDeletedElementsWhenRetunTypeIsCollectionLike() {
flushTestUsers();
List<User> result = repository.deleteByLastnameUsingAnnotatedQuery(firstUser.getLastname());
assertThat(result, hasItem(firstUser));
assertThat(result, hasSize(1));
}
/**
* @see DATAJPA-460
*/
@Test
public void deleteByUsingAnnotatedQueryShouldRemoveElementsMatchingDerivedQuery() {
flushTestUsers();
repository.removeByLastnameUsingAnnotatedQuery(firstUser.getLastname());
assertThat(repository.countByLastname(firstUser.getLastname()), is(0L));
}
/**
* @see DATAJPA-460
*/
@Test
public void deleteByUsingAnnotatedQueryShouldReturnNumberOfDocumentsRemovedIfReturnTypeIsLong() {
flushTestUsers();
assertThat(repository.removeByLastnameUsingAnnotatedQuery(firstUser.getLastname()), is(1L));
}
private Page<User> executeSpecWithSort(Sort sort) {
flushTestUsers();

View File

@@ -317,4 +317,26 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
* @see DATAJPA-496
*/
List<User> findByAttributesIn(Set<String> attributes);
/**
* @see DATAJPA-460
*/
Long removeByLastname(String lastname);
/**
* @see DATAJPA-460
*/
List<User> deleteByLastname(String lastname);
/**
* @see DATAJPA-460
*/
@Query(value = "select u from User u where u.lastname = ?1", delete = true)
List<User> deleteByLastnameUsingAnnotatedQuery(String lastname);
/**
* @see DATAJPA-460
*/
@Query(value = "select u from User u where u.lastname = ?1", delete = true)
Long removeByLastnameUsingAnnotatedQuery(String lastname);
}