TypeORM
에서는 데이터베이스에 접근하는 두가지 주요 방법으로 QueryBuilder
와 Repository
를 사용하는데 차이점을 비교해보자.
Repository
는 Entity
에 설정된 fetch type
을 가지고 관계를 가져오고 QueryBuilder
는 명시적으로 지정하지 않으면 기본 테이블만 가져온다.QueryBuilder
는 Repository
보다 SQL에 더 가깝기 때문에 더 복잡하거나 더 많은 SQL을 다루는 작업에 적합하다.Repository
메서드 내에서 더 복잡한 쿼리를 생성하기 위해 QueryBuilder
를 사용할 수 있으므로 두 가지 접근 방식이 상호 배타적이지 않다.세가지 가정을 세우고 하나씩 증명하며 진행해 보자.
async findRoomUsingRepository(text: string): Promise<RoomEntity[]> {
return await this.roomRepository.find({
where: {
title: Like(`%${text}%`),
},
})
방의 제목을 검색하기 위해 Repository
의 find
메소드를 이용해 작성을 했다. 안정성과 가독성 측면에서 우위를 보일 듯 하다.
@ManyToOne(() => UserEntity, (user) => user.room, {
eager: true
})
@OneToMany(() => UserAuthority, (userAuthority) => userAuthority.user, {
eager: true,
})
기본적으로 TypeORM
에서는 fetchType
을 지정해 주지 않으면 Eager
혹은 Lazy
도 적용되지 않는다. 확인을 위해 모두 Eager
로 설정해 주었다.
SELECT ... FROM "rooms" "RoomEntity" LEFT JOIN "user" "RoomEntity_user" ON "RoomEntity_user"."id"="RoomEntity"."user_id" LEFT JOIN "user_authority" "RoomEntity_user_authorities" ON "RoomEntity_user_authorities"."user_id"="RoomEntity_user"."id" WHERE ("RoomEntity"."title" LIKE $1) -- PARAMETERS: ["%hi%"]
따로 Join
을 해주지 않아도 Eager Loding
을 해오는 모습이다. Room 테이블에 연결된 user 테이블, user 테이블에 연결된 authorities 테이블을 Join
해주는 것을 확인.
async findRoomUsingQueryBuilder(text: string): Promise<RoomEntity[]> {
return await this.dataSource
.createQueryBuilder()
.select("room")
.from(RoomEntity, "room")
.leftJoinAndSelect('room.user', 'user')
.leftJoinAndSelect('user.authorities', 'authorities')
.where('room.title LIKE :title', { title: `%${title}%` })
.orderBy('room.createdAt', 'DESC')
.getMany();
}
}
QueryBuilder
를 이용해 작성해 보았다. 비슷한 결과를 보여주기 위해 조건들을 직접 명시 해주어야 한다.
SELECT ... FROM "rooms" "room" LEFT JOIN "user" "user" ON "user"."id"="room"."user_id" LEFT JOIN "user_authority" "authorities" ON "authorities"."user_id"="user"."id" WHERE "room"."title" LIKE $1 ORDER BY "room"."created_at" DESC -- PARAMETERS: ["%hi%"]
다음으로 넘어가보자.
async getUsersByRole(text: string): Promise<RoomEntity[]> {
const usersByRole = await this.roomRepository
.createQueryBuilder('room')
.leftJoinAndSelect('room.user', 'user')
.where('user.username = :text', { text })
// .where('user.username = :text OR room.description LIKE :description', {
// text,
// description: `%${text}%`,
// })
.getMany();
const usersByUsername = await this.roomRepository.find({
where: {
description: Like(`%${text}%`),
},
});
const allUsers = [
...usersByRole, ...usersByUsername
];
return allUsers;
}
두가지 방식을 결합해서 이용해 보았다. 주석처리한 부분은 레포지토리를 이용해 결합한 부분을 쿼리빌더만을 이용해 처리한 모습이다. QueryBuilder
혹은 Repository
만을 이용했을 때에 비해 DB I/O가 증가한 모습이다. 조금 더 복잡한 모델에서 효율이 나타날 듯 하다.
다음으로는 Repository
내에서 QueryBuilder
를 사용하자.
async findRoom(title: string): Promise<any> {
return await this.roomRepository
.createQueryBuilder('room')
.leftJoinAndSelect('room.user', 'user')
.leftJoinAndSelect('user.authorities', 'authorities')
.where('room.title LIKE :title', { title: `%${title}%` })
.orderBy('room.createdAt', 'DESC')
.getMany();
}
쿼리 빌더 만으로 구성했을 때와 크게 달라진 모습은 보이지 않는다.
SELECT ... FROM "rooms" "room" LEFT JOIN "user" "user" ON "user"."id"="room"."user_id" LEFT JOIN "user_authority" "authorities" ON "authorities"."user_id"="user"."id" WHERE "room"."title" LIKE $1 ORDER BY "room"."created_at" DESC
참고
What are the different use cases for using QueryBuilder vs. Repository in TypeORM?