※ 참고자료 ※
PyMongo 4.8.0 Documentation Reference Link
https://pymongo.readthedocs.io/en/stable/index.html
반갑습니다!
저번 시간까지 해서 insert_one(), insert_many()를 살펴봤습니다
데이터를 생성했으니, 이제 데이터베이스에 존재하는 데이터들을 조회할 수 있어야겠죠?
이번 포스트와 다음 포스트에서는 Read에 해당하는 예제들을 살펴보겠습니다!
저번 시간에 위와 같이 네 개의 샘플 데이터를 만들었었는데요
이번에는 특정 조건을 가진 하나의 도큐먼트를 조회해보겠습니다
name : "Brandon Led"에 해당하는 도큐먼트를 조회해볼까요?
참고로 오늘 시간을 위해서
해당 데이터를 생성할 때 일부러 "name"키의 값이 중복되도록 도큐먼트를 생성했습니다 ^^
보시는 바와 같이 두 번째, 네 번째 도큐먼트에서
"name":"Brandon Led"가 중복되는 것을 확인하실 수 있겠습니다
먼저 find_one() 함수에 대한 샘플 코드입니다
find_one() 함수의 파라미터로 임의의 조건을 딕셔너리 형태로 전달해주시면 되겠습니다
※ 참고 ※
코드를 보시면
find_one() 함수를 소개하는 목적에 있어서는, 딱히 중요한 내용은 아닌 부분이 있는데요
편의상, 해당 도큐먼트의 모든 키-밸류 값을 다 조회하려고 JSON으로 리턴하려는 의도 속에서
도큐먼트 생성시 자동으로 지정되는 "_id" 키의 value는 타입이 Object이기 때문에
JSON 직렬화가 불가능해서 편의상 문자열로 타입 변환을 해줬습니다
datetime, Object, function, python List 등등
not JSON serializable인 데이터 타입들이 몇 가지가 있죠? ^^
JSON으로 해당 데이터를 리턴해줄 때, 타입 변환이 필요하면 임의로 처리해주시면 되겠습니다 ^^
API 호출에 성공하여 해당 도큐먼트의 정보가 전달되었습니다!
근데 여기서 특기사항이 하나가 있습니다
위에 데이터를 보시면 "name":"Brandon Led"인 도큐먼트가 두 개가 있었죠?
그 중에서 반환받은 데이터는 name키의 값이 같은 도큐먼트 중 순서상 상단에 위치한 데이터였습니다
이렇듯, find_one() 함수는 해당 조건에 대한 결과를 하나만 반환해주기 때문에
만약 위 상황과 같이 조건에 부합한 결과가 둘 이상의 복수일 경우,
도큐먼트의 순서를 고려하여 순서상 최상단의 것 하나만 반환을 해줍니다 ^.^
그럼 이제는 여기서 find() 함수를 사용하여
"name":"Brandon Led" 조건에 부합하는 도큐먼트를 모두 조회해보겠습니다!
아까의 코드와 차이점은 없습니다
다만 조회한 도큐먼트의 모든 정보를 편의상 JSON으로 응답하기 위해
타입 변환만 반복문으로 처리했습니다 ^^
이제 포스트맨으로 API를 호출해서 한 번 확인해보겠습니다
하지만 위와 같은 오류 메세지를 확인하실겁니다 ^^
※ 참고1 ※
이 부분을 추가로 설명드리고 싶어서 일부러 위와 같이 진행을 했는데요
제가 상기에 기재한 코드에서 분명,
①find() 함수를 통해 조회한 결과를 변수에 저장한 다음
②client.close() 명령어 이후에
③"_id" 키의 값을 문자열로 변환하려고 했습니다
그랬더니 위와 같이 MongoClient가 close 된 이후에는 유효하지 않은 연산(InvalidOperation)이라고 하죠
근데, 분명 앞전 순서에서 find_one() 함수를 사용했을 때에는
client.close() 이후에 "_id" 키의 값을
문자열로 변환하는 연산이 정상 작동했는데 뭐가 다른걸까요?
find_one() 함수와 find() 함수의 작동 방식에 있어 중요한 차이가 있습니다
1. find_one():
(1) find_one() 함수는 해당 데이터베이스에서 조건에 부합한 도큐먼트 정보를 즉시 리턴합니다.
(2) 따라서 이 데이터는 더 이상 서버와의 연결과 상관이 없습니다.
(3) 그러므로 반환된 해당 도큐먼트 정보를 이미 변수에 별도로 저장했다면,
client.close() 명령 이후에 해당 데이터에 접근하여 수정 작업을 수행해도 아무 문제가 없습니다!
2. find():
(1) 하지만 find()는 해당 데이터가 아니라 Cursor 객체를 리턴하는데,
이 Cursor 객체는 서버와의 연결이 유지되는 동안에만 유효합니다.
(2) Cursor는 실제로 데이터를 가져오지 않으며,
데이터에 접근할 때마다 서버와의 연결을 사용하여 데이터를 가져옵니다.
(3) 따라서 client.close() 후에는 서버와의 연결이 끊어진 상태이기 때문에
Cursor 객체를 사용해 데이터를 조회할 수가 없습니다!
그렇기 때문에,
find_one()에서는 client.close() 후에도
리턴 받은 문서 데이터가 변수에 저장되어 있으므로 자유롭게 가공할 수 있었지만,
find()에서는 client.close() 전에 Cursor의 데이터를 모두 가져와야 합니다!
꼭 순서상 client.close() 이후에 해당 데이터에 접근해서 가공해야 한다면,
해당 커서로 리턴 받은 데이터를 전부 변수에 저장해놓으면 되는데요
리스트화 시켜서 변수에 저장하면
해당 데이터가 이미 변수에 저장되어 있으므로,
client.close() 명령 이후에도 해당 데이터를 자유롭게 가공할 수 있습니다 ^.^
※ 참고2 ※
그리고 제가 기재한 상기 코드에는 또 하나의 문제가 있습니다
분명 find() 함수는 리턴값으로 Cursor 객체를 반환한다고 했죠?
그럼 이 임의의 변수 result_list는 객체 참조 변수로서 해당 Cursor 객체를 가리키고 있겠네요
근데 여기서 for문을 이용해서, 딕셔너리를 요소로 갖는 리스트를 다루듯이 할 수 있을까요?
result_list 변수에 저장된 데이터가 리스트가 아닌데다가
이 상태로는 딕셔너리를 elements로 갖는다는 보장도 없죠
그렇기 때문에 리턴 받은 도큐먼트의 "_id" 값을 문자열로 변환하고 싶다면,
참고1과 같이 해당 데이터를 list화 시켜서 변수에 저장해주는 방법을 이용할 수가 있겠습니다
근데 지금과 같은 상황에서야 조회한 도큐먼트가 두 개 밖에 되지 않으니까 상관 없지만
실무에서는 몇 십, 몇 백을 조회해올 수도 있는데
리스트화 시켜서 직접 저장하는건 메모리 낭비가 너무 심하겠죠?
※ 참고3 ※
이 Cursor 객체를 직접 다루는 방법은 무엇이 있을까요?
먼저 이 Cursor 객체는 다음과 같은 주요 특징이 있는데요
① 반복 가능(iterable)
따라서 for 반복문을 통해 각각의 도큐먼트에 접근할 수 있습니다.
print() 문을 찍어보면 객체에 담겨있는 도큐먼트들이 하나씩 출력이 됩니다
② 슬라이스 연산 가능
따라서 특정 범위를 지정하여 데이터에 접근할 수 있습니다.
하지만 이 경우에도,
해당 범위의 도큐먼트가 직접 변수에 저장되는 것이 아니라,
해당 범위만큼의 데이터를 갖고 있는 Cursor 객체가 저장되는 것이기 때문에
데이터 가공이 필요하다면 해당하는 별도의 작업이 있어야 합니다 ^^
따라서 대량의 데이터를 조회하고
그 각각을 JSON으로 리턴하거나 별도로 가공하는 작업(ex 데이터 타입 변환)이 꼭 필요하다면,
슬라이스를 통해서 제한된 범위의 도큐먼트 정보만 최소로 가져와
Cursor 객체에 담은 후 처리하시면 되겠는데,
쉽게 말해서 쪼개서 갖고와서 쪼개서 처리하시면 되겠습니다 ^^
참고1~3을 통해서 여러 내용을 설명드렸는데요
다시 본론으로 돌아와서,
지금 조회하는 데이터의 수가 소수이므로
편의상 저는 바로 list화 시켜서 변수에 직접 할당하고 JSON으로 리턴 받도록 하겠습니다 ^^
제가 샘플로 사용하는 코드는 해당 주제 설명을 위해
편의상 사용하는 코드이므로 참고만 해주시기 바랍니다 ^^;;
아무튼 위와 같은 코드로 설정하고, 다시 Postman을 이용해서 API를 호출해보겠습니다
해당 컬렉션에서 "name" 키의 값이 "Brandon Led"인 도큐먼트가 두 개가 있었는데,
예상에 부합하게 두 도큐먼트를 잘 조회해왔습니다 ^^
코드에서, result_list가 Cursor 객체를 가리키는 객체 참조 변수가 된게 아니라
해당 객체가 가져온 데이터를 list화 해서 직접 저장한 것이었기 때문에
앞서 설명드린대로 간편하게 타입 변환(Object → String)을 하여
JSON 리턴 값으로 지정했으므로 위와 같은 결과를 리턴했습니다 ^^
이번 포스트에서는 CRUD 중 Read에 해당하는
find_one()과 find() 함수에 대해 알아보았습니다
다음 포스트에는 조회에 관련된 나머지 함수들에 대해서 살펴보도록 하겠습니다!
참고 내용이 길어져서 포스트도 덩달아 분량이 늘어났는데,
이번 포스트도 읽어주셔서 감사드리며 무더운 여름 더위 조심하시며
뿌듯한 개발 하시기 바랍니다 ^^