서론
코드래빗을 이용해 열심히 리뷰받고 있던 어느 날.. 코드래빗이 다음과 같은 제안을 해주었다.
관계 설정 시 eager 혹은 lazy 옵션을 명시적으로 설정하라는 내용이다.
🛠️ Refactor suggestion
관계 설정에 대한 추가 구성이 필요합니다.
다음 사항들을 고려해주세요:
1. @OneToOne 데코레이터에 eager 또는 lazy 로딩 옵션을 명시적으로 설정하는 것이 좋습니다.
2. 사용자 삭제 시 통계 데이터 처리를 위한 cascade 옵션 설정이 필요합니다.다음과 같이 수정하는 것을 제안드립니다:
@PrimaryColumn({ name: 'user_id', type: 'bigint' }) - @OneToOne(() => User) + @OneToOne(() => User, { eager: true, onDelete: 'CASCADE' }) @JoinColumn({ name: 'user_id' }) userId: number;
https://github.com/pudding-keep-in-touch/keep-in-touch-be-v2/pull/17#discussion_r1849672849
✨ [Feature] v1 to v2 API 이관 by jis-kim · Pull Request #17 · pudding-keep-in-touch/keep-in-touch-be-v2
Summary v1 files 삭제 entity file 생성 v1과 로직이 똑같은 /auth/google/login, /auth/google/callback , /users/:userId/nickname 구현 Describe your changes auth, user module 생성 사용자 관련 작업을 관리하는 UsersService 및 U...
github.com
항상 그렇지만.. AI의 조언은 곧이 곧대로 들으면 안되고 맞는 말인지 확인할 필요가 있다.
eager, lazy 옵션은 잘 사용한 적이 없었으므로 리뷰 받은 김에 둘이 어떤 옵션인지, 이 경우 정말 명시해야 하는지 알아보았다.
eager와 lazy
TypeORM 공식문서를 참고하였을 때, 두 가지 옵션은 각각 다음을 뜻한다.
eager: 항상 자동 로드
With eager loading enabled on a relation, you don't have to specify relations in the find command as it will
ALWAYS be loaded automatically.
eager: true
로 설정하면 findOne
이나 find
를 호출할 때 자동으로 관계 데이터를 가져온다
즉, relations
옵션을 별도로 지정하지 않아도 알아서 join해서 데이터를 반환한다.
QueryBuilder
를 사용할 때는 eager relation이 적용되지 않는다.
한 쪽 관계에서만 사용할 수 있고, 양쪽에서 사용되는 것은 허용되지 않는다.
lazy: 필요할 때 로드
lazy: true
로 설정하면 관계 데이터가 즉시 로드되지 않고, 실제로 접근할 때 로드된다.
따라서 필드 타입이 반드시 Promise
여야 한다.
Node.js는 비동기 방식으로 작동하므로 동기적으로 접근하여 프로퍼티를 가져오는 것이 불가능하기 때문이다.
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToMany,
JoinTable,
} from "typeorm"
import { Category } from "./Category"
@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number
@Column()
title: string
@Column()
text: string
@ManyToMany((type) => Category, (category) => category.questions)
@JoinTable()
categories: Promise<Category[]>
}
이런 relation을 save
하려면 다음과 같이 사용해야 한다.
const category1 = new Category()
category1.name = "animals"
await dataSource.manager.save(category1)
const category2 = new Category()
category2.name = "zoo"
await dataSource.manager.save(category2)
const question = new Question()
question.categories = Promise.resolve([category1, category2])
await dataSource.manager.save(question)
로드 하려면 await
으로 비동기 처리를 해줘야 한다.
const [question] = await dataSource.getRepository(Question).find()
const categories = await question.categories
// you'll have all question's categories inside "categories" variable now
TypeORM 문서에서는 lazy loding이 비표준적인 방법이라고 알려주고 있다.
다른 언어의 ORM은 (대표적으로 Java의 JPA) 관계 설정에서 lazy loading 이 기본 값인 경우가 있지만, TypeORM에서는 lazy loading을 권장하지 않는다.
Note: if you came from other languages (Java, PHP, etc.) and are used to use lazy relations everywhere - be careful. Those languages aren't asynchronous and lazy loading is achieved different way, that's why you don't work with promises there. In JavaScript and Node.JS you have to use promises if you want to have lazy-loaded relations. This is non-standard technique and considered experimental in TypeORM.
https://typeorm.io/eager-and-lazy-relations#eager-and-lazy-relations
TypeORM 관계 설정 시 기본값은 eager일까 lazy일까
이 쯤 알아보고 나니 자연스레 의문이 생겼다. TypeORM은 기본 설정으로 대체 뭘 쓸까? eager와 lazy 둘 다 아닌 것 같은데...
=> 둘 다 아닌 게 맞다.
TypeORM은 findOne
, find
등에서 relations
옵션을 명시적으로 지정하는 것을 기본으로 한다.
위 예시의 categories를 가져오려면 relations
에 명시해 주면 된다.
const question = await questionRepository.findOne({
where: { id: 1 },
relations: { categories: true } // v0.3 이상부터 적용
});
relations
에 이렇게 설정해주면 실제 쿼리 실행 시 JOIN
이 포함되어 SELECT
된다.
즉 필요한 경우에만 관계 데이터를 가져오는 방식으로 직관적이다.
그래서 이 PR에서 수정이 필요한지?
수정 제안이 온 MessageStatistics
를 사용할 때는 애초에 User
정보가 필요할 일이 없고, 필요하다면 join을 이용하면 될 일이다.
수정은 필요 없다고 결론을 내렸다.
정리
eager: true
는 관계된 데이터를 자동으로 로드하는 방식이며, 한쪽 관계에서만 사용할 수 있다.lazy: true
는 관계된 데이터를 실제 접근 시 비동기적으로 로드하는 방식이다.- TypeORM의 기본 설정은 eager나 lazy 옵션을 사용하지 않으며
relations
를 통해 명시적으로 관계 데이터를 불러온다.
'개발' 카테고리의 다른 글
[PostgreSQL] Sequence 값이 이상하다?! - Caching과 Gapless Assignment (0) | 2025.04.06 |
---|---|
[Docker] 이미지 크기 줄이다 chown한테 맞은 이야기 (2) | 2025.03.30 |
Notion API로 원하는 날짜에 일정 생성하기 (2) | 2024.10.09 |
[Passport.js] passport 에서 로그인과 callback route 를 나눠야 할까? (1) | 2023.11.04 |
[NestJS] 파일 업로드 구현하다 FileInterceptor 를 커스텀 한 사람이 있다? (1) | 2023.10.08 |