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:
committed by
Oliver Gierke
parent
dd64fe21ea
commit
99ae3c4567
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user