Skip to content

Commit

Permalink
DATAJDBC-551 - Supports derived delete.
Browse files Browse the repository at this point in the history
  • Loading branch information
lseeker committed Jun 18, 2020
1 parent 9edac33 commit 8743811
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ protected JdbcQueryExecution<?> getQueryExecution(JdbcQueryMethod queryMethod,
return extractor != null ? getQueryExecution(extractor) : singleObjectQuery(rowMapper);
}

private JdbcQueryExecution<Object> createModifyingQueryExecutor() {
protected JdbcQueryExecution<Object> createModifyingQueryExecutor() {

return (query, parameters) -> {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright 2020 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.repository.query;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

import org.springframework.data.domain.Sort;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.dialect.RenderContextFactory;
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.sql.Condition;
import org.springframework.data.relational.core.sql.Conditions;
import org.springframework.data.relational.core.sql.Delete;
import org.springframework.data.relational.core.sql.DeleteBuilder.DeleteWhere;
import org.springframework.data.relational.core.sql.Select;
import org.springframework.data.relational.core.sql.SelectBuilder.SelectWhere;
import org.springframework.data.relational.core.sql.StatementBuilder;
import org.springframework.data.relational.core.sql.Table;
import org.springframework.data.relational.core.sql.render.SqlRenderer;
import org.springframework.data.relational.repository.query.RelationalEntityMetadata;
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
import org.springframework.data.relational.repository.query.RelationalQueryCreator;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
* Implementation of {@link RelationalQueryCreator} that creates {@link Stream} of deletion {@link ParametrizedQuery}
* from a {@link PartTree}.
*
* @author Yunyoung LEE
* @since 2.1
*/
class JdbcDeleteQueryCreator extends RelationalQueryCreator<Stream<ParametrizedQuery>> {

private final RelationalMappingContext context;
private final QueryMapper queryMapper;
private final RelationalEntityMetadata<?> entityMetadata;
private final RenderContextFactory renderContextFactory;

/**
* Creates new instance of this class with the given {@link PartTree}, {@link JdbcConverter}, {@link Dialect},
* {@link RelationalEntityMetadata} and {@link RelationalParameterAccessor}.
*
* @param context
* @param tree part tree, must not be {@literal null}.
* @param converter must not be {@literal null}.
* @param dialect must not be {@literal null}.
* @param entityMetadata relational entity metadata, must not be {@literal null}.
* @param accessor parameter metadata provider, must not be {@literal null}.
*/
JdbcDeleteQueryCreator(RelationalMappingContext context, PartTree tree, JdbcConverter converter, Dialect dialect,
RelationalEntityMetadata<?> entityMetadata, RelationalParameterAccessor accessor) {
super(tree, accessor);

Assert.notNull(converter, "JdbcConverter must not be null");
Assert.notNull(dialect, "Dialect must not be null");
Assert.notNull(entityMetadata, "Relational entity metadata must not be null");

this.context = context;

this.entityMetadata = entityMetadata;
this.queryMapper = new QueryMapper(dialect, converter);
this.renderContextFactory = new RenderContextFactory(dialect);
}

@Override
protected Stream<ParametrizedQuery> complete(@Nullable Criteria criteria, Sort sort) {

RelationalPersistentEntity<?> entity = entityMetadata.getTableEntity();
Table table = Table.create(entityMetadata.getTableName());
MapSqlParameterSource parameterSource = new MapSqlParameterSource();

SqlContext sqlContext = new SqlContext(entity);

Condition condition = criteria == null ? null
: queryMapper.getMappedObject(parameterSource, criteria, table, entity);

// create select criteria query for subselect
SelectWhere selectBuilder = StatementBuilder.select(sqlContext.getIdColumn()).from(table);
Select select = condition == null ? selectBuilder.build() : selectBuilder.where(condition).build();

// create delete relation queries
List<Delete> deleteChain = new ArrayList<>();
deleteRelations(deleteChain, entity, select);

// crate delete query
DeleteWhere deleteBuilder = StatementBuilder.delete(table);
Delete delete = condition == null ? deleteBuilder.build() : deleteBuilder.where(condition).build();

deleteChain.add(delete);

SqlRenderer renderer = SqlRenderer.create(renderContextFactory.createRenderContext());
return deleteChain.stream().map(d -> new ParametrizedQuery(renderer.render(d), parameterSource));
}

private void deleteRelations(List<Delete> deleteChain, RelationalPersistentEntity<?> entity, Select parentSelect) {

for (PersistentPropertyPath<RelationalPersistentProperty> path : context
.findPersistentPropertyPaths(entity.getType(), p -> true)) {

PersistentPropertyPathExtension extPath = new PersistentPropertyPathExtension(context, path);
if (extPath.isEntity() && !extPath.isEmbedded()) {

SqlContext sqlContext = new SqlContext(extPath.getLeafEntity());

Condition inCondition = Conditions.in(sqlContext.getTable().column(extPath.getReverseColumnName()),
parentSelect);

Select select = StatementBuilder.select(sqlContext.getIdColumn()).from(sqlContext.getTable()).where(inCondition)
.build();
deleteRelations(deleteChain, extPath.getLeafEntity(), select);

deleteChain.add(StatementBuilder.delete(sqlContext.getTable()).where(inCondition).build());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.springframework.data.jdbc.repository.query;

import java.sql.ResultSet;
import java.util.stream.Stream;

import org.springframework.data.domain.Sort;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
Expand Down Expand Up @@ -77,7 +78,8 @@ public PartTreeJdbcQuery(RelationalMappingContext context, JdbcQueryMethod query

ResultSetExtractor<Boolean> extractor = tree.isExistsProjection() ? (ResultSet::next) : null;

this.execution = getQueryExecution(queryMethod, extractor, rowMapper);
this.execution = tree.isDelete() ? createModifyingQueryExecutor()
: getQueryExecution(queryMethod, extractor, rowMapper);
}

private Sort getDynamicSort(RelationalParameterAccessor accessor) {
Expand All @@ -94,6 +96,11 @@ public Object execute(Object[] values) {
RelationalParametersParameterAccessor accessor = new RelationalParametersParameterAccessor(getQueryMethod(),
values);

if (tree.isDelete()) {
return createDeleteQueries(accessor).map(query -> execution.execute(query.getQuery(), query.getParameterSource()))
.reduce((a, b) -> b);
}

ParametrizedQuery query = createQuery(accessor);
return this.execution.execute(query.getQuery(), query.getParameterSource());
}
Expand All @@ -104,4 +111,13 @@ protected ParametrizedQuery createQuery(RelationalParametersParameterAccessor ac
JdbcQueryCreator queryCreator = new JdbcQueryCreator(context, tree, converter, dialect, entityMetadata, accessor);
return queryCreator.createQuery(getDynamicSort(accessor));
}

private Stream<ParametrizedQuery> createDeleteQueries(RelationalParametersParameterAccessor accessor) {

RelationalEntityMetadata<?> entityMetadata = getQueryMethod().getEntityInformation();
JdbcDeleteQueryCreator queryCreator = new JdbcDeleteQueryCreator(context, tree, converter, dialect, entityMetadata,
accessor);

return queryCreator.createQuery();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,26 @@ public void deletingWithSet() {
assertThat(count).isEqualTo(0);
}

@Test // DATAJDBC-551
public void deleteByName() {

Element element1 = createElement("one");
Element element2 = createElement("two");

DummyEntity entity = createDummyEntity();
entity.content.add(element1);
entity.content.add(element2);

entity = repository.save(entity);

assertThat(repository.deleteByName("Entity Name")).isEqualTo(1);

assertThat(repository.findById(entity.id)).isEmpty();

Long count = template.queryForObject("select count(1) from Element", new HashMap<>(), Long.class);
assertThat(count).isEqualTo(0);
}

private Element createElement(String content) {

Element element = new Element();
Expand All @@ -210,7 +230,9 @@ private static DummyEntity createDummyEntity() {
return entity;
}

interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {}
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
long deleteByName(String name);
}

@Data
static class DummyEntity {
Expand Down

0 comments on commit 8743811

Please sign in to comment.