Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
Tags
- Dependency Injection
- 일급 객체
- 일급 컬렉션
- spring security
- synchronized
- builder
- Volatile
- Google OAuth
- Spring
- java
- lombok
- factory
- OAuth 2.0
Archives
- Today
- Total
HJW's IT Blog
조각집 프로젝트 기록 - 01 본문
주어진 요구사항에 따라 ERD 설계를 시작하였다.

만들다 보니 GROUP은 SQL 예약어이기 때문에 다른 이름으로 해야겠다는 생각이 들었다.
GROUP 과 BADGE 의 관계는 N:N 관계이기 때문에, 이 두 테이블의 PK를 Composite Key 로 사용하는 GROUP_BADGE 테이블을 생성하였다.
TAG 와 POST 의 관계 또한 N:N 관계이기에 Composite Key 를 생성하였다.
다음은 주요 table sql 이다.
CREATE TABLE `Groups` (
`GID` INTEGER PRIMARY KEY AUTOINCREMENT,
`GName` VARCHAR(100) NOT NULL,
`GImage` VARCHAR(255),
`GIntro` TEXT,
`IsPublic` BOOLEAN NOT NULL,
`GPassword` VARCHAR(255) NOT NULL,
`CreatedDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`GLikes` INTEGER DEFAULT 0,
`GBadgeCount` INTEGER DEFAULT 0,
`PostCount` INTEGER DEFAULT 0
);
CREATE TABLE `Posts` (
`PostID` INTEGER PRIMARY KEY AUTOINCREMENT,
`GID` INTEGER NOT NULL,
`Nickname` VARCHAR(50) NOT NULL,
`Title` VARCHAR(100) NOT NULL,
`Image` VARCHAR(255),
`Content` TEXT NOT NULL,
`Location` VARCHAR(100),
`MemoryMoment` DATETIME,
`IsPublic` BOOLEAN NOT NULL,
`PPassword` VARCHAR(255) NOT NULL,
`CreatedDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`LikeCount` INTEGER DEFAULT 0,
`CommentCount` INTEGER DEFAULT 0,
FOREIGN KEY (`GID`) REFERENCES `Groups`(`GID`) ON DELETE CASCADE
);
CREATE TABLE `Comments` (
`CommentID` INTEGER PRIMARY KEY AUTOINCREMENT,
`PostID` INTEGER NOT NULL,
`Nickname` VARCHAR(50) NOT NULL,
`Content` TEXT NOT NULL,
`Password` VARCHAR(255) NOT NULL,
`CreatedDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`PostID`) REFERENCES `Posts`(`PostID`) ON DELETE CASCADE
);
CREATE TABLE `Badges` (
`BadgeID` INTEGER PRIMARY KEY AUTOINCREMENT,
`Name` VARCHAR(100) NOT NULL,
`Description` TEXT NOT NULL,
`Condition` TEXT NOT NULL
);
CREATE TABLE `Group_Badge` (
`GID` INTEGER NOT NULL,
`BadgeID` INTEGER NOT NULL,
`ObtainedDate` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`GID`, `BadgeID`),
FOREIGN KEY (`GID`) REFERENCES `Groups`(`GID`) ON DELETE CASCADE,
FOREIGN KEY (`BadgeID`) REFERENCES `Badges`(`BadgeID`) ON DELETE CASCADE
);
CREATE TABLE `Tags` (
`TagID` INTEGER PRIMARY KEY AUTOINCREMENT,
`Name` VARCHAR(50) NOT NULL
);
CREATE TABLE `Post_Tag` (
`PostID` INTEGER NOT NULL,
`TagID` INTEGER NOT NULL,
PRIMARY KEY (`PostID`, `TagID`),
FOREIGN KEY (`PostID`) REFERENCES `Posts`(`PostID`) ON DELETE CASCADE,
FOREIGN KEY (`TagID`) REFERENCES `Tags`(`TagID`) ON DELETE CASCADE
);
현재 INDEX 를 어느 컬럼에 대해 생성 해야 할 지 고민중인데, 후보군은 다음과 같다
- Groups 의 Gname : 사용자들이 그룹명으로 검색할 수 있다
- Posts 의 Title : 사용자들은 게시글 검색을 할 수 있다
- Post 의 CreateDate : 개시글을 최신순으로 정렬할 때
- Post의 LikeCount : 개시글을 공감 순으로 정렬할 때
위 인덱스중 정말 필수적이라 생각되는 것은 idx_posts_createddate 인데, 기본적으로 게시글을 최신순으로 보여주는것이 일반적이기 때문이다.
Node.js 프로젝트 세팅
이제 프로젝트 기본 세팅을 시작하겠다.
node -v
npm -v
- node.js 와 npm 이 설치되어 있는지 확인 후,
npm init -y
- 초기화를 해주었다.
- 필자는 해당 프로젝트를 typescript로 진행할 것이기에, 필요한 개발 의존성을 설치해야 한다
npm install --save-dev typescript ts-node @types/node @types/express @types/sequelize
- typescript: TypeScript 컴파일러
- ts-node: TypeScript 코드를 직접 실행하기 위한 도구
- @types/*: TypeScript용 타입 정의 파일
- typescript는 개발환경에서만 사용되기 때문에 save-dev 옵션을 꼭 사용해야 한다
npx tsc --init
- tsconfig.json 생성
- tsc → typescript 컴파일러
//package.json 내에 작성
"scripts": {
"build": "tsc",
"start": "node dist/app.js",
"dev": "ts-node src/app.ts",
"format": "prettier --write ."
},
npm install express
npm install --save-dev @types/express
npm install sequelize mysql2
npm install --save-dev @types/sequelize
npm install sequelize-typescript
`Sequelize` 란?
- DB 작업을 도와주는 ORM(Object Relational Mapping) 라이브러리 이다
- 자바스크립트 객체와 관계형 DB를 서로 연결해 주는 도구이다.
Model 생성
/src/config/model 내부에 각 테이블의 모델을 생성하였다.
모델의 정의
- 테이블의 추상화 : 모델은 테이블을 코드 상에서 표현한 것으로 각 테이블의 구조를 클래스나 객체로 나타낸다
- 객체 지향적 접근을 가능하게 만들어, SQL 쿼리를 직접 작성하지 않고도 객체 지향적 방식으로 DB와 상호작용 할 수 있다
- 모델은 DB와의 CRUD 작업이 필요한 모든 곳에서 사용된다.
- 다음은 모델의 예시이다
sequelize.ts
이 코드는 sequelize-typescript 라이브러리 사용시, ORM 인스턴스를 설정하는 코드이다. DB 연결및 관련 설정을 처기화하고, 모델을 DB 테이블과 연결한다.
import { Sequelize } from 'sequelize-typescript';
import path from 'path';
export const sequelize = new Sequelize({
database: 'ZOGAKZIP',
dialect: 'mysql',
username: 'admin',
password: '1234',
storage: ':memory:',
models: [path.resolve(__dirname, '../models')] // 모델 폴더 위치
});
import { Table, Column, Model, DataType, PrimaryKey, AutoIncrement, Default, CreatedAt, UpdatedAt, AllowNull } from 'sequelize-typescript';
@Table({
tableName: 'Group',
timestamps: false
})
export default class Group extends Model<Group>{
@Column({
type: DataType.INTEGER,
primaryKey: true,
autoIncrement: true
})
GID!:number;
@Column({
type: DataType.STRING(100),
allowNull: false,
})
GName!:string;
@Column({
type: DataType.STRING(255)
})
GImage!:string;
@Column({
type: DataType.TEXT
})
GIntro!:string;
@Column({
type: DataType.BOOLEAN,
allowNull: false
})
IsPublic!: boolean;
@Column({
type: DataType.STRING(255),
allowNull: false
})
GPassword!: string;
@CreatedAt
@Column({
type: DataType.DATE,
defaultValue: DataType.NOW
})
CreatedDate!: Date;
@Default(0)
@Column({
type: DataType.INTEGER
})
GLikes!:number;
@Default(0)
@Column({
type: DataType.INTEGER
})
GBadgeCount!: number;
@Default(0)
@Column({
type: DataType.INTEGER
})
PostCount!: number;
}
프로젝트 구조

- src/config : DB 설정과 같은 구성 파일
- src/controllers : 라우트의 로직을 처리하는 컨트롤러
- models : DB 모델을 Sequelize를 통해 정의
- routes : 어플리케이션의 라우트 정의
MySQL 서버 설정
DB 생성
CREATE DATABASE ZOGAKZIP
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
- DEFAULT CHARACTER SET utf8mb4
- 기본 문자 집합을 utf8mb4 로 설정하여, 유니코드 문자를 4바이트 까지 지원하는 문자 집합으로 설정하였다. 이를 이용해, 이모지와 같은 다양한 문자를 저장할 수 있다.
- DEFAULT COLLATE utf8mb4_unicode_ci
- 기본 정렬 규칙을 정의하는 것이다.
- 대소문자를 구분하지 않고 case-insective 문자를 정렬 하고 비교하는 방식
사용자 생성 및 권한 부여
CREATE USER 'admin'@'localhost' IDENTIFIED BY '1234';
GRANT ALL PRIVILEGES ON ZOGAKZIP.* TO 'admin'@'localhost';
FLUSH PRIVILEGES;
- root user 를 사용하는것은 보안상의 이유로 안전하지 못하므로 admin 을 생성해 주었다.
환경변수 설정
root dirctory 에 .env 파일 생성
DB_USERNAME=admin
DB_PASSWORD=1234
DB_NAME=ZOGAKZIP
DB_HOST=localhost
DB_DIALECT=mysql
마이그레이션
config/config.js
//require('dotenv').config();
module.exports = {
development: {
username: 'admin',
password: '1234',
database: 'ZOGAKZIP',
host: '127.0.0.1',
dialect: 'mysql',
},
};
npm install --save-dev sequelize-cli-typescript
// 마이그레이션 파일 작성
npx sequelize-cli db:migrate
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('Group', {
GID: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
GName: {
type: Sequelize.STRING(100),
allowNull: false,
},
GImage: {
type: Sequelize.STRING(255),
allowNull: true,
},
GIntro: {
type: Sequelize.TEXT,
allowNull: true,
},
IsPublic: {
type: Sequelize.BOOLEAN,
allowNull: false,
},
GPassword: {
type: Sequelize.STRING(255),
allowNull: false,
},
CreatedDate: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.fn('NOW'),
},
GLikes: {
type: Sequelize.INTEGER,
allowNull: false,
defaultValue: 0,
},
GBadgeCount: {
type: Sequelize.INTEGER,
allowNull: false,
defaultValue: 0,
},
PostCount: {
type: Sequelize.INTEGER,
allowNull: false,
defaultValue: 0,
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Group');
},
};
Sequelize → Prisma 전환
npm install prisma --save-dev
npm install @prisma/client
// schema.prisma 작성
npx prisma migrate dev --name init
npx generate prisma
prisma 설정
// .env
DATABASE_URL="mysql://admin:1234@localhost:3306/zogakzip"
- schema.prisma
-
// This is your Prisma schema file, // learn more about it in the docs: <https://pris.ly/d/prisma-schema> // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? // Try Prisma Accelerate: <https://pris.ly/cli/accelerate-init> generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } model Badge { BadgeID Int @id @default(autoincrement()) @map("BadgeID") Name String @db.VarChar(100) Description String @db.Text Condition String @db.Text GroupBadges GroupBadge[] @@map("Badges") } model Comment { CommentID Int @id @default(autoincrement()) @map("CommentID") PostID Int Nickname String @db.VarChar(50) Content String @db.Text Password String @db.VarChar(255) CreatedDate DateTime @default(now()) @map("CreatedDate") post Post @relation(fields: [PostID], references: [PostID]) @@map("Comments") } model Group { GID Int @id @default(autoincrement()) @map("GID") GName String @db.VarChar(100) GImage String? @db.VarChar(255) GIntro String? @db.Text IsPublic Boolean GPassword String @db.VarChar(255) CreatedDate DateTime @default(now()) @map("CreatedDate") GLikes Int @default(0) GBadgeCount Int @default(0) PostCount Int @default(0) posts Post[] groupBadges GroupBadge[] @@map("Group") } model GroupBadge { GID Int BadgeID Int ObtainedDate DateTime @default(now()) @map("ObtainedDate") group Group @relation(fields: [GID], references: [GID]) badge Badge @relation(fields: [BadgeID], references: [BadgeID]) @@id([GID, BadgeID]) @@map("Group_Badge") } model Post { PostID Int @id @default(autoincrement()) @map("PostID") GID Int Nickname String @db.VarChar(51) Title String @db.VarChar(100) Image String? @db.VarChar(255) Content String @db.Text Location String? @db.VarChar(100) MemoryMoment DateTime @map("MemoryMoment") IsPublic Boolean PPassword String @db.VarChar(255) CreatedDate DateTime @default(now()) @map("CreatedDate") LikeCount Int @default(0) CommentCount Int @default(0) group Group @relation(fields: [GID], references: [GID]) comments Comment[] postTags PostTag[] @@map("Posts") } model PostTag { PostID Int TagID Int post Post @relation(fields: [PostID], references: [PostID]) tag Tag @relation(fields: [TagID], references: [TagID]) @@id([PostID, TagID]) @@map("Post_Tag") } model Tag { TagID Int @id @default(autoincrement()) @map("TagID") Name String @db.VarChar(50) postTags PostTag[] @@map("Tags") }
Prisma로 변경한 이유
- Type Safety → Prisma 는 정적 타입을 보장하며, DB 스키마에 변경이 생기면 자동으로 업데이트 된다
- 자동 생성 스키마 → prisma 는 스키마 우선 접근 방식을 제공한다. DB 스키마 변경시, 마이그레이션과 타입이 자동으로 업데이트되어 관리가 편하다
- 마이그레이션 시스템 자동화