# 개요
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 값을 알아낼 수 있다.
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 |
이를 테이블 그림으로 표현하면 아래 그림과 같다고 볼 수 있다.
# 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 관계가 되며 이는 아래 그림처럼 표현된다.
# 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 라는 테이블이 생성되었음을 볼 수 있다.
* 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 |
* 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
'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 |