TypeORM 이란?
TypeORM이란 NodeJS,Browser등의 환경에서 실행할 수 있는 ORM(Object-relational mapping)이며 JavaScript나 TypeScript로 사용할 수 있다. TypeORM은 Active Record, Data Mapper patterns를 지원하는데 이를 통해 loosely coupled,scalable, maintainable applications을 만들 수 있다는 장점이 있다.
DataSource
Database와의 상호작용을 위해 우선적으로 DataSource를 세팅해야 한다.
TypeORM의 `DataSource`는 database connection을 세팅하고 최초의 데이터베이스 접속 혹은 connection pool을 establish한다.
최초의 connection을 생성하기 위해서는 DataSource instanced의 initialize method를 call 해야한다.
Disconnection은 destory method가 call 되는 경우 이루어진다.
Creating a new DataSource
다음과 같이, DataSource 객체를 생성한 후 initialize 함수를 사용하여 database connection을 생성할 수 있다.
import { DataSource } from "typeorm"
const AppDataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "test",
password: "test",
database: "test",
})
AppDataSource.initialize()
.then(() => {
console.log("Data Source has been initialized!")
})
.catch((err) => {
console.error("Error during Data Source initialization", err)
})
How to use DataSource
DataSource를 세팅한 후, 해당 app을 export하여 다른 모듈에서 사용할 수 있다.
사용 예시는 아래와 같다.
import { AppDataSource } from "./app-data-source"
import { User } from "../entity/User"
export class UserController {
@Get("/users")
getAll() {
return AppDataSource.manager.find(User)
}
}
주로 생성한 DataSource 객체의 `.manager` 혹은 `.getRepository()`를 사용하여 database operation을 execute한다.
DataSourceOptions
DataSource 객체를 생성할 때 사용할 수 있는 configuration이다.
아래에서 각 option이 무엇을 의미하는지 살펴본다.
Common data source options
type
RDBMS type을 명시하는 옵션으로, 사용하는 데이터베이스 엔진을 명시하는 파트이다.
extra
extra 옵션은 underlying driver에게 전달된다. 만약 extar settings를 underlying database driver에게 전달하고 싶다면 설정하면 된다.
entities
data source에서 사용할 Entities나 Entity Schemas를 명시하는 파트이다.
logging
logging을 true로 설정하면 query와 error logging이 enable된다.
또한 특정한 로깅 대상을 설정할 수도 있다.
logger
logger는 logging 목적으로 사용된다.
`advanced-console`, `simple-console`, `file`등이 사용된다.
maxQueryExecutionTime
만약 쿼리의 실행시간이 해당 시간을 넘어가는 경우(ms 단위) logger가 해당 쿼리를 기록한다.
poolSize
최대의 active connections 수를 제한한다.
synchronize
만약 데이터베이스 스키마가 매 실행마다 자동으로 생성되어야 하는 경우 true로 설정한다.
실행마다 자동으로 스키마를 적용하기 때문에, production 환경에서 사용하는 경우 데이터베이스의 정보가 유실될 수 있어 반드시 develope 환경에서만 설정해야한다.
mysql / mariadb data source options
host
Database host를 의미하며, database가 실행되는 ip주소를 기입한다.
port
database가 실행중인 포트 번호를 기입한다.
mysql의 default port는 3306이다.
username
database username을 기입한다.
password
기입한 유저네임에 맞는 password를 기입한다.
database
database의 이름을 기입한다.
charset
connection에 사용될 charset을 설정한다. MySQL의 SQL-level에서는 collation이라고 한다.
만약 SQL-level의 charset이 설정되어 있다면 해당 설정을 default로 사용하게 된다.
connectionTimeout
MySQL서버로의 최초 connection 이후 timeout을 발생시킬 milliseconds를 기입한다.
디폴트값은 10000, 즉 10초이다.
acquireTimeout
MySQL서버로의 최초 connection 이후 timeout을 발생시킬 milliseconds를 기입한다.
디폴트값은 10000, 즉 10초이다.
connectionTimeout과 차이점이 없어 보일 수 있는데, TCP connection 단계에 대한 내용이 acqurieTimeout이고, connectionTimeout은 나머지 연결 과정에 대해 동작한다는 점에서 차이를 가진다.
Entity
Entity는 database table과 매핑되는 class를 의미한다.
다음과 같이 Entity를 정의할 수 있다.
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
firstName: string
@Column()
lastName: string
@Column()
isActive: boolean
}
이렇게 정의한 Entity는 아래와 같은 database table을 생성한다.
각 entity는 반드시 primary column을 가져야 한다.
정의된 entity는 data source option에 등록되어야 한다.
import { DataSource } from "typeorm"
import { User } from "./entity/User"
const myDataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "test",
password: "test",
database: "test",
entities: [User],
})
또는 entity가 들어 있는 directory를 명시하는 방식으로도 로드할 수 있다.
import { DataSource } from "typeorm"
const dataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "test",
password: "test",
database: "test",
entities: ["entity/*.js"],
})
Primary columns
각각의 entity는 반드시 최소 하나의 primary column을 가져야한다.
TypeORM에는 몇 가지 타입의 primary columns가 존재한다.
@PrimaryColumn()
import { Entity, PrimaryColumn } from "typeorm"
@Entity()
export class User {
@PrimaryColumn()
id: number
}
PrimaryGeneratedColumn()
import { Entity, PrimaryGeneratedColumn } from "typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
}
PrimaryGeneratedColumn("uuid")
import { Entity, PrimaryGeneratedColumn } from "typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn("uuid")
id: string
}
unique string인 uuid를 자동으로 생성한다.
Embedded Entites
중복되는 코드를 줄이기 위하여 embedded entites를 활용할 수 있다.
다음과 같이 User, Employee, Student 라는 3개의 entity가 존재한다고 가정해보자.
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: string
@Column()
firstName: string
@Column()
lastName: string
@Column()
isActive: boolean
}
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity()
export class Employee {
@PrimaryGeneratedColumn()
id: string
@Column()
firstName: string
@Column()
lastName: string
@Column()
salary: string
}
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity()
export class Student {
@PrimaryGeneratedColumn()
id: string
@Column()
firstName: string
@Column()
lastName: string
@Column()
faculty: string
}
Entity 구조를 살펴보면, firstName와 lastName이라는 칼럼이 중복되어 나타나는 것을 확인할 수 있다.
import { Column } from "typeorm"
export class Name {
@Column()
first: string
@Column()
last: string
}
위와 같은 class를 적용하고, 해당 클래스를 다음과 같이 적용하는 방식으로 코드 중복을 줄일 수 있다.
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
import { Name } from "./Name"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: string
@Column(() => Name)
name: Name
@Column()
isActive: boolean
}
Entity Inheritance
entity inheritance 패턴은 코드 중복을 줄이기 위한 방법 중 하나이다.
예를 들어, 다음과 같이 Photo, Question, Post entites가 존재한다고 가정하자.
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
description: string
@Column()
size: string
}
@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
description: string
@Column()
answersCount: number
}
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
description: string
@Column()
viewCount: number
}
모든 entities가 id, title, description을 가지는 것을 확인할 수 있다.
이러한 중복을 줄이기 위하여 Content라는 base class를 만들어 추상화를 강화할 수 있다.
export abstract class Content {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
description: string
}
@Entity()
export class Photo extends Content {
@Column()
size: string
}
@Entity()
export class Question extends Content {
@Column()
answersCount: number
}
@Entity()
export class Post extends Content {
@Column()
viewCount: number
}