spring boot自定義mongo審計


問題出現

以前通過@EnableMongoAuditing、@CreateDate、@LastModifiedDate進行實體類創建時間、修改時間的自動管理。
但為了實現多數據源的管理以及切換,自己覆蓋了mongoTemplate的bean,發現應用所有數據庫操作都出現"Couldn't find PersistentEntity"錯誤,去掉@EnableMongoAuditing注解后才能正常使用,但@CreateDate、@LastModifiedDate失效,創建時間、修改時間這些都要自己去設置,太麻煩了也容易漏,為了方便使用以及偷懶,需要實現所有數據庫存儲更新操作能自動插入、更新公用字段,如創建時間、修改時間、創建者、修改者信息、對所有數據庫數據邏輯刪除

解決的思路

為了實現這個功能,翻了下MongoTemplate的源碼,下面是關鍵的幾個方法

MongoTemplate.class

    public <T> T save(T objectToSave, String collectionName) {
        Assert.notNull(objectToSave, "Object to save must not be null!");
        Assert.hasText(collectionName, "Collection name must not be null or empty!");
        AdaptibleEntity<T> source = this.operations.forEntity(objectToSave, this.mongoConverter.getConversionService());
        return source.isVersionedEntity() ? this.doSaveVersioned(source, collectionName) : this.doSave(collectionName, objectToSave, this.mongoConverter);
    }

    protected <T> T doSave(String collectionName, T objectToSave, MongoWriter<T> writer) {
        objectToSave = ((BeforeConvertEvent)this.maybeEmitEvent(new BeforeConvertEvent(objectToSave, collectionName))).getSource();
        objectToSave = this.maybeCallBeforeConvert(objectToSave, collectionName);
        AdaptibleEntity<T> entity = this.operations.forEntity(objectToSave, this.mongoConverter.getConversionService());
        entity.assertUpdateableIdIfNotSet();
        MappedDocument mapped = entity.toMappedDocument(writer);
        Document dbDoc = mapped.getDocument();
        this.maybeEmitEvent(new BeforeSaveEvent(objectToSave, dbDoc, collectionName));
        objectToSave = this.maybeCallBeforeSave(objectToSave, dbDoc, collectionName);
        Object id = this.saveDocument(collectionName, dbDoc, objectToSave.getClass());
        T saved = this.populateIdIfNecessary(objectToSave, id);
        this.maybeEmitEvent(new AfterSaveEvent(saved, dbDoc, collectionName));
        return this.maybeCallAfterSave(saved, dbDoc, collectionName);
    }

   protected <T> T maybeCallBeforeConvert(T object, String collection) {
        return this.entityCallbacks != null ? this.entityCallbacks.callback(BeforeConvertCallback.class, object, new Object[]{collection}) : object;
    }

   protected <T> T maybeCallBeforeSave(T object, Document document, String collection) {
        return this.entityCallbacks != null ? this.entityCallbacks.callback(BeforeSaveCallback.class, object, new Object[]{document, collection}) : object;
    }

查看MongoTemplate的源碼會發現,新增以及更新操作調用的save方法,在執行前會有BeforeConvertEvent、BeforeSaveEvent兩個事件,我們只需要在這兩個事件里面將我們需要保存的內容做下處理,就可以實現所有數據庫新增、更新操作都自動加上創建時間這些信息,而且原來MongoAudit只有幾個固定的字段能用,自定義BeforeConvertCallback事件后,完全可以按自己的需要多管理幾個字段。
具體要實現一個接口,並且將自定義的callback處理set到要用的mongTemplate上面

@FunctionalInterface
public interface BeforeConvertCallback<T> extends EntityCallback<T> {
    T onBeforeConvert(T var1, String var2);
}

具體實現

BeforeConvertEvent事件自定義處理

class BeforeConvert implements BeforeConvertCallback<Object> {

        @NotNull
        @Override
        public Object onBeforeConvert(Object o, String s) {
            log.info("before convert callback");

            Map<String, Field> fieldMap = ReflectUtil.getFieldMap(o.getClass());
            String userName = SecurityUtils.getUsername();
            Date now = new Date();

            if (fieldMap.containsKey("id")) {
                Field id = fieldMap.get("id");

                if (id == null || StringUtils.isBlank((String) ReflectUtil.getFieldValue(o, id))) {
                    //沒有id時為新增
                    //創建時間
                    if (fieldMap.containsKey("createTime")) {
                        ReflectUtil.setFieldValue(o, "createTime", now);
                    }
                    //創建者
                    if (fieldMap.containsKey("createUser") && StringUtils.isNotBlank(userName)) {
                        ReflectUtil.setFieldValue(o, "createUser", userName);
                    }
                }
            }
            //更新日期
            if (fieldMap.containsKey("updateTime")) {
                ReflectUtil.setFieldValue(o, "updateTime", now);
            }
            //更新者
            if (fieldMap.containsKey("updateUser") && StringUtils.isNotBlank(userName)) {
                ReflectUtil.setFieldValue(o, "updateUser", userName);
            }
            //這里還可以加上其他各種需要設置的值

            return o;
        }
    }
    @Bean(name = "mongoTemplate")
    public DynamicMongoTemplate dynamicMongoTemplate() {
        Iterator<SimpleMongoClientDatabaseFactory> iterator = MONGO_CLIENT_DB_FACTORY_MAP.values().iterator();
        DynamicMongoTemplate mongoTemplate = new DynamicMongoTemplate(iterator.next());
	//將自定義事件處理放進mongoTemplate中
        mongoTemplate.setEntityCallbacks(EntityCallbacks.create(new BeforeConvert()));
        return mongoTemplate;
    }
   @Bean(name = "mongoDbFactory")
    public MongoDatabaseFactory mongoDbFactory() {
        Iterator<SimpleMongoClientDatabaseFactory> iterator = MONGO_CLIENT_DB_FACTORY_MAP.values().iterator();
        return iterator.next();
    }

最后的一些話

一開始我自定義BeforeSaveCallback,在里面修改了object的內容,但發現並沒有保存在數據庫中。在官方的文檔中說,在這事件里面修改object內容只是臨時的不會做持久化,需要修改轉換后document的內容才會保存進數據庫,官方原話:Entity callback method invoked before a domain object is saved. Can return either the same or a modified instance of the domain object and can modify Document contents. This method is called after converting the entity to a Document so effectively the document is used as outcome of invoking this callback. Changes to the domain object are not taken into account for saving, only changes to the document. Only transient fields of the entity should be changed in this callback. To change persistent the entity before being converted, use the BeforeConvertCallback.
官方文檔
為了避免實體轉document后字段名發生改變不好找,就選用了BeforeConvertEvent,在轉換前進行了處理,但其實在BeforeSaveEvent處理也是可以的

事件的發生順序

Event:
BeforeDeleteEvent

AfterDeleteEvent

BeforeConvertEvent

BeforeSaveEvent

AfterSaveEvent

AfterLoadEvent


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM