TypeORM이 무엇이고, 왜 필요한지, 그리고 어떻게 사용할 수 있는지에 대해 알아보자.
ORM???
ORM(Object Relational Mapping)은 말 그대로 Object와 관계형 데이터베이스의 데이터를 맵핑해주는 것을 의미한다.
일반적으로 OOP는 Object를 사용하고, RDB는 table을 사용한다.
이렇게 OOP의 모델과 RDB의 모델간에 불일치가 존재하고, ORM은 객체간의 관계를 바탕으로 SQL을 생성하여 불일치를 해결한다.
즉, 객체를 통해 간접적으로 데이터베이스의 데이터를 다룰 수 있게 된다.
Why People use ORM?
ORM을 사용함으로써 얻을 수 있는 이득은 아래와 같다.
- 객체지향적인 코드로 인해 더 직관적이고 비지니스 로직에 집중 할 수 있게 된다.
- SQL Query가 아닌 직관적인 메소드를 이용하여 좀 더 OOP적으로 프로그래밍이 가능하다.
- 선언문, 할당, 종료와 같은 코드를 거의 모두 없앨 수 있다.
- 각 객체에 대한 코드를 각각 작성하므로 가독성을 높일 수 있다.
- 재사용 및 유지보수성의 향상
- ORM은 독립적으로 작성 되어있고, 이렇게 작성된 각 객체들은 재활용 가능하다.
- 매핑 정보가 정확하여, ERD에 대한 의존성을 낮출 수 있다.
- DBMS에 대한 종속성 감소
- 객체간의 관계를 통해 자동으로 SQL을 생성하기에, DB에 종속적이지 않을 수 있다.
TypeORM???
typeORM은 이러한 ORM의 종류 중 하나로, NodeJS, Browser 등 다양한 환경에서 구동 가능하며 TS와 JS의 최신 문법을 지원한다.
특히, NestJS에서는 TypeORM의 사용을 적극 권장하고 있다.
How to use typeORM (in NestJS)???
구체적으로 어떻게 typeORM을 이용하여 코드를 작성 할 수 있는지, 그리고 이용하기 위해 필수적인 개념들에 대해 알아보자.
Entity
ORM을 한번이라도 써 본 사람이라면 한번 쯤은 들어봤을 Entity이다.
Entity는 database의 table과 맵핑되는 객체를 의미한다.
Entity는 @Entity 데코레이터를 이용하여, 생성 할 수 있다.
다음 코드는 해당 데코레이터를 통해 어떻게 Entity를 생성하는지 보여주는 실제 코드이다.
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
firstName: string
@Column()
lastName: string
@Column()
isActive: boolean
}
FYI, 이름에서 충분히 유추할 수 있겠지만, @PrimaryGeneratedColumn 데코레이터는 PK 칼럼을 생성, @Column은 일반적인 칼럼을 생성하게 된다.
위의 Entity는 아래와 같은 table을 생성하게 될 것이다.
+-------------+--------------+----------------------------+
| user |
+-------------+--------------+----------------------------+
| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
| firstName | varchar(255) | |
| lastName | varchar(255) | |
| isActive | boolean | |
+-------------+--------------+----------------------------+
그리고 각각의 Entity들은 dataSource 옵션에 등록되어야 한다.
DataSource
TypeORM을 이용하는 최종 목적은 Database와의 interaction일 것이다. 그리고 이러한 작업은 DatabaseSource가 설정 된 이후에만 가능하게 된다.
구체적으로 TypeORM의 DataSource는 database 연결 셋팅을 도와주고, 첫 데이터베이스와의 연결과 connection pool 설정을 도와준다.
FYI, Connection Pool이란,
DB를 이용하기 위해서는 DB Connection을 맺는 과정이 필요하다.
이러한 작업은 부하가 많이 걸리는 작업이라, 여러명이 Connection을 맺게 되면 서버에 부하가 생기게 된다.
해당 문제를 극복하기 위해서, TypeORM에서는 미리 정해진 Connection들을 만들어두고 재사용하게 되는데,
이를 Connection Pool 이라고 한다.
NestJS를 사용할 때 여러가지 방식으로 dataSource 옵션을 정의하고, 여기에 entity들을 등록 할 수 있다.
여기에서는 ormconfig.ts 파일을 통해 options를 정의하는 방법에 대해 알아보자.
1.ormconfig.ts 작성
import { ConnectionOptions } from 'typeorm';
const config: ConnectionOptions = {
type: 'mysql',
host: data.DATABASE_HOST,
port: data.DATABASE_PORT,
username: data.DATABASE_USER,
password: data.DATABASE_PASSWORD,
database: data.DATABASE_NAME,
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: false,
migrationsRun: false,
migrations: [__dirname + '/migrations/**/*{.ts,.js}'],
cli: {
migrationsDir: 'src/migrations',
},
};
export = config;
이때, 10번째 줄에 작성 된 것처럼, entity들을 등록 할 수 있다. entity들을 하나씩 import 해서 등록 할 수도 있겠지만,
일반적으로는 위에 작성 된 것처럼 와일드카드(*)를 사용해서 모든 entity 객체들을 한번에 등록하는 방법을 사용한다.
FYI,
migrationsRun: 자동적으로 migration을 실행하는지에 대한 여부 설정
migrations: migration에 사용될 파일들의 위치(개발환경에 따라 다르게 설정 가능하다.)
cli:{migrationsDir}: cli를 통해 migration 파일 생성시, 생성위치
자 이제 ormconfig.ts의 options은 모두 작성 되었다.
실제 프로젝트에 이 설정을 반영시켜 보자.
2. ormconfig.ts 등록
app.module.ts에 설정을 등록하자.
//...
import * as ormconfig from './ormconfig';
@Module({
imports: [
//...
TypeOrmModule.forRoot(ormconfig),
//...
],
controllers: [AppController],
providers: [
AppService,
{ provide: APP_GUARD, useClass: JwtAuthGuard },
{ provide: APP_GUARD, useClass: RolesGuard },
{ provide: APP_FILTER, useClass: HttpExceptionFilter },
],
exports: [AppService],
})
export class AppModule {}
FYI,
TypeOrmModule은 @nestjs/typeorm에서 제공되는 클래스이며, 아래와 같은 형식을 띄고 있다.
즉, TypeOrmModule.forRoot는 options를 인자로 받아, DynamicModuled을 리턴하게 되는 것이다.
export declare class TypeOrmModule {
static forRoot(options?: TypeOrmModuleOptions): DynamicModule;
}
Interaction with DB
위의 과정을 통해, DB와 연결하는 설정을 모두 마쳤다.
TypeORM에서는 어떻게 DB와 통신을 하기에, 더 직관적이라고 하는지 실제 코드를 통해 알아보자.
아래의 코드는 user라는 객체를 인자로 받아 usersRepository에 새로운 유저를 등록하는 메소드를 TypeORM을 통해 구현한 모습이다.
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User) private usersRepository: Repository<User>){}
async create(user: User): Promise<any> {
await this.usersRepository.save(user);
return user;
}
}
TypeORM에서 제공하는 Repository라는 객체에 연결하고 싶은 Table과 맵핑되는 Entity(User)를 제네릭 타입으로 넣어주고,
해당 Entity를 UsersService객체의 private 변수로 등록해준다.
그리고 이렇게 등록된 Repository에 있는 save라는 메소드를 통해, 어떠한 쿼리문도 없이 database에 새로운 user를 넣어 줄 수 있는 것이다.
얼마나 심플하고 직관적인가!
Another way to interact with DB
위에서는 Repository를 사용하는 방법을 사용했지만 사실, TypeORM에서 DB와 interact하는 방법은 다양하다.
(e.g. QueryBuilder, Entity Manager 등)
이러한 방법에 대해서는 공식홈페이지에 자세히 나와있으니, 이 부분을 참고하자.
'TypeORM' 카테고리의 다른 글
[TypeORM] bulk insert (한번에 여러 칼럼 udpate) (0) | 2022.09.26 |
---|---|
TypeORM 관계 설정하기(ManyToOne, OneToMany) (1) | 2022.06.03 |
entity 와 db table 비교하여 migration 파일 생성 (feat. nestjs, mysql, typeorm) (0) | 2022.05.25 |
테이블 간 관계 설정 (0) | 2022.04.01 |