Reputation: 965
I would like to have Documents stored with an UUID id and createdAt / updatedAt fields. My solution was working with Spring Boot 2.1.x. After I upgraded from Spring Boot 2.1.11.RELEASE to 2.2.0.RELEASE my test for MongoAuditing failed with createdAt = null
. What do I need to do to get the createdAt field filled again?
This is not just a testproblem. I ran the application and it has the same behaviour as my test. All auditing fields stay null.
I have a Configuration to enable MongoAuditing and UUID generation:
@Configuration
@EnableMongoAuditing
public class MongoConfiguration {
@Bean
public GenerateUUIDListener generateUUIDListener() {
return new GenerateUUIDListener();
}
}
The listner hooks into the onBeforeConvert
- I guess thats where the trouble starts.
public class GenerateUUIDListener extends AbstractMongoEventListener<IdentifiableEntity> {
@Override
public void onBeforeConvert(BeforeConvertEvent<IdentifiableEntity> event) {
IdentifiableEntity entity = event.getSource();
if (entity.isNew()) {
entity.setId(UUID.randomUUID());
}
}
}
The document itself (I dropped the getter and setters):
@Document
public class MyDocument extends InsertableEntity {
private String name;
}
public abstract class InsertableEntity extends IdentifiableEntity {
@CreatedDate
@JsonIgnore
private Instant createdAt;
}
public abstract class IdentifiableEntity implements Persistable<UUID> {
@Id
private UUID id;
@JsonIgnore
public boolean isNew() {
return getId() == null;
}
}
A complete minimal example can be find here (including a test) https://github.com/mab/auditable With 2.1.11.RELEASE the test succeeds with 2.2.0.RELEASE it fails.
Upvotes: 4
Views: 5267
Reputation: 965
For me the best solution was to switch from event UUID generation to a callback based one. With the implementation of Ordered
we can set the new callback to be executed after the AuditingEntityCallback
.
public class IdEntityCallback implements BeforeConvertCallback<IdentifiableEntity>, Ordered {
@Override
public IdentifiableEntity onBeforeConvert(IdentifiableEntity entity, String collection) {
if (entity.isNew()) {
entity.setId(UUID.randomUUID());
}
return entity;
}
@Override
public int getOrder() {
return 101;
}
}
I registered the callback with the MongoConfiguration
. For a more general solution you might want to take a look at the registration of the AuditingEntityCallback
with the `MongoAuditingBeanDefinitionParser.
@Configuration
@EnableMongoAuditing
public class MongoConfiguration {
@Bean
public IdEntityCallback registerCallback() {
return new IdEntityCallback();
}
}
Upvotes: 4
Reputation: 4983
MongoTemplate
works in the following way on doInsert()
this.maybeEmitEvent
- emit an event (onBeforeConvert
, onBeforeSave
and such) so any AbstractMappingEventListener
can catch and act upon like you did with GenerateUUIDListener
this.maybeCallBeforeConvert
- call before convert callbacks like mongo auditing
like you can see in source code of MongoTemplate.class
src (831-832)
protected <T> T doInsert(String collectionName, T objectToSave, MongoWriter<T> writer) {
BeforeConvertEvent<T> event = new BeforeConvertEvent(objectToSave, collectionName);
T toConvert = ((BeforeConvertEvent)this.maybeEmitEvent(event)).getSource(); //emit event
toConvert = this.maybeCallBeforeConvert(toConvert, collectionName); //call some before convert handlers
...
}
MongoAudit
marks createdAt
only to new entities by checking if entity.isNew() == true
because your code (UUID) already set the Id
the createdAt
is not populated (the entity is not considered new)
you can do the following (order by best to worst):
UUID
and use String
for your id, let the mongo itself create and manage it's entities ids (this how MongoTemplate
actually works lines 811-812)UUID
at the code level, convert from/to String
when inserting and retrieving from the db2.1.11.RELEASE
updateAt
by GenerateUUIDListener
as well as id
(rename it NewEntityListener
or smth), basically implement the auditisNew()
logic that don't depends only on the entity id
in version
2.1.11.RELEASE
the order of the methods was flipped (MongoTemplate.class
804-805) so your code worked fine
as an abstract approach, the nature of event is to be sort of send-and-forget (async compatible), so it's a very bad practice to change the object itself, there is NO grantee for order of computation, if any
this is why the audit build on callbacks and not events, and that's why Pivotal don't (need to) keep order between versions
Upvotes: 1