[java] MongoTemplate 을 이용한 Mongodb join 예
mongodb를 사용하는 spring boot 기반 프로젝트 진행 중 join 에 대해서 rdb 와 달라 정리하고자 한다.
mongodb 의 data는 다음과 같다.
Data
[users] collection
{
_id: ObjectId("612da21a9a29cfeea9a7a74d"),
tntId: ObjectId("61276d4c7790a01da4bac0fd"),
name: "아무개1",
groupId: ObjectId("612d79f11e9f447e0e046ec8"),
...
}
[group] collection
{
_id: ObjectId("612d79f11e9f447e0e046ec4"),
tntId: ObjectId("61276d4c7790a01da4bac0fd"),
groupName: "임시그룹1"
}
[route] collection
_id: ObjectId("612dc12ea45efb224a986ce5"),
tntId: ObjectId("61276d4c7790a01da4bac0fd"),
target: {
route "test",
list: \["612da21a9a29cfeea9a7a74d"\] // users._id 값들이 String 타입으로 추가됨.
}
...
}
위에서 보는 것처럼 users
, group
, target
이라는 3개의 collection 이 있고 target
의 target.list
에는 users._id
값이 String
타입으로 추가되는 구조이다.
target.list
를 기준으로 users._id
, users.name
, group._id
, group.groupName
을 가져오는 mongotemplate
을 사용한 java
소스는 다음과 같다.
MongoTemplate 을 사용한 java 소스
public List getTargetViewList(String _id) {
// mongodb routes 와 group 을 join
// sql => select * from (select u.*, g.* from users u left outer join group g on u.groupId = g._id) agg_groups
LookupOperation lookupOperation = LookupOperation.newLookup()
.from("group")
.localField("groupId")
.foreignField("_id")
.as("agg_groups");
// agg_groups 가 list 로 나와서 object 로 변환
// (user 의 group 은 1:1 매핑)
// {
// "_id": xxx,
// "name": xxx,
// "agg_groups: [ { ... } ] // => "agg_group": { ... }
// }
ProjectionOperation projectionOperation =
Aggregation.project("agg_groups")
.andInclude("_id")
.andInclude("name")
.and(ArrayOperators.ArrayElemAt.arrayOf("agg_groups").elementAt(0)).as("agg_group")
;
// user 와 group을 합친 결과에서
// target list 에 있는 user._id 와 매치. 즉, in 절 추가
// sql => select * from ( agg_group ) where id in ( target_list )
List targetUserIdList = mongoTemplate.findById(_id, Target.class, "target").getList();
MatchOperation matchOperation = Aggregation.match(Criteria.where("_id").in(targetUserIdList));
// 위 형태에서 필요한 정보를 한 object 로 변경
// {
// "user_id": xxx,Routeroute
// "user_name": xxx,
// "group_id": xxx,
// "group_name: xxx
// }
ProjectionOperation convertAggGroupsOperation =
Aggregation.project()
.and("_id").as("user_id")
.andExclude("_id") // _id 와 user_id 가 중복되므로 _id 는 제거
.and("name").as("user_name")
.and("agg_group._id").as("group_id")
.and("agg_group.groupName").as("group_name")
;
// 조건을 순서대로 대입하여 mongodb 쿼리 생성
Aggregation aggregation = Aggregation.newAggregation(
lookupOperation,
projectionOperation,
matchOperation,
convertAggGroupsOperation
);
// 기준이 되는 users 를 여기다가 넣기
return mongoTemplate.aggregate(aggregation, "users", HashMap.class).getMappedResults();
}
위 소스 중 Route.class
는 route
collection 을 담기 위한 entity class 이다.
@Document("route")
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Schema // swagger
class Route{
@Id
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(description = "Object ID", required = true)
private ObjectId _id;
@Schema(description = "소속 테넌트 ID", required = true)
private ObjectId tntId;
private Target target;
}
@Getter
@Setter
@Schema // swagger
class Target {
private String kind;
private List list;
}
이렇게 원하는 결과를 만들어 냈다.
(이 글을 보는 분들 중 제 글에 잘못되거나 태클 꺼리가 보이실 경우 생각하시는 바가 맞을겁니다...)