[TypeScript][PostgreSQL][TSyringe][Express]TypeORM 2 사용해보기

소개





  • 이번에는 TypeORM을 이용하여 관계를 해보도록 하겠습니다.

    환경


  • Node.js 버전 16.3.0
  • TypeScript 버전 4.3.4
  • 익스프레스 ver.4.17.1
  • pg ver.8.6.0
  • 티시린지 ver.4.5.0
  • TypeORM ver.0.2.34
  • ts-node ver.10.0.0

  • 외래 키



    아래와 같이 외래 키를 추가할 수 있습니다.

    author.ts




    import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
    
    @Entity('author')
    export class Author {
        @PrimaryGeneratedColumn()
        id: number = -1;
        @Column({
            name: 'name',
            type: 'text',
            nullable: false
        })
        name: string = '';
    }
    


    장르.ts




    import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
    
    @Entity('genre')
    export class Genre {
        @PrimaryGeneratedColumn()
        id: number = -1;
        @Column({
            name: 'name',
            type: 'text',
            nullable: false
        })
        name: string = '';
    }
    


    book.ts




    import {Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn} from "typeorm";
    import { Author } from "./author";
    import { Genre } from "./genre";
    @Entity('book')
    export class Book {
        @PrimaryGeneratedColumn()
        id: number = -1;
        @Column({
            name: 'name',
            type: 'text',
            nullable: false
        })
        name: string = '';
        @ManyToOne(type => Author)
        @JoinColumn({ name: 'authorId', referencedColumnName: 'id' })
        author: Author = new Author();
    
        @ManyToOne(type => Genre)
        @JoinColumn({ name: 'genreId', referencedColumnName: 'id' })
        genre: Genre = new Genre();
        @Column({
            name: 'last_update_date',
            nullable: false,
            type: 'timestamp with time zone'
        })
        lastUpdateDate: Date = new Date();
    }
    


    null 제약이 아님



    한 가지 문제는 "authorId"및 "genreId"에 not null 제약 조건을 추가할 수 없다는 것입니다.

    그래서 속성을 명시적으로 추가합니다.

    book.ts




    ...
    export class Book {
    ...
        @Column({
            name: 'authorId',
            type: 'integer',
            nullable: false
        })
        authorId: number = 0;
    
        @ManyToOne(type => Author)
        @JoinColumn({ name: 'authorId', referencedColumnName: 'id' })
        author: Author = new Author();
    
        @Column({
            name: 'genreId',
            type: 'integer',
            nullable: false
        })
        genreId: number = 0;
    
        @ManyToOne(type => Genre)
        @JoinColumn({ name: 'genreId', referencedColumnName: 'id' })
        genre: Genre = new Genre();
    ...
    }
    


    이제 아래와 같이 행을 추가할 수 있습니다.

    dataContext.ts




    import "reflect-metadata";
    import { singleton } from "tsyringe";
    import { Connection, createConnection } from "typeorm";
    
    // I must not connect two or more times.
    @singleton()
    export class DataContext {
        private connection: Connection|null = null;
        public async getConnection(): Promise<Connection> {
            if(this.connection != null) {
                return this.connection;
            }
            this.connection = await createConnection();
            return this.connection;
        } 
    }
    


    bookService.ts




    import { autoInjectable } from "tsyringe";
    import { Connection, QueryRunner } from "typeorm";
    import { DataContext } from "../data/dataContext";
    import { Author } from "../entities/author";
    import { Book } from "../entities/book";
    import { Genre } from "../entities/genre";
    
    @autoInjectable()
    export class BookService {
        public constructor(private context: DataContext) {
        }
        public async createSeedData() {
            const connection = await this.context.getConnection();
            const queryRunner = connection.createQueryRunner();
            await queryRunner.startTransaction();
            try {
                const authors = await this.createAuthors(connection, queryRunner);
                const genres = await this.createGenres(connection, queryRunner);
                const result = await this.createBooks(connection, queryRunner, authors, genres);
    
                if(result === true) {
                    await queryRunner.commitTransaction();
                }
            } catch (err) {
                console.error(err);
                await queryRunner.rollbackTransaction();
            }
        }
        private async createAuthors(connection: Connection, queryRunner: QueryRunner): Promise<readonly Author[]> {
            const items = await connection.getRepository(Author)
                .createQueryBuilder('author')
                .getMany();
            if(items.length > 0) {
                return items;
            }
            const newItem = new Author();
            newItem.name = 'David Flanagan';
            await queryRunner.manager.save(newItem);
            return [newItem];
        }
        private async createGenres(connection: Connection, queryRunner: QueryRunner): Promise<readonly Genre[]> {
            const items = await connection.getRepository(Genre)
                .createQueryBuilder('genre')
                .getMany();
            if(items.length > 0) {
                return items;
            }
            const programming = new Genre();
            programming.name = 'Programming';
            await queryRunner.manager.save(programming);
            const manga = new Genre();
            manga.name = 'Manga';
            await queryRunner.manager.save(manga);
            const cooking = new Genre();
            cooking.name = 'Cooking';
            await queryRunner.manager.save(cooking);
    
            return [programming, manga, cooking];
        }
        private async createBooks(connection: Connection, queryRunner: QueryRunner,
                authors: readonly Author[], genres: readonly Genre[]): Promise<boolean> {
            const items = await connection.getRepository(Book)
                .createQueryBuilder('book')
                .getMany();
            if(items.length > 0) {
                return false;
            }
            const author = authors[0];
            const genre = genres.find(g => g.name === 'Programming');
            const newItem = new Book();
            newItem.name = 'Javascript: The Definitive Guide';
            newItem.price = 6318;
            newItem.author = author;
            newItem.genre = genre ?? genres[0];
    
            await queryRunner.manager.save(newItem);
            return true;
        }
    }
    


  • Relations - TypeORM
  • Many-to-one / one-to-many relations - TypeORM

  • 내부 조인



    Entity Framework Core에서 "Book"데이터를 가져올 때 외래 키로 "Genre"및 "Author"인스턴스를 설정할 수 있습니다.

    var books = await context.Books
        .Include(b => b.Genre)
        .Include(b => b.Author)
        .ToListAsync();
    


    TypeORM은 어떻습니까?

    자동으로 설정되지 않기 때문에 "innerJoinAndSelect"를 사용합니다.

    bookService.ts




    ...
    export class BookService {
    ...    
        public async getBooks(): Promise<readonly Book[]> {
            const connection = await this.context.getConnection();
            return await connection.getRepository(Book)
                .createQueryBuilder('book')
                .innerJoinAndSelect('book.genre', 'genre')
                .innerJoinAndSelect('book.author', 'author')
                .getMany();
        }
    ...
    }
    


    index.ts




    import "reflect-metadata";
    import express from 'express';
    import { container } from 'tsyringe';
    import { BookService } from "./books/bookService";
    
    const port = 3000;
    const app = express();
    app.use(express.json());
    app.use(express.raw());
    app.use(express.static('clients/public'));
    
    app.get('/books', async (req, res) => {
        const books = container.resolve(BookService);
        res.json(await books.getBooks());
    });
    app.listen(port, () => {
        console.log(`Example app listening at http://localhost:${port}`)
    });
    


    결과




  • Many-to-one / one-to-many relations - TypeORM

  • TypeORM의 통화 유형?



    "책"테이블에 "가격"열을 추가하고 싶습니다.
    하지만 어떤 유형을 사용해야 합니까?

    C#에서는 보통 "decimal"을 사용합니다.
    그러나 JavaScript 및 TypeScript에는 유형이 없습니다.
    그리고 "BigInt"는 정수만 처리할 수 있습니다.

    Dinero.js 과 같은 통화 유형을 처리하기 위한 일부 라이브러리가 있습니다.
    그러나 그들은 JavaScript|TypeScript 세계에서만 사용하기 위한 것입니다.

    결국 "숫자"유형을 사용하기로 결정했습니다.

    book.ts




    export class Book {
    ...
        @Column({
            name: 'price',
            type: 'money',
            nullable: false
        })
        price: number = 0;
    }
    


  • Javascript: The Definitive Guide
  • BigInt - MDN
  • 좋은 웹페이지 즐겨찾기