(최종 프로젝트 진행중)
어제의 Nest.js에 이어 오늘은 TypeORM을 깊게 들여다봤다.
공식 문서를 보러가기 전에 TypeORM의 소스 파일들을 직접 옮겨다니며 이리저리 이해해보고 가설을 세워보고 무식하게 실험으로 증명해보며 적고 정리하는 시간을 가졌다. 노트에 정리한 양이 너무 많기도 하고 시간이 오래 걸려서, 과연 효율적으로 공부한 것인지 의문이긴 하지만 덕분에 TypeORM이 어떻게 구성되어있는지 전반적으로 이해할 수 있었다. 어제 Nest.js보다 더 제대로 개념을 잡게 된 것 같다.
내일(모레) 할 일:
- One to Many 등 관계 설정법을 마저 알아보고 Entity 작성하기
- GitHub Actions 대략이라도 세팅 해보고 팀원과 얘기 나누기
※ 이하는 스스로 공부하며 적어둔 노트이며 불확실한 내용이 있을 수 있습니다. 학습용으로 적합하지 않음을 유념해주세요. ※
에러 QueryFailedError: ER_NONUNIQ_TABLE: Not unique table/alias: 'article’ ✔️
에러 QueryFailedError: ER_NONUNIQ_TABLE: Not unique table/alias: 'article’ ✔️
발생 상황: 조회순으로 데이터를 정렬해 가져오기 위해 board의 controller와 service, module에 기능을 더하고 TypeORM 커스텀 레파지토리 메소드도 만들었다. GET http://localhost:3000/board/hot-articles
로 API 호출을 하였는데 위 에러가 뜸.
추가한 코드는 이렇다:
// src/board/board.controller.ts
@Controller('board')
export class BoardController {
constructor(private readonly boardService: BoardService) {}
...
@Get('/articles/:id')
async getArticleById(@Param('id') articleId: number) {
return await this.boardService.getArticleById(articleId);
}
}
// src/board/board.service.ts
@Injectable()
export class BoardService {
constructor(private articleRepository: ArticleRepository) {}
...
async getHotArticles() {
return await this.articleRepository.getArticlesByViewCount();
}
}
// src/board/board.module.ts
import { ArticleRepository } from './article.repository';
...
@Module({
imports: [TypeOrmModule.forFeature([Article])],
controllers: [BoardController],
providers: [BoardService, ArticleRepository],
})
export class BoardModule {}
// 커스텀 레파지토리 전체 추가
// src/board/article.repository.ts
import { Injectable } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { Article } from './article.entity';
@Injectable()
export class ArticleRepository extends Repository<Article> {
constructor(private dataSource: DataSource) {
super(Article, dataSource.createEntityManager());
}
async getArticlesByViewCount() {
const result = await this.createQueryBuilder()
.select('article')
.from(Article, 'article') // 이 부분이 문제였음.
.orderBy('article.view', 'DESC')
.getMany();
return result;
}
}
에러 메세지 전문:
[Nest] 15736 - 2023. 03. 04. 오후 3:09:32 ERROR [ExceptionsHandler] ER_NONUNIQ_TABLE: Not unique table/alias: 'article'
QueryFailedError: ER_NONUNIQ_TABLE: Not unique table/alias: 'article'
at Query.<anonymous> (C:\Users\USER\Desktop\Sparta\04.3_nest_practice_with_typeorm\src\driver\mysql\MysqlQueryRunner.ts:222:33)
at Query.<anonymous> (C:\Users\USER\Desktop\Sparta\04.3_nest_practice_with_typeorm\node_modules\mysql\lib\Connection.js:526:10)
at Query._callback (C:\Users\USER\Desktop\Sparta\04.3_nest_practice_with_typeorm\node_modules\mysql\lib\Connection.js:488:16)
at Query.Sequence.end (C:\Users\USER\Desktop\Sparta\04.3_nest_practice_with_typeorm\node_modules\mysql\lib\protocol\sequences\Sequence.js:83:24)
at Query.ErrorPacket (C:\Users\USER\Desktop\Sparta\04.3_nest_practice_with_typeorm\node_modules\mysql\lib\protocol\sequences\Query.js:92:8)
at Protocol._parsePacket (C:\Users\USER\Desktop\Sparta\04.3_nest_practice_with_typeorm\node_modules\mysql\lib\protocol\Protocol.js:291:23)
at Parser._parsePacket (C:\Users\USER\Desktop\Sparta\04.3_nest_practice_with_typeorm\node_modules\mysql\lib\protocol\Parser.js:433:10)
at Parser.write (C:\Users\USER\Desktop\Sparta\04.3_nest_practice_with_typeorm\node_modules\mysql\lib\protocol\Parser.js:43:10)
at Protocol.write (C:\Users\USER\Desktop\Sparta\04.3_nest_practice_with_typeorm\node_modules\mysql\lib\protocol\Protocol.js:38:16) at Socket.<anonymous> (C:\Users\USER\Desktop\Sparta\04.3_nest_practice_with_typeorm\node_modules\mysql\lib\Connection.js:88:28)
시도: 알아보니 두 테이블 이상에서 같은 이름의 컬럼을 조회하려고 할 때 발생하는 SQL 에러라고 한다. 두 테이블을 조인해 조회하려고 할 때 자주 발생된다는 듯 하다. 필드명이 같아도 테이블명이 다르면 식별이 가능할텐데 하는 의문점이 남았지만 SQL 쿼리문 문제라는 데서 힌트를 얻어 커스텀 레파지토리 코드를 다시 살펴보았다.
시도1 : 테이블 alias를 ‘article’에서 ‘a’로 바꿈.
async getArticlesByViewCount() {
const result = await this.createQueryBuilder()
.select('article')
.from(Article, 'article')
.from(Article, 'a')
.orderBy('article.view', 'DESC')
.orderBy('a.view', 'DESC')
.getMany();
return result;
}
⇒ 에러 QueryFailedError: ER_BAD_FIELD_ERROR: Unknown column 'article' in 'field list’ 발생. 분명히 되어야 할 것 같은데 왜 안되는지 쿼리문이 궁금해짐.
해결: 아래의 에러로 이어져 해결됨.
에러 QueryFailedError: ER_BAD_FIELD_ERROR: Unknown column 'article' in 'field list’ ✔️
에러 QueryFailedError: ER_BAD_FIELD_ERROR: Unknown column 'article' in 'field list’ ✔️
(지난 에러에 이어서)
시도 2: 쿼리문을 출력해 볼 수 있도록 코드 수정
async getArticlesByViewCount() {
const result = await this.createQueryBuilder()
.select('article')
.from(Article, 'a')
.orderBy('a.view', 'DESC');
console.log(result.getSql()); // result.getQuery();
return result.getMany();
}
=>
SELECT article FROM `articles` `Article`, `articles` `a` WHERE `a`.`deletedAt` IS NULL ORDER BY `a`.`view` DESC
⇒ FROM 부분이 이상하다.
시도 3:
TypeORM 공식 문서 중 https://typeorm.io/select-query-builder#what-are-aliases-for에 따르면…
createQueryBuilder('user')
= createQueryBuilder().select("user").from(User, "user")
= SELECT ... FROM users user
이고,
createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.name = :name", { name: "Timber" })
= SELECT ... FROM users user WHERE user.name = 'Timber'
이라고 한다. 뭐라구?! select(’user’)하면 user 컬럼만 가져와야 하는 거 아냐? 게다가 where만 빼면 나랑 거의 똑같은 코드인데 얘는 잘 되는 거라고 하니…
…
시도 6:
왜인지 모르겠으나 이렇게 하면 not unique table/alias 에러로 인해 안 되고: ✔️
//
async getArticlesByViewCount() {
const result = await this.createQueryBuilder()
.select('a')
.from(Article, 'a')
.orderBy('a.view', 'DESC');
console.log(result.getSql());
return result.getMany();
}
=>
SELECT `article`.`id` AS `article_id`, `article`.`author` AS `article_author`, `article`.`title` AS `article_title`, `article`.`content` AS `article_content`, `article`.`view` AS `article_view`, `article`.`createdAt` AS `article_createdAt`, `article`.`updatedAt` AS `article_updatedAt`, `article`.`deletedAt` AS `article_deletedAt`
FROM `articles` `Article`, `articles` `article`
WHERE `article`.`deletedAt` IS NULL
ORDER BY `article`.`view` DESC
이렇게 해주면 된다:
//
async getArticlesByViewCount() {
const result = await this.createQueryBuilder()
.select('a')
.from(Article, 'a')
.orderBy('a.view', 'DESC');
console.log(result.getSql());
return result.getMany();
}
=>
SELECT `a`.`id` AS `a_id`, `a`.`author` AS `a_author`, `a`.`title` AS `a_title`, `a`.`content` AS `a_content`, `a`.`view` AS `a_view`, `a`.`createdAt` AS `a_createdAt`, `a`.`updatedAt` AS `a_updatedAt`, `a`.`deletedAt` AS `a_deletedAt`
FROM `articles` `Article`, `articles` `a`
WHERE `a`.`deletedAt` IS NULL
ORDER BY `a`.`view` DESC
⇒ from에 테이블이 두 개인 게 문제.
Query Builder를 생성하는 데에는
- dataSource.createQueryBuilder()와
- dataSource.manager.createQueryBuilder(),
- dataSource.getRepository().createQueryBuilder()
이렇게 세 가지가 있는데, 위의 코드는 그 중 3번째인 Repository<Article>를 사용하는 방식이기 때문에 발생하는 문제인 것 같다. 즉 이미 Repository 객체가 FROM 테이블로 ‘articles’를 ‘Article’이라는 alias로 지정해 두었는데, 거기다 쓸 데 없이 .from(Article, ‘article’)를 덧붙이는 형국인 것이다.
시도 7: 위의 가정을 테스트하기 위해 .from(Article, ‘article’) 부분을 지우고 실행해보았다.
async getArticlesByViewCount() {
const result = await this.createQueryBuilder()
.select('article')
// .from(Article, 'article')
.orderBy('article.view', 'DESC');
console.log(result.getSql());
return result.getMany();
}
=>
SELECT article
FROM `articles` `Article`
WHERE `Article`.`deletedAt` IS NULL
ORDER BY article.view DESC
=>
QueryFailedError: ER_BAD_FIELD_ERROR: Unknown column 'article' in 'field list'
⇒ 오 일단 FROM에 나타나는 테이블이 하나로 줄어들었다. ‘Repository.createQueryBuilder()로 쿼리할 땐 이미 FROM으로 지정된 테이블이 있어서 .from을 덧붙이는 것은 사족이다’는 생각을 입증했다.
⇒ 덧붙여 이 때는 .select(’article’)이 컬럼을 선택하는 것으로 인식됐음. ✔️
⇒ (왜냐면 테이블 alias는 암묵적으로 ‘articles’ AS ‘Article’
이라고 지정된 상태이기 때문. .select()에서의 ‘article’을 테이블 alias인 Article이라고 인식하지 않아서 ‘테이블이 아니면 필드명이겠군’하고 찾았는데 그런 필드명이 없는 것이다. 대신 .select(’Article’)이라고 수정하면 ‘테이블 Article을 의미하는 것이 맞군. 그렇다면 전체 필드를 가져오자’라고 인식하고 전체 필드를 잘 가져온다.) ✔️
시도 8: .select(’article’)도 삭제하니 원하는 대로 ‘조회순’이 높은 것부터 모든 목록이 잘 출력되었다!
async getArticlesByViewCount() {
const result = await this.createQueryBuilder()
// .select('article')
// .from(Article, 'article')
.orderBy('article.view', 'DESC');
console.log(result.getSql());
return result.getMany();
}
=>
SELECT `Article`.`id` AS `Article_id`, `Article`.`author` AS `Article_author`, `Article`.`title` AS `Article_title`, `Article`.`content` AS `Article_content`, `Article`.`view` AS `Article_view`, `Article`.`createdAt` AS `Article_createdAt`, `Article`.`updatedAt` AS `Article_updatedAt`, `Article`.`deletedAt` AS `Article_deletedAt`
FROM `articles` `Article`
WHERE `Article`.`deletedAt` IS NULL
ORDER BY article.view DESC
여태까지 정리:
- TypeORM의 개념 Repository를 이용한 커스텀 쿼리(createQueryBuilder)는, 디폴트로
FROM `DB 테이블명` AS `해당 테이블 클래스 명`
쿼리가 설정되어 있다. (굳이굳이 '해당 테이블 클래스 명'을 Article에서 Article3로 몽땅 변경한 후 실행해본 결과, 짐작대로FROM `articles` `Article3`
이렇게 쿼리문이 짜임을 확인함. ) 디폴트 값은 그렇고, createQueryBuilder()에 인수로 원하는 alias명을 넣어줄 수도 있다.@Injectable() export class ArticleRepository extends Repository<Article> { constructor(private dataSource: DataSource) { super(Article, dataSource.createEntityManager()); } async getArticlesByViewCount() { const result = await this.createQueryBuilder('a') .select('a.view') .orderBy('A.view', 'DESC'); console.log(result.getSql()); return result.getMany(); } } => SELECT `a`.`view` AS `a_view` FROM `articles` `a` // 디폴트는 FROM `articles` `Article` 이었음. WHERE `a`.`deletedAt` IS NULL ORDER BY A.view DESC
- 이 때 .select()에는 조회고자 하는 컬럼명을 넣는 것이 맞고, ‘Article.view’처럼 풀네임으로 적어줘야 한다. 일반 SQL 쿼리문에서처럼 테이블 추론 그런 거 없다.
- 제일 처음에 왜 ‘article’ 부분에서 “table/alias가 unique하지 않다”고 에러가 떴냐면,
FROM `articles` `Article`, `articles` `article`
같은 쿼리 결과가 나왔을 때 'Article'과 'article'을 대소문자 구분하지 않고 같은 alias로 인식해서이다.
- 그래서 시도6에서 임의로 .from(Article, ‘a’)라고 alias를 바꿔줬을 때
FROM `articles` `Article`, `articles` `a`
가 되어 ‘unique하지 않은 able/alias’ 에러가 해결되었던 것이다.
- .select() 부분에서 단일 문자열을 인수로 넣어줄 때, ‘a’하면 ‘*’과 같은 의미가 되고 ‘a.view’같이 풀네임 컬럼명 하나를 넣어주면 그 컬럼만 조회해온다.
- 여러 컬럼을 조회하고 싶을 땐
.select(['a.id', 'a.view'])
처럼 배열 안에 넣어주면 된다.
- 마지막으로 진짜 사소한 거긴 한데,
// 잘 동작하는 쿼리문 async getArticlesByViewCount() { const result = await this.createQueryBuilder() .select(['a.id', 'a.view']) .from(Article, 'a') .orderBy('A.view', 'DESC'); console.log(result.getSql()); return result.getMany(); } => SELECT `a`.`id` AS `a_id`, `a`.`view` AS `a_view` FROM `articles` `Article`, `articles` `a` WHERE `a`.`deletedAt` IS NULL ORDER BY A.view DESC => (출력 결과 예시) [ { "id": 4, "view": 13 }, { "id": 1, "view": 12 }, { "id": 5, "view": 0 }, { "id": 3, "view": 0 } ]
⇒ 잘 보면 쿼리문이 만들어질 때, SELECT 절과 FROM 절에서는 넘겨주는 문자열이 모두 백틱(`)으로 감싸여져서 대소문자 구분을 확실히 해야 한다.
⇒ 그런데 ORDER BY 파트는 옵션을 문자열로 줬어도 쿼리문에는 백틱으로 감싸이지 않은 raw SQL, 즉 대소분자를 구분하지 않는 상태로 주어지기 때문에 대소문자 구분에서 자유롭다. 그래서 위와 같이 ‘A.view’로 대문자로 써줘도 잘 작동한다.
(얼레벌레 TypeORM 공식 문서 참고: https://typeorm.io/select-query-builder#getting-values-using-querybuilder)
데이터베이스 연결할 때 옵션 - entities, synchronize, logging
데이터베이스 연결할 때 옵션 - entities, synchronize, logging
import "reflect-metadata";
import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";
createConnection({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "admin",
database: "test",
entities: [
Photo
],
synchronize: true,
logging: false
}).then(connection => {
// here you can start to work with your entities
}).catch(error => console.log(error));
synchronize
를 설정하면 애플리케이션을 실행할 때마다 엔터티가 데이터베이스와 동기화된다.
- logging: true나 logging: [’error’, ‘query’] 등을 선택할 수 있다. 콘솔에 쿼리를 찍어준다.
query
- logs all queries.error
- logs all failed queries and errors.schema
- logs the schema build process.warn
- logs internal orm warnings.info
- logs internal orm informative messages.log
- logs internal orm log messages.
- entities가 여러 개 추가될 경우, 하나의 폴더로 모으고 다음과 같이 폴더를 통째로 연결지어줄 수 있다:
entities: [ __dirname + "/entity/*.js" ],
(참고: https://orkhan.gitbook.io/typeorm/readme_ko#undefined-11)
(https://orkhan.gitbook.io/typeorm/docs/logging#enabling-logging)
[TypeORM] EntityManager 써보기
[TypeORM] EntityManager 써보기
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)
EntityManager는 기본적으로 entity repository들을 다 담아놓은 묶음이기 때문에, 저렇게 Article과 같이 원하는 테이블(을 가리키는 TypeORM 모델 클래스명)을 지정하여 불러준다.
이런 커스텀 레파지토리 기본 골격이 있다고 할 때:
import { Injectable } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { Article } from './article.entity';
@Injectable()
export class ArticleRepository extends Repository<Article> {
constructor(private dataSource: DataSource) {
// super(Article, dataSource.createEntityManager());
super(Article, dataSource.manager);
}
/*
...
원하는 쿼리 메소드 작성
...
*/
}
⇒ dataSource.manager와 dataSource.createEntityManager() 얘네 둘이 바로 EntityManager이다. 둘 다 똑같이 EntityManager를 반환한다.
CRUD는 따로 공부하기로 한다.
[TypeORM] Repository 써보기
[TypeORM] Repository 써보기
EntityManager에서 하나의 테이블을 지정해서 불러오면 그게 바로 Repository가 된다.
import { User } from "./entity/User"
const userRepository = dataSource.getRepository(User)
const user = await userRepository.findOneBy({ //
id: 1,
})
user.name = "Umed"
await userRepository.save(user)
⇒ entity Manager였다면 findOneBy(User, {})
처럼 테이블명을 함께 써줘야 했겠지만, Repository는 그 자체로 이미 하나의 테이블을 가리키고 있으므로 findOneBy에서 테이블을 또 지정해줄 필요는 없다.
[TypeORM] DataSource와 EntityManager, Repository의 관계성
[TypeORM] DataSource와 EntityManager, Repository의 관계성
1. 커스텀 레파지토리를 만들다가…
발견한 사실들을 정리한다:
@Injectable()
export class ArticleRepository extends Repository<Article> {
constructor(private dataSource: DataSource) {
// super(Article, dataSource.createEntityManager());
super(Article, dataSource.manager); // 이것도 문제 없이 작동함.
// => repository.d.ts에서의 정의:
// constructor(target: EntityTarget<Entity>, manager: EntityManager, queryRunner?: QueryRunner): Repository<Entity>;
// this.dataSource = dataSource; // 이 줄이 없어도 문제없이 동작.
// dataSource.manager.connection.manager.connection.manager....
}
async updateTitleById(id: number, new_title: string) {
const article = await this.dataSource.manager.findOneBy(Article, {
id,
});
article.title = new_title;
await this.dataSource.manager.save(article);
}
}
- 생성자 이후의 메소드에서 this는 Repository<Article>이다. (EntityManager나 DataSource가 아님)
- 여기 constructor에서 this.dataSource = dataSource라고 정의해주지 않아도 (아무것도 하지 않아도) 다른 메소드에서 this.dataSource로 접근이 가능하다!
- 어떻게 this.dataSource가 접근 가능한지 모르겠다. 이건 다시 말해 Repository.dataSource라는 건데, Repository 클래스에게는 속성이 target, manager, queryRunner밖에 정의되어 있지 않다.
⇒ 꼭지 3까지 가보면 알겠지만 ‘어째서 Repository.dataSource가 호출될 수 있는지 알 수 없음’이 결론이다.
- 어떻게 this.dataSource가 접근 가능한지 모르겠다. 이건 다시 말해 Repository.dataSource라는 건데, Repository 클래스에게는 속성이 target, manager, queryRunner밖에 정의되어 있지 않다.
2. 실제로 typeorm/repository/Repository.js에 가서 Repository 클래스 정의를 보았다
class Repository {
// -----------------------------------------------
// Constructor
// -----------------------------------------------
constructor(target, manager, queryRunner) {
this.target = target;
this.manager = manager;
this.queryRunner = queryRunner;
}
// -----------------------------------------------
// Accessors
// -----------------------------------------------
/**
* Entity metadata of the entity current repository manages.
*/
get metadata() {
return this.manager.connection.getMetadata(this.target);
}
// -----------------------------------------------
// Public Methods
// -----------------------------------------------
/**
* Creates a new query builder that can be used to build a SQL query.
*/
createQueryBuilder(alias, queryRunner) {
return this.manager.createQueryBuilder(this.metadata.target, alias || this.metadata.targetName, queryRunner || this.queryRunner);
}
...
}
이렇게 생성자가 그저 ‘타겟’ Entity(=테이블), EntityManager, QueryRunner 인자를 받아 그대로 속성으로 지정만 해주고 있음을 알 수 있다.
- 그렇담 this.dataSource는 어떻게 가능한 거지? ⇒ 생성자 아래의 metadata()가 수상하다.
- createQueryBuilder() 내에서도 어떻게 this.metadata.target이 사용 가능한지,
- 왜 this.target을 쓰지 않고 굳이 metadata를 거친 것을 사용하는 건지.
3. get matadata()를 분석해보았다
get metadata() {
return this.manager.connection.getMetadata(this.target);
}
일단 get 선언으로 인해 metadata() 메소드는 getter로써 단축형 this.metadata
로 호출이 가능하다.
더 분석하기 전에 먼저 알아둬야 할 점은
(마지막 꼭지 “5. 최종 관계 정리”를 참고하면)
this.manager = EntityManager
this.manager.connection = DataSource
this.manager.connection.getMetadata(Article) = EntityMetadata
이리하여 어쨌든 get metadata()의 리턴 타입은 EntityMetadata가 된다.
- createQueryBuilder()에 따르면 EntityMetadata 클래는 최소한 target, targetName 속성을 가지고 있어야 하는데…
가지고 있다:
// Repository.js createQueryBuilder(alias, queryRunner) { return this.manager.createQueryBuilder(this.metadata.target, alias || this.metadata.targetName, queryRunner || this.queryRunner); } // EntityMetadata.d.ts /** * Target class to which this entity metadata is bind. * Note, that when using table inheritance patterns target can be different rather then table's target. * For virtual tables which lack of real entity (like junction tables) target is equal to their table name. */ // => 이게 바로 Article 같은 클래스명인 것이고, target: Function | string; /** * Gets the name of the target. */ targetName: string;
EntityManager의 creqteQueryBuilder 사용 예:
EntityManager.createQueryBuilder(Article, 'article') .select(['article.id', 'article.title']) .getMany();
⇒
Article
= 실제 테이블명과 상관 없는, Entity(=클래스명) = EntityMetadata.target⇒
‘article’
= 실제 테이블명과 상관 없는, 쿼리문 실행시에(만) AS 뒤에 붙일 alias = EntityMetadata.targetName
그리고 EntityMetadata까지 살펴보았지만…
⇒ 어디에도 보이지 않는 곳에서, 암묵적으로 Repository 클래스 내에 Repository.dataSource를 정의해주는 곳이 없다는 것이 결론이다.
- 일단 Repository의 속성값엔 없음.
- 속성이 아니라면 setter나 getter 메소드일 수도? → getter 메소드도 없음.
- 유일한 getter인 metadata()도 단지 EntityMetadata를 반환할 뿐이고
- EntityMetadata 클래스에도 DataSource를 따로 지정해주지 않는다. (connection: DataSource라는 속성만 있을 뿐이다.)
- Repository의 부모 클래스에서 정의되었을 수도 있지 않을까? → Repository가 따로 상속받는 클래스가 없음.
- Repository.dataSource는 대체 왜 작동한단 말인가.
4. 다시 생각해보니까…
제일 처음 의문점에서
async updateTitleById(id: number, new_title: string) {
const article = await this.dataSource.manager.findOneBy(Article, {
const article = await this.manager.findOneBy(Article, {
id,
});
article.title = new_title;
await this.dataSource.manager.save(article);
await this.manager.save(article);
}
여기서 결국 EntityManager의 쿼리 메소드를 쓰고 싶은 거라면 굳이 Repository.dataSource.manager.findOneBy
로 돌아돌아 갈 게 아니라 Repository.manager.findOneBy
처럼 쓰면 되겠더라는 것을 깨달았다.
5. 어쨌든 최종적으로 TypeORM의 세 메인 객체 DataSource와 EntityManager, Repository 관계성을 정리한다
- DataSource.manager ⇒ EntityManager
- EntityManager.connection ⇒ DataSource
따라서 DataSource.manager.connection.manager.connection.manager…로 물고 물릴 수 있는 관계이다.
- Repository.manager ⇒ EntityManager
- EntityMetadata.connection ⇒ DataSource
- DataSource.getRepository ⇒ Repository
DataSource가 왜 필요하냐면 EntityManager에 접근하려다보니.
EntityManager가 왜 필요하냐면 createQueryBuilder() 등 쿼리 메소드를 사용하려다 보니.
그런데 Repository에서도 곧바로 EntityManager로 접근할 수 있다. 즉, DataSource.manager도 되지만 Repository.manager도 똑같이 된다.
일단 한 티켓을 따서 브랜치를 만들고, 여러 티켓을 해결했을 경우 풀 리퀘 제목을 [SPA-33][SPA-34] 이런 식으로 해주면 Linear에도 두 티켓 완료라고 반영이 된다고 한다!
Uploaded by N2T