Spring Data MongoDB - 使用自定义 Id 字段时注释 @CreatedDate 不起作用

Posted

技术标签:

【中文标题】Spring Data MongoDB - 使用自定义 Id 字段时注释 @CreatedDate 不起作用【英文标题】:Spring Data MongoDB - Annotation @CreatedDate does not work while using with custom Id field 【发布时间】:2019-06-13 09:24:31 【问题描述】:

我有一个简单的 Persistable 类:

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;
    

还有一个简单的存储库:

public interface ProfileRepository extends MongoRepository<Profile, String> 


我的 Spring Boot 应用程序类也使用 @EnableMongoAuditing 进行了注释。但我仍然无法使注释 @CreatedDate 起作用。

ProfileRepository.save(new Profile("user1")) 写入没有字段 createdDate 的实体。我做错了什么?

编辑:这是我的应用程序类(没有@EnableMongoRepositories,但它可以工作,因为我猜存储库位于子包中)

@SpringBootApplication
@EnableMongoAuditing
public class Application 

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

编辑:同样添加注释 EnableMongoRepositories 并没有改变任何东西。

【问题讨论】:

它对我有用... 奇怪.. 2 小时我试图找出为什么它对我不起作用。我已经创建了我的实体,其 ID 为“new Profile(“username”)”,然后保存了它。你也可以这样试试吗 你能显示配置吗?我猜你有@EnableMongoRepositories。 在您的情况下,因为您始终设置用户名,所以 spring 存储库始终尝试将实体处理为更新(保存)而不是插入,因为 isNew 始终返回 false。所以 createdDate 字段永远不会被设置。我敢打赌,如果您包含 lastModified 字段,它将被设置。尝试实现Auditable 接口并在自定义 id 的情况下自己设置审计字段。更多here 进一步思考可能会滚动我们自己的Auditable 实现可能无法工作,因为 isNew 将返回 false 并且 spring 存储库将仅从实体复制 lastModifed 字段。所以看起来你必须更新 isNew 实现来区分插入和更新请求。 【参考方案1】:

我自己也遇到了这个问题,这是因为您自己创建了 id。

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

通过这样做,mongo 认为它不是一个新对象,并且不使用 @CreatedDate 注释。您也可以使用 @Document 注释而不是实现 Persistable 类,如下所示:

@Document
public class Profile

【讨论】:

顺便说一句,您永远不应该将用户名设置为 id,如果您决定用户名可以更改怎么办。 id 永远不应该改变,我建议你做 2 个变量,Id 和 Username。 你不知道我可以想象现实生活中用户名可以成为有效ID的例子 在我看来这仍然是一个不好的做法。 好吧,恕我直言,总的来说,我们应该更喜欢自然主键。当然,我们不能破坏不变量,例如一个名字可以改变。尽可能选择自然主键而不是代理项 但无论如何,我认为上述问题正在发生,因为他使用的是自然 id,他正在定义自己的 id。如果他将使用生成的 id(由 mongo 生成),则 creationDate 将起作用。【参考方案2】:

如果那是您真正的课程 (Profile),那么您无法使用标准工具进行任何操作。

您的isNew 方法将始终返回false,因为您自己设置了用户名,当Profile 即将被Spring 保存时,它将检查isNew,您可能已经设置了username . @LastModifiedDate 适用于您的情况,但 @CreatedDate 不会。

如果您没有可以在 isNew 方法中使用的其他字段,那么您必须手动设置 createdDate 的值(或者可能有某种拦截器可以包装所有 mongo 模板方法,但我不会那样做)。

例如,检查具有给定用户名的配置文件是否已经存在于数据库中,如果存在,只需获取它的 createdDate(您可以在此处使用投影)并设置为您将要保存的配置文件。否则将 createdDate 设置为新日期。

【讨论】:

【参考方案3】:

只需将@Version 字段添加到@Document 类并离开@EnableMongoAuditing

@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;
     
 

这是一个相关问题:https://jira.spring.io/browse/DATAMONGO-946

【讨论】:

【参考方案4】:

对我来说,我只是这样做:

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

并确保 Get/Set 可用。 希望对您有所帮助

【讨论】:

【参考方案5】:

我有类似的情况,有时我需要手动设置 id,如果不先搜索数据库来确定,真的无法提前知道 id 是新的还是更新的。我也知道在实体的生命周期中,我将多次更新我的实体,但只创建一次。所以为了避免额外的数据库操作,我选择了这个解决方案:

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

这是利用我在 Profile 上还有一个 @LastModifiedDate 字段这一事实,该字段在保存实体时始终由 spring 数据更新。

【讨论】:

【参考方案6】:

如 Spring Data MongoDB 问题DATAMONGO-946 中所述,创建日期功能使用isNew() 方法来确定是否应设置创建日期,因为实体是新实体。在您的情况下,您的 isNew 方法始终返回 false,因为始终设置了 username

问题中的 cmets 为这个问题提供了两种可能的解决方案。

Persistable解决方案

第一个选项是修复isNew 策略,使其正确注册新对象。 cmets 中建议的一种方法是更改​​实现以检查 createdDate 字段本身,因为它应该只设置在非新对象上。

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

持久化实体解决方案

第二个选项是从实现Persistable 改为使用持久化实体,并使用@Version 注释在持久化的MongoDB 实体中注入version 属性。请注意,这更改数据的持久化方式,因为它会在数据中添加一个自动递增的version 字段。

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;
    

【讨论】:

以上是关于Spring Data MongoDB - 使用自定义 Id 字段时注释 @CreatedDate 不起作用的主要内容,如果未能解决你的问题,请参考以下文章

如何在带有自定义过滤器的 Spring Data mongodb 中使用分页和排序?

Spring Boot MongoDB REST - 自定义存储库方法

spring data mongodb Query 及分页

Spring Data MongoDB 是不是支持枚举?

使用 Spring Data MongoRepository 进行更新查询的自定义方法

无法在 SpEL 中为 Spring Data MongoDB 集合名称解析 bean