엔터티 매니저

ClassBinu·2024년 5월 21일

F-lab

목록 보기
28/65

엔터티 매니저

엔터티 매니저로 모든 엔터티에 대해서 CRUD 작업 가능
엔터티 매니저는 엔터티 레포지토리를 한 곳에 모아 놓은 장소와 같다.

데이터소스를 통해 엔터티 매니저에 접근할 수 있음.

import { DataSource } from "typeorm"
import { User } from "./entity/User"

const myDataSource = new DataSource(/*...*/)
const user = await myDataSource.manager.findOneBy(User, {
    id: 1,
})
user.name = "Umed"
await myDataSource.manager.save(user)

레포지토리

레포지토리는 엔터티 매니저와 비슷한다. 하지만 이건 제한된 구체적인 (특정) 엔터티에만 작동함.
엔터티 매니저를 통해 레포지토리에 접근할 수 있음.

import { User } from "./entity/User"

const userRepository = dataSource.getRepository(User)
const user = await userRepository.findOneBy({
    id: 1,
})
user.name = "Umed"
await userRepository.save(user)

레포지토리 3종류

  • Repository
  • TreeREpository
  • MongoRepository

Find Options

find

userRepository.find({
    select: {
        firstName: true,
        lastName: true,
    },
})

위 코드는 아래 쿼리로 작동함.

SELECT "firstName", "lastName" FROM "user"

relations

메인 엔터티에서 로드하고 서브 엔터티도 같이 로드 가능

userRepository.find({
    relations: {
        profile: true,
        photos: true,
        videos: true,
    },
})
userRepository.find({
    relations: {
        profile: true,
        photos: true,
        videos: {
            videoAttributes: true,
        },
    },
})

위 코드는 아래 쿼리로 작동함.

SELECT * FROM "user"
LEFT JOIN "profile" ON "profile"."id" = "user"."profileId"
LEFT JOIN "photos" ON "photos"."id" = "user"."photoId"
LEFT JOIN "videos" ON "videos"."id" = "user"."videoId"

SELECT * FROM "user"
LEFT JOIN "profile" ON "profile"."id" = "user"."profileId"
LEFT JOIN "photos" ON "photos"."id" = "user"."photoId"
LEFT JOIN "videos" ON "videos"."id" = "user"."videoId"
LEFT JOIN "video_attributes" ON "video_attributes"."id" = "videos"."video_attributesId

where

userRepository.find({
    where: {
        firstName: "Timber",
        lastName: "Saw",
    },
})

쿼리

SELECT * FROM "user"
WHERE "firstName" = 'Timber' AND "lastName" = 'Saw'

이런 식으로 아래 항목 작동함.

핵심은 sql 쿼리로 날릴 수 있는 대부분을 엔터티 매니저(또는 레포지토리)를 통해 DB에 쿼리를 보낼 수 있음.(정확히는 sql로 변환)

or

order

LessThanOrEqual

비교는 이런 식으로

import { LessThanOrEqual } from "typeorm"

const loadedPosts = await dataSource.getRepository(Post).findBy({
    likes: LessThanOrEqual(10),
})

raw

Raw 함수는 TypeORM에서 SQL의 원시(raw) 쿼리 부분을 직접적으로 작성할 수 있게 해주는 기능

find 옵션은 자주 쓰면서 숙지하면 좋을 듯!
https://typeorm.io/find-options

커스텀 레포지토리

오호 이런 식으로 커스텀 레포지토리 만들 수 있다!

// user.repository.ts
export const UserRepository = dataSource.getRepository(User).extend({
    findByName(firstName: string, lastName: string) {
        return this.createQueryBuilder("user")
            .where("user.firstName = :firstName", { firstName })
            .andWhere("user.lastName = :lastName", { lastName })
            .getMany()
    },
})

// user.controller.ts
export class UserController {
    users() {
        return UserRepository.findByName("Timber", "Saw")
    }
}

커스텀 저장소에서 트랜잭션 활용하기

await connection.transaction(async (manager) => {
    // in transactions you MUST use manager instance provided by a transaction,
    // you cannot use global entity managers or repositories,
    // because this manager is exclusive and transactional

    const userRepository = manager.withRepository(UserRepository)
    await userRepository.createAndSave("Timber", "Saw")
    const timber = await userRepository.findByName("Timber", "Saw")
})

트랜잭션은 scope를 지닌 다는 것 기억하기!

엔터티 API

쿼리 러너(Query Runner): 데이터베이스 쿼리를 실행하는 데 사용되는 객체나 인터페이스
엔터티 매니저는 내부적으로 쿼리 러너를 통해 DB와 통신한다.
근데 엔터티 매니저를 통하지 않고 개발자가 직접 쿼리 러너 인스턴스로 저수준에서 DB에 쿼리를 보낼 수 있다.

이거랑 똑같다!

const user = manager.create(User) // same as const user = new User();

create & save vs insert

create는 우선 인스턴스 생성하고 save를 호출할 때 없으면 insert, 있으면 update
insert는 그냥 바로 생성

전자는 엔터티 생성 후 추가적인 처리 및 검증 로직 적용 쉬움
insert는 빨리 추가할 수 있는데 성능도 더 좋긴 함.
근데 엔티티가 이미 존재하면 오류발생할 수 있고, 검증 로직 적용이 어려움.

레포지토리 API

객체 관계 정리하기

DataSource

TypeORM에서 중심이 되는 객체
애플리케이션과 데이터베이스 간의 연결을 관리

얘가 EntityManager, Repository를 생성하고 제공하는 것

EntityManager

개별 데이터베이스 트랜잭션을 관리하는 객체
모든 데이터베이스 CRUD 작업은 EntityManager를 통해 수행됨
DataSource에서 생성됨

Repository

특정 엔티티 타입에 대한 데이터베이스 작업을 더 쉽게 처리하기 위해 추상화된 API를 제공
각 엔티티에 대해 하나의 Repository가 존재하며,
이건 EntityManager를 통해 생성됨
얘는 엔터티에 대한 CRUD 작업을 간소화하고, 엔터티 특정 로직을 캡슐화

Manager

EntityManager의 다른 표현
Repository의 EntityManager 인스턴스를 참조할 때 사용함.
manager - The EntityManager used by this repository.

queryRunner

QueryRunner는 데이터베이스 연결과 트랜잭션 관리를 더 세밀하게 제어할 수 있게 해주며, 복잡한 트랜잭션 시나리오 또는 여러 개의 리포지토리 간 트랜잭션을 관리할 때 주로 사용됩니다.

import { DataSource } from "typeorm";

// 데이터 소스를 설정합니다.
const dataSource = new DataSource({
    type: "mysql",
    host: "localhost",
    port: 3306,
    username: "your_username",
    password: "your_password",
    database: "your_database"
});

async function runDatabaseOperations() {
    // QueryRunner 생성
    const queryRunner = dataSource.createQueryRunner();

    // 연결 시작
    await queryRunner.connect();

    // 트랜잭션 시작
    await queryRunner.startTransaction();

    try {
        // 데이터베이스 작업 수행, 예를 들어 엔터티 저장
        const user = queryRunner.manager.create("User", { firstName: "John", lastName: "Doe" });
        await queryRunner.manager.save(user);

        // 더 복잡한 쿼리를 직접 실행할 수도 있습니다.
        await queryRunner.query(`UPDATE users SET firstName = 'Jane' WHERE lastName = 'Doe'`);

        // 트랜잭션 커밋
        await queryRunner.commitTransaction();
    } catch (error) {
        // 에러 발생 시 롤백
        await queryRunner.rollbackTransaction();
        console.error("Transaction rolled back because of:", error);
    } finally {
        // 연결 해제
        await queryRunner.release();
    }
}

// 함수 실행
runDatabaseOperations();

0개의 댓글