Map-Reduce
MongoDB의 Document를 다른 형태로 Mapping하는 것을 Map 이라고 한다.
Document나 Mapping한 데이터 내 여러 값을 취해 합계나 평균 등을 만드는 작업을 Reduce라고 한다.
이렇게 Doucment의 데이터를 매핑하고 취합하는 것을 Map-Reduce 작업이라고 한다.
Aggregation
MongoDB에서는 MapReduce보다 향상된 Aggregate라는 기능을 제공한다.
aggregate는 일종의 리눅스와 비슷한 pipeline을 가지면 각 단계별로 진행을 마친 후에 result을 가져오게 된다.
아래와 같은 순서로 pipeline을 진행한 후에 결과값을 가져오게 된다.
collection > $project > $match > $group > $sort > $skip > $limit > $unwind > $out
$project, $match 등 각 단계를 stage라고 한다.
예시를 위해 test라는 Collection을 만들고, Doucment의 구조는 name, sex, subject, socre로 구성되어 있으며 아래 사진과 같다. 참고로, MongoDBCompass라는 프로그램을 사용하면 Doucments와 Aggregations, 인덱싱하는 작업 등을 편하게 할 수 있다.
MongoDBCompass에서 Aggregations로 Aggregate하는 작업을 하였다.
작업 단계는 $match > $group > $sort 이다.
1 단계: $match 로 성별이 여자인 Document만 필터링 하였다.
2 단계: $group 로 Doucment 내 필요한 정보만 새로운 객체로 Grouping 하였다.
1단계에서 $match로 필터링한 Document를 이용해서 $group으로 grouping 하는 작업을 하였다.
새로운 객체의 _id는 name으로 하였다. $sum을 이용해서 score의 총합을 totalScore로 구하고, $avg를 이용해서 score의 평균을 avgScore로 구했다.
{
_id = "$name", -- "$Document 내 필드 이름"
"totalScore":{
$sum: "$score"
},
"avgScore":{
$avg: "$score"
}
}
3 단계: $sort로 Grouping된 정보를 정렬하였다.
$sort에서 정렬 기준 필드와 정렬 방향을 정해주면 된다. 정렬 방향은 오름차순은 1이고, 내림차순은 -1이다.
여기서는 Grouping된 정보의 totalScore를 정렬 기준으로 하고 내림차순으로 정렬했다.
Spring에서 MongoTemplate으로 Aggregation하기
MongoDBCompass에서 $match>$group>$sort 순서로 Aggregate한 것을 Spring의 MongoTemplate으로 구현하였다.
Document 데이터 입력
우선 테스트를 Document를 만들고 데이터를 입력했다.
아래는 Document class이다. @Document를 붙이고 사용하는 Collection을 정해주면 된다.
여기서 사용한 Collection 이름은 test이다.
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@Document(collection = "test")
public class TestDocument {
@Id
private String id;
private String name;
private String sex;
private String subject;
private int score;
public TestDocument(String name, String sex, String subject, int score) {
this.name = name;
this.sex = sex;
this.subject = subject;
this.score = score;
}
}
데이터 입력을 위해 Post API를 만들고 AggregationTestService의 insertTestData() 메서드를 호출한다.
@RestController
@RequiredArgsConstructor
@RequestMapping("/aggregation")
public class AggregationTestController {
private final AggregationTestService aggregationTestService;
@PostMapping
public ResponseEntity<Void> insertTestData(){
aggregationTestService.insertTestData();
return ResponseEntity.ok().build();
}
}
insertTestData()에서 아래와 같이 데이터를 만들어서 Collection에 insert 한다.
@Service
@RequiredArgsConstructor
public class AggregationTestService {
private final MongoTemplate mongoTemplate;
public void insertTestData() {
List<TestDocument> dataList = new ArrayList<TestDocument>();
dataList.add(new TestDocument("가몽고", "여자", "국어", 100));
dataList.add(new TestDocument("가몽고", "여자", "수학", 60));
dataList.add(new TestDocument("나몽고", "여자", "국어", 100));
dataList.add(new TestDocument("나몽고", "여자", "수학", 100));
dataList.add(new TestDocument("다몽고", "남자", "국어", 70));
dataList.add(new TestDocument("다몽고", "남자", "수학", 30));
dataList.add(new TestDocument("라몽고", "남자", "국어", 80));
dataList.add(new TestDocument("라몽고", "남자", "수학", 40));
mongoTemplate.insertAll(dataList);
}
}
Document 데이터 Aggregate
Aggregate한 데이터를 가져오기 위해 Get API를 만들었다.
AggregationTestService의 mongoTemplateAggregate() 메서드를 호출한다.
import java.util.List;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.tradlinx.zimgo.doc.model.TestResult;
import com.tradlinx.zimgo.doc.service.AggregationTestService;
import lombok.RequiredArgsConstructor;
@RestController
@RequiredArgsConstructor
@RequestMapping("/aggregation")
public class AggregationTestController {
private final AggregationTestService aggregationTestService;
@GetMapping
public ResponseEntity<List<TestResult>> aggregationTest() throws Exception {
List<TestResult> resultList = aggregationTestService.mongoTemplateAggregation();
return ResponseEntity.ok().body(resultList);
}
@PostMapping
public ResponseEntity<Void> insertTestData(){
aggregationTestService.insertTestData();
return ResponseEntity.ok().build();
}
}
mongoTemplateAggregate 메서드만 작성했다.
Aggregate의 결과가 담긴 Document 클래스는 org.bson.Document이다.
@Service
@RequiredArgsConstructor
public class AggregationTestService {
private final MongoTemplate mongoTemplate;
public List<TestResult> mongoTemplateAggregation() {
// 1단계: $match
// sex가 여자인 Document만 필터링
MatchOperation matchOperation =
Aggregation.match(Criteria.where("sex").is("여자"));
// 2단계: $group
GroupOperation groupOperation = // {
Aggregation.group("name") // _id : "$name"
.sum("score").as("totalScore") // "totalScore":{ $sum: "$score" },
.avg("score").as("avgScore"); // "avgScore":{ $avg: "$score" }
// }
// 3단계: $sort
// totalScore 내림차순으로 정렬
SortOperation sortOperation =
Aggregation.sort(Direction.DESC, "totalScore");
// match, group, sort Operation을 순서대로 넣기.
Aggregation aggregation =
Aggregation.newAggregation(
matchOperation
,groupOperation
,sortOperation
);
// org.bson.Document 객체를 이용해 결과를 가져온다.
List<Document> results =
// aggregation 객체, collection 이름, 응답 받아올 객체 클래스
mongoTemplate.aggregate(aggregation, "test", Document.class)
.getMappedResults();
// Document의 결과를 Java에서 사용할 응답 객체로 매핑한다.
List<TestResult> resultList =
results.stream()
.map(TestResult::new)
.collect(Collectors.toList());
return resultList;
}
}
아래는 응답 객체로 사용한 TestResult이다.
생성자에서 Aggregate의 결과로 나온 Document의 값을 TestResult의 필드와 매핑한다.
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TestResult {
private String name;
private Integer totalScore;
private Double avgScore;
public TestResult(Document document) {
name = document.getString("_id");
totalScore = document.getInteger("totalScore");
avgScore = document.getDouble("avgScore");
}
}
Get API를 호출하면 아래와 같이 응답을 받아온다.
MongoDBCompass의 Aggregations에서 Aggregate한 것과 같은 결과가 나왔다.
[GET] http://localhost:8080/aggregation
[
{
"name": "나몽고",
"totalScore": 200,
"avgScore": 100.0
},
{
"name": "가몽고",
"totalScore": 160,
"avgScore": 80.0
}
]
참고자료
https://www.mongodb.com/docs/manual/aggregation/
https://m.blog.naver.com/ijoos/221312444591
'Database & ORM > MongoDB' 카테고리의 다른 글
MongoDB에서 JPA 상속 관계 설정 - @Inheritance, @DiscriminatorColumn, @DiscriminatorValue (0) | 2022.04.20 |
---|---|
MongoDB 선택 이유 및 MongoDB에서 RDB+MongoDB로 변경 (0) | 2022.04.19 |