본문 바로가기

데이터베이스

Node와 MongoDB(+Mongoose)

반응형

이번에 교회 서비스를 개발하면서 MongoDB를 사용했는데요.

SQL 문법에 익숙했는데 새로운 문법을 배우니 기록해두면 좋겠다는 생각이 들었습니다. 스키마 관리를 위해 Mongoose를 추가했습니다. DB 생성과 설정하는 방법은 이전 포스팅을 참고해서 진행하실 수 있습니다.

 

SQL 문법과 비교해서 하나씩 살펴보겠습니다.

예제에서 사용될 컬렉션은 다음과 같습니다.

 

사용자 : users

export const UserScheme = new Schema({
  _id: { type: ObjectId },
  userName: { type: String },
  roleCode: { type: String },
  createDate: { type: Date, default: Date.now },
});

 

역할 : roles

export const RoleScheme = new Schema({
  _id: { type: ObjectId },
  roleName: { type: String },
});

 

INSERT

먼저 roles 컬렉션에 데이터를 삽입해보겠습니다.

데이터를 삽입하는 방법으로는 단일 삽입과 다중 삽입이 있습니다. 

 

단일 삽입

단일 삽입 명령어로는 save()를 사용할 수 있습니다. 삽입하려는 객체 생성 후 save()를 하게 되면 데이터가 저장됩니다.

const role = new Roles({
  _id: new mongoose.Types.ObjectId(),
  roleName: "관리자",
});

await role.save();

 

다중 삽입

N개의 데이터의 삽입이 필요한 경우 bulkWrite()를 사용할 수 있습니다.

bulkWrite()는 삽입 뿐 아니라 수정, 삭제까지 지원됩니다. 다음과 같이 배열로 작성하여 사용합니다.

await Roles.bulkWrite([
      {
        insertOne: {
          document: {
            _id: new mongoose.Types.ObjectId(),
            roleName: "중간관리자",
          },
        },
      },
      {
        insertOne: {
          document: {
            _id: new mongoose.Types.ObjectId(),
            roleName: "사용자",
          },
        },
      },
]);

 

UPDATE

이번에는 저장된 데이터의 변경을 알아보겠습니다.

데이터 변경으로 updateOne()를 사용할 수 있습니다. 첫 번째 인자값에는 where 절에 해당하는 조건을 넣게 되고, 두 번째 인자값의 $set 안에는 변경할 값을 넣습니다. 

조건이 여러개인 경우 { _id: new ObjectId(`abcd`), roleName: "사용자" } 와 같은 형태로 사용할 수 있고, 값을 여러개 변경하게 된다면 { $set: {a : "foo", b : "bar"} } 와 같은 형태로 사용할 수 있습니다.

await Roles.updateOne(
      { _id: new ObjectId(`65dc8d3b3832911cb8056337`) },
      {
        $set: { roleName: "일반사용자" },
      }
);

 

MERGE

SQL MERGE문과 같은 기능을 하는 명령문으로 updateOne()을 사용할 수 있습니다. 이 때는 upsert 옵션을 사용하게 되고 값을 true로 하면 됩니다.

await Roles.updateOne(
      {
        _id: new mongoose.Types.ObjectId(),
      },
      {
        $set: {
          roleName: "테스터",
        },
      },
      {
        upsert: true,
      }
);

 

DELETE

데이터를 삭제하는 경우 deleteOne()를 사용할 수 있습니다.

await Roles.deleteOne({
      _id: new ObjectId(`65dc90043832911cb805633a`),
});

 

SELECT

이제 저장된 데이터를 조회해보겠습니다.

데이터를 조회하는 경우 find()를 사용할 수 있습니다. 여기에 체이닝 되어 있는 lean() 함수는 성능을 개선시키는 함수로 사용됐습니다.

Mongoose의 리턴 값에는 Document 클래스의 인스턴스가 존재하는데 이를 통해 get(), set(), save() 등 여러 함수의 사용이 가능합니다. 하지만 조회 결과만 필요로 할 때는 이러한 리턴 값이 필요하지 않습니다. 이 때, lean() 함수를 체이닝하게 되면 불필요한 데이터 없이 POJO(Plain Old Javascript Object)를 리턴하게 되어 성능에 큰 이점을 가져옵니다. 

// 데이터 전체 조회 (좌)
const roleList = await Roles.find().lean();

// 역할명으로 정렬 (우)
const roleList = await Roles.find().lean().sort({ roleName: 1 });

 

조회 결과에 특정 필드만 가져오고 싶은 경우 find 함수의 두 번째 인자값으로 조회될 필드를 명시합니다. 조회될 필드에는 1값을 주고 필요하지 않는 필드는 생략합니다. _id는 0으로 명시해줘야 조회되지 않습니다.

const roleList = await Roles.find(
      {},
      {
        _id: 0,
        roleName: 1,
      }
    )
      .lean()
      .sort({ roleName: 1 });

 

특정 필드의 조건 검색을 하고 싶은 경우 find 함수의 첫 번째 인자값에 입력합니다. 

SQL LIKE 문과 같이 문자열을 포함하는 데이터를 찾고 싶은 경우에는  $regex 옵션을 사용합니다.

// 역할명이 관리자인 데이터 조회 (좌)
const roleList = await Roles.find({ roleName: "관리자" }).lean();

// 역할명이 관리를 포함하는 데이터 조회 (우)
const roleList = await Roles.find({ roleName: { $regex: "관리" } }).lean();

 

JOIN

users 컬렉션에 데이터를 입력하고 roles 컬렉션과 조인해보겠습니다.

조인의 기준이 되는 필드는 roleCode라는 필드입니다. users 컬렉션에서는 roles 컬렉션의 _id를 String 형태로 저장했습니다.

 

조인시에 aggeregate()를 사용할 수 있습니다. 여기서 userName이 에디인 사용자의 roleName이 무엇인지 확인하는 명령문을 만들어보겠습니다.

첫번째 인자값에는 where 절에 해당하는 조건을 입력할 수 있습니다. $match 안에 검색할 필드명과 값을 입력합니다.

두번째 인자값에서는 $addFields - $toObjectId를 통해 users 컬렉션의 roleCode를 ObjectId로 변환하는 작업을 한 뒤 roleCode로 반환했습니다. 이 값을 통해 roles 컬렉션의 _id와 비교하게 됩니다.

세번째 인자값에 $lookup을 통해 roles 컬렉션과 users 컬렉션의 roleCode를 비교하게 됩니다.

from은 조인할 컬렉션 roles을 입력합니다. 

localField에는 기준 컬렉션인 users의 roleCode를 입력합니다.

foreignField에는 roles 컬렉션의 _id(기준 컬렉션과 비교할 필드)를 입력합니다.

as는 별칭을 의미합니다. 다섯번째 인자값에 사용된 $project의 "$roles.roleName"가 여기에 정의된 별칭을 의미합니다.

 

네번째 인자값에 사용된 $unwind는 조인된 데이터가 배열로 되는 것을 풀어서 보여줍니다. (아래 좌측 이미지는 unwind가 사용되지 않은 경우입니다)

 

다섯번째 인자값에 $project를 통해 출력할 데이터를 정의합니다. 기준 컬렉션의 데이터는 0과 1로 구분합니다.(0:조회X, 1:조회O)

 const user = await Users.aggregate([
      {
        $match: {
          userName: "에디",
        },
      },
      {
        $addFields: { roleCode: { $toObjectId: "$roleCode" } },
      },
      {
        $lookup: {
          from: "roles",
          localField: "roleCode",
          foreignField: "_id",
          as: "roles",
        },
      },
      { $unwind: "$roles" },
      {
        $project: {
          _id: 1,
          userName: 1,
          roleName: "$roles.roleName",
        },
      },
]);

return Response.json({
      message: "OK",
      result: user,
});

 

반응형