sequelize

박상은·2021년 11월 27일
0

🎁 분류 🎁

목록 보기
13/16

모든 예시에는 아래 모델이 정의됐다고 가정하고 예시 코드를 작성하겠습니다.

const { Sequelize, DataTypes, Model } = require("sequelize");

const config = {
  username: "sequelizeUser",
  password: "sequelize",
  database: "sequelizeDB",
  host: "127.0.0.1",
  port: 3306,
  dialect: "mysql",
  logging: false, // SQL문 로그를 콘솔에 표시하지 않음
  freezeTableName: true, // sequelize의 모델명과 DB의 테이블명을 일치하게 해줌
};

const { database, username, password } = config;

const sequelize = new Sequelize(database, username, password, config);

// define방식으로 유저 모델 생성 ( sequelize에서만 생성한 것 )
const User = sequelize.define("User", {
  firstName: { type: DataTypes.STRING, allowNull: false },
  lastName: { type: DataTypes.STRING },
  age: { type: DataTypes.INTEGER, defaultValue: 0 }
}, {
  // options
});

// class방식으로 게시글 모델 생성 ( sequelize에서만 생성한 것 )
class Post extends Model {}
Post.init(
  {
    contents: {
      type: DataTypes.STRING,
      allowNull: false,
    },
  },
  {
    sequelize,
    // options
  },
);

1. sequelize란

Node.jsORM도구입니다.

2. 설치

  • npm i sequelize sequelize-cli

  • 드라이버
    1. mysql: npm i mysql2
    2. Postgres: npm i pg pg-hstore
    3. mariadb: npm i mariadb

3. 기본 세팅

sequelize-cli를 설치했을 경우 npx sequelize init를 실행하면 기본 폴더들과 기본 코드가 생성됩니다.

3.1 드라이버를 이용해서 sequelize와 DB 연결

// config 예시
const config = {
  "username": "nodebirdUser",
  "password": "nodebird",
  "database": "nodebirdDB",
  "host": "127.0.0.1",
  "port": 3306,
  "dialect": "mysql",
  logging: false,		// SQL문 로그를 콘솔에 표시하지 않음
  freezeTableName: true,	// sequelize의 모델명과 DB의 테이블명을 일치하게 해줌
};

const { database, username, password } = config;

const Sequelize = require("sequelize");
const sequelize = new Sequelize(database, username, password, config)

3.2 DB와 sequelize 동기화

아래 모델 생성 부분부터 보고 해당 내용을 보는 것이 좋습니다.
동기화 한다는 의미는 sequelize에 정의한 모델을 실제로 DB에 생성한다는 것을 의미합니다.

// 특정 모델만 동기화
User.sync
  .sync({ force: false, alter: false })
  .then(() => console.log("User 테이블 생성 완료"))
  .catch(console.error);

// sequelize에 정의한 모든 모델 동기화
sequelize
  .sync({ force: false, alter: false })
  .then(() => console.log("DB 연결 성공!"))
  .catch(error => console.error("DB 연결 실패 >> ", error));

3.3 테이블 삭제

sequelize에 정의한 모델에 해당하는 테이블을 삭제하는 방법입니다.

(async () => {
  // 특정 모델만 삭제
  await User.drop();
  
  // 전체 모델 삭제
  await sequelize.drop();
})();

4. 모델 정의

sequelize.defind()이나 extends Model 둘 중에 아무거나 사용해도 결과적으로 같은 코드입니다.

4.1 모델 컬럼 정의

정의할게 type밖에 없는 경우에는 생략 가능합니다.
primary key가 없는 경우 자동적으로 integerauto_increment를 적용한 id컬럼이 생성됩니다.

  • type: 타입 정의 ( DataTypes을 사용함 )
  • allowNull: null 허용 여부
  • unique: uniqueKey 적용 여부
  • defaultValue: 기본값 적용
  • primary: primaryKey 적용 여부
  • autoIncrement: auto_increment 적용 여부

4.2 DataTypes

  • 문자열
    1. DataTypes.STRING: varchar(255)
    2. DataTypes.STRING(20): varchar(20)
    3. DataTypes.TEXT: text

  • 불리언
    1. DataTypes.BOOLEAN: tinyint(1)

  • 숫자
    1. DataTypes.INTEGER: integer
    2. DataTypes.BIGINT: bigint
    3. DataTypes.FLOAT: float
    4. DataTypes.DOUBLE: double

  • 시간
    1. DataTypes.DATETIME: datetime
    2. DataTypes.NOW

4.3 모델 옵션값

4.3.1 freezeTableName

기본 동작은 sequelize모델명의 복수형태로 테이블을 생성하는데 그것을 막아주는 옵션입니다.
즉, 모델명과 같은 이름의 테이블 생성해줍니다.

4.3.2 tableName

동기화할 경우 DB의 테이블명을 직접 지정하는 옵션입니다.

4.3.3 modelName

sequelize의 모델명을 직접 지정하는 옵션입니다.

4.3.4 timestamps

createdAt, updatedAt 컬럼 추가 및 자동으로 처리해줍니다.
단, 데이터를 추가할 경우 자동으로 updatedAt이 업데이트되지만 DB에 default로 값이 들어가 있는 것은 아닙니다.

4.3.5 createdAt과 updatedAt

bool값을 넣으면 컬럼추가이고, 문자열을 넣으면 해당 문자열을 이름으로 갖는 컬럼을 생성합니다.

4.1 sequelize.define()을 이용한 모델 정의

기본 형태는 sequelize.define(모델명, 테이블정의, 테이블옵션) 입니다.

const { Sequelize, DataTypes } = require("sequelize");

const config = {
  username: "sequelizeUser",
  password: "sequelize",
  database: "sequelizeDB",
  host: "127.0.0.1",
  port: 3306,
  dialect: "mysql",
};

const { database, username, password } = config;

const sequelize = new Sequelize(database, username, password, config);

// define을 이용한 모델 생성
const User = sequelize.define(
  // 모델명
  "User",
  // 컬럼 정의
  {
    _id: {
      type: DataTypes.INTEGER.UNSIGNED,
      allowNull: false,
      primaryKey: true,
      autoIncrement: true,
    },
    name: {
      type: DataTypes.STRING(30),
      allowNull: true,
      unique: true,
    },
  },
  // 옵션값
  {
    charset: "utf8",
    collate: "utf8_general_ci",
  }
);

4.2 class를 이용한 모델 정의

const { Sequelize, DataTypes, Model } = require("sequelize");

const config = {
  username: "sequelizeUser",
  password: "sequelize",
  database: "sequelizeDB",
  host: "127.0.0.1",
  port: 3306,
  dialect: "mysql",
};

const { database, username, password } = config;

const sequelize = new Sequelize(database, username, password, config);

class Post extends Model {}

Post.init(
  // 컬럼 정의
  {
    contents: {
      type: DataTypes.STRING,
      allowNull: false,
    },
  },
  // 옵션값
  {
    sequelize,
  },
);

5. 모델 인스턴스 처리 및 DB와 동기화

클래스로 모델을 정의했다고 해서 new를 이용해서 모델을 생성하면 안 됩니다.
이유는 모르겠지만 공식 문서에 명시해놨습니다.

5.1 모델 인스턴스 생성 및 동기화

  1. build()
  2. save()
  3. create()
// 모델 인스턴스 생성 => 동기처리 ( DB에 생성하는 것이 아니기 때문에 비동기로 처리할 이유가 없음 )
const userModel = User.build({ firstName: "john", lastName: "smith" });

// 생성한 모델 인스턴스 동기화
(async () => {
  await userModel.save();
})();

// 모델 인스턴스 생성과 동기화 동시 처리
(async () => {
  const createdUser = await User.create({ firstName: "john", lastName: "smith" });
})();

// 출력팁 ( 스코프에 맞진 않지만 출력된다고 가정함 )
// 생성된 결과물을 확인하고 싶을 때 그냥 반환값을 콘솔에 찍으면 복잡한 형태로 찍힘
// 따라서 아래처럼 변형시켜서 찍으면 정확한 결과값만 볼 수 있음
console.log(createdUser.toJSON());
console.log(JSON.stringify(createdUser, null, 4));

5.2 생성한 모델 인스턴스 변경 및 동기화

  1. 직접 변경
  2. set()
  3. update()
(async () => {
  const createdUser = await userModel.create({ firstName: "john", lastName: "smith" });
  
  // 방법 1) 직접 변경
  createdUser.firstName = "aaaa";
  // 방법 2) set() 이용해서 한번에 변경
  createdUser.set({
  	firstName: "bbbb",
    lastName: "cccc",
  });
  // 방법1, 2를 통한 변경사항 동기화
  await createdUser.save();
  
  // 방법 3) 변경과 동기화 동시 실행
  await createdUser.update({
  	firstName: "dddd",
    lastName: "eeee",
  });
})();

5.3 생성한 모델 인스턴스 삭제 및 동기화

  1. destory()
  2. reload()
(async () => {
  const createdUser = await userModel.create({ firstName: "john", lastName: "smith" });
  
  // reload
  console.log(createdUser.firstName);	// "john"
  createdUser.firstName = "aaa";
  console.log(createdUser.firstName);	// "aaa"
  await createdUser.reload();
  console.log(createdUser.firstName);	// "john"
  
  // 생성한 모델 인스턴스 삭제 및 동기화
  await createdUser.destroy();
})();

5.4 정수 증가 및 감소

  1. increment()
  2. decrement()
(async () => {
  const createdUser = await userModel.create({ firstName: "john", lastName: "smith", age: 10 });
  
  // 방법 1)
  await createdUser.increment("age", { by: 2 });
  
  // 방법 2)
  await createdUser.increment({ age: 2 });
  
  // decrement는 반대로 작동
})();

6. 모델 쿼리

5.1 SELECT 쿼리

5.1.1 findAll()

해당 테이블의 모든 데이터를 찾습니다.

// SELECT * FROM Users;
(async () => {
  const users = await User.findAll();
})();

5.1.2 findByPk()

해당 테이블의 특정 primaryKey에 해당하는 데이터를 가져옵니다.

// SELECT * FROM Users WHERE id = 1;
// 현재 primary key가 id라서 위처럼 연산함
(async () => {
  const user = await User.findByPk(1);
})();

5.1.3 findOne()

해당 테이블에서 조건에 제일 처음 만족하는 데이터를 가져옵니다.

// SELECT * FROM Users WHERE age = 20 LIMIT = 1;
(async () => {
  const user = await User.findOne({ where: { age: 20 } });
})();

5.1.4 findOrCreate()

없으면 생성하고 있으면 그냥 가져오며, 생성여부를 같이 반환함

(async () => {
  const [userData, created] = await User.findOrCreate({ where: { id: 1 } });
})();

5.1.5 findAndCountAll()

5.2 INSERT 쿼리

(async () => {
  await User.create({
    nickname: "any",
    email: "a@naver.com",
    password: "암호화된 비밀번호"
  });
})();

5.3 UPDATE 쿼리

(async () => {
  await User.update(
    {
      nickname: "any"
    }, {
      where: { id: 1 },
    });
})();

5.4 DELETE 쿼리

삭제한 컬럼의 개수를 반환합니다.

(async () => {
  await User.destroy({
    where: { id: 1 },
  });
  
  await User.destroy({
    truncate: true,
  });
})();

6. 모델 쿼리 속성

특정 컬럼에 대한 검색, as, join 연산 등에 대한 사용법을 정리하겠습니다.

6.1 attributes 예시

특정 row만 가져올 때 사용하는 속성입니다.

// SELECT firstName, createdAt FROM Users;
User.findAll({
  attributes: ["firstName", "createdAt"],
});

// SELECT firstName, createdAt as `생성 날짜` FROM Users;
User.findAll({
  attributes: ["firstName", ["createdAt", "생성 날짜"]],
});

// SELECT id, firstName, lastNAme, createdAt FROM Users;
User.findAll({
  attributes: {
    exclude: ["updatedAt"],
  },
});

6.2 where 예시

조건에 해당하는 column만 가져오는 속성입니다.

6.2.1 Op.eq

동등 비교인 [Op.eq]는 생략이 가능하기 때문에 이후 예시에서는 생략하겠습니다.

// SELECT * FROM Users WHERE id = 1;
User.findAll({
  where: {
    id: {
      [Op.eq]: 1
    }
  }
});

// [Op.eq]는 생략
User.findAll({
  where: {
    id: 1
  }
});

6.2.2 Op.and

AND연산입니다.

// SELECT * FROM Users WHERE id = 1 AND age = 0;
User.findAll({
  where: {
    [Op.and]: [
      {
        id: {
          [Op.eq]: 1,
        },
      },
      {
        age: {
          [Op.eq]: 0,
        },
      },
    ]
  },
});

// [Op.eq]는 생략
User.findAll({
  where: {
    [Op.and]: [
      {
        id: 1
      },
      {
        age: 0
      },
    ]
  },
});

6.2.3 Op.or

User.findAll({
  where: {
    [Op.or]: [
      { id: 1 },
      { firstName: "john" }
    ]
  }
});

6.2.4 각종 연산자들

  • Op.ne: !=
  • Op.is: is null?
  • Op.not: is false?
  • Op.gt: >
  • Op.gte: >=
  • Op.lt: <
  • Op.lte: <=
  • Op.between: between
  • Op.notBetween: notBetween
  • Op.in: 포함 여부
  • Op.notIn: 비포함 여부

이외에도 많이 있으니 필요시 찾아서 사용하면 됩니다.

6.2.5 특정 컬럼에 메소드 사용

const users = await User.findAll({
  where: [sequelize.where(sequelize.fn("char_length", sequelize.col("firstName")), 4)],
});

6.3.4 ORDER BY

(async () => {
  // SELECT * FROM Users ORDER BY age DESC, _id DESC;
  const users = await User.findAll({
    order: [
      ["age", "DESC"],
      ["_id", "DESC"],
    ],
  });
})();

// include를 사용한 경우 가장 바깥에 "[모델, 컬럼, 기준]"순서로 적어주면 됩니다.
(async () => {
  // SELECT * FROM Users INNER JOIN Posts ON Users._id = Posts.UserId ORDER BY Users.createdAt DESC, Posts.createdAt ASC
  const users = await User.findAll({
    attributes: ["_id", "name", "createdAt"]
    include: {
      model: Post,
      attributes: ["_id", "content", "createdAt"],
	  // 여기에다가 order 적으면 오류도 안 나고 아무 효과도 없음 ( 아래코드 효과없음 )
      order: ["crteatedAt", "ASC"]
    },
 	// 여기처럼 가장 바깥에다가 정렬 기준을 적어줘야 함
    order: [
 	  ["createdAt", "DESC"]
      [Post, "createdAt", "ASC"],
    ],
  });
})();

6.3.5 LIMIT/OFFSET

// 5번째부터 5개 읽기
(async () => {
  await User.findAll({ limit: 5, offset: 5 });
})();

6.3.6 count(), max(), min(), sum(), increment(), decrement()

(async () => {
  // SELECT * FROM Users LIMIT 2, 3
  await User.findAll({ limit: 3, offset: 2 });
  
  // SELECT COUNT(*) AS `count` FROM Users WHERE age = 0;
  await User.count({ where: { age: 0 } });
  
  // SELECT MAX(age) AS `max` FROM Users;
  await User.max("age");
  
  // SELECT MIN(age) AS `min` FROM Users;
  await User.min("age");
  
  // SELECT SUM(age) AS `sum` FROM Users;
  await User.sum("age");
  
  // UPDATE Users SET age = age + 5, updatedAt='2021-12-26 03:29:27' WHERE _id = 1;
  await User.increment({ age: 5 }, { where: { id: 1 } });
  
  // decrement는 반대로 작동
})();

=== >> 여기부터 이어서 정리하기 << ===

7. getter/setter/가상필드

추후에 필요시 사용해보고 작성 예정

8. 유효성 검사와 제약 조건

유효성 검사는 sequelize에서 검사하고 실패시 DB로 쿼리자체를 보내지 않음
제약 조건은 DB에서 검사하고 실패시 sequelize에서 받아서 js로 변환해서 보여줌

추후에 필요시 사용해보고 작성 예정

9. 원시 쿼리

간단한 쿼리는 sequelize.query()를 이용해서 결과를 얻을 수 있다.

(async () => {
  const [results, metadata] = await sequelize.query("SELECT * FROM Users;");
})();

필수적인 부분은 아니라고 생각해서 자세한건 직접 찾아보기

10. 관계 ( associate )

  • 소스 모델: 앞에 오는 모델
  • 대상 모델: 뒤에 오는 모델

기본적으로 모델명Id라는 이름을 가진 foregin key가 생성된다.

10.1 HasOne와 BelongsTo ( 1 : 1 )

일대일관계를 정의할 때 사용한다.
일대일관계일 경우 foregin key가 어디에 있어도 상관없으므로 본인 판단에 의해서 foregin key를 넣을 테이블을 정하면 된다.

BelongsTo()를 사용한 모델에 foregin key가 붙게 된다.

// 만약 유저가 게시글을 하나만 작성할 수 있다고 가정하고 진행
User.hasOne(Post);
Post.belongsTo(User);

/**
 * 위처럼 코드 작성시 Post테이블에 UserId라는 컬럼이 생기고
 * 해당 컬럼이 user.id를 참조하는 foreign key가 된다.
*/

10.2 HasMany와 BelongsTo ( 1 : N )

BelongsTo를 정의한 모델에 foreignKey가 생성됨

User.hasMany(Post);
Post.belongsTo(User);

// 여기서도 마찬가지로 Post테이블에 UserId가 생긴다.

10.3 BelongsToMany ( N : M )

새로운 모델이 생성되며, through속성을 통해서 모델명을 정해야 함

User.belongsToMany(Post, { through: "UserPosts" });
Post.belongsToMany(User, { through: "UserPosts" });

// UserPosts라는 중간 테이블이 생기고 UserId, PostI라는 foreignKey를 컬럼으로 가진다.

10.4 옵션

위 4가지 함수의 두 번째 인자로 옵션을 지정해 줄 수 있다.

10.4.1 onDelete / onUpdate

RESTRICT, CASCADE, SET DEFAULT, SET NULL값을 줄 수 있으며,
onDeleteSET NULL, onUpdateCASECADE가 기본값이다.

10.4.2 foreignKey

관계에서 기본적으로 제공되는 foregin key의 값을 변경시키는데 사용한다.

// 둘중에 하나만 적거나 { name: }부분은 생략해도 된다.
// foreginkey의 type, allowNull, defaultValue 등을 지정할 수 있다.
Foo.hasOne(Bar, {
  foreignKey: {
    name: 'myFooId'
  }
});
Bar.belongsTo(Foo, {
  foreignKey: {
    name: 'myFooId'
  }
});

10.4.3 through

N : M 관계일 경우에 새로 생성될 중간 테이블의 이름을 정한다.

중간 테이블은 관계를 가지는 두개의 테이블의 식별자를 foregin key로 갖는 컬럼 두개를 가진다.

10.4.4 as

foregin key에 대한 별칭을 지정한다.
아직 글만 읽어서 추후에 테스트 후 작성함

10.5 관계 메서드

as설정을 줬을 경우 모델명대신 설정한 이름으로 메서드명이 바뀐다.

10.5.1 User.hasOne(Post) 일 경우

  1. userInstance.getPost();
  2. userInstance.setPost();
  3. userInstance.createPost();

10.5.2 User.belongsTo(Post) 일 경우

  1. userInstance.getPost();
  2. userInstance.setPost();
  3. userInstance.createPost();

10.5.3 User.hasMany(Post) 일 경우

  1. userInstance.getPosts();
  2. userInstance.countPosts();
  3. userInstance.hasPost();
  4. userInstance.hasPosts();
  5. userInstance.setPosts();
  6. userInstance.addPost();
  7. userInstance.addPosts();
  8. userInstance.removePost();
  9. userInstance.removePosts();
  10. userInstance.createPost();

10.5.3 User.belongsToMany(Post) 일 경우

  1. userInstance.getPosts();
  2. userInstance.countPosts();
  3. userInstance.hasPost();
  4. userInstance.hasPosts();
  5. userInstance.setPosts();
  6. userInstance.addPost();
  7. userInstance.addPosts();
  8. userInstance.removePost();
  9. userInstance.removePosts();
  10. userInstance.createPost();

10.5.4 관계 메서드 예시

// User와 Post가 1 : N 관계일 경우 ( foreginKey: UserId )

(async () => {
  // 유저 생성
  const createdUser = await User.create({ name: "user1" });
  
  // 게시글 생성
  const createdPost1 = await Post.create({ content: "content1" });
  const createdPost2 = await Post.create({ content: "content2" });
  
  // 관계 메서드 사용 예시
  await createdUser.getPosts();		// null
  await createdUser.countPosts();	// 0
  await createdUser.hasPosts();		// false
  await createdUser.setPosts([creatdPosts1, createdPost2]);	// 유저 정보 반환
  await createdUser.addPosts([creatdPosts1, creatdPosts2]);	// 유저 정보 반환
  // remove의 반환값이 뭔지 도저히 모르겠음
  // posts의 컬럼이 삭제되는 것이 아니고 관계를 가지는 값만 제거됨 ( UserId )
  await createdUser.removePosts([createdPosts1, createPost2]);
  await createdUser.createPost({ content: "생성할 게시글의 컨텐츠" });	// 유저와 게시글이 join된 값이 반환됨
})();

11. include

관계를 지정했을 경우 사용 가능하며, join 연산을 해줍니다.

  • 예시
// 유저와 게시글 1 : N
const userWithPosts = await User.findAll({ include: Post });
const postsWithUser = await Post.findAll({ include: User });

// userWithPosts
[
  {
    _id,
    name,
    Posts: [{ _id, content }, { _id, content }]
  },
  // ...
]

// postsWithUser
[
  {
    _id,
    content,
    User: { _id, name }
  },
  // ...
]

11.1 속성들

  1. model: 어떤 모델을 조인할지 지정하는 속성값입니다.
  2. as: 모델의 별칭을 지정한 경우 적어주는 속성값입니다.
  3. required: inner join, outer join을 결정하는 속성값입니다.
  4. right: outer join일 경우 right outer join을 결정하는 속성값입니다.
  5. all, nested: 연관된 모든 테이블을 가져올지 결정하는 속성값입니다.

11.2 조건절 사용

include 내부에서 where 사용 시 joinon에 조건이 들어가게 됩니다.
최상위 where에 조건을 넣는 방법은 최상위 where에서 $테이블명.컬럼$ 형태로 사용하면 됩니다.

11.3 중간 테이블 관리 ( N : M )

N : M관계에서 include를 사용하면 중간 테이블의 모든 데이터를 가져오게 됩니다.

중간 테이블의 데이터를 관리하려면 throughattritures를 사용하면 됩니다.

따로 정리할 것

  • targetKey

  • sourceKey

  • sequelize.authenticate()

try {
  await sequelize.authenticate();
  console.log('Connection has been established successfully.');
} catch (error) {
  console.error('Unable to connect to the database:', error);
}
  • sequelize.close();: 연결 끊기
  • 기본적으로 sequelize에는 단수(User), 테이블에서는 복수(users), 불규칙 복수형도 자동적으로 적용됨 ( person -> people )
  • sequelize.drop(): 모든 테이블 삭제
  • 모델.drop(): 해당 테이블과 관련된 테이블 삭제

getter/setter는 아직 테스트안해봄

0개의 댓글