Skip to content

Commit 2f97ff2

Browse files
JaeYeon KimJaeYeon Kim
authored andcommitted
JdbcAggregateOperations delete by query
Issue link: #1978 Add deleteAllByQuery method to JdbcAggregateOperations This method enables deleting aggregates based on a query by performing the following steps: 1. Select root IDs matching the query with a SELECT ... FOR UPDATE to lock the rows. 2. Delete all sub-entities associated with the selected root IDs. 3. Delete the root entities identified by the selected IDs. Signed-off-by: JaeYeon Kim <[email protected]>
1 parent 0581a6e commit 2f97ff2

File tree

16 files changed

+401
-0
lines changed

16 files changed

+401
-0
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/AggregateChangeExecutor.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
* @author Myeonghyeon Lee
3131
* @author Chirag Tailor
3232
* @author Mikhail Polivakha
33+
* @author Jaeyeon Kim
3334
* @since 2.0
3435
*/
3536
class AggregateChangeExecutor {
@@ -101,10 +102,16 @@ private void execute(DbAction<?> action, JdbcAggregateChangeExecutionContext exe
101102
executionContext.executeBatchDeleteRoot(batchDeleteRoot);
102103
} else if (action instanceof DbAction.DeleteAllRoot<?> deleteAllRoot) {
103104
executionContext.executeDeleteAllRoot(deleteAllRoot);
105+
} else if (action instanceof DbAction.DeleteRootByIdIn<?> deleteRootByIdIn) {
106+
executionContext.executeDeleteRootByIdIn(deleteRootByIdIn);
107+
} else if (action instanceof DbAction.DeleteByRootIdIn<?> deleteByRootIdIn) {
108+
executionContext.executeDeleteByRootIdIn(deleteByRootIdIn);
104109
} else if (action instanceof DbAction.AcquireLockRoot<?> acquireLockRoot) {
105110
executionContext.executeAcquireLock(acquireLockRoot);
106111
} else if (action instanceof DbAction.AcquireLockAllRoot<?> acquireLockAllRoot) {
107112
executionContext.executeAcquireLockAllRoot(acquireLockAllRoot);
113+
} else if (action instanceof DbAction.AcquireLockAllRootByQuery<?> acquireLockAllRootByQuery) {
114+
executionContext.executeAcquireLockRootByQuery(acquireLockAllRootByQuery);
108115
} else {
109116
throw new RuntimeException("unexpected action");
110117
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.springframework.data.mapping.PersistentPropertyPath;
4141
import org.springframework.data.mapping.PersistentPropertyPathAccessor;
4242
import org.springframework.data.relational.core.conversion.DbAction;
43+
import org.springframework.data.relational.core.conversion.SelectIdsDbActionExecutionResult;
4344
import org.springframework.data.relational.core.conversion.DbActionExecutionResult;
4445
import org.springframework.data.relational.core.conversion.IdValueSource;
4546
import org.springframework.data.relational.core.mapping.AggregatePath;
@@ -60,6 +61,7 @@
6061
* @author Myeonghyeon Lee
6162
* @author Chirag Tailor
6263
* @author Mark Paluch
64+
* @author Jaeyeon Kim
6365
*/
6466
@SuppressWarnings("rawtypes")
6567
class JdbcAggregateChangeExecutionContext {
@@ -72,6 +74,7 @@ class JdbcAggregateChangeExecutionContext {
7274
private final DataAccessStrategy accessStrategy;
7375

7476
private final Map<DbAction<?>, DbActionExecutionResult> results = new LinkedHashMap<>();
77+
private final Map<DbAction.SelectIds<?>, SelectIdsDbActionExecutionResult> selectIdsDbActionExecutionResult = new LinkedHashMap<>();
7578

7679
JdbcAggregateChangeExecutionContext(JdbcConverter converter, DataAccessStrategy accessStrategy) {
7780

@@ -169,6 +172,34 @@ <T> void executeDeleteAll(DbAction.DeleteAll<T> delete) {
169172
accessStrategy.deleteAll(delete.getPropertyPath());
170173
}
171174

175+
<T> void executeDeleteRootByIdIn(DbAction.DeleteRootByIdIn<T> deleteRootByIdIn) {
176+
SelectIdsDbActionExecutionResult result = getRequiredSelectIdsResult(deleteRootByIdIn.getSelectIdsAction());
177+
178+
List<Object> rootIds = new ArrayList<>(result.getSelectedIds());
179+
if (rootIds.isEmpty()) {
180+
return;
181+
}
182+
accessStrategy.delete(rootIds, deleteRootByIdIn.getEntityType());
183+
}
184+
185+
<T> void executeDeleteByRootIdIn(DbAction.DeleteByRootIdIn<T> deleteByRootIdIn) {
186+
SelectIdsDbActionExecutionResult result = getRequiredSelectIdsResult(deleteByRootIdIn.getSelectIdsAction());
187+
188+
List<Object> rootIds = new ArrayList<>(result.getSelectedIds());
189+
if (rootIds.isEmpty()) {
190+
return;
191+
}
192+
accessStrategy.delete(rootIds, deleteByRootIdIn.getPropertyPath());
193+
}
194+
195+
private SelectIdsDbActionExecutionResult getRequiredSelectIdsResult(DbAction.SelectIds selectIdsAction) {
196+
SelectIdsDbActionExecutionResult result = selectIdsDbActionExecutionResult.get(selectIdsAction);
197+
if (result == null) {
198+
throw new IllegalArgumentException("Expected SelectIdsDbActionExecutionResult for given selectIdsAction but found none");
199+
}
200+
return result;
201+
}
202+
172203
<T> void executeAcquireLock(DbAction.AcquireLockRoot<T> acquireLock) {
173204
accessStrategy.acquireLockById(acquireLock.getId(), LockMode.PESSIMISTIC_WRITE, acquireLock.getEntityType());
174205
}
@@ -177,6 +208,13 @@ <T> void executeAcquireLockAllRoot(DbAction.AcquireLockAllRoot<T> acquireLock) {
177208
accessStrategy.acquireLockAll(LockMode.PESSIMISTIC_WRITE, acquireLock.getEntityType());
178209
}
179210

211+
<T> void executeAcquireLockRootByQuery(DbAction.AcquireLockAllRootByQuery<T> acquireLock) {
212+
213+
List<?> rootIds = accessStrategy.acquireLockAndFindIdsByQuery(acquireLock.getQuery(), LockMode.PESSIMISTIC_WRITE, acquireLock.getEntityType());
214+
215+
selectIdsDbActionExecutionResult.put(acquireLock, new SelectIdsDbActionExecutionResult(rootIds, acquireLock));
216+
}
217+
180218
private void add(DbActionExecutionResult result) {
181219
results.put(result.getAction(), result);
182220
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
* @author Diego Krupitza
3838
* @author Myeonghyeon Lee
3939
* @author Sergey Korotaev
40+
* @author Jaeyeon Kim
4041
*/
4142
public interface JdbcAggregateOperations {
4243

@@ -324,4 +325,13 @@ public interface JdbcAggregateOperations {
324325
* @param <T> the type of the aggregate roots.
325326
*/
326327
<T> void deleteAll(Iterable<? extends T> aggregateRoots);
328+
329+
/**
330+
* Deletes all aggregates of the given type that match the provided query.
331+
*
332+
* @param query Must not be {@code null}.
333+
* @param domainType the type of the aggregate root. Must not be {@code null}.
334+
* @param <T> the type of the aggregate root.
335+
*/
336+
<T> void deleteAllByQuery(Query query, Class<T> domainType);
327337
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
* @author Diego Krupitza
7171
* @author Sergey Korotaev
7272
* @author Mikhail Polivakha
73+
* @author Jaeyeon Kim
7374
*/
7475
public class JdbcAggregateTemplate implements JdbcAggregateOperations {
7576

@@ -461,6 +462,17 @@ public <T> void deleteAll(Iterable<? extends T> instances) {
461462
}
462463
}
463464

465+
@Override
466+
public <T> void deleteAllByQuery(Query query, Class<T> domainType) {
467+
468+
Assert.notNull(query, "Query must not be null");
469+
Assert.notNull(domainType, "Domain type must not be null");
470+
471+
MutableAggregateChange<?> change = createDeletingChange(query, domainType);
472+
473+
executor.executeDelete(change);
474+
}
475+
464476
private <T> void verifyIdProperty(T instance) {
465477
// accessing the id property just to raise an exception in the case it does not exist.
466478
context.getRequiredPersistentEntity(instance.getClass()).getRequiredIdProperty();
@@ -639,6 +651,13 @@ private MutableAggregateChange<?> createDeletingChange(Class<?> domainType) {
639651
return aggregateChange;
640652
}
641653

654+
private MutableAggregateChange<?> createDeletingChange(Query query, Class<?> domainType) {
655+
656+
MutableAggregateChange<?> aggregateChange = MutableAggregateChange.forDelete(domainType);
657+
jdbcEntityDeleteWriter.writeForQuery(query, aggregateChange);
658+
return aggregateChange;
659+
}
660+
642661
private <T> List<T> triggerAfterConvert(Iterable<T> all) {
643662

644663
List<T> result = new ArrayList<>();

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/CascadingDataAccessStrategy.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
* @author Chirag Tailor
4545
* @author Diego Krupitza
4646
* @author Sergey Korotaev
47+
* @author Jaeyeon Kim
4748
* @since 1.1
4849
*/
4950
public class CascadingDataAccessStrategy implements DataAccessStrategy {
@@ -119,6 +120,11 @@ public <T> void acquireLockAll(LockMode lockMode, Class<T> domainType) {
119120
collectVoid(das -> das.acquireLockAll(lockMode, domainType));
120121
}
121122

123+
@Override
124+
public <T> List<?> acquireLockAndFindIdsByQuery(Query query, LockMode lockMode, Class<T> domainType) {
125+
return collect(das -> das.acquireLockAndFindIdsByQuery(query, lockMode, domainType));
126+
}
127+
122128
@Override
123129
public long count(Class<?> domainType) {
124130
return collect(das -> das.count(domainType));

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DataAccessStrategy.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
* @author Chirag Tailor
4444
* @author Diego Krupitza
4545
* @author Sergey Korotaev
46+
* @author Jaeyeon Kim
4647
*/
4748
public interface DataAccessStrategy extends ReadingDataAccessStrategy, RelationResolver {
4849

@@ -194,6 +195,18 @@ public interface DataAccessStrategy extends ReadingDataAccessStrategy, RelationR
194195
*/
195196
<T> void acquireLockAll(LockMode lockMode, Class<T> domainType);
196197

198+
/**
199+
* Acquire a lock on all aggregates that match the given {@link Query} and return their identifiers.
200+
* The resulting SQL will include a {@code SELECT id FROM … WHERE … (LOCK CLAUSE)} to retrieve and lock the matching rows.
201+
*
202+
* @param query the query specifying which entities to lock. Must not be {@code null}.
203+
* @param lockMode the lock mode to apply to the query (e.g. {@code FOR UPDATE}). Must not be {@code null}.
204+
* @param domainType the domain type of the entities to be locked. Must not be {@code null}.
205+
* @param <T> the type of the domain entity.
206+
* @return a {@link List} of ids corresponding to the rows locked by the query.
207+
*/
208+
<T> List<?> acquireLockAndFindIdsByQuery(Query query, LockMode lockMode, Class<T> domainType);
209+
197210
/**
198211
* Counts the rows in the table representing the given domain type.
199212
*

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.springframework.data.relational.core.sql.LockMode;
4040
import org.springframework.data.relational.core.sql.SqlIdentifier;
4141
import org.springframework.jdbc.core.RowMapper;
42+
import org.springframework.jdbc.core.SingleColumnRowMapper;
4243
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
4344
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
4445
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
@@ -63,6 +64,7 @@
6364
* @author Diego Krupitza
6465
* @author Sergey Korotaev
6566
* @author Mikhail Polivakha
67+
* @author Jaeyeon Kim
6668
* @since 1.1
6769
*/
6870
public class DefaultDataAccessStrategy implements DataAccessStrategy {
@@ -259,6 +261,28 @@ public <T> void acquireLockAll(LockMode lockMode, Class<T> domainType) {
259261
operations.getJdbcOperations().query(acquireLockAllSql, ResultSet::next);
260262
}
261263

264+
@Override
265+
public <T> List<?> acquireLockAndFindIdsByQuery(Query query, LockMode lockMode, Class<T> domainType) {
266+
267+
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
268+
String acquireLockByQuerySql = sql(domainType).getAcquireLockAndFindIdsByQuery(query, parameterSource, lockMode);
269+
270+
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(domainType);
271+
RelationalPersistentProperty idProperty = entity.getRequiredIdProperty();
272+
273+
return operations.query(acquireLockByQuerySql, parameterSource, getIdRowMapper(idProperty));
274+
}
275+
276+
private RowMapper<?> getIdRowMapper(RelationalPersistentProperty idProperty) {
277+
RelationalPersistentEntity<?> complexId = context.getPersistentEntity(idProperty.getType());
278+
279+
if (complexId == null) {
280+
return SingleColumnRowMapper.newInstance(idProperty.getType(), converter.getConversionService());
281+
} else {
282+
return new EntityRowMapper<>(context.getRequiredPersistentEntity(idProperty.getType()), converter);
283+
}
284+
}
285+
262286
@Override
263287
public long count(Class<?> domainType) {
264288

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DelegatingDataAccessStrategy.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
* @author Chirag Tailor
4040
* @author Diego Krupitza
4141
* @author Sergey Korotaev
42+
* @author Jaeyeon Kim
4243
* @since 1.1
4344
*/
4445
public class DelegatingDataAccessStrategy implements DataAccessStrategy {
@@ -119,6 +120,11 @@ public <T> void acquireLockAll(LockMode lockMode, Class<T> domainType) {
119120
delegate.acquireLockAll(lockMode, domainType);
120121
}
121122

123+
@Override
124+
public <T> List<?> acquireLockAndFindIdsByQuery(Query query, LockMode lockMode, Class<T> domainType) {
125+
return delegate.acquireLockAndFindIdsByQuery(query, lockMode, domainType);
126+
}
127+
122128
@Override
123129
public long count(Class<?> domainType) {
124130
return delegate.count(domainType);

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
* @author Hari Ohm Prasath
7171
* @author Viktor Ardelean
7272
* @author Kurt Niemi
73+
* @author Jaeyeon Kim
7374
*/
7475
public class SqlGenerator {
7576

@@ -377,6 +378,18 @@ String getAcquireLockAll(LockMode lockMode) {
377378
return this.createAcquireLockAll(lockMode);
378379
}
379380

381+
/**
382+
* Create a {@code SELECT id FROM … WHERE … (LOCK CLAUSE)} statement based on the given query.
383+
*
384+
* @param query the query to base the select on. Must not be null.
385+
* @param parameterSource the source for holding the bindings.
386+
* @param lockMode Lock clause mode.
387+
* @return the SQL statement as a {@link String}. Guaranteed to be not {@literal null}.
388+
*/
389+
String getAcquireLockAndFindIdsByQuery(Query query, MapSqlParameterSource parameterSource, LockMode lockMode) {
390+
return this.createAcquireLockByQuery(query, parameterSource, lockMode);
391+
}
392+
380393
/**
381394
* Create a {@code INSERT INTO … (…) VALUES(…)} statement.
382395
*
@@ -594,6 +607,23 @@ private String createAcquireLockAll(LockMode lockMode) {
594607
return render(select);
595608
}
596609

610+
private String createAcquireLockByQuery(Query query, MapSqlParameterSource parameterSource, LockMode lockMode) {
611+
612+
Assert.notNull(parameterSource, "parameterSource must not be null");
613+
614+
Table table = this.getTable();
615+
616+
SelectBuilder.SelectWhere selectBuilder = StatementBuilder
617+
.select(getIdColumns())
618+
.from(table);
619+
620+
Select select = applyQueryOnSelect(query, parameterSource, selectBuilder)
621+
.lock(lockMode)
622+
.build();
623+
624+
return render(select);
625+
}
626+
597627
private String createFindAllSql() {
598628
return render(selectBuilder().build());
599629
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
* @author Christopher Klein
6464
* @author Mikhail Polivakha
6565
* @author Sergey Korotaev
66+
* @author Jaeyeon Kim
6667
*/
6768
public class MyBatisDataAccessStrategy implements DataAccessStrategy {
6869

@@ -253,6 +254,11 @@ public <T> void acquireLockAll(LockMode lockMode, Class<T> domainType) {
253254
sqlSession().selectOne(statement, parameter);
254255
}
255256

257+
@Override
258+
public <T> List<?> acquireLockAndFindIdsByQuery(Query query, LockMode lockMode, Class<T> domainType) {
259+
throw new UnsupportedOperationException("Not implemented");
260+
}
261+
256262
@Override
257263
public <T> T findById(Object id, Class<T> domainType) {
258264

0 commit comments

Comments
 (0)