Description
László Csontos opened DATAMONGO-1617 and commented
Under normal circumstances the MongoDB driver generates a unique ID for objects to be persisted. The default set of types for which this works out-of-the-box are enumerated here: org.springframework.data.mongodb.core.mapping.MongoSimpleTypes.AUTOGENERATED_ID_TYPES, that is, ObjectId, String and BigInteger.
Let's consider the following example:
public class Customer implements Persistable<UUID> {
@Id
private UUID id;
...
public UUID getId() { return id; }
public void setId(UUID id) { this.id = id; }
public boolean isNew() { return (getId() == null); }
...
When an instance of this object is saved, it already has to contain an UUID instance otherwise the following exception occurs:
Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: Cannot autogenerate id of type java.util.UUID for entity of type hello.Customer!
at org.springframework.data.mongodb.core.MongoTemplate.assertUpdateableIdIfNotSet(MongoTemplate.java:1304) ~[spring-data-mongodb-1.10.0.RELEASE.jar:na]
at org.springframework.data.mongodb.core.MongoTemplate.doInsert(MongoTemplate.java:845) ~[spring-data-mongodb-1.10.0.RELEASE.jar:na]
at org.springframework.data.mongodb.core.MongoTemplate.insert(MongoTemplate.java:793) ~[spring-data-mongodb-1.10.0.RELEASE.jar:na]
at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.save(SimpleMongoRepository.java:80) ~[spring-data-mongodb-1.10.0.RELEASE.jar:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_65]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_65]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_65]
at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_65]
Even if someone tries to define a custom entity listener to generate UUIDs for entities automatically, it's not going to work due to the way how MongoTemplate is implemented.
@Component
public class GenerateUUIDListener extends AbstractMongoEventListener<Customer> {
@Override
public void onBeforeConvert(BeforeConvertEvent<Customer> event) {
Customer customer = event.getSource();
if (customer.isNew()) {
customer.setId(UUID.randomUUID());
}
}
}
The following code was taken from MongoTemplate.doInsert()_.
protected <T> void doInsert(String collectionName, T objectToSave, MongoWriter<T> writer) {
assertUpdateableIdIfNotSet(objectToSave);
initializeVersionProperty(objectToSave);
maybeEmitEvent(new BeforeConvertEvent<T>(objectToSave, collectionName));
DBObject dbDoc = toDbObject(objectToSave, writer);
maybeEmitEvent(new BeforeSaveEvent<T>(objectToSave, dbDoc, collectionName));
Object id = insertDBObject(collectionName, dbDoc, objectToSave.getClass());
populateIdIfNecessary(objectToSave, id);
maybeEmitEvent(new AfterSaveEvent<T>(objectToSave, dbDoc, collectionName));
}
Here the problem is that assertUpdateableIdIfNotSet(objectToSave) is called before emitting BeforeConvertEvent and this way entity listeners don't have the chance to populate custom ID fields.
My suggestion is that emitting the BeforeConvertEvent could come before checking the ID and the setting the version fields to fix this.
What do you think about this approach?
Affects: 1.10 GA (Ingalls)
Reference URL: https://github.com/springuni/gs-accessing-data-mongodb/commits/DATAMONGO-1617
Referenced from: pull request #443
Backported to: 1.10.1 (Ingalls SR1), 1.9.8 (Hopper SR8)