Skip to content

Commit 08085d3

Browse files
blake-baumanfmbenhassine
authored andcommitted
Allow keys other than _id for primary key in MongoItemWriter
When moving data between different data store types, such as between MongoDB and a SQL database, not all systems have a "_id" as their pimary key. Most often it has a different name or can be multiple keys/columns used as a composite key. This change allows the app to specify an alternate key or a set of keys which, together, can be used as the primary key for a MongoDB insert/update/upsert. Signed-off-by: blake_bauman <[email protected]>
1 parent 3076957 commit 08085d3

File tree

2 files changed

+84
-5
lines changed

2 files changed

+84
-5
lines changed

spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemWriter.java

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
package org.springframework.batch.item.data;
1818

19+
import static java.util.stream.Collectors.toList;
20+
21+
import java.util.List;
22+
1923
import org.bson.Document;
2024
import org.bson.types.ObjectId;
2125

@@ -92,6 +96,8 @@ public enum Mode {
9296

9397
private Mode mode = Mode.UPSERT;
9498

99+
private List<String> primaryKeys = List.of(ID_KEY);
100+
95101
public MongoItemWriter() {
96102
super();
97103
this.bufferKey = new Object();
@@ -150,6 +156,27 @@ public String getCollection() {
150156
return collection;
151157
}
152158

159+
/**
160+
* Set the primary keys to associate with the document being written. These fields
161+
* should uniquely identify a single object.
162+
* @param primaryKeys The primary keys to use.
163+
* @since 5.2.3
164+
*/
165+
public void setPrimaryKeys(List<String> primaryKeys) {
166+
Assert.notEmpty(primaryKeys, "The primaryKeys list must have one or more keys.");
167+
168+
this.primaryKeys = primaryKeys;
169+
}
170+
171+
/**
172+
* Get the list of primary keys associated with the document being written.
173+
* @return the list of primary keys
174+
* @since 5.2.3
175+
*/
176+
public List<String> getPrimaryKeys() {
177+
return primaryKeys;
178+
}
179+
153180
/**
154181
* If a transaction is active, buffer items to be written just before commit.
155182
* Otherwise write items using the provided template.
@@ -200,9 +227,14 @@ private void remove(Chunk<? extends T> chunk) {
200227
for (Object item : chunk) {
201228
Document document = new Document();
202229
mongoConverter.write(item, document);
203-
Object objectId = document.get(ID_KEY);
204-
if (objectId != null) {
205-
Query query = new Query().addCriteria(Criteria.where(ID_KEY).is(objectId));
230+
231+
List<Criteria> criteriaList = primaryKeys.stream()
232+
.filter(document::containsKey)
233+
.map(key -> Criteria.where(key).is(document.get(key)))
234+
.collect(toList());
235+
if (!criteriaList.isEmpty()) {
236+
Query query = new Query();
237+
criteriaList.forEach(query::addCriteria);
206238
bulkOperations.remove(query);
207239
}
208240
}
@@ -216,8 +248,21 @@ private void upsert(Chunk<? extends T> chunk) {
216248
for (Object item : chunk) {
217249
Document document = new Document();
218250
mongoConverter.write(item, document);
219-
Object objectId = document.get(ID_KEY) != null ? document.get(ID_KEY) : new ObjectId();
220-
Query query = new Query().addCriteria(Criteria.where(ID_KEY).is(objectId));
251+
252+
Query query = new Query();
253+
List<Criteria> criteriaList = primaryKeys.stream()
254+
.filter(document::containsKey)
255+
.map(key -> Criteria.where(key).is(document.get(key)))
256+
.collect(toList());
257+
258+
if (criteriaList.isEmpty()) {
259+
Object objectId = document.get(ID_KEY) != null ? document.get(ID_KEY) : new ObjectId();
260+
query.addCriteria(Criteria.where(ID_KEY).is(objectId));
261+
}
262+
else {
263+
criteriaList.forEach(query::addCriteria);
264+
}
265+
221266
bulkOperations.replaceOne(query, document, upsert);
222267
}
223268
bulkOperations.execute();

spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/MongoItemWriterBuilder.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.batch.item.data.builder;
1818

19+
import java.util.List;
20+
1921
import org.springframework.batch.item.data.MongoItemWriter;
2022
import org.springframework.batch.item.data.MongoItemWriter.Mode;
2123
import org.springframework.data.mongodb.core.MongoOperations;
@@ -37,6 +39,8 @@ public class MongoItemWriterBuilder<T> {
3739

3840
private Mode mode = Mode.UPSERT;
3941

42+
private List<String> primaryKeys = List.of();
43+
4044
/**
4145
* Set the operating {@link Mode} to be applied by this writer. Defaults to
4246
* {@link Mode#UPSERT}.
@@ -76,6 +80,32 @@ public MongoItemWriterBuilder<T> collection(String collection) {
7680
return this;
7781
}
7882

83+
/**
84+
* Set the primary keys to associate with the document being written. These fields
85+
* should uniquely identify a single object.
86+
* @param primaryKeys The keys to use.
87+
* @see MongoItemWriter#setPrimaryKeys(List)
88+
* @since 5.2.3
89+
*/
90+
public MongoItemWriterBuilder<T> primaryKeys(List<String> primaryKeys) {
91+
this.primaryKeys = List.copyOf(primaryKeys);
92+
93+
return this;
94+
}
95+
96+
/**
97+
* Set the primary keys to associate with the document being written. These fields
98+
* should uniquely identify a single object.
99+
* @param primaryKeys The keys to use.
100+
* @see MongoItemWriter#setPrimaryKeys(List)
101+
* @since 5.2.3
102+
*/
103+
public MongoItemWriterBuilder<T> primaryKeys(String... primaryKeys) {
104+
this.primaryKeys = List.of(primaryKeys);
105+
106+
return this;
107+
}
108+
79109
/**
80110
* Validates and builds a {@link MongoItemWriter}.
81111
* @return a {@link MongoItemWriter}
@@ -88,6 +118,10 @@ public MongoItemWriter<T> build() {
88118
writer.setMode(this.mode);
89119
writer.setCollection(this.collection);
90120

121+
if (!this.primaryKeys.isEmpty()) {
122+
writer.setPrimaryKeys(this.primaryKeys);
123+
}
124+
91125
return writer;
92126
}
93127

0 commit comments

Comments
 (0)