Skip to content

Commit 31c75cb

Browse files
committed
DATAJDBC-551 - Supports derived delete.
1 parent 9c4753f commit 31c75cb

File tree

4 files changed

+181
-7
lines changed

4 files changed

+181
-7
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractJdbcQuery.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ protected JdbcQueryExecution<?> getQueryExecution(JdbcQueryMethod queryMethod,
9696
return extractor != null ? getQueryExecution(extractor) : singleObjectQuery(rowMapper);
9797
}
9898

99-
private JdbcQueryExecution<Object> createModifyingQueryExecutor() {
99+
protected JdbcQueryExecution<Object> createModifyingQueryExecutor() {
100100

101101
return (query, parameters) -> {
102102

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.repository.query;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.stream.Stream;
21+
22+
import org.springframework.data.domain.Sort;
23+
import org.springframework.data.jdbc.core.convert.JdbcConverter;
24+
import org.springframework.data.mapping.PersistentPropertyPath;
25+
import org.springframework.data.relational.core.dialect.Dialect;
26+
import org.springframework.data.relational.core.dialect.RenderContextFactory;
27+
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
28+
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
29+
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
30+
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
31+
import org.springframework.data.relational.core.query.Criteria;
32+
import org.springframework.data.relational.core.sql.Condition;
33+
import org.springframework.data.relational.core.sql.Conditions;
34+
import org.springframework.data.relational.core.sql.Delete;
35+
import org.springframework.data.relational.core.sql.DeleteBuilder.DeleteWhere;
36+
import org.springframework.data.relational.core.sql.Select;
37+
import org.springframework.data.relational.core.sql.SelectBuilder.SelectWhere;
38+
import org.springframework.data.relational.core.sql.StatementBuilder;
39+
import org.springframework.data.relational.core.sql.Table;
40+
import org.springframework.data.relational.core.sql.render.SqlRenderer;
41+
import org.springframework.data.relational.repository.query.RelationalEntityMetadata;
42+
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
43+
import org.springframework.data.relational.repository.query.RelationalQueryCreator;
44+
import org.springframework.data.repository.query.parser.PartTree;
45+
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
46+
import org.springframework.lang.Nullable;
47+
import org.springframework.util.Assert;
48+
49+
/**
50+
* Implementation of {@link RelationalQueryCreator} that creates {@link Stream} of deletion {@link ParametrizedQuery}
51+
* from a {@link PartTree}.
52+
*
53+
* @author Yunyoung LEE
54+
* @since 2.1
55+
*/
56+
class JdbcDeleteQueryCreator extends RelationalQueryCreator<Stream<ParametrizedQuery>> {
57+
58+
private final RelationalMappingContext context;
59+
private final QueryMapper queryMapper;
60+
private final RelationalEntityMetadata<?> entityMetadata;
61+
private final RenderContextFactory renderContextFactory;
62+
63+
/**
64+
* Creates new instance of this class with the given {@link PartTree}, {@link JdbcConverter}, {@link Dialect},
65+
* {@link RelationalEntityMetadata} and {@link RelationalParameterAccessor}.
66+
*
67+
* @param context
68+
* @param tree part tree, must not be {@literal null}.
69+
* @param converter must not be {@literal null}.
70+
* @param dialect must not be {@literal null}.
71+
* @param entityMetadata relational entity metadata, must not be {@literal null}.
72+
* @param accessor parameter metadata provider, must not be {@literal null}.
73+
*/
74+
JdbcDeleteQueryCreator(RelationalMappingContext context, PartTree tree, JdbcConverter converter, Dialect dialect,
75+
RelationalEntityMetadata<?> entityMetadata, RelationalParameterAccessor accessor) {
76+
super(tree, accessor);
77+
78+
Assert.notNull(converter, "JdbcConverter must not be null");
79+
Assert.notNull(dialect, "Dialect must not be null");
80+
Assert.notNull(entityMetadata, "Relational entity metadata must not be null");
81+
82+
this.context = context;
83+
84+
this.entityMetadata = entityMetadata;
85+
this.queryMapper = new QueryMapper(dialect, converter);
86+
this.renderContextFactory = new RenderContextFactory(dialect);
87+
}
88+
89+
@Override
90+
protected Stream<ParametrizedQuery> complete(@Nullable Criteria criteria, Sort sort) {
91+
92+
RelationalPersistentEntity<?> entity = entityMetadata.getTableEntity();
93+
Table table = Table.create(entityMetadata.getTableName());
94+
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
95+
96+
SqlContext sqlContext = new SqlContext(entity);
97+
98+
Condition condition = criteria == null ? null
99+
: queryMapper.getMappedObject(parameterSource, criteria, table, entity);
100+
101+
// create select criteria query for subselect
102+
SelectWhere selectBuilder = StatementBuilder.select(sqlContext.getIdColumn()).from(table);
103+
Select select = condition == null ? selectBuilder.build() : selectBuilder.where(condition).build();
104+
105+
// create delete relation queries
106+
List<Delete> deleteChain = new ArrayList<>();
107+
deleteRelations(deleteChain, entity, select);
108+
109+
// crate delete query
110+
DeleteWhere deleteBuilder = StatementBuilder.delete(table);
111+
Delete delete = condition == null ? deleteBuilder.build() : deleteBuilder.where(condition).build();
112+
113+
deleteChain.add(delete);
114+
115+
SqlRenderer renderer = SqlRenderer.create(renderContextFactory.createRenderContext());
116+
return deleteChain.stream().map(d -> new ParametrizedQuery(renderer.render(d), parameterSource));
117+
}
118+
119+
private void deleteRelations(List<Delete> deleteChain, RelationalPersistentEntity<?> entity, Select parentSelect) {
120+
121+
for (PersistentPropertyPath<RelationalPersistentProperty> path : context
122+
.findPersistentPropertyPaths(entity.getType(), p -> true)) {
123+
124+
PersistentPropertyPathExtension extPath = new PersistentPropertyPathExtension(context, path);
125+
if (extPath.isEntity() && !extPath.isEmbedded()) {
126+
127+
SqlContext sqlContext = new SqlContext(extPath.getLeafEntity());
128+
129+
Condition inCondition = Conditions.in(sqlContext.getTable().column(extPath.getReverseColumnName()),
130+
parentSelect);
131+
132+
Select select = StatementBuilder.select(sqlContext.getIdColumn()).from(sqlContext.getTable()).where(inCondition)
133+
.build();
134+
deleteRelations(deleteChain, extPath.getLeafEntity(), select);
135+
136+
deleteChain.add(StatementBuilder.delete(sqlContext.getTable()).where(inCondition).build());
137+
}
138+
}
139+
}
140+
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQuery.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.jdbc.repository.query;
1717

1818
import java.sql.ResultSet;
19+
import java.util.stream.Stream;
1920

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

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

80-
this.execution = getQueryExecution(queryMethod, extractor, rowMapper);
81+
this.execution = tree.isDelete() ? createModifyingQueryExecutor()
82+
: getQueryExecution(queryMethod, extractor, rowMapper);
8183
}
8284

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

99+
if (tree.isDelete()) {
100+
return createDeleteQueries(accessor).map(query -> execution.execute(query.getQuery(), query.getParameterSource()))
101+
.reduce((a, b) -> b);
102+
}
103+
97104
ParametrizedQuery query = createQuery(accessor);
98105
return this.execution.execute(query.getQuery(), query.getParameterSource());
99106
}
@@ -104,4 +111,13 @@ protected ParametrizedQuery createQuery(RelationalParametersParameterAccessor ac
104111
JdbcQueryCreator queryCreator = new JdbcQueryCreator(context, tree, converter, dialect, entityMetadata, accessor);
105112
return queryCreator.createQuery(getDynamicSort(accessor));
106113
}
114+
115+
private Stream<ParametrizedQuery> createDeleteQueries(RelationalParametersParameterAccessor accessor) {
116+
117+
RelationalEntityMetadata<?> entityMetadata = getQueryMethod().getEntityInformation();
118+
JdbcDeleteQueryCreator queryCreator = new JdbcDeleteQueryCreator(context, tree, converter, dialect, entityMetadata,
119+
accessor);
120+
121+
return queryCreator.createQuery();
122+
}
107123
}

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryWithCollectionsIntegrationTests.java

+23-5
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@
2727
import java.util.HashSet;
2828
import java.util.Set;
2929

30-
import org.junit.ClassRule;
31-
import org.junit.Rule;
3230
import org.junit.Test;
3331
import org.junit.runner.RunWith;
3432
import org.springframework.beans.factory.annotation.Autowired;
@@ -45,8 +43,6 @@
4543
import org.springframework.test.context.ContextConfiguration;
4644
import org.springframework.test.context.TestExecutionListeners;
4745
import org.springframework.test.context.junit4.SpringRunner;
48-
import org.springframework.test.context.junit4.rules.SpringClassRule;
49-
import org.springframework.test.context.junit4.rules.SpringMethodRule;
5046
import org.springframework.transaction.annotation.Transactional;
5147

5248
/**
@@ -188,14 +184,36 @@ public void deletingWithSet() {
188184
assertThat(count).isEqualTo(0);
189185
}
190186

187+
@Test // DATAJDBC-551
188+
public void deleteByName() {
189+
190+
Element element1 = createElement("one");
191+
Element element2 = createElement("two");
192+
193+
DummyEntity entity = createDummyEntity();
194+
entity.content.add(element1);
195+
entity.content.add(element2);
196+
197+
entity = repository.save(entity);
198+
199+
assertThat(repository.deleteByName("Entity Name")).isEqualTo(1);
200+
201+
assertThat(repository.findById(entity.id)).isEmpty();
202+
203+
Long count = template.queryForObject("select count(1) from Element", new HashMap<>(), Long.class);
204+
assertThat(count).isEqualTo(0);
205+
}
206+
191207
private Element createElement(String content) {
192208

193209
Element element = new Element();
194210
element.content = content;
195211
return element;
196212
}
197213

198-
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {}
214+
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
215+
long deleteByName(String name);
216+
}
199217

200218
@Configuration
201219
@Import(TestConfiguration.class)

0 commit comments

Comments
 (0)