๐ง ์ค๋๋ ํฌ์คํธ๋ผ ํ์ฌ TypeORM๊ณผ ๋ฒ์ ํธํ์ด ๋ง์ง ์์ ์ ์์ต๋๋ค. ๊ฐ๋ ์ ์ธ ๊ด์ ์์ ๋ด์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค.
์ด๋ฒ์๋ TypeORM
์์ ์์ฃผ ์ด์ฉํ๋ QueryBuilder
์ ์ฝ๋๋์ ์ค์ด๋ ๋ฒ์ ๋ํด ์ดํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
QueryBuilder ์ฌ์ฌ์ฉ์ฑ ๋์ด๊ธฐ
์๋น์ค๋ฅผ ๋ง๋ค๋ฉด์ ์กฐํ๊ฐ ํ์ํ ์ฟผ๋ฆฌ๋ ๋ชจ๋ TypeORM
์ QueryBuilder
ํจํด์ ์ด์ฉํ์ฌ ๋ง๋ค๊ณ ์์ต๋๋ค. QueryBuilder
๊ฐ TypeORM
์ findOne
๊ณผ ๊ฐ์ ํจ์๋ณด๋ค ๋ ์ธ๋ฐํ๊ฒ(์กฐ์ธ๋ ํ
์ด๋ธ where, order, having, group๋ฑ)
์ฟผ๋ฆฌ๋ฅผ ์กฐ์ ํ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
// user.service.ts
import { Injectable } from "@nestjs/common"
import { UserRepository } from "./user.repository"
@Injectable()
export class UserService {
constructor(private readonly userRepository: UserRepository) {}
public async findOneByUserId(userId: number) {
return this.userRepository.findOneByUserId(userId)
}
public async findOneByEmail(email: string) {
return this.userRepository.findOneByEmail(email)
}
public async findOneByNickname(nickname: string) {
return this.userRepository.findOneByNickname(nickname)
}
}
// user.repository.ts
import { AbstractRepository, EntityRepository } from "typeorm"
import { User } from "./user.entity"
@EntityRepository(User)
export class UserRepository extends AbstractRepository<User> {
public async findOneByUserId(userId: number) {
const qb = this.repository
.createQueryBuilder("User")
.leftJoinAndSelect("User.userAuth", "userAuth")
.leftJoinAndSelect("User.userProfile", "userProfile")
qb.andWhere("User.id = :id", { id: userId })
return qb.getOne()
}
public async findOneByEmail(email: string) {
const qb = this.repository
.createQueryBuilder("User")
.leftJoinAndSelect("User.userAuth", "userAuth")
.leftJoinAndSelect("User.userProfile", "userProfile")
qb.andWhere("User.email = :email", { email })
return qb.getOne()
}
public async findOneByNickname(nickname: string) {
const qb = this.repository
.createQueryBuilder("User")
.leftJoinAndSelect("User.userAuth", "userAuth")
.leftJoinAndSelect("User.userProfile", "userProfile")
qb.andWhere("User.nickname = :nickname", { nickname })
return qb.getOne()
}
}
ํ์ง๋ง ๋น์ฆ๋์ค ์กฐํ ๋ก์ง์ด ๋ง์์ง ์๋ก, QueryBuilder
ํจ์๋ฅผ ๋ฐ๋ณตํด์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์์ก์ต๋๋ค. ๋ฌธ์ ๋ ํฌ๊ฒ 3๊ฐ์ง ๊ฒฝ์ฐ์์ต๋๋ค.
- ๋๋ฌด ๋ง์ด ๋ฐ๋ณต.. ๋ ๋ฐ๋ณต
- Or ์ฐ์ฐ์ ์ด๋ป๊ฒ ํ์ง?
- FindOperator๋ก ์ฐ์ฐ์ ๋ฒ์ ๋ํ๊ธฐ
๋๋ฌด ๋ง์ด ๋ฐ๋ณต.. ๋ ๋ฐ๋ณต
์์ ์์ ์ ๊ฐ์ด Service
์์ ์กฐํ์ ๋จ์๊ฐ ๋ง์์ง ๋๋ง๋ค Repository
์๋ ๊ฐ์ด ๋์ด๋๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ์ด๋ ๋ค์๊ณผ ๊ฐ์ด Repository
์ ์กฐํ ๋ฉ์๋๋ฅผ ๋ฌถ์ด์ฃผ์ด ํด๊ฒฐํ ์ ์์ต๋๋ค.
// user.service.ts
import { Injectable } from "@nestjs/common"
import { UserRepository } from "./user.repository"
@Injectable()
export class UserService {
constructor(private readonly userRepository: UserRepository) {}
public async findOneByUserId(userId: number) {
return this.userRepository.findOne({ id: userId })
}
public async findOneByEmail(email: string) {
return this.userRepository.findOne({ email })
}
public async findOneByNickname(nickname: string) {
return this.userRepository.findOne({ nickname })
}
}
// user.repository.ts
import { AbstractRepository, EntityRepository } from "typeorm"
import { User } from "./user.entity"
export interface UserFindOneOptions {
id?: number
email?: string
nickname?: string
}
@EntityRepository(User)
export class UserRepository extends AbstractRepository<User> {
public async findOne({ id, email, nickname }: UserFindOneOptions = {}) {
const qb = this.repository
.createQueryBuilder("User")
.leftJoinAndSelect("User.userAuth", "userAuth")
.leftJoinAndSelect("User.userProfile", "userProfile")
if (id) qb.andWhere("User.id = :id", { id })
if (email) qb.andWhere("User.email = :email", { email })
if (nickname) qb.andWhere("User.nickname = :nickname", { nickname })
return qb.getOne()
}
}
์ฌ๊ธฐ์ ์ฃผ์ํด์ผํ ๊ฒ์ด ์์ต๋๋ค. Service
์์ this.userRepository.findOne()
์ ๊ฐ์ด ํ๋ผ๋ฏธํฐ๋ฅผ ๋๊ธฐ์ง ์๊ณ ํธ์ถํ๋ฉด, ํด๋น ํ
์ด๋ธ์ ์กด์ฌํ๋ ๊ฐ์ฅ ์ฒซ๋ฒ์งธ ๊ฐ์ฒด๊ฐ ๊ฐ์ ธ์์ง๋๋ค. Query๋ก ๋ณํํ๋ฉด SELECT * FROM USER LIMIT 1
ํ๋ ๊ฒ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋๊น์! ๋ฐ๋ผ์ ๋น ํ๋ผ๋ฏธํฐ๋ฅผ ๋๊ธธ ๊ฒฝ์ฐ, null
๊ฐ์ ๋ฐํํ๋ ์์ธ์ฒ๋ฆฌ๊ฐ ํ์ํฉ๋๋ค.
// user.repository.ts
import { pickBy, isNil, negate } from "lodash"
// ๊ฐ์ฒด์ null, undefined ๊ฐ์ธ ํค๋ค์ ์ง์์ค๋๋ค.
// ์์ธํ ์๋ฆฌ๋ lodash๋ฅผ ์ฐธ๊ณ ํ์ธ์.
export const removeNilFromObject = (object: object) => {
return pickBy(object, negate(isNil))
}
@EntityRepository(User)
export class UserRepository extends AbstractRepository<User> {
public async findOne(options: UserFindOneOptions = {}) {
// ๋น ๊ฐ์ฒด์ผ ๊ฒฝ์ฐ null์ ๋ฐํํฉ๋๋ค.
if (Object.keys(removeNilFromObject(options)).length === 0) return null
const { id, email, nickname } = options
// ...
return qb.getOne()
}
}
Or ์ฐ์ฐ์ ์ด๋ป๊ฒ ํ์ง?
๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ๋ง๋ findAll
ํจ์๋ฅผ ๋ณด๊ฒ ์ต๋๋ค.
// user.repository.ts
import { AbstractRepository, EntityRepository } from "typeorm"
import { User } from "./user.entity"
export interface UserFindAllWhereOptions {
id?: number
email?: string
nickname?: string
}
export interface UserFindAllOptions {
where?: UserFindAllWhereOptions
skip?: number
take?: number
}
@EntityRepository(User)
export class UserRepository extends AbstractRepository<User> {
// ...
// ํ๋ฒ์ ๋ง์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํด skip, take๋ก ํ์ด์ง๋ค์ดํ
ํฉ๋๋ค.
public async findAll(options: UserFindAllOptions = {}) {
const { where, skip, take } = options
const qb = this.repository
.createQueryBuilder("User")
.leftJoinAndSelect("User.userAuth", "userAuth")
.leftJoinAndSelect("User.userProfile", "userProfile")
if (where) {
const { id, email, nickname } = where
if (id) qb.andWhere("User.id = :id", { id })
if (email) qb.andWhere("User.email = :email", { email })
if (nickname) qb.andWhere("User.nickname = :nickname", { nickname })
}
qb.skip(skip ?? 0)
qb.skip(take ?? 20)
const [items, total] = await qb.getManyAndCount()
return { items, total }
}
}
ํ์ฌ ๋จ๊ณ์์ findAll
ํจ์๋ AND
์ฐ์ฐ๋ง ๊ฐ๋ฅํฉ๋๋ค. ๋๋ค์์ด โalfredโ ์ด๊ฑฐ๋, id๊ฐ 5์ธ User
๋ฅผ ๊ฐ์ ธ์ค์ง ๋ชปํ์ง์. OR ์ฐ์ฐ์ด ๊ฐ๋ฅํ๊ฒ ํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผํ ๊น์?
์ฌ๊ธฐ์๋ถํฐ ๋๋์ ์ธ ๋ฆฌํฉํฐ๋ง์ด ํ์ํฉ๋๋ค. where ์ ๊ฐ์ฒด๊ฐ ๋ค์ด๊ฐ๋ฉด And์ด๊ณ , ๋ฐฐ์ด์ ๋ฃ์ผ๋ฉด Or๋ผ๊ณ ์ธ์ํ๊ฒ ํ๋ฉด ์ด๋จ๊น์ ?
ID๊ฐ 5์ด๊ณ , Nickname์ด 'alfred'์ธ ์ ์
this.userRepository.findAll({ where: {id: 5, nickname: 'alfred'} })
ID๊ฐ 5์ด๊ฑฐ๋, Nickname์ด 'alfred'์ธ ์ ์
this.userRepository.findAll({ where: [{id: 5},{nickname: 'alfred'}] })
๋จผ์ UserFindAllOptions
ํ์
์ ๋ค์๊ณผ ๊ฐ์ด ์ ์ํฉ๋๋ค.
export interface UserFindAllWhereOptions {
id?: number
email?: string
nickname?: string
}
export interface UserFindAllOptions {
where?: UserFindAllWhereOptions | UserFindAllWhereOptions[]
skip?: number
take?: number
}
๊ทธ๋ฆฌ๊ณ UserRepository
findAll
ํจ์๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์ ์ํฉ๋๋ค.
// user.repository.ts
import { AbstractRepository, Brackets, EntityRepository } from "typeorm"
import { User } from "./user.entity"
export interface UserFindAllWhereOptions {
id?: number
email?: string
nickname?: string
}
export interface UserFindAllOptions {
where?: UserFindAllWhereOptions | UserFindAllWhereOptions[]
skip?: number
take?: number
}
@EntityRepository(User)
export class UserRepository extends AbstractRepository<User> {
// ...
public async findAll(options: UserFindAllOptions = {}) {
const { where, skip, take } = options
const qb = this.repository
.createQueryBuilder("User")
.leftJoinAndSelect("User.userAuth", "userAuth")
.leftJoinAndSelect("User.userProfile", "userProfile")
if (where) {
// ๊ฐ์ฅ ์์์ AND ์ฐ์ฐ์ผ๋ก ๊ดํธ๋ฅผ ์์์ค์ผํ๋ค: ( (...) OR (...) OR (...) )
if (Array.isArray(where)) {
qb.andWhere(
new Brackets(qb => {
// where์ด ๋ฐฐ์ด์ด๋ฉด OR ์ฐ์ฐ: (...) OR (...) OR (...)
where.forEach(wh => {
qb.orWhere(
// OR ์ฐ์ฐ
new Brackets(qb => {
// where์ ๋ฐฐ์ด ํ ์์๋ง๋ค ๊ดํธ๋ฅผ ์์ด๋ค
const { id, email, nickname } = wh
if (id) qb.andWhere(`User.id = ${id}`)
if (email) qb.andWhere(`User.email = "${email}"`)
if (nickname) qb.andWhere(`User.nickname = "${nickname}"`)
})
)
})
})
)
} else {
const { id, email, nickname } = where
if (id) qb.andWhere("User.id = :id", { id })
if (email) qb.andWhere("User.email = :email", { email })
if (nickname) qb.andWhere("User.nickname = :nickname", { nickname })
}
}
qb.skip(skip ?? 0)
qb.skip(take ?? 20)
const [items, total] = await qb.getManyAndCount()
return { items, total }
}
}
์์ง ๋ถ์์ ํ ๋ถ๋ถ์ด ์กฐ๊ธ ์์ง๋ง, OR ์ฐ์ฐ์ ํ ์ ์๋ ๋ผ๋๋ ์์ฑํ์ต๋๋ค! ํ์ง๋ง Repository
ํด๋์ค์ ๋ฉ์น๊ฐ ๋๋ฌด ์ปค์ ธ๋ฒ๋ ธ์ต๋๋ค. ์ฟผ๋ฆฌ ๋น๋์ ๋ก์ง ์ฐ์ฐ์ด ๋ง์์ก๊ธฐ ๋๋ฌธ์
๋๋ค. ์ด๋ฅผ QueryApplier
๋ผ๋ ํด๋์ค๋ฅผ ๋ฐ๋ก ๋ง๋ค์ด ์ํํ๊ฒ ํ๋ฉด ์ด๋จ๊น์?
๋จผ์ ์ปค์คํ
๋ ํฌ์งํ ๋ฆฌ ์์ ์ถ์ ํด๋์ค๋ฅผ ์ ์ํ์ฌ ๊ทธ ๊ณณ์์ QueryApplier
๋ฅผ ํฉ์ฑ์ํค๊ฒ ์ต๋๋ค. AbstractEntityRepository
๋ผ๋ class
๋ฅผ ์๋ก ๋ง๋ญ๋๋ค.
// entity.repository.ts
import {
AbstractRepository,
WhereExpression,
Brackets,
FindOperator,
} from "typeorm"
export abstract class AbstractEntityRepository<
T
> extends AbstractRepository<T> {
protected readonly queryApplier: EntityQueryApplier
constructor() {
super()
this.queryApplier = new EntityQueryApplier()
}
}
export interface BuildWhereOptionsFunction<T> {
({
filterQuery,
where,
}: {
filterQuery: (query: string) => void
where: T
}): void
}
export interface ApplyOptions<T> {
qb: WhereExpression
where?: T | T[]
buildWhereOptions: BuildWhereOptionsFunction<T>
}
class EntityQueryApplier {
public apply<T>({ qb, where, buildWhereOptions }: ApplyOptions<T>) {
if (!where) return
if (Array.isArray(where)) {
qb.andWhere(
new Brackets(qb => {
where.forEach(wh => {
qb.orWhere(
new Brackets(qb => {
this.applyBuildOptions({ qb, where: wh, buildWhereOptions })
})
)
})
})
)
} else {
this.applyBuildOptions({ qb, where, buildWhereOptions })
}
}
private applyBuildOptions<T>({
qb,
where,
buildWhereOptions,
}: {
qb: WhereExpression
where: T
buildWhereOptions: BuildWhereOptionsFunction<T>
}) {
buildWhereOptions({
where,
filterQuery: (query: string) => {
qb.andWhere(query)
},
})
}
}
์์ ๊ฐ์ด queryBuilder์ where ๋ก์ง์ ๋ฐ๋ก ํด๋์ค๋ก ๋นผ๋ ๋๋ค.
์ ์ถ์ ๋ ํฌ์งํ ๋ฆฌ ํด๋์ค๋ฅผ ์ด์ฉํ์ฌ UserRepository
์ findAll
์ ๋ฆฌํฉํฐ๋งํด๋ณด๊ฒ ์ต๋๋ค.
// user.repository.ts
import { Brackets, EntityRepository } from "typeorm"
import { AbstractEntityRepository } from "./entity.repository.v2"
import { User } from "./user.entity"
export interface UserFindAllWhereOptions {
id?: number
email?: string
nickname?: string
}
export interface UserFindAllOptions {
where?: UserFindAllWhereOptions | UserFindAllWhereOptions[]
skip?: number
take?: number
}
@EntityRepository(User)
export class UserRepository extends AbstractEntityRepository<User> {
// ...
public async findAll(options: UserFindAllOptions = {}) {
const { where, skip, take } = options
const qb = this.repository
.createQueryBuilder("User")
.leftJoinAndSelect("User.userAuth", "userAuth")
.leftJoinAndSelect("User.userProfile", "userProfile")
this.queryApplier.apply({
qb,
where,
buildWhereOptions: ({ filterQuery, where }) => {
const { id, email, nickname } = where
filterQuery(`User.id = ${id}`)
filterQuery(`User.email = "${email}"`) // ๋ฌธ์์ด์ ํฐ ๋ฐ์ดํ๋ก ๊ฐ์ธ์ค์ผ ํฉ๋๋ค.
filterQuery(`User.nickname = "${nickname}"`)
},
})
qb.skip(skip ?? 0)
qb.skip(take ?? 20)
const [items, total] = await qb.getManyAndCount()
return { items, total }
}
}
UserRepository
๋ AbstractEntityRepository
๋ฅผ ์์๋ฐ์ ๋ค์๊ณผ ๊ฐ์ด ์ฟผ๋ฆฌ where
์กฐํ ๋ก์ง์ QueryApplier
๋ก ์์ํฉ๋๋ค.
์์ผ๋ก ์ปค์คํ ๋ ํฌ์งํ ๋ฆฌ๋ฅผ ๋ง๋ค๋๋ง๋ค OR ์กฐํ๋ฅผ ํฌํจํ ๋ก์ง์ ๊ฐ๋จํ๊ฒ ์ค์ผ ์ ์๊ฒ ๋์์ต๋๋ค.
FindOperator๋ก ์ฐ์ฐ์ ๋ฒ์ ๋ํ๊ธฐ
TypeORM Repository ๊ธฐ๋ณธ ๋ด์ฅ ํจ์์ธ find
, findOne
์ ์ด์ฉํ๋ฉด ์ปฌ๋ผ ๊ฐ์ ๋ค์๊ณผ ๊ฐ์ FindOperator
(In
, LessThan
, Like
๋ฑ)๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
this.userRepository.find({ where: { id: In([1,2,3,4,5]) } })
์ฐ๋ฆฌ๊ฐ ๋ง๋ findOne
, findAll
์๋ ์ด๋ฐ FindOperator
๋ฅผ ์ ์ฉํ ์ ์๊ฒ ํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
์ค์ ๋ก ์ฟผ๋ฆฌ๋ฅผ ์ ์ฉํ๋ ๋ถ๋ถ์ QueryApplier
์ applyBuildOptions
ํจ์ ๋ด๋ถ์ filterQuery
์
๋๋ค. ๊ทธ ๊ณณ์ FindOperator
๊ฐ์ด ๋ค์ด๊ฐ ์ ์๋๋ก ์์ ํ๊ฒ ์ต๋๋ค. ์ฝ๋๊ฐ ๋ค์ ๊ธธ์ง๋ง ์ฌ์ ๋ฅผ ๊ฐ๊ณ ์ฝ์ด๋ณด์๊ธธ ๋ฐ๋๋๋ค.
// entity.repository.ts
import { isNil } from "lodash"
import {
AbstractRepository,
WhereExpression,
Brackets,
FindOperator,
} from "typeorm"
export type EntityFindOperator<T> = T | FindOperator<T>
export abstract class AbstractEntityRepository<
T
> extends AbstractRepository<T> {
protected readonly queryApplier: EntityQueryApplier
constructor() {
super()
this.queryApplier = new EntityQueryApplier()
}
}
export interface BuildWhereOptionsFunction<T> {
({
filterQuery,
where,
}: {
filterQuery: (property: string, valueOrOperator: any) => void
where: T
}): void
}
export interface ApplyOptions<T> {
qb: WhereExpression
where?: T | T[]
buildWhereOptions: BuildWhereOptionsFunction<T>
}
class EntityQueryApplier {
public apply<T>({ qb, where, buildWhereOptions }: ApplyOptions<T>) {
if (!where) return
if (Array.isArray(where)) {
qb.andWhere(
new Brackets(qb => {
where.forEach(wh => {
qb.orWhere(
new Brackets(qb => {
this.applyBuildOptions({ qb, where: wh, buildWhereOptions })
})
)
})
})
)
} else {
this.applyBuildOptions({ qb, where, buildWhereOptions })
}
}
private applyBuildOptions<T>({
qb,
where,
buildWhereOptions,
}: {
qb: WhereExpression
where: T
buildWhereOptions: BuildWhereOptionsFunction<T>
}) {
buildWhereOptions({
where,
filterQuery: (property, valueOrOperator) => {
if (isNil(valueOrOperator)) return
qb.andWhere(
this.computeFindOperatorExpression(property, valueOrOperator)
)
},
})
}
/**
* ์ปฌ๋ผ๊ณผ ๋น๊ต๊ฐ์ ๋ํ Raw Query๋ฅผ ๊ณ์ฐํ์ฌ ๋ฐํํด์ค๋ค. Referenced by TypeORM.
* @param property Column ์ด๋ฆ
* @param operator FindOperator ๋๋ ์ง์ ๊ฐ(Literal์ Equal์ฐ์ฐ์ ํ๋ค.)
* @returns
*/
private computeFindOperatorExpression(
property: string,
operator: FindOperator<any> | any
) {
const wrappedValue = (value: any) => {
// ํฐ ๋ฐ์ดํ ํ์ฑ
if (typeof value === "string") return `"${value.replace(/"/g, '\\"')}"`
else if (value instanceof Date) return `"${value.toISOString()}"`
return value
}
if (!(operator instanceof FindOperator))
return `${property} = ${wrappedValue(operator)}`
switch (operator.type) {
case "not":
if (operator.child) {
return `NOT(${this.computeFindOperatorExpression(
property,
operator.child
)})`
} else {
return `${property} != ${wrappedValue(operator.value)}`
}
case "lessThan":
return `${property} < ${wrappedValue(operator.value)}`
case "lessThanOrEqual":
return `${property} <= ${wrappedValue(operator.value)}`
case "moreThan":
return `${property} > ${wrappedValue(operator.value)}`
case "moreThanOrEqual":
return `${property} >= ${wrappedValue(operator.value)}`
case "equal":
return `${property} = ${wrappedValue(operator.value)}`
case "like":
return `${property} LIKE ${wrappedValue(operator.value)}`
case "between":
return `${property} BETWEEN ${wrappedValue(
operator.value[0]
)} AND ${wrappedValue(operator.value[1])}`
case "in":
if (operator.value.length === 0) {
return "0=1"
}
return `${property} IN (${operator.value
.map(v => wrappedValue(v))
.join(", ")})`
case "any":
return `${property} = ANY(${wrappedValue(operator.value)})`
case "isNull":
return `${property} IS NULL`
}
throw new TypeError(
`Unsupported FindOperator ${FindOperator.constructor.name}`
)
}
}
๋ฆฌํฉํฐ๋ง๋ AbstractEntityRepository
๋ฅผ ํ ๋๋ก UserRepository
๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ ํฉ๋๋ค.
// user.repository.ts
import { EntityRepository } from "typeorm"
import {
AbstractEntityRepository,
EntityFindOperator,
} from "./entity.repository"
import { User } from "./user.entity"
export interface UserFindAllWhereOptions {
id?: EntityFindOperator<number> // export type EntityFindOperator<T> = T | FindOperator<T>
email?: EntityFindOperator<string>
nickname?: EntityFindOperator<string>
}
export interface UserFindAllOptions {
where?: UserFindAllWhereOptions | UserFindAllWhereOptions[]
skip?: number
take?: number
}
@EntityRepository(User)
export class UserRepository extends AbstractEntityRepository<User> {
// ...
public async findAll(options: UserFindAllOptions = {}) {
const { where, skip, take } = options
const qb = this.repository
.createQueryBuilder("User")
.leftJoinAndSelect("User.userAuth", "userAuth")
.leftJoinAndSelect("User.userProfile", "userProfile")
this.queryApplier.apply({
qb,
where,
buildWhereOptions: ({ filterQuery, where }) => {
const { id, email, nickname } = where
filterQuery("User.id", id) // id๋ FindOperator์ผ์๋, ๋ฆฌํฐ๋ด ๊ฐ์ผ ์๋ ์์ต๋๋ค!
filterQuery("User.email", email)
filterQuery("User.nickname", nickname)
},
})
qb.skip(skip ?? 0)
qb.skip(take ?? 20)
const [items, total] = await qb.getManyAndCount()
return { items, total }
}
}
์ด์ ๋ค ๋๋ฌ์ต๋๋ค! ๊ณ ์ ๋ง์ผ์
จ์ต๋๋ค. ์ด์ UserService
์์ ๋ค์๊ณผ ๊ฐ์ด Repository
์ ๊ทผ์ด ๊ฐ๋ฅํฉ๋๋ค.
// user.service.ts
import { Injectable } from "@nestjs/common"
import { In, LessThan } from "typeorm"
import { UserRepository } from "./user.repository"
@Injectable()
export class UserService {
constructor(private readonly userRepository: UserRepository) {}
// ...
public async findAllTest() {
// ID๊ฐ 5๋ณด๋ค ์๊ฑฐ๋, ๋๋ค์์ด alfred, beygee์ธ User๋ฅผ ๋ฐํํฉ๋๋ค!
return this.userRepository.findAll({
where: [{ id: LessThan(5) }, { nickname: In(["alfred", "beygee"]) }],
})
}
}
์ดํ ๋ก Service
์์๋ Repository
where
์กฐ๊ฑด๋ฌธ์ ๊ฐ์ฒด ํํ๋ก ๋ ๋ฆฌ๊ณ , Repo
์์๋ ๊ทธ ์กฐ๊ฑด์ QueryBuilder
๋ก ๋ณํํ์ฌ ์์
ํ๋ ๋ก์ง์ ๊ตฌํํด๋ณด์์ต๋๋ค.
์์ง ์ฌ๊ธฐ์ ๊ธฐ ๊ฐ์ ํด์ผํ ์ฌํญ๋ค์ด ๋ณด์ด๊ณ , ๊ฐ ๊ธธ์ด ํ์ฐธ ๋จ์ ๊ฒ ๊ฐ์ต๋๋ค. ๋ ์ข์ ๊ตฌํ์ฌํญ์ด ์๋ค๋ฉด ๊ณต์ ํด์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค.