데이터베이스의 하나의 테이블은 다양한 외래키를 가지는 경우가 흔합니다. 그리고 이런 테이블 데이터를 조회할 때 외래키에 대한 정보가 필요한 경우가 있습니다. 만약 게시물을 작성하는 기능을 구현했다고 가정합시다. 그럼 게시물 테이블과 유저 테이블은 N:1 관계를 맺고 있겠조? 이때 게시물 테이블에서 특정 게시물 데이터를 조회하는 과정에 해당 게시물을 작성한 유저의 pk값만 불러오고 싶다면 어떻게 할까요? ( 해당 게시물을 작성한 유저 정보 전부가 아닙니다. pk만! ex) 해당 게시물을 작성한 유저의 pk값 하나만 알고 싶은겁니다. ) 오늘은 typeorm을 이용한 조회 시 연관 관계 설정된 테이블의 모든 정보를 조회하지 않고 단순히 연관 관계 설정된 컬럼의 단순 데이터 하나만 필요할 때 어떻게 설정해야하는지 공유하도록 하겠습니다.
기존 설정법
이전에 저는 엔티티의 외래키를 설정할 때는 다음과 같이 간단히 설정해왔습니다.
# Post 엔티티
@Entity()
export class Post {
@PrimaryGeneratedColumn({ type: 'int' })
id: number;
@ManyToOne(() => User, (user) => user.posts, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
user: User;
...
}
이때 문제가 발생했었조. 바로 user라는 이름으로 설정된 부분의 값이 필요할 때 발생하는 문제였는데요. typeorm에서 제공하는 기본 메서드인 find 혹은 findBy 등의 메서드를 관련 옵션을 따로 설정하지 않고 활용하면 엔티티에서 설정된 일반 컬럼들만 조회됩니다. 즉, 위 예시에서는 id 컬럼값만 조회되는 것이죠. 실제 데이터베이스를 확인해보면 @ManyToOne으로 설정된 user 정보가 userId라는 컬럼으로 자동 생성되어 연관 관계가 맺어진 모습을 확인할 수 있음에도 불구하고 해당 userId 컬럼값은 조회되지 않습니다.
이런 이유로 다양한 방법을 시도해 userId 컬럼값을 조회하려 했었지만 성능상 이슈가 발생할 수 있다고 생각했습니다. 그 이유는 다음과 같습니다.
시도한 방법 및 문제점
1. find 메서드 relations 옵션 사용하기
typeorm의 find 메서드에서 relations 옵션은 연관 관계가 설정된 테이블 정보를 eager loading을 활용해 불러오는 방식입니다. 예제 코드는 다음과 같습니다.
const posts = await this.postRepository.find({
where: {
id: id,
},
relations: ['user'] // 유저 정보 eager loading
});
이 코드를 실행한 결과 post 테이블의 일반 컬럼값들과 외래키로 설정된 User 테이블 일반 컬럼값들이 조회됨을 확인할 수 있습니다. 하지만 이때 문제가 발생합니다. join 쿼리가 실행된다는 것인데요. 만약 post 조회 시 user 데이터도 모두 조회해아한다면 문제가 없겠지만, post 조회 시 단순 user pk값만 필요한 경우는 쓸 데 없는 작업이 됩니다.
2. querybuilder 사용하기
먼저 typeorm에서 제공하는 메서드가 아니라 querybuilder를 사용해 직접 sql문을 작성하는 방법이 있을 것 같습니다. 이 경우에는 다음과 같이 정확히 내가 필요한 컬럼만 명시해 컬럼 데이터를 조회할 수 있습니다.
const subscribeInfos = await this.postRepository.createQueryBuilder('post')
.select([
'post.id',
'post.userId', // 외래키만 선택
...
])
.where('post.id = :postId', { postId })
.getMany();
위 방법은 성능상 문제는 없다고 판단되었습니다. 하지만 기본 메서드를 활용하는 것이 아니라 sql 구문을 직접 작성하는 것과 비슷한 방식으로 코드가 길어진다고 생각되어 더 간단한 방식이 없을까 고민되었습니다.
3. find 메서드 select 옵션 사용하기
typeorm의 find 메서드를 활용할 때 select 옵션을 활용하면 내가 원하는 컬럼을 선택할 수 있습니다. 마치 위 쿼리 빌더 예제 속 select와 같은 기능이죠. 코드가 더 단순해지므로 querybuilder보다 더 좋은 방법이라고 생각했습니다. 그래서 해당 방법을 활용한 코드를 다음과 같이 작성했습니다.
const posts = await this.postRepository.find({
select: [
'id',
'userId', // 외래키 직접 명시
],
where: {
id: id,
},
});
하지만 위 코드를 사용한 결과 userId 컬럼값이 조회되지 않았습니다. 왜 그럴까요? 그 이유는 엔티티 설정에 있었습니다.
4. 엔티티 설정에서 외래키 컬럼 명시하기
위에서 select를 이용했음에도 해당 userId 컬럼값이 조회되지 않은 이유는 엔티티 설정에 있었습니다. 저는 기존에 엔티티에서 외래키를 설정할 때 이렇게만 설정했는데요.
# Post 엔티티
@Entity()
export class Post {
@PrimaryGeneratedColumn({ type: 'int' })
id: number;
@ManyToOne(() => User, (user) => user.posts, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
user: User;
...
}
이렇게만 설정해도 알아서 userId라는 컬럼값이 생기면서 알아서 연관 관계가 맺어졌기 때문인데요. 하지만 이때 직접 @Column 데코레이터를 활용해 컬럼명을 명시하지 않으면 이 값을 조회하기 위해 해당 컬럼명을 사용하지 못한다는 것을 확인할 수 있었습니다. 그래서 다음과 같이 설정한 후 2번 select 옵션을 적용한 find 메서드를 실행해 보았습니다.
# Post 엔티티
@Entity()
export class Post {
@PrimaryGeneratedColumn({ type: 'int' })
id: number;
@ManyToOne(() => User, (user) => user.posts, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
user: User;
@Column() // 직접 명시
userId: number;
...
}
이렇게 @Column 데코레이터를 이용해 userId라는 이름을 직접 명시하니 find 메서드 select 옵션에 userId를 사용할 수 있었고, 반환된 결과 속에서도 userId 값을 성공적으로 확인할 수 있었습니다.
정리
테이블 조회 시 외래키로 설정된 정보를 relations나 eager 옵션 등을 이용해 조회한다면 join 쿼리가 실행됩니다. 만약 join이 필요없는 경우라면 더 단순화시키는 것이 이득입니다. 이때는 엔티티 설정에서 외래키로 설정할 컬럼의 이름을 직접 명시하고, 이를 활용해 typeorm의 find 메서드 select 옵션에 해당 이름을 넣어줍니다. 그럼 결과적으로 join 쿼리는 실행되지 않고 외래키로 설정된 컬럼 데이터도 바로 조회할 수 있습니다.
'데이터베이스, ORM > TypeORM' 카테고리의 다른 글
[TypeORM] 골치 아픈 Distinct 쿼리 피해 소요 시간 줄이기 (0) | 2025.03.22 |
---|---|
[TypeORM] 내가 트랜잭션을 사용하는 이유와 조심해야할 부분 (0) | 2024.12.17 |
[TypeORM] SQL 쿼리 튜닝 - 1. findBy VS findOneBy 뭐가 더 좋을까? (0) | 2024.11.25 |
[TypeORM] @OneToMany, @ManyToOne - 옵션: onDelete / onUpdate (0) | 2024.11.23 |
[TypeORM] @OneToMany, @ManyToOne - 옵션: eager / lazy (0) | 2024.11.23 |