使用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 results = mongoTemplate.aggregate(agg, "tags", TagCount.class);
List 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 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命令不一致结果的情况,都可以通过上述方法解决。

你可能感兴趣的:(技术)