7

I have a simple Persistable Class:

public class Profile implements Persistable<String>{

    @Id
    private String username;

    @CreatedDate
    public Date createdDate;

    public Profile(String username) {
        this.username = username;
    }

    @Override
    public String getId() {
        return username;
    }

    @Override
    public boolean isNew() {
        return username == null;
    }
}

And a simple repository:

public interface ProfileRepository extends MongoRepository<Profile, String> {

}

My Spring Boot Application class is also annotated with @EnableMongoAuditing. But i still can't get the annotation @CreatedDate work.

ProfileRepository.save(new Profile("user1")) writes the entity without the field createdDate. What do i do wrong?

EDIT: This is my Application class (without @EnableMongoRepositories, but it works since the repositories are in the sub-packages i guess)

@SpringBootApplication
@EnableMongoAuditing
public class Application {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

EDIT: Also adding the annotation EnableMongoRepositories did not change anything.

13
  • it worked for me... Commented Nov 1, 2017 at 12:47
  • strange.. for 2 hours i am trying to find out why it does not work for me. I have created my entity with its Id "new Profile("username")", and then saved it. Could you also try it that way Commented Nov 1, 2017 at 12:50
  • Can you show the configuration ? You have @EnableMongoRepositories I Guess. Commented Nov 1, 2017 at 12:50
  • 2
    In your case as you always have username set spring repository always try to process entity as update(save) rather than insert because isNew always return false. So the createdDate field is never set. I bet if you include lastModified field it will be set.Try implementing Auditable interface and set the audit fields yourself in case of custom id. More here Commented Nov 1, 2017 at 14:22
  • 1
    On further thinking may be rolling our own implementation of Auditable may not work because isNew will return false and spring repository will copy only the lastModifed field from entity. So it looks like you have to update the isNew implementation to differentiate between insert and update request. Commented Nov 1, 2017 at 14:32

6 Answers 6

7

simply just add @Version field to you @Document class and leave @EnableMongoAuditing i.e.

@Document
public class Profile implements Persistable<String>{

     @Version      
     private Long version;
    
     @Id
     private String username;

     @CreatedDate
     public Date createdDate;

     public Profile(String username) {
         this.username = username;
     }

     @Override
     public String getId() {
         return username;
     }

     @Override
     public boolean isNew() {
         return username == null;
     }
 }

Here is a related issue: https://jira.spring.io/browse/DATAMONGO-946

Sign up to request clarification or add additional context in comments.

Comments

5

I just ran into this problem myself, it happens because you are creating the id yourself.

public Profile(String username) {
        this.username = username;
    }

By doing this, mongo thinks it is not a new object and doesn't use the @CreatedDate annotation. You could also use the @Document annotation instead of implementing the Persistable Class, like this:

@Document
public class Profile{}

6 Comments

By the way, you should never set username as an id, what if you decide that a username can change. An id should never change, I suggest you make 2 variables, Id and Username.
You don't know the domain I can imagine examples from real life where username can become a valid ID
In my eyes it's still a bad practice.
Well, IMHO in general we should prefer natural primary keys. Of course we can't break invariants such that e.g. a name can change. Just choose natural primary keys over surrogates when it's possible
but anyway, i think the problem above is occurring because he is using a natural id, an id he is defining himself. if he would use a generated id (generated by mongo) the creationDate would work.
|
2

As described in Spring Data MongoDB issue DATAMONGO-946, the created date functionality uses the isNew() method to determine whether the created date should be set since the entity is new. In your case, your isNew method is always returning false, since the username is always set.

The comments in the issue presents two possible solutions to this problem.

Persistable solution

The first option is to fix the isNew strategy so that it correctly registers new objects. One way suggested in the comments would be to change the implementation to check the createdDate field itself, since it should only be set on non-new objects.

@Override
public boolean isNew() {
    return createdDate == null;
}

Persistent entity solution

The second option is to change from implementing Persistable to instead use a persistent entity and use the @Version annotation to inject a version property in the persisted MongoDB entity. Note that this will change how the data is persisted, as it adds an auto-incrementing version field to the data.

import org.springframework.data.mongodb.core.mapping.Document;

@Document
public class Profile {
    @Id
    private String username;

    @CreatedDate
    public Date createdDate;

    @Version
    public Integer version;

    public Profile(String username) {
        this.username = username;
    }
}

1 Comment

It seems it does not work for reactive MongoDB (WebFlux Spring Boot + reactive MongoDB). I tried and it does not add createdDate. Does anybody know if it is applicable for reactive MongoDB?
1

If that's your real class (Profile) then there's nothing you can do with standard tools.

Your isNew method will always return false, because you set the username by yourself and when Profile is about to be saved by Spring it will check the isNew and you probably have username already set. @LastModifiedDate would work in your case, but @CreatedDate won't.

If you haven't got other fields that you can use in isNew method then you have to set the value of createdDate manually (or maybe there's some kind of interceptor that may wrap all mongo template method, but I wouldn't go that way).

For example, check if profile with given username already exists in DB, if so just get it's createdDate(you can use projection here) and set to the profile that you're about to save. Otherwise set createdDate to new date.

Comments

-1

For me, i just do it as:

    @CreatedDate
    @Field("created_date")
    @JsonIgnore
    private Instant createdDate = Instant.now();

And make sure the Get/Set is available. Hope this help

Comments

-1

I have a similar situation where I needed to set the id manually sometimes and there really is no way to know ahead of time if the id will be a new one or an update without first searching the database to be sure. I also know that over the lifetime of the entity, I will be updating my entity many times, but creating it only once. So to avoid the extra database operations, I went with this solution:

    Profile savedProfile = profileRepo.save(profile);
    if (savedProfile.getCreatedDate() == null ){
        savedProfile.setCreatedDate(savedProfile.getLastModifiedDate());
        savedProfile = profileRepo.save(profile);
    }

This is taking advantage of the fact that I also have a @LastModifiedDate field on Profile as well that is always updated by spring data when an entity is saved.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.