Description
Here's an easy question: I want to update or insert a document based on identifiers that together are a "logical ID".
What is the best way?
I asked this question on StackOverflow/-Exchange with more than 2k views, but no answers.
We already have custom JPA methods for find
etc. (e.g. findOneByColorAndBrand
), which are "implemented" automatically by Spring. Is there any hope we get the same for save
(=> saveByColorAndBrand
)?
I'm so confused that this topic is not described anywhere in the internet, what am I doing differently than others? How are people saving their documents usually, if not with these "logical IDs"?
Example:
@Document(collection = "cars")
public class CarEntity {
@Id
private ObjectId id;
private String color;
private String brand;
private Instant repairedAt;
...
}
Each car is identifiable with the combination of color
and brand
, which is the logical ID to upsert a document. I want to update the repairedAt
field.
Now there are (to my knowledge) three good ways to handle that.
Option 1: Custom repository with custom upsertOneByColorAndBrand
which either
1.a) uses the CarRepository
internally:
public void upsertOneByColorAndBrand(final CarEntity carNew,
final String color,
final String brand) {
// findOneByColorAndBrand is no custom implementation, just from the interface
var carExisting = repo.findOneByColorAndBrand(color, brand);
if (carExisting == null) {
// write to DB if not existent
repo.save(carNew);
} else {
// update document if existing
carExisting.setRepairedAt(carNew.getRepairedAt());
repo.save(carExisting);
}
}
1.b) or uses MongoTemplate
internally:
public void upsertOneByColorAndBrand(final CarEntity carNew,
final String color,
final String brand) {
// define find query
Query query = new Query();
query.addCriteria(Criteria.where("color").is(color));
query.addCriteria(Criteria.where("brand").is(brand));
// create document from entity
Document doc = new Document();
mongoTemplate.getConverter().write(carNew, doc);
Update update = Update.fromDocument(doc);
// upsert
mongoTemplate.upsert(query, update, CarEntity.class);
}
I compared the performance of option 1.a and option 1.b and the difference is in the milliseconds for a three digit amount of documents. I think that's because 1.a) splits up the "find" and "save" operation.
Option 2: You could also skip the whole custom repository implementation and just define the ID of the document yourself, based on the combination of color
& brand
, as the combination of these are unique in this example. When doing that, you can just use the native repo.save(car)
, as it will automagically use the new composite ID to operate, which skips the whole "find" part.
Are there huge downsides/benefits with option 2? In your experience, is using a composite ID in the Spring Data environment a good idea? Would you rather use compound indexes or "set" the ID manually when creating the entity?