【问题标题】:Why does Spring Data MongoDB not expose events for update…(…) methods?为什么 Spring Data MongoDB 不公开更新…(…) 方法的事件?
【发布时间】:2016-03-07 00:43:20
【问题描述】:

mongoOperations 的更新似乎不会触发 AbstractMongoEventListener 中的事件。

This post indicates that was at least the case in Nov 2014

目前有什么方法可以收听如下更新事件吗?如果是这样的话,这似乎是一个很大的遗漏。

MongoTemplate.updateMulti()

谢谢!

【问题讨论】:

    标签: spring spring-data-mongodb event-listener


    【解决方案1】:

    这不是疏忽。事件至少是围绕域对象或文档的生命周期设计的,这意味着它们通常包含您感兴趣的域对象的实例。

    另一方面,更新完全在数据库中处理。所以MongoTemplate 中没有处理任何文档甚至域对象。考虑这一点基本上与 JPA @EntityListeners 仅针对首先加载到持久性上下文中的实体触发的方式相同,但在执行查询时不会触发,因为查询的执行是在数据库中发生的。

    【讨论】:

    • 感谢您的评论,但我仍然认为这是一个疏忽。您对“至少围绕域对象或文档的生命周期设计事件”的评论对我来说并不排除更新,您在其中按类引用域对象,如 MongoTemplate.updateMulti(query, update, DomainObject .class,collectionName);这意味着 $update 是域对象的一部分,可由 $query 引用,并且属于 DomainObject 类型。
    • 即使不排除对你的更新,唯一的实现方式基本上会破坏更新操作的效率,因为我们必须先阅读文档,物化ist,触发事件然后写回来。这完全颠覆了更新操作的意图,更新操作应该在在数据库中执行。如果您希望将域对象具体化,请读取它们、更改它们并将它们写回。你不能同时拥有两者。这就是数据库(在本例中为 MongoDB)的工作原理。
    • 如果假设的更新侦听器 (onBeforeUpdate) 只是提供查询 DBO 并将 DBO 更新给侦听器会怎么样。这将至少服务于我正在考虑的两个用例。首先是过期一个 etag 对象(存储在另一个数据库中),其次是发送一个带有更新状态信息的 webhook。
    • 虽然我不同意这个理由,也不是这里的解决方案[必然]必须颠覆 update() 的效率,但这是我原来问题的正确答案。
    • @Version 是如何处理这个问题的呢?当您调用 mongoTemplate.updateFIrst(...) 时,Spring Data Mongo 会将 , "$inc" : { "version" : { "$numberLong" : "1" } } 添加到您的查询中。
    【解决方案2】:

    我知道现在回答这个问题为时已晚,我对 MongoTemplate.findAndModify 方法有同样的情况,我需要事件的原因是出于审计目的。这就是我所做的。

    1.EventPublisher(MongoTemplate 的方法)

    public class CustomMongoTemplate extends MongoTemplate {
    
        private ApplicationEventPublisher applicationEventPublisher;
    
    
        @Autowired
        public void setApplicationEventPublisher(ApplicationEventPublisher
                                                             applicationEventPublisher) {
            this.applicationEventPublisher = applicationEventPublisher;
        }
    
       //Default Constructor here
    
        @Override
        public <T> T findAndModify(Query query, Update update, Class<T> entityClass) {
            T result = super.findAndModify(query, update, entityClass);
    
            //Publishing Custom Event on findAndModify
            if(result!=null && result instanceof Parent)//All of my Domain class extends Parent
                this.applicationEventPublisher.publishEvent(new AfterFindAndModify
                        (this,((Parent)result).getId(),
                                result.getClass().toString())
                );
    
            return result;
        } }
    

    2.应用事件

    public class AfterFindAndModify extends ApplicationEvent {
    
        private DocumentAuditLog documentAuditLog;
    
        public AfterFindAndModify(Object source, String documentId,
                                String documentObject) {
            super(source);
            this.documentAuditLog = new DocumentAuditLog(documentId,
                    documentObject,new Date(),"UPDATE");
        }
    
        public DocumentAuditLog getDocumentAuditLog() {
            return documentAuditLog;
        }
    }
    

    3.应用监听器

    public class FindandUpdateMongoEventListner implements ApplicationListener<AfterFindAndModify> {
    
        @Autowired
        MongoOperations mongoOperations;
    
        @Override
        public void onApplicationEvent(AfterFindAndModify event) {
            mongoOperations.save(event.getDocumentAuditLog());
        }
    }
    

    然后

    @Configuration
    @EnableMongoRepositories(basePackages = "my.pkg")
    @ComponentScan(basePackages = {"my.pkg"})
    public class MongoConfig extends AbstractMongoConfiguration {
    
        //.....
    
        @Bean
        public FindandUpdateMongoEventListner findandUpdateMongoEventListner(){
            return new FindandUpdateMongoEventListner();
        }   
    
    }
    

    【讨论】:

      【解决方案3】:

      您可以监听数据库更改,甚至是完全在您的程序之外的更改(MongoDB 4.2 和更新版本)。

      (代码是 kotlin 语言。java 也一样)

      @Autowired private lateinit var op: MongoTemplate
      
      @PostConstruct
      fun listenOnExternalChanges() {
          Thread {
              op.getCollection("Item").watch().onEach {
                  if(it.updateDescription.updatedFields.containsKey("name")) {
                      println("name changed on a document: ${it.updateDescription.updatedFields["name"]}")
                  }
              }
          }.start()
      }
      

      此代码仅在启用复制时有效。即使只有一个节点也可以启用它:

      将以下副本集详细信息添加到 mongodb.conf(/etc/mongodb.conf 或 /usr/local/etc/mongod.conf 或 C:\Program Files\MongoDB\Server\4.0\bin\mongod.cfg)文件

      replication:
        replSetName: "local"
      

      重启mongo服务,然后打开mongo控制台运行这个命令:

      rs.initiate()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-12-23
        • 1970-01-01
        • 2019-05-07
        • 2012-05-18
        • 2016-04-02
        • 2020-05-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多