From 99ae3c4567fe31352f9b29aac6f3041cd32a4a61 Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Wed, 12 Mar 2014 18:59:22 +0100 Subject: [PATCH] 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. --- .../data/jpa/repository/Query.java | 8 ++ .../repository/query/AbstractJpaQuery.java | 5 +- .../repository/query/JpaQueryExecution.java | 40 ++++++++ .../jpa/repository/query/JpaQueryMethod.java | 11 +++ .../repository/query/PartTreeJpaQuery.java | 19 +++- .../jpa/repository/UserRepositoryTests.java | 95 +++++++++++++++++++ .../jpa/repository/sample/UserRepository.java | 22 +++++ 7 files changed, 198 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/repository/Query.java b/src/main/java/org/springframework/data/jpa/repository/Query.java index 849726006..ce24d324b 100644 --- a/src/main/java/org/springframework/data/jpa/repository/Query.java +++ b/src/main/java/org/springframework/data/jpa/repository/Query.java @@ -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; } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java index 2c2c63551..466cb9b42 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java @@ -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()); diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java index a18bcc076..a9068dcb7 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java @@ -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; + } + } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java index 0342e786e..b2f35aad6 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java @@ -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); + } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java index 3b0c25417..4942db55b 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java @@ -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) 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. * diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index fd61d16c8..44ad4b494 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -1298,6 +1298,101 @@ public class UserRepositoryTests { assertThat(result, hasItems(firstUser, secondUser)); } + + /** + * @see DATAJPA-460 + */ + @Test + public void deleteByShouldReturnListOfDeletedElementsWhenRetunTypeIsCollectionLike() { + + flushTestUsers(); + + List 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 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 executeSpecWithSort(Sort sort) { flushTestUsers(); diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index d227ff291..f017b155f 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -317,4 +317,26 @@ public interface UserRepository extends JpaRepository, JpaSpecifi * @see DATAJPA-496 */ List findByAttributesIn(Set attributes); + + /** + * @see DATAJPA-460 + */ + Long removeByLastname(String lastname); + + /** + * @see DATAJPA-460 + */ + List deleteByLastname(String lastname); + + /** + * @see DATAJPA-460 + */ + @Query(value = "select u from User u where u.lastname = ?1", delete = true) + List deleteByLastnameUsingAnnotatedQuery(String lastname); + + /** + * @see DATAJPA-460 + */ + @Query(value = "select u from User u where u.lastname = ?1", delete = true) + Long removeByLastnameUsingAnnotatedQuery(String lastname); }