mongoDB, $lookup

$lookup을 사용하여 join한 효과를 얻을수 있습니다.

테스트 환경

mongoDB version : 4.0.5-18
사용툴 : Robo 3T - 1.3

테스트 데이터

A collection

{
    "_id": ObjectId("a1"),
    "name": "a1 name"
},
{
    "_id": ObjectId("a2"),
    "name": "a2 name"
}

B collection

{
    "_id": ObjectId("b1"),
    "group": [
        {
            "aid": "a1"
        }
    ]
},
{
    "_id": ObjectId("b2"),
    "group": [
        {
            "aid": "a1"
        }
    ]
},
{
    "_id": ObjectId("b3"),
    "group": [
        {
            "aid": "a2"
        }
    ]
}

위와같이 테스트데이터가 있다고 가정할때 내가 기대하는 결과는 다음과 같다.

{
    "_id": ObjectId("a1"),
    "name": "a1 name",
    "aGroup": [
        {
            "_id": ObjectId("b1"),
            "group": [
                {
                    "aid": "a1"
                }
            ]
        },
        {
            "_id": ObjectId("b2"),
            "group": [
                {
                    "aid": "a1"
                }
            ]
        }
    ]
},
{
    "_id": ObjectId("a2"),
    "name": "a2 name",
    "aGroup": [
        {
            "_id": ObjectId("b3"),
            "group": [
                {
                    "aid": "a2"
                }
            ]
        }
    ]
}

$lookup 사용

db.a.aggregate([
    $lookup: {
        from: "b",
        localField: "_id",
        foreignField: "group.aid",
        as: "aGroup"
    }
])

위와같이 SQL을 작성하였을때 오류가 발생한다. localField: “_id”와 foreignField: “group.aid”의 Type이 달라 오류가 발생하는것을 볼수있을것이다.
데이터 타입을 맞춰줄수 있는 방법은 여러가지가 있는것으로 보인다. 여러방법으로 시도하여 정상작동하는 방법을 찾을수있었다. $addFields를 사용한 방법인데 하나의 필드를 추가하고 타입을 맞춰 사용하는 방법이다.

$addFields 사용

db.a.aggregate([
    {
        $addFields: {
            aid: { $toString: "$_id"}
        }
    },
    {
        $lookup: {
            from: "b",
            localField: "aid",
            foreignField: "group.aid",
            as: "aGroup"
        }
    }
])

위에서 설명했듯이 “aid”필드를 하나추가하고 ObjectId Type을 String Type으로 변경하였다.

구현

// $addFields
AggregationOperation addFields = new AggregationOperation() {
    @Override
    public DBObject toDBObject(AggregationOperationContext aggregationOperationContext) {
        BasicDBObject addFieldsObject = new BasicDBObject();
        addFieldsObject.append("aid", new BasicDBObject("$toString", "$_id"));
        return new BasicDBObject("$addFields", addFieldsObject);
    }
};

// #lookup
LookupOperation lookupOperation = LookupOperation.newLookup()
    .from("b")
    .localField("aid")
    .foreignField("group.aid")
    .as("bData");

// Aggregation
Aggregation agg = Aggregation.newAggregation(
    addFields,
    lookupOperation
);

// Result
AggregationResults<A> results = mTemplate.aggregate(agg, "a", A.class);
List<A> list = results.getMappedResults();

A.class에서 aEntity를 받을수있는 변수를 선언해줘야 한다.

public class A {
    private String id;
    private String name;
    private List<B> bData;
}