Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 98394e9

Browse files
JaeYeon KimJaeYeon Kim
JaeYeon Kim
authored and
JaeYeon Kim
committed
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 <JaeYeon.Kim@ibm.com>
1 parent d98ef18 commit 98394e9

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)

AltStyle によって変換されたページ (->オリジナル) /