개발관련/기타

Prisma - 객체 간의 관계 설정, 1:N, 1:1, N:M

localslave 2024. 9. 26. 09:16

참조: https://dodote10.tistory.com/624

 

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

 

Prisma Client API | Prisma Documentation

API reference documentation for Prisma Client.

www.prisma.io

 

 

 

[출처 - Building Production-Ready Apps with Prisma Client for NodeJS, Naimish Verma]

https://www.udemy.com/course/building-production-ready-apps-with-prisma-client-for-nodejs/?couponCode=KEEPLEARNING