-
Prisma - 객체 간의 관계 설정, 1:N, 1:1, N:MFrameWork/ORM 2024. 4. 16. 10:33
Prisma 객체간의 관계 설정
이번 게시물에서는 DB에서 자주 사용되고 블로그를 통해 많이 소개되었던 1대다(1:N), 1대1(1:1), 다대다(N:M) 관계들을 Prisma는 어떤 인터페이스를 사용해서 이를 설정할 수 있도록 하는지 알아볼것이다.
1 : N (One to Many Relations)
이 관계의 경우는 테이블 A의 하나의 레코드는 테이블 B의 여러 레코드와 관련되어 있다. 다만 테이블 B는 하나의 A의 레코드하고만 연결된다. 위 그림에서 와 같이 사용자는 여러번 주문을 할수 있다. 하지만 주문은 주문자로써의 사용자정보를 하나만 가진다.
1 : 1 (One to One Relations)
한명의 사용자는 하나의 프로필만 가지게 되고 프로필 입장에서도 하나의 사용자만 가지게 된다.
N : M (Many to Many Relations)
테이블 A의 여러 레코드는 테이블B의 여러 레코드와 관련있을수 있으며 테이블B도 테이블A의 여러 레토드와 관련있을 수 있다. 위 그림의 예시에서 처럼 한권의 책을 열러 작가가 쓸수도 있고 작가 한명이 여러개의 책을 쓸 수 도 있다.
Prisma Relationship
1:1 관계 맺기
schema.prisma file
model User{ id Int @id @default(autoincrement()) email String @unique profile Profile? } model Profile{ id Int @id @default(autoincrement()) name String addr String phone String userId Int @unique // foreign key user User @relation(fields: [userId],references: [id]) }
모델 Profile이 userId(모델 User에 id)를 알게 하려면 위와 같이 추가적인 설정이 필요하다. 그래서 model Profile 제일 마지막에 user속성을 추가하고 Type은 User로 한다. 위 @relation괄호의 fields는 모델 Profile이 참조하고있는 외래키로 userId 속성을 가르키고, references는 모델 User 입장에서 참조를 당한 키로 User의 primary키인 id를 가르킨다.
위 방식은 일대다(1:N) 방식에서도 사용하는 방식이지만 현재는 일대일(1:1)방식에 사용되었으므로 모델 Profile의 userId값을 @unique 표시한다.
prisma가 모델 Profile의 userId속성이 모델 User id에서 참조되었다는 것을 아는 것은 모델 Profile의 user속성을 통해 이루어진다. use칼럼은 실제로 테이블에 칼럼으로 생성되지 않는다. prisma에서 내부 참조용으로만 사용된다.
1:1 관계 맺기 예제 코드
export const getUser = async (req,res) => { const users = await prisma.user.findMany({ include:{ profile :true } }); res.json(users); } export const getUserId = async (req, res) => { const user = await prisma.user.findUnique({ where:{ id:req.params.id }, include:{ profile:true } }); res.json(user); } export const createUser = async (req,res) => { const user = await prisma.user.create({ data : req.body }); res.json(user); } export const createProfile = async (req, res)=> { // 1. 사용자 정보를 가져오고 const user = await prisma.user.findUniqueOrThrow({ where:{ id: +req.params.id }, }); // 2. 데이터를 구성하고 const data = { ...req.body, userId: user.id } // 3. 프로파일을 만든다. const profile = await prisma.profile.create({data}); res.json(profile); }
1: N 관계 맺기
schema.prisma file
enum ArticleState{ DRAFT PUBLISHED } // user may have one or multiple article and article must be associated to a user // user은 하나 혹은 여러개의 article을 가질수 있으며 article은 user와 연결되야 한다. model Article{ id Int @id @default(autoincrement()) title String content String state ArticleState createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userId Int user User @relation(fields: [userId],references: [id]) @@map("articles") } // users and profiles // user may have a single profile and profile must be linked to a user // user는 하나의 profile을 가질수 있고 그렇다면 그것은 user 레코드와 연결된다. model User{ id Int @id @default(autoincrement()) email String @unique profile Profile? articles Article[] }
1:N의 경우 여러개의 테이블의 레코드를 가지게되는 테이블 쪽이 즉 user와 article의 관계에서 한명의 user는 여러개의 articled을 가질수 있지만 article은 하나의 user밖에 갖지 못하므로 모델 User에서의 article은 Article배열 형태의 type을가지게 된다. 그리고 모델 Article에서 userId도 @unique값을 가지지 않게 된다.
User와 Article 데이터 가져오기
조건
- 모든 user가 가진 각각의 article과 그 갯수 반환받기
- user정보과 PUBLISHED되 article가져오기
export const getArticles = async (req, res) => { const where = { state: req.query.state? req.query.state: ('DRAFT'||'PUBLISHED') }; const articleAndCount = await prisma.user.findMany({ include:{ articles:{ where }, _count:{ select:{ articles:{ where } } } } }); res.json(articleAndCount); }
특정 User의 조건에 만족하는 Article삭제하기
export const deleteUnPublishedArticle = async (req,res) => { const user = await prisma.user.findUnique({ where:{ id: +req.params.id }}); if(!user){ res.status(404).send("해당하는 사용자가 존재하지 않습니다. ") } const deletedCount = await prisma.article.deleteMany({ where :{ userId: user.id, state : req.query.state ? req.query.state : 'DRAFT' || 'PUBLISHED' } }); res.json(deletedCount); }
N:M 관계 맺기
schema.prisma file
model User{ id Int @id @default(autoincrement()) email String @unique name String profile Profile? articles Article[] } model Product{ id Int @id @default(autoincrement()) title String price Float } model Tag { id Int @id @default(autoincrement()) name String }
product는 여러개의 tag를 가진다. tag역시 하나rk 여러개의 제품과 관련이 있을 수 있다.
Prisma N:M관계의 두가지 유형
암시적 방법에서의 다대다 관계는 피벗테이블이나 조인 테이블이 존재하지 않는다. 때문에 Prima에 의해 백그라운드에서 피벗테이블의 역할을 하는 테이블이 생성된다.
암시적 다대다 관계
schema.prisma file
model Product{ id Int @id @default(autoincrement()) title String price Float tag Tag[] } model Tag { id Int @id @default(autoincrement()) name String product Product[] }
특정 모델이 다른 모델의 primary key를 갖지 않고 서로가 서로의 데이터를 배열형태로 가질수 있다고 schema.prisma 파일을 수정한후 prisma migrate dev 명령어를 터미널에서 실행한다.
터미널에서 명령어를 실행한후 데이터베이스를 확인하면 _ProductToTag라는 테이블이 생성된 것을 확인할 수 있다. 이것이 Prisma가 암시적으로 N:M 관계를 지원하는 방식이다. 이방식을 사용하면 N:M 관계를 이어주는 테이블을 직접 생성하고 관리할 필요 없이 이 작업을 Prisma가 대신 수행해준다.
Product 생성코드
export const createProduct = async (req,res) => { const products = await prisma.product.create({ data:{ ...req.body, tag :{ create:req.body.tag } } }) res.json(products); }
POST - /product, request body
{ "title":"test Product with tag", "price":50, "tag":[{"name":"cool"},{"name":"cute"}] }
GET - /product
{ "id": 6, "title": "test Product with tag", "price": 50, "tag": [ { "id": 7, "name": "cool" }, { "id": 8, "name": "cute" } ] }
명시적 다대다 관계
schema.prisma file
model User{ id Int @id @default(autoincrement()) email String @unique name String profile Profile? articles Article[] carts Cart[] } model Product{ id Int @id @default(autoincrement()) title String price Float tag Tag[] carts Cart[] } model Cart{ id Int @id @default(autoincrement()) userId Int productId Int user User @relation(fields: [userId], references:[id]) product Product @relation(fields: [productId], references: [id]) quantity Int }
이번에는 명시적인 다대다 관계를 prisma가 어떻게 관리하는지를 알아보기 위해 모델 Cart를 만든다. Cart는 User에게 종속되어 있고 또 여러개의 제품과 관계를 가진다. 즉 모델 Cart는 User와 Product의 서로의 N:M 관계를 관리하는 조인테이블이되고 User와 Product의 primary key만을 가지는 단순한 테이블 기능만을 가지는 것이아니라 User가 몇개의 Product를 주문할 것인지에대한 quantity 즉 수량에 대한 정보도 가지고 있어야 하기 때문에 prisma에 의해 암시적 조인테이블을 생성하게 하는것이아니라 Cart라는 테이블로 생성되어 명시적으로 관리된다.
Cart 생성 코드
export const createCart = async (req,res) =>{ // 1. 사용자 정보 확인 const user = await prisma.user.findFirstOrThrow( { where:{ id : +req.params.userId } }) // 2. 제품 정보 얻기 const product = await prisma.product.findFirstOrThrow({ where:{ id: req.body.product } }) // 3. 데이터 구조화 하고 cart 데이터 만들기 const cart = await prisma.cart.create({ data:{ quantity: req.body.quantity, userId: user.id, productId: product.id } }); res.json(cart); }
GET - /users/:userId/carts
[ { "id": 1, "userId": 4, "productId": 6, "quantity": 2, "product": { "id": 6, "title": "test Product with tag", "price": 50 } }, { "id": 2, "userId": 4, "productId": 4, "quantity": 3, "product": { "id": 4, "title": "product4", "price": 50 } } ]
Prisma - Doc/Reference/Prisma Client API
https://www.prisma.io/docs/orm/reference/prisma-client-reference
[출처 - Building Production-Ready Apps with Prisma Client for NodeJS, Naimish Verma]
'FrameWork > ORM' 카테고리의 다른 글
Sequelize - 실전활용(Update,Delete,Relation설정) (0) 2024.04.24 Prisma - Transactions과 다양한 부가기능 (0) 2024.04.17 Prisma - 🐠시작하기 환경설정, 쿼리 로깅, CRUD (0) 2024.04.12 Sequelize - 시퀄라이즈 시작 (설정,모델만들기,관계설정) (0) 2024.03.21 ORM - 영속성 관리(플러시, 준영속) (1) 2024.02.29