使用Spring Data MongoDB 做聚合操作出現的問題及解決方案

前言

在MongoDB中,“$”符號是有特殊意義的,一般用來表示採取一些系統預定義的一些操作,比如比較操作。但是如果在記錄文檔中的key中出現“$”符號,會怎麼樣呢?

MongoDB的方案

經測試,在MongoDB的命令行中,使用帶“$”符號的key進行數據添加修改和其它聚合操作都沒有問題。

Spring Data MongoDB 聚合的使用

Spring Data MongoDB 使用的是org.springframework.data.mongodb.core.aggregation包中的類進行聚合操作,代碼如下

import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

Aggregation agg = newAggregation(
    project("tags"),
    unwind("tags"),
    group("tags").count().as("n"),
    project("n").and("tag").previousOperation(),
    sort(DESC, "n")
);

AggregationResults<TagCount> results = mongoTemplate.aggregate(agg, "tags", TagCount.class);
List<TagCount> tagCount = results.getMappedResults();

在使用過程中,如果需要group操作的字段沒有包含“$”字符就不會出現問題。如果需要group的字段中包含“$”字符,則只會返回一條“_id”爲null的記錄,這不是正確的結果。

經調試和查看源碼,發現所有聚合操作都使用了Fields.AggregationField去封裝文檔的key,在初始化的過程中,都會對文檔的key執行其中的cleanUp方法,代碼如下:

        private static final String cleanUp(String source) {

            if (source == null) {
                return source;
            }

            if (Aggregation.SystemVariable.isReferingToSystemVariable(source)) {
                return source;
            }

            int dollarIndex = source.lastIndexOf('$');
            return dollarIndex == -1 ? source : source.substring(dollarIndex + 1);
        }

經過這個方法處理後,所有包含“$”的屬性,都變成了“$”後的字符串表示需要操作的key。也就是說,使用Spring Data MongoDB提供的默認聚合操作方案,不能正確處理帶“$”的key。

解決方案

後面對Spring Data MongoDB中聚合操作進一步深挖,發現在構建Aggregation對象時,其參數與Fields.AggregationField無關,只需要實現AggregationOperation接口即可,代碼如下:

    /**
     * Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
     * 
     * @param operations must not be {@literal null} or empty.
     */
    public static Aggregation newAggregation(List<? extends AggregationOperation> operations) {
        return newAggregation(operations.toArray(new AggregationOperation[operations.size()]));
    }

    /**
     * Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
     * 
     * @param operations must not be {@literal null} or empty.
     */
    public static Aggregation newAggregation(AggregationOperation... operations) {
        return new Aggregation(operations);
    }

而AggregationOperation接口只有一個方法:

public interface AggregationOperation {

    /**
     * Turns the {@link AggregationOperation} into a {@link DBObject} by using the given
     * {@link AggregationOperationContext}.
     * 
     * @return the DBObject
     */
    DBObject toDBObject(AggregationOperationContext context);
}

看到這裏,那麼問題就好解決了,只要實現AggregationOperation接口,並避免使用Fields.AggregationField去處理需要進行聚合的字段就行了。並且AggregationOperation接口中只有一個toDBObject方法,而AggregationOperationContext接口中是有一個getMappedObject方法返回DBObject對象的,代碼如下:

public interface AggregationOperationContext {

    /**
     * Returns the mapped {@link DBObject}, potentially converting the source considering mapping metadata etc.
     * 
     * @param dbObject will never be {@literal null}.
     * @return must not be {@literal null}.
     */
    DBObject getMappedObject(DBObject dbObject);

    /**
     * Returns a {@link FieldReference} for the given field or {@literal null} if the context does not expose the given
     * field.
     * 
     * @param field must not be {@literal null}.
     * @return
     */
    FieldReference getReference(Field field);

    /**
     * Returns the {@link FieldReference} for the field with the given name or {@literal null} if the context does not
     * expose a field with the given name.
     * 
     * @param name must not be {@literal null} or empty.
     * @return
     */
    FieldReference getReference(String name);
}

實現AggregationOperation接口就相當簡單了,直接用DBObject就好了,如下:

public class BaseOperation implements AggregationOperation {
    private DBObject operation;

    public StartGroupOperation(DBObject operation) {
        this.operation = operation;
    }

    @Override
    public DBObject toDBObject(AggregationOperationContext context) {
        return context.getMappedObject(operation);
    }
}

用法如MongoDB命令一樣,將相應的聚合操作語句放入DBObject裏面,然後構造Aggregation就可以了。

類似的,用Aggregation中的方法不能解決或結果與用MongoDB命令不一致結果的情況,都可以通過上述方法解決。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章