::: IT인터넷 :::

pymongo를 이용한 MongoDB의 CRUD 클래스 구현

곰탱이푸우 2022. 8. 22. 08:20
pymongo를 이용하여 MongoDB의 CRUD 클래스를 구현하는 방법에 대해 알아본다.
 
pymongo로 MongoDB를 사용하는 방법에 대해서는 아래 포스팅을 참고한다.
 
MongoDB의 mongosh에서 Database, Collection, Documnet를 사용하는 방법은 다음 포스팅을 참고한다.
 

CRUD 클래스 구현

이전 포스팅에서 pymongo의 개요와 자주 사용하는 함수들에 대해 알아보았다.
해당 함수들을 그대로 사용해도 되지만, Document 개수에 따라 호출하는 API가 다르기 때문에 번거롭다.
 
이러한 경우 pymongo에서 제공하는 기능들을 래핑한 자체 라이브러리를 만들어서 사용하면 편리하다.
외부 라이브러리를 래핑 (Wrapping)하는 이유는 아래 문서를 참고한다.
예제 편의상 기본적인 예외 처리들은 생략한다.
실제로 사용하는 경우 전달 인자들의 유효성을 체크하는 부분을 추가하면 더 좋다. 
 
데이터 처리 부분은 JSON 포맷을 사용한다.
예제를 작성하지는 않지만 데이터를 JSON으로 변환하거나 JSON 포맷을 검증하는 기능이 추가되면 좋을 것 같다.
 
CRUD 클래스를 구현한 예제는 아래 포스팅을 참고한다.

클래스 정의

CRUD 클래스의 기본 형태를 정의하고 생성자와 를 작성한다.
 
기본적인 형태는 다음과 같다.
import pymongo
 
class MongoDB():
    def __init__(self, host='localhost', port=27017, user=None, password=None):
        self.host = host
        self.port = port
        self.user = user
        self.password = password
 
        self.db_name = None    # Database 이름 
        self.coll_name = None    # Collection 이름
 
        # MongoDB 연결
        if self.user is None or self.password is None:
            self.conn = pymongo.MongoClient(host=self.host, port=self.port)
        else:
            self.conn = pymongo.MongoClient(host=self.host, port=self.port, 
                                            user=self.user, password=self.password)
 
        self.db = None
 
클래스의 인스턴스가 생성될때 MongoDB에 연결되고, 인스턴스가 제거 될때 연결이 해제되면 좋을 것이다.
 
따라서 클래스의 생성자 부분에서 pymongo의 MongoClient 클래스를 사용하여 인스턴스를 생성한다.
MongoClient 클래스는 MongoDB의 주소, 포트, 계정, 비밀번호, Database와 Collection 이름을 전달 받는다.
host와 port를 전달하지 않으면 기본 값인 'localhost'와 27017을 전달한다.
 
계정명과 password가 노출되는 것이 꺼려질 경우, 적절한 다른 방법을 사용한다.
 
 

연결

사용하고자 하는 MongoDB의 Database에 연결한다.
 
Collection을 지정하는 방법은 아래와 같이 클래스의 멤버 변수를 지정하는 방식을 사용하면 된다.
# collection 지정 방법
Database인스턴스.collection_name.함수명(~~)
 
예를 들어 test라는 Collection의 모든 Document를 조회하는 경우 다음과 같이 사용한다.
## 실제 예제
# test Collection의 모든 Document 조회
for item in db.test.find():
    print(item)
 
Collection에 대한 연결은 생성하지 않아도 되므로, Database에 대한 연결만 정의한다.
import pymongo

class MongoDB():
    ... 생략 ...

    def connect(self, db_name=None):
        if db_name is None:
            raise AttributeError("db_name is not allowed None")

        self.db_name = db_name    # Database 이름

        # pymongo의 Database 인스턴스
        self.db = self.conn.get_database(self.db_name)
 

 

해제

연결 되어 있는 MongoDB 인스턴스를 명시적으로 해제한다.
import pymongo

class MongoDB():
    ... 생략 ...

    def close(self):
        if self.conn is not None:
            self.conn.close()
 
만약 명시적으로 해제 명령을 사용하지 않았을 경우를 대비해서 소멸자에도 정의한다.
 
연결을 해제하지 않고 해당 인스턴스가 종료되면 소멸자에 의해 연결을 해제한다.
import pymongo

class MongoDB():
    ... 생략 ...

    def __del__():
        if self.conn is not None: 
            self.close()
 

 

CRUD 기능 정의

CRUD 클래스의 기본 구조 작성에 이어 실제 Document를 다루는 기능들을 정의한다.
 

데이터 입력

특정 Database의 Collection에 Document를 입력하는 기능을 작성한다.
pymongo의  Collection 인스턴스의 insert 계열 함수를 사용한다.
 
한 개인 경우 insert_one, 두 개 이상인 경우 insert_many 함수를 사용한다.
따라서 insert 메소드 하나를 구현하고 전달 받은 Document 개수에 따라 구분하여 호출한다.
 
다음과 같이 구현할 수 있다.
import pymongo

class MongoDB():
    ... 생략 ...
   
    # 호출 전에 documents 전달 인자 생성과 타입, 형식 검증 필요
    def insert(self, coll_name=None, documents=None) -> int:
        if coll_name is None: 
            raise AttributeError("coll_name is not allowed None")
        if documents is None:
            raise AttributeError("db_name is not allowed None")

        # 임시로 collection 연결 생성
        coll = self.db.get_collection(coll_name)

        # 전달 된 Document가 하나이면 insert_one 함수 호출
        if type(documents) is dict:
            result = coll.insert_one(documents)
            return 1
        # 전달 된 Document가 2개 이상이거나, 1개가 리스트 타입으로 전달되면 insert_many 함수 호출
        elif type(documents) is list and type(document[0]) is dict:
            result = coll.insert_many(documents)
            return len(result.inserted_ids)
        # 그 외에는 예외 발생 (타입 오류)
        else:
            raise TypeError("documents is not Document Type for MongoDB!")
 
반환 값은 입력에 성공한 Document의 개수이다.
 
 

데이터 조회

특정 Database와 Collection의 데이터를 조회하는 기능을 작성한다.
pymongo의  Collection 인스턴스의 find 계열 함수를 사용한다.
 
그러나 find 계열 함수가 여러 개이고, find 함수 자체도 다양한 전달 인자를 갖는다.
따라서 적절한 기능만 입력으로 받는 래핑 함수를 만드는 것이 쉽지 않다.
 
개인적으로 Apache Spark에 익숙하기 때문에 Spark의 조회 방법을 참고하여 정리한다.
// 기본형태
// DataFrame변수.select(컬럼명, ...)
//    .where(조건문)
//    .orderBy(컬럼명.정렬방법, ...)  // 정렬 방법은 asc (오름차), desc (내림차)
//    .show(요약출력여부=True, 출력개수=20)

// 예제 1
testDF.select("name", "age").where(col("age") > 25).show()
// 예제 2
testDF.select("name", "age").where(col("age") > 25).orderBy(col("age").desc).show(false)
 
Spark과 pymongo의 조회 API를 비교하면 다음과 같다.
pymongo
Spark
비고
find 함수
select + where + show 함수 조합
데이터 조회
filter 인자
where(조건문)
검색 조건
projection 인자
select(컬럼명)
출력할 항목 선택
skip 인자
추가 코딩 필요
건너 뛸 범위 지정
limit 인자
show(출력개수)
화면에 출력할 개수
sort 인자
orderBy(컬럼명.정렬방법)
정렬 방법 지정
 
Spark은 기본적으로 최대 20개까지만 출력해준다.
MongoDB의 경우 find는 전부, find_one은 하나만 출력하는데, 간단한 데이터 탐색에 적절한 크기는 아니다.
따라서 Spark 처럼 기본으로 최대 20개만 출력하고, 그 외엔 지정한 개수만큼 출력한다.
 
그리고 기본적으로 find만 사용해서 정의한다.
다음과 같이 구현할 수 있다.
import pymongo

class MongoDB():
    ... 생략 ...

    # 호출 전에 filter, projection. sort 전달 인자 생성과 타입, 형식 검증 필요
    def find(self, coll_name=None, filter=None, projection=None, skip=0, limit=20, sort=None) -> list:
        if coll_name is None: 
            raise AttributeError("coll_name is not allowed None")
        if skip is None:
            raise AttributeError("skip is not allowed None")
        if limit is None: 
            raise AttributeError("limit is not allowed None")

        if filter is not None and type(filter) is not dict:
            raise AttributeError("filter should be None or dict")
        if projection is not None and type(projection) is not dict:
            raise AttributeError("projection should be None or dict")
        if sort is not None and type(sort) is not dict:
            raise AttributeError("sort should be None or dict")

        # 임시로 collection 연결 생성
        coll = self.db.get_collection(coll_name)

        result = coll.find(filter=filter, projection=projection, skip=skip, limit=limit, sort=sort)

        return result
 

 

filter의 조건 지정은 아래 포스팅을 참고한다.
sort, limit, skip 전달 인자 관련 내용은 아래 포스팅을 참고한다.
반환 값은 조회 된 Document의 목록이다.
 

데이터 수정

특정 Database의 Collection에 Document를 수정하는 기능을 작성한다.
pymongo의  Collection 인스턴스의 update 계열 함수를 사용한다.
 
한 개인 경우 update_one, 두개 이상인 경우 update_many 함수를 제공한다.
따라서 update_many 함수를 사용하는 것으로 통일하고, update 함수를 작성한다.
 
다음과 같이 구현할 수 있다.
import pymongo

class MongoDB():
    ... 생략 ...
   
    # 호출 전에 update, filter 전달 인자 생성과 타입, 형식 검증 필요
    def update(self, coll_name=None, filter=None, update=None) -> int:
        if coll_name is None: 
            raise AttributeError("coll_name is not allowed None")
        if filter is None:
            raise AttributeError("filter is not allowed None")
        if update is None: 
            raise AttributeError("update is not allowed None")

        if type(filter) is not dict:
            raise AttributeError("filter should be dict")
        if type(update) is not dict:
            raise AttributeError("update should be dict")

        # 임시로 collection 연결 생성
        coll = self.db.get_collection(coll_name)

        result = coll.update_many(filter=filter, update=update)
        return result.modified_count
 
반환 값은 수정에 성공한 Document의 개수이다.

 

 

데이터 삭제

특정 Database의 Collection에 Document를 삭제하는 기능을 작성한다.
pymongo의  Collection 인스턴스의 delete 계열 함수를 사용한다.
 
삭제 대상이 한 개인 경우 delete_one, 두 개 이상인 경우 delete_many 함수를 제공한다.
따라서 delete_many 함수를 사용하는 것으로 통일하고, delete 함수를 작성한다.
 
다음과 같이 구현할 수 있다.
import pymongo

class MongoDB():
    ... 생략 ...
   
    # 호출 전에 update, filter 전달 인자 생성과 타입, 형식 검증 필요
    def delete(self, coll_name=None, filter=None) -> int:
        if coll_name is None: 
            raise AttributeError("coll_name is not allowed None")

        if type(filter) is not dict:
            raise AttributeError("filter should be dict")

        # 임시로 collection 연결 생성
        coll = self.db.get_collection(coll_name)

        result = coll.delete_many(filter=filter)
        return result.deleted_count
 
반환 값은 삭제에 성공한 Document의 개수이다.
 
 

기타

기본적인 CRUD 기능 외에 다른 함수들을 사용하고자 하는 경우에 해당한다.
클래스 생성자에 정의한 conn, db 변수와 Collection 이름이 중요하다.
 
자체적으로 정의한 CRUD 함수를 사용하지 않고, 해당 인스턴스에 내장 된 메소드들을 직접 호출한다.
사용 빈도가 높으면 위의 CRUD와 같은 별도의 함수로 래핑해서 사용한다.
 
MongoDB 연결이나 관련 기능을 사용하고자 하는 경우 아래와 같이 사용한다.
test = MongoDB(user="mongo", password="mongo")
test.connect("test")    # test Database 사용

# conn은 pymongo의 mongoClient의 인스턴스
test.conn.사용할함수(전달인자)    # 연결된 세션에 관한 함수 호출

# db는 pymongo의 Database 인스턴스
test.db.사용할함수(전달인자)    # 연결 된 Database에 관한 함수 호출