JS

schema.prisma 정리

728x90

# 개요

schema.prisma 파일엔 크게 3가지 부분으로 나뉜다

 

1. Data Source

: Data Source 부분은 어떤 DB 와 연결할것인지 설정하는 부분이다.

prisma 에서 지원하는 DB 는 PostgreSQL, MySQL, SQLite 가 있으며

이런식으로 작성할 수 있다

(PostgreSQL 기준 예시)

datasource db {
  provider = "postgresql"
  url      = "postgresql://johndoe:mypassword@localhost:5432/mydb?schema=public"
}
cs

 

또는 prisma 폴더내에 .env 파일을 생성하여

다음과 같이 url 을 숨길수 있다. 

또한 다수의 DB 와 연결이 가능하다.

datasource db {
  provider = ["postgresql""mysql"]
  url      = env("DATABASE_URL")
}
cs

 

 

2. Generator

: Generator 부분은 prisma client 명령어 사용시에 생성될 내용(prisma binary file)을 정의하는 부분이다.

generator {
    provider      = "prisma-clinet-js"
    binaryTargets = ["windows""darwin"]
}
cs

 

이런 식으로 작성하게 되면,

Prisma Client 가 사용하게 될 binary file 을 윈도우 운영체제와 MacOS 에 맞춰서 생성하겠다

라는 의미가 된다.

아래의 사이트에 가면 OS 별 binaryTarget 값을 알아낼 수 있다.

www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/prisma-schema-reference#binarytargets-options

 

 

 

3. Data Model Definitions

: DB 에 대한 스키마를 정의하는 부분으로, model, attributes, enum 부분으로 나뉜다.

model 은 관계형 데이터베이스에서 테이블에 해당하고, 여러개의 fields 로 구성되어 있다.

attributes 는 field 와 model 에 대한 함수이며 (아래 예제에서 @id @unique 등 을 의미함),

enum 은 c/c++ 언어에서의 그 enum 과 유사하다.

 

 

아래의 예제는 블로그 작성시 예제 모델을 기록한 코드이다.

model User {
  id      Int      @id @default(autoincrement())
  email   String   @unique
  name    String?
  role    Role     @default(USER)
  posts   Post[]
  profile Profile?
}
model Profile {
  id      Int     @id @default(autoincrement())
  bio     String
  user    User    @relation(fields: [userId], references: [id])
  userId  Int
}
model Post {
  id         Int         @id @default(autoincrement())
  createdAt  DateTime    @default(now())
  title      String
  published  Boolean     @default(false)
  author     User        @relation(fields: [authorId], references: [id])
  authorId   Int
  categories Category[]  @relation(references: [id])
}
model Category {
  id    Int     @id @default(autoincrement())
  name  String
  posts Post[]  @relation(references: [id])
}
enum Role {
  USER
  ADMIN
}
cs

 

 

이를 테이블 그림으로 표현하면 아래 그림과 같다고 볼 수 있다.

 

 

출처 : https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/data-model/

 

 

# Data Model

위와 같이 데이터 모델을 설정할 때, 개인적으로 가장 헷갈린다고 생각되는 부분은

'모델(테이블) 간의 관계 정의' 부분이라 생각한다.

그래서 이 부분만 좀 집중적으로 파보려한다.

 

먼저 모델 간의 관계를 설정하기 위해서는 기본적으로 

@relation 이라는 attribute 를 정의해야한다.

 

아래 예제를 보면

model User {
  id      Int            @id @default(autoincrement())
  posts   Post[]
}
model Post {
  id         Int         @id @default(autoincrement())
  author     User        @relation(fields: [authorId], references: [id])
  authorId   Int         
}
 
cs

 

author 와 posts field 는 두 모델간 관계를 정의한 field 값(column 값)으로 DB 에는 드러나지 않는,

prisma client 에서만 다루는 필드값이다.

그리고 scalar field 값인 authorId 는 DB 에 직접적으로 들어가는 값으로 두 모델이 서로 참조하기 위한 외래키 (Foreign Key) 값에 해당한다.

 

두 모델이 관계가 있음을 나타내기 위해서는 위 처럼 반드시 @relation 을 정의해야 한다.

 

@relation 의 fields 부분은 외래키를 써주고 (authorId),

references 부분은 기본키 (Primary Key) 값을 써준다 (id)

(* 이때 id 값은 Post 의 id 가 아니라, User 의 id 이다)

 

이 두 모델은 "한 명의 유저가 여러개의 포스팅을 가진다" 라는 의미가 되어

1:N 관계가 되며 이는 아래 그림처럼 표현된다.

출처 : https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/relations#overview

 

 

# Relations

Prisma 에서는 아래와 같은 3가지 타입의 "관계" 가 존재한다 (이건 근데 다른 ORM 도 똑같다)

 

1:1 관계, 1:N 관계, M:N 관계

 

1. 1:1 관계

공식문서에 나온 모델을 예로 들면

model User {
  id        Int       @id @default(autoincrement())
  profile   Profile?
}
model Profile {
  id      Int    @id @default(autoincrement())
  user    User   @relation(fields: [userId], references: [id])
  userId  Int    
}
cs

 

 

이 스키마의 의미는

"한 유저가 한개 또는 0개의 프로필을 가질 수 있고, 프로필은 반드시 한명의 유저를 포함해야한다" 가 된다.

User 의 profile은 ? 가 붙었기 때문에 옵션 값이 되고,

Profile 의 user 는 ? 가 안 붙었기 때문에 필수 값이 되었다.

그리고 외래키는 userId, 기본키는 id 가 되었다.

 

이를 SQL 로 변환하면 아래 처럼 된다

CREATE TABLE "User" (
    id SERIAL PRIMARY KEY
);
CREATE TABLE "Profile" (
    id SERIAL PRIMARY KEY,
    "userId" INTEGER NOT NULL UNIQUE,
    FOREIGN KEY ("userId"REFERENCES "User"(id)
);
cs

 

 

키를 하나씩 매칭하는 1:1 관계 말고도

Composite key 를 이용해서, 1:1 관계를 형성하기도 한다.

공식 문서의 예를 들면, 

model User {
  firstName String
  lastName  String
  profile   Profile?
  @@id([firstName, lastName])
}
model Profile {
  id               Int      @id @default(autoincrement())
  user             User     @relation(fields: [userFirstName, userLastName], references: [firstName, lastName])
  userFirstName  String     
  userLastName   String
  bio            String
}
cs

 

 

User 모델의 기본 키 값이 firstName, lastName 으로 두개를 사용했고,

Profile 모델의 외래키도 두개로 맞췄다

 

이는 SQL 로 변환시에 다음과 같이 나타난다

CREATE TABLE "User" (
    firstName TEXT,
    lastName TEXT,
    PRIMARY KEY ("firstName","lastName")
);
CREATE TABLE "Profile" (
    id SERIAL PRIMARY KEY,
    "userFirstName" TEXT NOT NULL UNIQUE,
    "userLastName" TEXT NOT NULL UNIQUE,
    FOREIGN KEY ("userFirstName""userLastName"REFERENCES "User"("firstName""lastName"),
    bio TEXT
);
cs

 

 

그리고 이 관계 정의를 기반으로한 resolver 코드의 예시는 

다음과 같이 작성되야 한다

const user = await prisma.user.create({
  data: {
    firstName: "gil dong",
    lastName: "hong",
    profile: {
      create: {
        bio: "Hello World"
      }
    }
  }
})
cs

 

 

user 를 생성할때, 관계를 맺고 있는 profile 값도 생성하기 위해서 nested 형태로 (중첩된 JS 객체 형태) 정의해 줘야한다.

 

 

 

2. 1:N 관계

관계형 데이터베이스에서 1:1 관계와 1:N 관계의 주된 차이점은 

외래키에 UNIQUE 제약조건이 붙느냐 안붙느냐의 차이이다.

 

공식문서의 예제를 보면,

model User {
  id        Int      @id @default(autoincrement())
  posts     Post[]
}
model Post {
  id        Int   @id @default(autoincrement())
  author    User  @relation(fields: [authorId], references: [id])
  authorId  Int
}
cs

 

한명의 유저가 0개 또는 그이상의 포스팅을 가지면서

포스팅은 반드시 한명의 유저(작가) 를 갖고있어야만 하는 스키마를 prisma 로 정의했다

 

이는 SQL 로 변환될때 아래와 같이 변환된다.

CREATE TABLE "User" (
    id SERIAL PRIMARY KEY
);
CREATE TABLE "Post" (
    id SERIAL PRIMARY KEY,
    "authorId" integer NOT NULL,
    FOREIGN KEY ("authorId"REFERENCES "User"(id)
);
cs

 

앞선 1:1 관계와는 다르게 

SQL 변환문에서 외래키에 해당하는 authorId 가 UNIQUE 제약조건이 없는것을 알 수 있다.

이로 인해서 한 명의 유저가 다수의 포스팅을 가질 수 있는 관계가 형성된다.

 

1:N 관계 또한 1:1 관계 처럼 

composite key 를 이용해서 다수의 PK, FK 를 갖는 모델을 선언할 수 있다.

(이 부분은 profile 을 post[] 로 바꾸면 되기에 매우 간략해서 생략)

 

 

* 단 1:N 관계를 prisma 에 정의할때 주의해야될 점은

배열로 선언한 부분에 옵션 값인 ? 를 붙여선 안된다

model User {
  id        Int      @id @default(autoincrement())
  posts     Post[]?
}
model Post {
  id        Int   @id @default(autoincrement())
  author    User?  @relation(fields: [authorId], references: [id])
  authorId  Int?
}
cs

Post[]? 는 불가능

 

 

 

3. M:N 관계

관계형 데이터베이스에선 테이블간의 관계를 형성할때, JOIN 같은 연산을 사용해서 

테이블간 관계 형성을 시도한다.

prisma 에서는 모델 간 M:N 관계 형성에 있어서 implicit 방법과 explicit 방법을 사용해서 정의한다.

 

 

-1) implicit

model Post {
  id         Int        @id @default(autoincrement())
  categories Category[]
}
model Category {
  id    Int    @id @default(autoincrement())
  posts Post[]
}
cs

이 스키마는 한 포스팅이 여러개의 카테고리를 가질 수 있으면서 

동시에, 한 카테고리가 여러개의 포스팅을 가질 수 있는 M:N 관계를 나타낸다.

 

prisma 는 이처럼 M:N 관계 작성시에 @relation attribute 를 작성하지 않고,

두 테이블이 암묵적으로 이런 관계를 형성하고 있음을 나타낼 수 있다.

 

 

이 스키마가 DB 에 저장될때는 

다음과 같은 SQL 로 변경되어 적용된다.

CREATE TABLE "Category" (
    id SERIAL PRIMARY KEY
);
CREATE TABLE "Post" (
    id SERIAL PRIMARY KEY
);
 
CREATE TABLE "_CategoryToPost" (
    "A" integer NOT NULL REFERENCES "Category"(id),
    "B" integer NOT NULL REFERENCES "Post"(id)
);
CREATE UNIQUE INDEX "_CategoryToPost_AB_unique" ON "_CategoryToPost"("A" int4_ops,"B" int4_ops);
CREATE INDEX "_CategoryToPost_B_index" ON "_CategoryToPost"("B" int4_ops);
cs

 

prisma 에서 작성하지 않았던 _CategoryToPost 라는 테이블이 생성되었음을 볼 수 있다.

 

출처 : https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/relations#one-to-one-relations

 

 

* implicit 방법으로 테이블 관계를 형성하는 방법 중 

직접 @relation 에 이름을 줘서 자동으로 생성되는 테이블의 이름을 바꿀수도 있다.

예를 들면,

model Post {
  id         Int         @id @default(autoincrement())
  categories Category[]  @relation("MyRelationTable")
}
model Category {
  id    Int     @id @default(autoincrement())
  posts Post[]  @relation("MyRelationTable")
}
cs

 

이렇게 @relation 에 MyRelationTable 이라 작성하면,

앞의 예제에서는 _CategoryToPost 라 작성되었는데,

이 예제에서는 _MyRelationTable 이라는 이름으로 테이블이 생성된다.

 

 

* 만약 prisma migration 을 사용하지 않고 introspection 을 사용한다면,

사용하는 DB에 테이블 이름 앞에 언더바를 붙여야 prisma 에서 위와 같은 implicit M:N relation 을 만들 수 있다.

언더바를 안붙이고 CategoryToPost 로 테이블을 생성해서 introspection 을 했다면 

Post 와 Category 가 연결된 테이블이 아닌 별도의 관련없는 테이블이 생성된다.

 

 

 

-2) explicit

 

M:N 관계를 표현하는 또다른 방법으로는

직접 prisma 에 두 모델간 연결 관계를 표현하는 또 다른 모델을 직접 선언하면 된다.

model Post {
  id         Int            @id @default(autoincrement())
  title      String
  categories CategoriesOnPosts[]
}
model Category {
  id    Int                 @id @default(autoincrement())
  name  String
  posts CategoriesOnPosts[]
}
model CategoriesOnPosts {
  post        Post     @relation(fields: [postId], references: [id])
  postId      Int       
  category    Category @relation(fields: [categoryId], references: [id])
  categoryId  Int      
  createdAt   DateTime @default(now())
  @@id([postId, categoryId])
}
cs

 

이 스키마 값은 SQL 로 변환시 다음과 같이 나타난다

CREATE TABLE "Category" (
    id SERIAL PRIMARY KEY
);
CREATE TABLE "Post" (
    id SERIAL PRIMARY KEY
);
 
CREATE TABLE "CategoryToPost" (
    "categoryId" integer NOT NULL,
    "postId" integer NOT NULL,
    "createdAt" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY ("categoryId")  REFERENCES "Category"(id),
    FOREIGN KEY ("postId"REFERENCES "Post"(id)
);
CREATE UNIQUE INDEX "CategoryToPost_category_post_unique" ON "CategoryToPost"("category" int4_ops,"post" int4_ops);
cs

 

 

 

출처 : https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/relations#one-to-one-relations

 

 

 

* Self Relations

앞서 언급한 내용들은 서로 다른 테이블끼리 참조하는 경우였다.

그러나, 테이블이 자기 자신을 참조하여 관계를 형성하는 경우가 있다.

예를들면, 유저 테이블 안에 follower 와 followee 가 있을 때, 각각의 주체는 결국 유저다.

유저 테이블이 유저 테이블을 참조하여 관계를 형성하는 형태인것.

 

이에 대해서 하나씩 알아보면

 

-1) 1:1 self relations

model User {
  id             Int     @default(autoincrement()) @id
  name           String?
  successorId Int?
  successor   User?   @relation("BlogOwnerHistory", fields: [successorId], references: [id])
  predecessor User?   @relation("BlogOwnerHistory")
}
cs

 

이 모델의 경우 "BlogOwnerHistory" 라는 relation 이름으로 자기 자신과의 참조를 수행하고 있다.

전임 블로그 주인자인 predecessor, 후임 블로그 주인자인 successor 필드를 통해서 1:1 관계를 만들어내고 있다.

 

이런식의 관계를 정의할때 중요한 것은

반드시 @relation attribute 를 만들어주고 이름을 지정해줘야 한다는것

그리고, 한쪽 필드가 외래키를 가지고 있어야 한다는 점이다 (여기선 succesorId)

 

successor 에 외래키를 주든, predecessor 에 외래키를 주든 어느쪽이든 상관없다

한쪽이 반드시 가져야 되는 조건만 있다

 

이를 SQL 로 변환하면 다음과 같이 나타난다

CREATE TABLE "User" (
    id SERIAL PRIMARY KEY,
    "name" TEXT,
    "successorId" INTEGER
);
ALTER TABLE "User" ADD CONSTRAINT fk_successor_user FOREIGN KEY ("successorId"REFERENCES "User" (id);
ALTER TABLE "User" ADD CONSTRAINT successor_unique UNIQUE ("successorId");
cs

 

 

-2) 1:N self relations

model User {
  id       Int      @id @default(autoincrement())
  name     String?
  teacherId Int?
  teacher  User?     @relation("TeacherStudents", fields: [teacherId], references: [id])
  students User[]   @relation("TeacherStudents")
}
cs

 

이 경우는, 학생이 0명 또는 그 이상의 여러명이 될 수 있고,

선생이 0명 또는 1명이 될 수 있는 그런 모델을 의미한다.

N 에 해당하는 관계는 students 가 가지고 있으며 이를 SQL 로 나타내면 다음과 같다

CREATE TABLE "User" (
    id SERIAL PRIMARY KEY,
"name" TEXT,
    "teacherId" INTEGER
);
ALTER TABLE "User" ADD CONSTRAINT fk_teacherid_user FOREIGN KEY ("teacherId"REFERENCES "User" (id);
cs

 

 

-3) M:N self relations

model User {
  id          Int      @id @default(autoincrement())
  name        String?
  followedBy  User[]   @relation("UserFollows", references: [id])
  following   User[]   @relation("UserFollows", references: [id])
}
cs

이 경우는 implicit M:N self relation 으로 

 

SQL 로 변환시에 다음과 같이 나타난다

CREATE TABLE "User" (
    id integer DEFAULT nextval('"User_id_seq"'::regclass) PRIMARY KEY,
    name text
);
CREATE TABLE "_UserFollows" (
    "A" integer NOT NULL REFERENCES "User"(id) ON DELETE CASCADE ON UPDATE CASCADE,
    "B" integer NOT NULL REFERENCES "User"(id) ON DELETE CASCADE ON UPDATE CASCADE
);
cs

 

 

 

 

 

- Reference

1. www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/relation-queries

2. www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/relations#many-to-many-relations

728x90

'JS' 카테고리의 다른 글

# 33 가지 Javascript 필수 개념 - 7. Expression vs Statement  (0) 2020.09.27
# 33 가지 Javascript 필수 개념 - 6. Scope  (0) 2020.09.25
prisma 2 query 문제  (0) 2020.09.09
GraphQL Server 구성  (1) 2020.09.07
webpack (1)  (0) 2020.09.06