비슷한 목적을 가진 다른 TypeORM 방법.
우리는 흔히 Pagination 또는 Id를 받아 무한 스크롤을 구현할 때 take() 또는 skip()을 사용하여
데이터의 개수 제한을 둡니다.
허나 어느 상황에 take()를 써야하고 skip()을 써야할지 정확히 아는것이 중요합니다.
이번 시간에는 이 두가지의 차이에 대해 알아보겠습니다.
먼저 limit에 대해 알아보겠습니다.
TypeORM에서 소개하는 Limit의 내용입니다.
* Set's LIMIT - maximum number of rows to be selected.
* NOTE that it may not work as you expect if you are using joins.
* If you want to implement pagination, and you are having join in your query,
* then use instead take method instead.
하나씩 하나씩 살펴보겠습니다.
TypeORM은 JOIN을 사용한다면 LIMIT을 지양하라고 강조하고 있습니다.
그 이유가 무엇일까요?
단순한 말 대신 코드와 쿼리로 보면서 설명하겠습니다.
우선 News와 NewsToThemes entity가 있다고 가정하겠습니다.
News
id: number;
title: string;
content: string;
createdAt: Date;
publishAt: Date;
NewsToThemes
id: number;
newsId: number;
theme: string;
가짜로 생각하고 만들다 보니 조금 엉성할 수 있어도 이해해주시길 바랍니다.
const builder = this.getRepository(News).createQueryBuilder('news')
.leftJoinAndSelect('news.newsToThemes').limit(10);
위의 결과 쿼리는 무엇일까요?
SELEECT * FROM news LEFT JOIN news_to_themes ON news.id = news_to_themes.news_id LIMIT 10
위와 같은 결과가 나올 것입니다. (*은 귀찮아서 모든 컬럼값을 대체했습니다.)
위의 쿼리는 LIMIT 10이 먼저 평가됩니다.
이제 take메소드를 확인해보겠습니다.
const builder = this.getRepository(News).createQueryBuilder('news')
.leftJoinAndSelect('news.newsToThemes').take(10);
EXPLAIN SELECT
DISTINCT `distinctAlias`.`news_id` AS `ids_news_id`
FROM
(
SELECT
`news`.`id` AS `news_id`,
`news`.`title` AS `news_title`,
`news`.`content` AS `news_content`,
`news`.`publish_at` AS `news_publish_at`,
`news`.`created_at` AS `news_created_at`,
`newsToThemes`.`id` AS `newsToThemes_id`,
`newsToThemes`.`news_id` AS `newsToThemes_news_id`,
`newsToThemes`.`theme` AS `newsToThemes_theme`,
FROM
`news` `news`
LEFT JOIN `news_to_themes` `newsToThemes` ON `newsToThemes`.`news_id` = `news`.`id`
) `distinctAlias`
LIMIT 10;
위와 같은 쿼리가 날라가게 됩니다.
둘의 차이가 느껴지시나요 휴먼?
take는 서브쿼리로 조인을 먼저 한 후 LIMIT을 적용시킵니다.
따라서 조인을 할 경우에는 take로 조인의 식을 먼저 평가 후 LIMIT으로 제한을 두어야 내가 원하는 결과가 제대로 나오게 되는 것입니다.
근데 take는 여기서 끝이 아닙니다!!!
한가지 쿼리가 더 날라가는데 그것은 바로 두두두두두둥탁!
SELECT
`news`.`id` AS `news_id`,
`news`.`title` AS `news_title`,
`news`.`content` AS `news_content`,
`news`.`publish_at` AS `news_publish_at`,
`news`.`created_at` AS `news_created_at`,
`newsToThemes`.`id` AS `newsToThemes_id`,
`newsToThemes`.`news_id` AS `newsToThemes_news_id`,
`newsToThemes`.`theme` AS `newsToThemes_theme`
FROM
`news` `news`
LEFT JOIN `news_to_themes` `newsToThemes` ON `newsToThemes`.`news_id` = `news`.`id`
WHERE
(
`news`.`id` IN (
1, 2, 3, 4, 5, 6,
7, 8, 9, 10
)
)
처음 날린 쿼리의 결과를 객체로 받아 그 id값으로 다시 한번 10개의 데이터를 얻어오는 것입니다!!
여기까지만 보고 아~~ 그렇구나 하고 끝낸다면 나중에 후회하게 될것입니다!!
핵심은 바로 결과를 객체로 받는다입니다!! 객체로 받으면 객체로 받는거지 뭐가 그렇게 중요한데?? 싶으실텐데 take 또는 limit을 혼자만 쓸 때는 문제가 없습니다! 허나 orderBy를 붙인다면 얘기가 달라지게 됩니다.
const builder = this.getRepository(News).createQueryBuilder('news')
.leftJoinAndSelect('news.newsToThemes').limit(10).orderBy('news.id', 'DESC');
위의 코드는 문제 없이 정상동작합니다.
허나 아래의 코드를 살펴보겠습니다!!
const builder = this.getRepository(News).createQueryBuilder('news')
.leftJoinAndSelect('news.newsToThemes').limit(10).orderBy('news.publish_at', 'DESC');
이것은 정상동작할까요?
이것은 에러입니다!! 삐삑 에러 발견!!
Cannot read properties of undefined (reading 'databaseName')
위와 같은 에러메세지가 자기주장을 하면서 나타날것입니다!!
그 이유가 바로 위에서 말씀드렸던 객체로 반환하기 때문입니다!!
이제 Nest로 우리가 Entity 정의하는 부분을 살펴보겠습니다.
@Entity()
export class News {
@PrimaryGeneratedColumn({ type: 'bigint', unsigned: true })
id: number;
@Column({ type: 'datetime', width: 3 })
publishAt: Date;
}
보통 위와 같이 사용합니다.
DB의 값 _(스네이크 케이스)가 TS로 넘어오면서 캐멀케이스가 되고 TS값이 DB로 넘어갈때 스네이크 케이스로 변환되어야 되기 때문에 namingStragry를 사용합니다. 그게 아니라면 아래와 같이 매번 정의해야 할 것입니다.
@Entity()
export class News {
@PrimaryGeneratedColumn({ type: 'bigint', unsigned: true })
id: number;
@Column({ type: 'datetime', width: 3, name: 'publish_at' })
publishAt: Date;
}
따라서 take와 Order By를 함께 사용할 때에는 객체로 변경된 값을 사용하셔야 됩니다.
아래 살짝 정리해드리겠습니다.
use `.orderBy('entity.creatdAt', 'DESC')` with `.skip()` and `.take()`
use `.orderBy('entity.creatd_at', 'DESC')` with `.offset()` and `.limit()`
limit 또는 offset은 원시 필드 이름, take, skip은 entity필드 이름을 사용하는 것이 정답입니다.
그게 알아서 값이 바뀌더라도 위의 내용을 기억하시고 사용하시면 나중에라도 도움이 될 것입니다.
위에서 언급한 예는 항상 getMany() 또는 getOne()을 사용할때로 가정합니다.
getRawMany()는 피하는것이 좋습니다.
스택오버플로우
티스토리 8:20 개발실Log
GitHub TypeORM
GitHub TypeORM 제일 도움된 문서