2024. 1. 31. 20:42ㆍ카테고리 없음
목차
1. 학습 내용
Prisma ORM을 사용하기 전에는 데이터베이스에 SQL(Structured Query Language)을 이용하여 직접 쿼리(Query)를 요청하는 Raw Query를 사용하였다.
Raw Query는 엄청나게 긴 쿼리를 수행하거나 트랜잭션을 직접적으로 관리하는 등 데이터베이스가 지원하는 대다수의 기능을 SQL만으로 간편하게 사용할 수 있는 장점을 가지지만, DB에 수정이 가해졌을 경우 영향을 받는 코드 모두에 바뀐 부분을 반영하는 수정이 필요하게 되며 복잡한 프로젝트일수록 이 수정에 시간이 소요된다는 단점을 가지고 있다.
또, 사용자가 전달한 데이터를 데이터베이스에 직접 요청하게 되므로 사용자가 악의적인 쿼리로 서버에 접속하여 관리자의 계정을 탈취하거나 다른 사용자들의 정보를 탈취하는 SQL 인젝션에 대한 취약점을 가지게 된다.
이를 해결하기 위해, SQL이 아닌 JS만으로 DB를 다루는 ORM이라는 기술이 나타나게 되었다.
2. 내용 정리
ORM(Object Relational Mapping) :
객체(Object)와 데이터베이스의 관계(Relation)을 연결(Mapping) 해주는 도구.
Prisma :
여러가지의 관계형 데이터베이스에 사용 가능한 ORM
ORM을 사용하는 이유 :
- 사용하는 데이터베이스가 언제 바뀔 지 알 수 없다.
(옵션값을 수정하는 것으로, MySQL에서 Oracle, MariaDB로의 변경 가능)
- 데이터베이스에서 사용하는 DB또는 Table속성이 변경되었을 때 빠르게 수정이 가능하다.
(prisma model을 수정하는 것으로 DB나 Table의 속성 수정 가능)
단점 :
- JOIN과 UNION 연산자를 동시에 사용하는 복잡한 쿼리를 작성할 경우, ORM으로 구현하기 위해 SQL 보다는 ORM을 더 깊게 이해해야 하는 상황이 발생.
- 서브 쿼리를 포함하는 복잡한 쿼리를 작성하거나, ORM의 SQL로 변환해주는 시간 조차 아까운 극한의 성능을 요구하는 쿼리가 필요한 상황에서는 Raw Query를 사용하는 것이 더욱 좋을 수 있다.
Prisma 시작
yarn init -y // yarn으로 프로젝트 초기화
yarn add express prisma @prisma/client // 필요한 라이브러리 설치
yarn add -D nodemon
npx prisma init // prisma를 초기화, 사용 구조 생성
- prisma는 Prisma를 터미널에서 사용할 수 있도록 도구를 설치하는 패키지.
- prisma 폴더 안에 schema.prisma 파일 생성
- root 폴더에 .env 파일이 생성
- root 폴더에 .gitignore 파일이 생성
- @prisma/client는 Node.js 에서 Prisma를 사용할 수 있게 한다.
- nodemon은 개발 코드가 변경되었을 때 자동으로 서버 재시작을 해주는 패키지.
1. schema.prisma
Prisma가 사용할 데이터베이스의 설정 정보를 정의하기 위해 사용하는 파일.
1) datasource :
데이터베이스에 대한 정의를 하기 위해 사용.
Prisma가 데이터베이스를 연결할 수 있도록 설정하고, 관리하는 데 필요한 정보를 설정하는 구문.
datasource db {
provider = "mysql" // 데이터베이스 엔진 유형
url = env("DATABASE_URL") // 데이터베이스 연결 정보 .env 파일의 DATABASE_URL 로부터 읽어온다.
}
// .env
DATABASE_URL="엔진유형://<ID>:<PW>@<RDS엔드포인트>:<포트>/<PATH(db명)>?<ARGUMENTS(db설정)>"
2) generator :
prisma 클라이언트를 생성하는 방식을 설정하는 구문. 딱히 수정할 예정은 없다.
3) Prisma model
특정 Table과 Column의 속성값을 입력하여, 데이터베이스와 Express 프로젝트를 연결 (Mapping)시킨다.
Prisma가 사용할 데이터베이스의 테이블 구조를 정의하기 위해 사용된다.
model에 작성된 정보를 바탕으로 Prisma Client를 통해 JavaScript에서 MySQL의 테이블을 조작할 수 있다.
model Products {
productId Int @id @default(autoincrement()) @map("productId")
productName String @unique @map("productName")
price Int @default(1000) @map("price")
info String? @map("info") @db.Text
createdAt DateTime @default(now()) @map("createdAt")
updatedAt DateTime @updatedAt @map("updatedAt")
@@map("Products")
}
데이터 유형 : 각 필드의 데이터를 어떤 형식으로 저장할 것인지 결정.
데이터 유형 뒤에 ?가 붙게 된다면, NULL을 허용하는 컬럼이 된다.
UNIQUE 제약 조건과 AUTO_INCREMENT 제약조건을 사용할 수 있다.
@@map("Products")는 Products 테이블을 MySQL에서도 Products란 이름으로 사용하겠다는 뜻.
→ @@map() 을 작성하지 않으면, 테이블명의 대문자는 전부 소문자로 치환된다.
4) Prisma DB, Table 생성하기(Prisma CLI 명령어)
npx prisma db push
추가적인 prisma CLI 명령어
- [prisma db push]
- schema.prisma 파일에 정의된 설정값을 실제 데이터베이스에 반영(push)
- 내부적으로 prisma generate가 실행
- 데이터베이스 구조를 변경하거나 새로운 테이블을 생성할 수 있다.
- [prisma init]
- Prisma를 사용하기 위한 초기 설정을 생성
- 이 명령어를 실행하면 schema.prisma 파일과 같은 필요한 설정 파일들이 생성
- [prisma generate]
- Prisma Client를 생성하거나 업데이트
- 대표적으로, schema.prisma 파일에 변경 사항이 생겼거나, 데이터베이스 구조가 변경되었을 때, 이 명령어를 사용해 Prisma Client를 최신 상태로 유지할 수 있다.
- [prisma db pull]
- 현재 연결된 데이터베이스의 구조를 prisma.schema 파일로 가져온다.(pull)
- 데이터베이스에서 구조 변경이 발생했을 때, 이 명령어로 Prisma Schema를 최신 상태로 유지할 수 있다.
- 이후 prisma generate 명령어를 사용해 변경 사항을 Prisma Client에 반영할 수 있다.
3. 예제
4) prisma method
// 게시글 생성
router.post('/posts', async (req, res, next) => {
const { title, content, password } = req.body;
const post = await prisma.posts.create({
data: {
title,
content,
password,
},
});
return res.status(201).json({ data: post });
});
create 메서드를 사용해 post를 생성
/** 게시글 전체 조회 API **/
router.get('/posts', async (req, res, next) => {
const posts = await prisma.posts.findMany({
select: {
postId: true,
title: true,
createdAt: true,
updatedAt: true,
},
});
return res.status(200).json({ data: posts });
});
findMany 메서드로 전체 조회
router.get('/posts/:postId', async (req, res, next) => {
const { postId } = req.params;
const post = await prisma.posts.findFirst({
where: { postId: +postId },
select: {
postId: true,
title: true,
content: true,
createdAt: true,
updatedAt: true,
},
});
return res.status(200).json({ data: post });
});
findFirst 메서드로 특정 post 하나 조회
+ select, where를 사용
/** 게시글 수정 API **/
router.put('/posts/:postId', async (req, res, next) => {
const { postId } = req.params;
const { title, content, password } = req.body;
const post = await prisma.posts.findUnique({
where: { postId: +postId },
});
if (!post)
return res.status(404).json({ message: '게시글이 존재하지 않습니다.' });
else if (post.password !== password)
return res.status(401).json({ message: '비밀번호가 일치하지 않습니다.' });
await prisma.posts.update({
data: { title, content },
where: {
postId: +postId,
password,
},
});
return res.status(200).json({ data: '게시글이 수정되었습니다.' });
});
findUnique와 update 메서드를 통한 특정 게시물 수정 구현
+ postId와 password 검증후 수정
/** 게시글 삭제 API **/
router.delete('/posts/:postId', async (req, res, next) => {
const { postId } = req.params;
const { password } = req.body;
const post = await prisma.posts.findFirst({ where: { postId: +postId } });
if (!post)
return res.status(404).json({ message: '게시글이 존재하지 않습니다.' });
else if (post.password !== password)
return res.status(401).json({ message: '비밀번호가 일치하지 않습니다.' });
await prisma.posts.delete({ where: { postId: +postId } });
return res.status(200).json({ data: '게시글이 삭제되었습니다.' });
});
findFirst와 delete 메서드를 통해 특정 게시물의 삭제를 구현.
+ password 검증 후 삭제
4. 생각 정리
원래는 오늘 TIL부터 개인과제에 대한 TIL을 작성하고 싶었지만,
개발 일정이 늦어져(대부분 나의 무능력함과 멍청함에 의해.) 급하게 Prisma TIL로 대체되다.
나는 능이버섯이다.