ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Prisma - 객체 간의 관계 설정, 1:N, 1:1, N:M
    FrameWork/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

     

    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

     

     

    댓글

Designed by Tistory.