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
- spring security
- Volatile
- synchronized
- factory
- Google OAuth
- 일급 객체
- Dependency Injection
- OAuth 2.0
- builder
- lombok
- 일급 컬렉션
- Spring
- java
Archives
- Today
- Total
HJW's IT Blog
프로젝트 : 조각집 [2024-09-20 기록] 본문
주어진 요구사항에 따라 ERD 설계를 시작하였다. 주어진 요구사항은 다음과 같았다
### 그룹
**그룹 등록**
- 그룹명, 대표 이미지, 그룹 소개, 그룹 공개 여부, 비밀번호를 입력하여 그룹을 등록합니다.
**그룹 수정**
- 비밀번호를 입력하여 그룹 등록 시 입력했던 비밀번호와 일치할 경우 그룹 정보 수정이 가능합니다.
**그룹 삭제**
- 비밀번호를 입력하여 그룹 등록 시 입력했던 비밀번호와 일치할 경우 그룹 삭제가 가능합니다.
**그룹 목록 조회**
- 등록된 그룹 목록을 조회할 수 있습니다.
- 각 그룹의 이미지(한 장), 그룹명, 그룹 소개, 그룹 공개 여부, 디데이(생성 후 지난 일수), 획득 배지수, 추억수, 그룹 공감수가 표시됩니다.
- 공개 그룹 목록과 비공개 그룹 목록을 구분하여 조회합니다.
- 최신순, 게시글 많은순, 공감순, 획득 배지순으로 정렬 가능합니다.
- 그룹명으로 검색 가능합니다.
**그룹 상세 조회**
- 그룹 목록 페이지에서 그룹을 클릭할 경우 그룹 상세 조회가 가능합니다.
- 비공개 그룹의 경우 비밀번호를 입력하여 그룹 등록시 입력한 비밀번호와 일치할 경우 조회 가능합니다.
- 각 그룹의 대표 이미지, 그룹명, 그룹 소개, 그룹 공개 여부, 디데이(생성 후 지난 일수), 획득 배지 목록, 추억수, 그룹 공감수가 표시됩니다.
- 공감 보내기 버튼을 클릭할 경우 그룹의 공감수를 높일 수 있으며, 공감은 클릭할 때마다 중복해서 보낼 수 있습니다.
- 해당 그룹의 추억 목록이 표시됩니다.
### 게시글(추억)
**게시글 등록**
- 닉네임, 제목, 이미지(한 장), 본문, 태그, 장소, 추억의 순간, 추억 공개 여부, 비밀번호를 입력하여 추억 등록이 가능합니다.
**게시글 수정**
- 비밀번호를 입력하여 추억 등록 시 입력했던 비밀번호와 일치할 경우 추억 수정이 가능합니다.
**게시글 삭제**
- 비밀번호를 입력하여 추억 등록 시 입력했던 비밀번호와 일치할 경우 추억 삭제가 가능합니다.
**게시글 목록 조회**
- 그룹 상세 조회를 할 경우 그 그룹에 해당되는 추억 목록이 같이 조회됩니다.
- 각 추억의 닉네임, 추억 공개 여부, 제목, 이미지, 태그, 장소, 추억의 순간, 추억 공감수, 댓글수가 표시됩니다.
- 공개 추억 목록과 비공개 추억 목록을 구분하여 조회합니다.
- 최신순, 댓글순, 공감순으로 정렬 가능합니다.
- 제목, 태그로 검색 가능합니다.
**게시글 상세 조회**
- 추억 목록에서 추억을 클릭할 경우 추억 상세 조회가 가능합니다.
- 닉네임, 제목, 이미지(한 장), 본문, 태그, 장소, 추억의 순간, 추억 공개 여부, 추억 공감수, 댓글수가 표시됩니다.
- 공감 보내기 버튼을 클릭할 경우 그룹의 공감수를 높일 수 있으며, 공감은 클릭할 때마다 중복해서 보낼 수 있습니다.
- 해당 추억의 댓글 목록이 조회됩니다.
### 댓글
**댓글 등록**
- 닉네임, 댓글 내용, 비밀번호를 입력하여 댓글 등록이 가능합니다.
**댓글 수정**
- 비밀번호를 입력하여 댓글 등록 시 입력했던 비밀번호와 일치할 경우 댓글 수정이 가능합니다.
**댓글 삭제**
- 비밀번호를 입력하여 댓글 등록 시 입력했던 비밀번호와 일치할 경우 댓글 삭제가 가능합니다.
**댓글 목록 조회**
- 추억을 조회할 경우 그 추억에 해당되는 댓글 목록이 조회됩니다.
- 닉네임, 댓글 생성 날짜, 댓글 내용이 표시됩니다.
### 배지
- 그룹은 일정 조건을 달성하면 자동으로 배지를 획득합니다.
- 배지의 종류
- 7일 연속 추억 등록
- 추억 수 20개 이상 등록
- 그룹 생성 후 1년 달성
- 그룹 공간 1만 개 이상 받기
- 추억 공감 1만 개 이상 받기
- 공감 1만 개 이상의 추억이 하나라도 있으면 획득
이에 따라 다음과 같이 ERD 를 설계하였다.

만들다 보니 GROUP은 SQL 예약어이기 때문에 다른 이름으로 해야겠다는 생각이 들었다.
GROUP 과 BADGE 의 관계는 N:N 관계이기 때문에, 이 두 테이블의 PK를 Composite Key 로 사용하는 GROUP_BADGE 테이블을 생성하였다.
TAG 와 POST 의 관계 또한 N:N 관계이기에 Composite Key 를 생성하였다.
현재 INDEX 를 어느 컬럼에 대해 생성 해야 할 지 고민중인데, 후보군은 다음과 같다
- Groups 의 Gname : 사용자들이 그룹명으로 검색할 수 있다
- Posts 의 Title : 사용자들은 게시글 검색을 할 수 있다
- Post 의 CreateDate : 개시글을 최신순으로 정렬할 때
- Post의 LikeCount : 개시글을 공감 순으로 정렬할 때
위 인덱스중 정말 필수적이라 생각되는 것은 idx_posts_createddate 인데, 기본적으로 게시글을 최신순으로 보여주는것이 일반적이기 때문이다.
이제 프로젝트 기본 세팅을 시작하겠다.
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;
}
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 스키마 변경시, 마이그레이션과 타입이 자동으로 업데이트되어 관리가 편하다
- 마이그레이션 시스템 자동화