MongoDB POJO Class 매핑하기

Sonar0·2022년 12월 11일
0

POJO Class 매핑

MongoDB를 쓰는 가장 큰 이유는 객체지향 프로그래밍 모델을 Document에 그대로 사용할 수 있다는 장점 때문이다. JDBC에서 ORM을 따로 쓰는 것처럼 복잡한 설정 필요 없이 간단하게 POJO class를 Collection의 Document에 매핑할 수 있다.

Product.class

  • Product 객체는 모든 클래스에서 사용
import lombok.*;
import org.bson.codecs.pojo.annotations.BsonId;
import org.bson.codecs.pojo.annotations.BsonProperty;
import org.bson.types.ObjectId;

import java.time.LocalDateTime;
// 모든 필드를 파라미터로 갖는 생성자
@AllArgsConstructor
// 아무 파라미터도 받지 않는 생성자
@NoArgsConstructor
// 필드를 가져오는 getter 함수
@Getter
// 필드를 설정하는 setter 함수
@Setter
// 출력을 용이하게 하는 함수
@ToString
public class Product {
    @BsonId
    ObjectId id;
    String name;
    @BsonProperty("updated_at")
    LocalDateTime updatedAt;
    String contents;
    int Price;
}

Create(Insert)

collection 객체에 insertOne 또는 insertMany 함수에 객체를 통채로 넘겨준다. 이것이 가능하려면 먼저 POJO를 해석할 수 있는 Codec이 등록되어있어야 한다.

Main.class

import com.mongodb.client.result.InsertManyResult;
import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.PojoCodecProvider;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry;
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;

public class Main {
    public static void main(String[] args) {

        // 클래스에 매핑해서 해석할 수 있는 정보를 MongoDB 드라이버에게 알려주어야 한다.
        // Product에 대한 codec
        CodecProvider pojoCodecProvider = PojoCodecProvider.builder().automatic(true).build();
        CodecRegistry codecRegistry = fromRegistries(getDefaultCodecRegistry(), fromProviders(pojoCodecProvider));

        String uri = "mongodb://localhost:27017";
        // MongoClient 객체 하나가 하나의 connection을 의미
        MongoClient mongoClient = MongoClients.create(uri);
        MongoDatabase mongoDatabase =  mongoClient.getDatabase("mongodb").withCodecRegistry(codecRegistry);
        // collection 이 다루는 class를 Document -> Product 변경
        // getCollection은 class 를 입력하면 TDocument를 return 해준다. getCollection( collectionName , class)
        MongoCollection<Product> collection = mongoDatabase.getCollection("products", Product.class);

        // DateTime 을 어떻게 포매팅할지
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        List<Product> products = new ArrayList<>();
		// 데이터 insert
        // _id 는 자동으로 생성
        products.add(new Product(null, "shoes1", LocalDateTime.parse("2022-08-01 01:00:00", formatter),
                "This is shoes1", 10000));
        products.add(new Product(null, "shoes2", LocalDateTime.parse("2022-08-01 02:00:00", formatter),
                "This is shoes2", 20000));
        products.add(new Product(null, "shoes3", LocalDateTime.parse("2022-08-01 03:00:00", formatter),
                "This is shoes3", 30000));
        products.add(new Product(null, "shoes4", LocalDateTime.parse("2022-08-01 04:00:00", formatter),
                "This is shoes4", 40000));
        products.add(new Product(null, "shoes5", LocalDateTime.parse("2022-08-01 05:00:00", formatter),
                "This is shoes5", 50000));
        products.add(new Product(null, "shoes6", LocalDateTime.parse("2022-08-01 06:00:00", formatter),
                "This is shoes6", 60000));
        products.add(new Product(null, "backpack", LocalDateTime.parse("2022-08-02 04:00:00", formatter),
                "This is backpack", 50000));
        products.add(new Product(null, "shirt", LocalDateTime.parse("2022-08-03 05:00:00", formatter),
                "This is shirt", 20000));
        products.add(new Product(null, "glasses", LocalDateTime.parse("2022-08-04 06:00:00", formatter),
                "This is glasses", 10000));
        InsertManyResult insertManyResult = collection.insertMany(products);
        // 내가 보낸 쿼리의 Acknowleged가 True 라면 = 응답이 왔다면
        if(insertManyResult.wasAcknowledged()) {
            // insert 된 ID를 전달받아 출력
            System.out.println("Inserted ids : " + insertManyResult.getInsertedIds());
        }
    }
}

결과

9개의 데이터가 올바르게 들어간 것을 확인할 수 있다.

Read(find)

find() 함수로 검색을 할 수 있다. 검색 결과는 MongoIterator인데, JDBC와 유사하게 cursor 방식으로 데이터를 확인할 수 있다.

Main.class

import com.mongodb.client.*;
import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.PojoCodecProvider;

import java.time.format.DateTimeFormatter;

import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry;
import static com.mongodb.client.model.Filters.regex;
import static com.mongodb.client.model.Projections.include;
import static com.mongodb.client.model.Sorts.descending;
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;
import static com.mongodb.client.model.Filters.eq;

public class Main {
    public static void main(String[] args) {

        // 클래스에 매핑해서 해석할 수 있는 정보를 MongoDB 드라이버에게 알려주어야 한다.
        // Product에 대한 codec
        CodecProvider pojoCodecProvider = PojoCodecProvider.builder().automatic(true).build();
        CodecRegistry codecRegistry = fromRegistries(getDefaultCodecRegistry(), fromProviders(pojoCodecProvider));

        String uri = "mongodb://localhost:27017";
        // MongoClient 객체 하나가 하나의 connection을 의미
        MongoClient mongoClient = MongoClients.create(uri);
        MongoDatabase mongoDatabase = mongoClient.getDatabase("mongodb").withCodecRegistry(codecRegistry);
        // collection 이 다루는 class를 Document -> Product 변경
        // getCollection은 class 를 입력하면 TDocument를 return 해준다. getCollection( collectionName , class)
        MongoCollection<Product> collection = mongoDatabase.getCollection("products", Product.class);

        // DateTime 을 어떻게 포매팅할지
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        System.out.println(" --- cursor --- ");
        System.out.println();
        // JDBC의 커서와 동일한 형태의 Mongo Cursor 를 반환
        MongoCursor<Product> cursor = collection.find().cursor();
        // 커서의 다음이 가르킬 곳이 True라면
        while(cursor.hasNext()) {
            // 커서를 다음으로 이동
            System.out.println(cursor.next());
        }
        System.out.println();
        System.out.println(" ---- find eq ----- ");
        System.out.println();

        // price 필드의 값이 10000 인 값을 조회.
        MongoCursor<Product> cursor2 = collection.find(eq("price",10000)).cursor();
        while (cursor2.hasNext()) {
            System.out.println(cursor2.next());
        }

        System.out.println();
        System.out.println(" ---- find regex(like) + sort ----- ");
        System.out.println();

        // regular expression / name 필드에 shoes가 포함된 모든 레코드 조회
        // sort.descending 을 import / indexes.descending 은 텍스트 관련 정렬
        MongoCursor<Product> cursor3 = collection.find(regex("name","shoes")).sort((descending("price"))).cursor();
        while (cursor3.hasNext()) {
            System.out.println(cursor3.next());
        }

        System.out.println();
        System.out.println(" ---- projection 원하는 데이터만 조회 (다른 필드값은 기본값으로 조회) ----- ");
        System.out.println();

        MongoCursor<Product> cursor4 = collection.find(regex("name","shoes")).projection(include("name")).sort((descending("price"))).cursor();
        while (cursor4.hasNext()) {
            System.out.println(cursor4.next());
        }
    }
}

결과

 --- cursor --- 

Product(id=639489cb3e8aff05670e251b, name=shoes1, updatedAt=2022-08-01T01:00, contents=This is shoes1, Price=10000)
Product(id=639489cb3e8aff05670e251c, name=shoes2, updatedAt=2022-08-01T02:00, contents=This is shoes2, Price=20000)
Product(id=639489cb3e8aff05670e251d, name=shoes3, updatedAt=2022-08-01T03:00, contents=This is shoes3, Price=30000)
Product(id=639489cb3e8aff05670e251e, name=shoes4, updatedAt=2022-08-01T04:00, contents=This is shoes4, Price=40000)
Product(id=639489cb3e8aff05670e251f, name=shoes5, updatedAt=2022-08-01T05:00, contents=This is shoes5, Price=50000)
Product(id=639489cb3e8aff05670e2520, name=shoes6, updatedAt=2022-08-01T06:00, contents=This is shoes6, Price=60000)
Product(id=639489cb3e8aff05670e2521, name=backpack, updatedAt=2022-08-02T04:00, contents=This is backpack, Price=50000)
Product(id=639489cb3e8aff05670e2522, name=shirt, updatedAt=2022-08-03T05:00, contents=This is shirt, Price=20000)
Product(id=639489cb3e8aff05670e2523, name=glasses, updatedAt=2022-08-04T06:00, contents=This is glasses, Price=10000)

 ---- find eq ----- 

Product(id=639489cb3e8aff05670e251b, name=shoes1, updatedAt=2022-08-01T01:00, contents=This is shoes1, Price=10000)
Product(id=639489cb3e8aff05670e2523, name=glasses, updatedAt=2022-08-04T06:00, contents=This is glasses, Price=10000)

 ---- find regex(like) + sort ----- 

Product(id=639489cb3e8aff05670e2520, name=shoes6, updatedAt=2022-08-01T06:00, contents=This is shoes6, Price=60000)
Product(id=639489cb3e8aff05670e251f, name=shoes5, updatedAt=2022-08-01T05:00, contents=This is shoes5, Price=50000)
Product(id=639489cb3e8aff05670e251e, name=shoes4, updatedAt=2022-08-01T04:00, contents=This is shoes4, Price=40000)
Product(id=639489cb3e8aff05670e251d, name=shoes3, updatedAt=2022-08-01T03:00, contents=This is shoes3, Price=30000)
Product(id=639489cb3e8aff05670e251c, name=shoes2, updatedAt=2022-08-01T02:00, contents=This is shoes2, Price=20000)
Product(id=639489cb3e8aff05670e251b, name=shoes1, updatedAt=2022-08-01T01:00, contents=This is shoes1, Price=10000)

 ---- projection 원하는 데이터만 조회 다른 조회는 더미값 ----- 

Product(id=639489cb3e8aff05670e2520, name=shoes6, updatedAt=null, contents=null, Price=0)
Product(id=639489cb3e8aff05670e251f, name=shoes5, updatedAt=null, contents=null, Price=0)
Product(id=639489cb3e8aff05670e251e, name=shoes4, updatedAt=null, contents=null, Price=0)
Product(id=639489cb3e8aff05670e251d, name=shoes3, updatedAt=null, contents=null, Price=0)
Product(id=639489cb3e8aff05670e251c, name=shoes2, updatedAt=null, contents=null, Price=0)
Product(id=639489cb3e8aff05670e251b, name=shoes1, updatedAt=null, contents=null, Price=0)

Process finished with exit code 0

Update

업데이트는 대상을 고르는 filter, 내용인 update 이 두 종류의 BSON parameter를 이용해서 update 한다. 조건은 fields method 로 여러개를 이어 붙일 수 있다.
update는 set, unset, set on insert, increment, multiply, rename, min, max, current date 등 타입 안정성을 제공하는 전용 함수들이
많다. 참고

Main.class

name 필드에 shoes가 포함 된 레코드에서 price 필드가 10000원보다 큰 레코드의 price에 0.9배를 해준다.

import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.result.UpdateResult;
import com.mongodb.internal.bulk.UpdateRequest;
import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.PojoCodecProvider;

import java.time.format.DateTimeFormatter;

import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry;
import static com.mongodb.client.model.Filters.gt;
import static com.mongodb.client.model.Filters.regex;
import static com.mongodb.client.model.Projections.fields;
import static com.mongodb.client.model.Updates.mul;
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;

public class Main {
    public static void main(String[] args) {

        // 클래스에 매핑해서 해석할 수 있는 정보를 MongoDB 드라이버에게 알려주어야 한다.
        // Product에 대한 codec
        CodecProvider pojoCodecProvider = PojoCodecProvider.builder().automatic(true).build();
        CodecRegistry codecRegistry = fromRegistries(getDefaultCodecRegistry(), fromProviders(pojoCodecProvider));

        String uri = "mongodb://localhost:27017";
        // MongoClient 객체 하나가 하나의 connection을 의미
        MongoClient mongoClient = MongoClients.create(uri);
        MongoDatabase mongoDatabase = mongoClient.getDatabase("mongodb").withCodecRegistry(codecRegistry);
        // collection 이 다루는 class를 Document -> Product 변경
        // getCollection은 class 를 입력하면 TDocument를 return 해준다. getCollection( collectionName , class)
        MongoCollection<Product> collection = mongoDatabase.getCollection("products", Product.class);

        // updateMany 의 return type은 UpdateResult 이다.
        // fields를 사용하여 여러 조건을 넣을 수 있다
        // regex = regular expression , gt = greater than
        // name 필드에 shoes가 들어간 레코드에 price 필드가 10000원보다 큰 레코드의 price에 0.9배를 해준다.
        UpdateResult updateResult = collection.updateMany(fields(regex("name","shoes"),
                                        gt("price",10000)),
                                        mul("price",0.9));

        // 쿼리에 응답이 있다면
        if(updateResult.wasAcknowledged()) {
            // update 된 개수를 출력
            System.out.println("modified count : " + updateResult.getModifiedCount());
        }
    }
}

결과

modified count : 5

Process finished with exit code 0

원래 데이터

바뀐 데이터

Delete

삭제는 filter 조건만 넣으면 삭제가 된다.
연관된 document가 있는 경우 cascading 옵션이 없으므로 삭제시 참조하는 데이터를 어떻게 처리할지를 고민해서 자동화할 수 있도록 해야한다.
(cascading 자동화 코드를 작성해 놓으면 편하다.)

main.class

import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.internal.bulk.DeleteRequest;
import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.PojoCodecProvider;

import java.time.format.DateTimeFormatter;

import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry;
import static com.mongodb.client.model.Filters.*;
import static com.mongodb.client.model.Projections.fields;
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;

public class Main {
    public static void main(String[] args) {

        // 클래스에 매핑해서 해석할 수 있는 정보를 MongoDB 드라이버에게 알려주어야 한다.
        // Product에 대한 codec
        CodecProvider pojoCodecProvider = PojoCodecProvider.builder().automatic(true).build();
        CodecRegistry codecRegistry = fromRegistries(getDefaultCodecRegistry(), fromProviders(pojoCodecProvider));

        String uri = "mongodb://localhost:27017";
        // MongoClient 객체 하나가 하나의 connection을 의미
        MongoClient mongoClient = MongoClients.create(uri);
        MongoDatabase mongoDatabase = mongoClient.getDatabase("mongodb").withCodecRegistry(codecRegistry);
        // collection 이 다루는 class를 Document -> Product 변경
        // getCollection은 class 를 입력하면 TDocument를 return 해준다. getCollection( collectionName , class)
        MongoCollection<Product> collection = mongoDatabase.getCollection("products", Product.class);

        // name 에 shoes 가 포함되어 있고, price가 10000원 이상이고, price가 20000원 미만인 레코드를 삭제
        // 10000 <= price < 20000
        DeleteResult deleteResult = collection.deleteMany(fields(regex("name", "shoes"),
                                                                    gte("price", 10000),
                                                                    lt("price", 20000)
        ));

        if(deleteResult.wasAcknowledged()) {
            System.out.println("delete : " + deleteResult.getDeletedCount());
        }
    }
}

결과

delete : 2

Process finished with exit code 0

원래 데이터

바뀐 데이터

Aggregation (집계)

MongoDB 에서 집계를 위한 연산을 Aggregation Pipeline 기능을 통해서 할 수 있다.
Aggregation 에서 사용하는 대표적인 기능은 다음 3종류이다.

  • $match : 원하는 조건에 맞는 document 만 고른다. SQL의 WHERE 조건절이라고 생각하면 된다.
  • $group : 어느 단위로 묶어서 집계를 할지 정한다. SQL의 group by 라고 생각하면 된다.
  • $sort , $limit , $count : 집계 결과에 대해서 한정한다.

데이터 추가

$group의 효과적인 확인을 위해 데이터를 추가해준다.

원래 데이터

바뀐 데이터 (위에 Create 재활용)

Main.class

import com.mongodb.client.*;
import com.mongodb.client.model.Accumulators;
import com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.Sorts;
import org.bson.Document;
import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.PojoCodecProvider;

import java.time.format.DateTimeFormatter;
import java.util.Arrays;

import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry;
import static com.mongodb.client.model.Filters.gt;
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;

public class Main {
    public static void main(String[] args) {

        // 클래스에 매핑해서 해석할 수 있는 정보를 MongoDB 드라이버에게 알려주어야 한다.
        // Product에 대한 codec
        CodecProvider pojoCodecProvider = PojoCodecProvider.builder().automatic(true).build();
        CodecRegistry codecRegistry = fromRegistries(getDefaultCodecRegistry(), fromProviders(pojoCodecProvider));

        String uri = "mongodb://localhost:27017";
        // MongoClient 객체 하나가 하나의 connection을 의미
        MongoClient mongoClient = MongoClients.create(uri);
        MongoDatabase mongoDatabase = mongoClient.getDatabase("mongodb").withCodecRegistry(codecRegistry);

        // POJO classs 를 쓰면 안되고 일반 Document class 를 사용해야 함
        // collection에 있는 데이터만 가져오는게 아니라 가져온 데이터를 기반으로 avg_price 필드를 만들었기 때문에
        // product document 의 형식에 벗어난다. 따라서 product document의 pojo class 매칭을 사용할 수 없고 기본 Document class를 사용해야 한다.
        MongoCollection<Document> collection = mongoDatabase.getCollection("products");

        // AggregateIterable의 type 을 받아야함
        MongoCursor<Document> cursor = collection.aggregate(
                Arrays.asList(
                        // match() 로 필터링 price > 10000
                        Aggregates.match(gt("price",10000)),
                        // 필드이름 앞에 $를 붙혀야 해당 필드를 찾을 수 있다. (필드에 조건을 걸려면)
                        // name 으로 group by
                        // group by 한 price들의 평균을 구한다 그 필드의 이름은 avg_price로 한다.
                        Aggregates.group("$name", Accumulators.avg("avg_price", "$price")),
                        Aggregates.sort(Sorts.descending("avg_price"))
                )
        // .iterator() 추가
        ).iterator();

        while (cursor.hasNext()) {
            System.out.println(cursor.next());
        }
    }
}

결과

name 필드를 기준으로 Group by 되었으며 각 그룹의 평균 가격을 avg_price 필드에서 나타냈다.
Aggregation 을 활용할 때는 모든 Document type 을 POJO class 로 만들어 놓지 않는 이상 필드의 타입과 이름을 외워서 조회하는 수 밖에 없다.

Document{{_id=glasses, avg_price=100000.0}}
Document{{_id=backpack, avg_price=65000.0}}
Document{{_id=shoes6, avg_price=62000.0}}
Document{{_id=shirt, avg_price=55000.0}}
Document{{_id=shoes5, avg_price=52500.0}}
Document{{_id=shoes4, avg_price=43000.0}}
Document{{_id=shoes3, avg_price=33500.0}}
Document{{_id=shoes2, avg_price=30000.0}}
Document{{_id=shoes1, avg_price=20000.0}}

Process finished with exit code 0
profile
초보 개발자

0개의 댓글

관련 채용 정보