Computer Science

Spring Data MongoDB에서 Union 연산을 수행하는법

BEOKS 2023. 7. 19. 11:22

목차

  1. 소개
  2. $UnionWith의 지원 버전 확인
  3. 네이티브 쿼리를 이용한 Union 연산
    • 네이티브 쿼리 실행 방법
    • MongoCollection.aggregate를 사용한 네이티브 쿼리 실행
  4. 코드 예시
    • MongoClient 설정
    • MongoCollection 가져오기
    • 매치 쿼리 생성
    • 매치 쿼리를 List<Document>로 변환
    • $unionWith로 쿼리 감싸기
    • 집계 연산 쿼리 추가
    • 최종 쿼리 실행

1. 소개

MongoDB 4.4부터 SQL의 Union all과 유사한 연산인 $UnionWith를 지원합니다. 이 기능은 데이터베이스의 여러 컬렉션을 합치는 데 사용됩니다. 그러나 Spring Data MongoDB의 최신 버전인 spring-boot-start-data-mongodb:3.0.7은 spring-data-mongodb:4.0.6에 의존하고 있어 $UnionWith와 관련된 기능을 사용할 수 없습니다.

따라서 네이티브 쿼리를 사용해야 합니다. 네이티브 쿼리는 MongoDatabase 인터페이스의 runCommand 메서드나 MongoCollectionaggregate 메서드를 사용하여 실행할 수 있습니다. 이를 통해 네이티브 쿼리 요청을 MongoDB 쿼리로 전달할 수 있습니다.

이 글에서는 MongoCollectionaggregate 메서드를 사용하여 네이티브 쿼리를 실행하는 방법을 알아보겠습니다.

2. $UnionWith의 지원 버전 확인

$UnionWith 연산을 사용하기 전에 MongoDB 서버의 버전을 확인해야 합니다. $UnionWith는 MongoDB 4.4 이상에서 지원되므로 해당 버전 이상이어야 합니다.

3. 네이티브 쿼리를 이용한 Union 연산

3.1. 네이티브 쿼리 실행 방법

네이티브 쿼리를 실행하는 방법은 MongoDatabaserunCommand 메서드나 MongoCollectionaggregate 메서드를 사용하는 것입니다. 이 중 MongoCollection.aggregate를 사용하여 네이티브 쿼리를 실행해보겠습니다.

3.2. MongoCollection.aggregate를 사용한 네이티브 쿼리 실행

MongoTemplate에서 사용할 컬렉션을 지정하여 MongoCollection 인터페이스의 객체를 가져옵니다. 그런 다음, 네이티브 쿼리를 작성하고 List<Document> 형식으로 변환한 후, MongoCollection.aggregate(List<Document>) 메서드에 전달하여 쿼리를 실행합니다.

4. 코드 예시

아래는 Spring Data MongoDB에서 Union 연산을 수행하는 예시 코드입니다.

4.1. MongoClient 설정

먼저, MongoClient를 생성하여 Spring Data MongoDB가 접근해야 할 데이터베이스를 확인합니다.

@Configuration
public class MongoConfig {

    @Bean
    public MongoClient mongoClient() {
        ConnectionString connectionString = new ConnectionString("mongodb://localhost:27017/test");
        MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
            .applyConnectionString(connectionString)
            .build();

        return MongoClients.create(mongoClientSettings);
    }
}

4.2. MongoCollection 가져오기

MongoTemplate에서 사용할 컬렉션을 지정하여 MongoCollection 인터페이스 객체를 가져옵니다.

mongoTemplate.getDb().getCollection(COLLECTION_NAME)

4.3. 매치 쿼리 생성

Union에 사용할 매치 쿼리를 생성하는 메서드를 작성합니다.

private List<String> generateMatchQuery(List<Target> targets) {
    return targets.stream().map(target -> {
        return "{\n"
            + "  $match :{\n"
            + "    where : \"" + target.getPageUrl() + "\",\n"
            + "    \"what.uniqueSelector\" : \"" + target.getHtmlElement() + "\",\n"
            + "    when : {\n"
            + "      $gte : ISODate(\"" + target.getApplyStart() + "\"),\n"
            + "      $lte : ISODate(\"" + target.getApplyEnd() + "\"),\n"
            + "    },\n"
            + "    \"what.eventType\" : \"" + target.getEventType() + "\"\n"
            + "  }\n"
            + "}";
    }).toList();
}

4.4. 매치 쿼리를 List<Document>로 변환

생성된 여러 개의 매치 쿼리를 바탕으로 List<Document>를 생성합니다. 첫 번째 쿼리는 그대로 추가하고, 나머지 쿼리는 $unionWith로 감싸서 리스트에 추가합니다.

private List<Document> convert(List<Target> targets){
    if(targets.size()==0){
        throw new AnalyzeServiceException();
    }
    List<String> matchQuery= generateMatchQuery(targets);
    List<Document> documents=new ArrayList<>();
    documents.add(Document.parse(matchQuery.get(0)));
    for(int idx=1;idx<targets.size();idx++){
        documents.add(Document.parse(wrapUnionCommand(matchQuery.get(idx))));
    }
    return documents;
}

private String wrapUnionCommand(String query){
    return "{ $unionWith : { coll : \""+COLLECTION_NAME+"\", pipeline: ["
        +query+"]}}";
}

4.5. 집계 연산 쿼리 추가

중복 데이터를 제거하기 위해 집계 연산 쿼리를 추가하는 기능을 구현합니다. $unionWith 쿼리는 중복 데이터를 제거하지

않으므로 집계 연산을 추가하여 중복을 제거해야 합니다.

public String query(List<Document> convertResult, String aggregationQueryJson) {
    List<Document> fullQuery= new ArrayList<>(convertResult.size()+2);
    convertResult.forEach(document -> fullQuery.add(document));
    fullQuery.add(Document.parse("{ $group: {_id: \"$$ROOT\"} }"));
    fullQuery.add(Document.parse(aggregationQueryJson));
    return convert(runAggregate(fullQuery));
}

이제 네이티브 String 쿼리를 List<Document> 형식으로 변환하는 과정을 마쳤습니다. 이를 MongoCollection.aggregate(List<Document>)에 전달하여 Union 연산을 수행할 수 있습니다.