如何使用 Mongo 审计和 UUID 作为 id 与 Spring Boot 2.2.x?

How to use Mongo Auditing and a UUID as id with Spring Boot 2.2.x?

我希望使用 UUID id 和 createdAt / updatedAt 字段存储文档。我的解决方案是使用 Spring Boot 2.1.x。在我从 Spring Boot 2.1.11.RELEASE 升级到 2.2.0.RELEASE 之后,我对 MongoAuditing 的测试失败了 createdAt = null。我需要做什么才能再次填充 createdAt 字段?

这不仅仅是一个测试问题。我 运行 应用程序,它与我的测试具有相同的行为。所有审核字段保持为空。

我有一个启用 MongoAuditing 和 UUID 生成的配置:

@Configuration
@EnableMongoAuditing
public class MongoConfiguration {
    @Bean
    public GenerateUUIDListener generateUUIDListener() {
        return new GenerateUUIDListener();
    }
}

侦听器挂接到 onBeforeConvert - 我想这就是麻烦开始的地方。

public class GenerateUUIDListener extends AbstractMongoEventListener<IdentifiableEntity> {
    @Override
    public void onBeforeConvert(BeforeConvertEvent<IdentifiableEntity> event) {
        IdentifiableEntity entity = event.getSource();
        if (entity.isNew()) {
            entity.setId(UUID.randomUUID());
        }
    }
}

文档本身(我删除了 getter 和 setter):

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

可以在此处找到完整的最小示例(包括测试)https://github.com/mab/auditable 使用 2.1.11.RELEASE 测试成功,使用 2.2.0.RELEASE 失败。

MongoTemplatedoInsert()

上按以下方式工作
  • this.maybeEmitEvent - 发出一个 事件 onBeforeConvertonBeforeSave 等),因此任何 AbstractMappingEventListener 都可以捕获并采取行动就像你对 GenerateUUIDListener
  • 所做的一样
  • this.maybeCallBeforeConvert - 在转换前调用 回调mongo auditing

就像你在 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 通过检查 entity.isNew() == true

是否仅对新实体标记 createdAt

因为您的代码 (UUID) 已经设置了 Id createdAt 未填充(该实体不被视为新实体)

您可以执行以下操作(按最佳到最差排序):

  • 忘掉 UUID 并使用 String 作为您的 ID,让 mongo 自己创建和管理它的实体 ID(这就是 MongoTemplate 的实际工作方式,第 811 行-812)
  • 在代码级别保留 UUID,在从 db
  • 插入和检索时转换 from/to String
  • 像这样创建自定义存储库 post
  • 留在2.1.11.RELEASE
  • 通过GenerateUUIDListenerid设置updateAt(重命名为NewEntityListener或smth),基本上实现审计
  • 实现一个 new isNew() 逻辑,它不仅仅依赖于实体 id

in version 2.1.11.RELEASE the order of the methods was flipped (MongoTemplate.class 804-805) so your code worked fine

作为一种抽象方法,事件的本质是发送后忘记(异步兼容),因此更改对象本身是一种非常糟糕的做法,NO计算顺序的受让人,如果有的话

这就是审计基于 回调 而不是 事件 的原因,这也是 Pivotal 不(需要)保持秩序的原因版本之间

对我来说,最好的解决方案是从事件 UUID 生成切换到基于回调的生成。通过 Ordered 的实现,我们可以将新回调设置为在 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;
    }
}

我用 MongoConfiguration 注册了回调。对于更通用的解决方案,您可能需要查看 AuditingEntityCallback 与 `MongoAuditingBeanDefinitionParser.

的注册
@Configuration
@EnableMongoAuditing
public class MongoConfiguration {
  @Bean
  public IdEntityCallback registerCallback() {
    return new IdEntityCallback();
  }
}