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); }