Skip to content

Latest commit

ย 

History

History
1185 lines (841 loc) ยท 44.3 KB

File metadata and controls

1185 lines (841 loc) ยท 44.3 KB

TypeORM์€ NodeJS, Browser, Cordova, PhoneGap, Ionic, React Native, NativeScript, Expo ๋ฐ Electron ํ”Œ๋žซํผ์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ORM์ด๋ฉฐ TypeScript ๋ฐ JavaScript(ES5, ES6, ES7, ES8)์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. TypeORM์˜ ๋ชฉํ‘œ๋Š” ํ•ญ์ƒ ์ตœ์‹  JavaScript ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜๊ณ  ๋ช‡ ๊ฐœ์˜ ํ…Œ์ด๋ธ”์ด ์žˆ๋Š” ์ž‘์€ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์—์„œ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์žˆ๋Š” ๋Œ€๊ทœ๋ชจ ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์— ์ด๋ฅด๊ธฐ๊นŒ์ง€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“  ์ข…๋ฅ˜์˜ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ๊ฐœ๋ฐœํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๋Š” ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

TypeORM์€ ํ˜„์žฌ ์กด์žฌํ•˜๋Š” ๋‹ค๋ฅธ ๋ชจ๋“  JavaScript ORM๊ณผ ๋‹ฌ๋ฆฌ Active Record ๋ฐ Data Mapper ํŒจํ„ด์„ ๋ชจ๋‘ ์ง€์›ํ•œ๋‹ค. ์ฆ‰, ๊ณ ํ’ˆ์งˆ์˜ ๋А์Šจํ•˜๊ฒŒ ๊ฒฐํ•ฉ๋œ ํ™•์žฅ ๊ฐ€๋Šฅํ•˜๊ณ  ์œ ์ง€ ๊ด€๋ฆฌ ๊ฐ€๋Šฅํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐ€์žฅ ์ƒ์‚ฐ์ ์ธ ๋ฐฉ์‹์œผ๋กœ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

TypeORM์€ Hibernate, Doctrine ๋ฐ Entity Framework์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ORM์˜ ์˜ํ–ฅ์„ ๋งŽ์ด ๋ฐ›๋Š”๋‹ค.

ํŠน์ง•

  • DataMapper์™€ ActiveRecord์„ ๋ชจ๋‘ ์ง€์›.
  • ํ•ญ๋ชฉ ๋ฐ ์—ด.
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ„ ์—ด ์œ ํ˜•.
  • ์—”ํ„ฐํ‹ฐ ๊ด€๋ฆฌ์ž.
  • ๋ ˆํฌ์ง€ํ† ๋ฆฌ ๋ฐ ์‚ฌ์šฉ์ž ์ง€์ • ๋ ˆํฌ์ง€ํ† ๋ฆฌ.
  • ๋ช…ํ™•ํ•œ ๊ฐ์ฒด ๊ด€๊ณ„ํ˜• ๋ชจ๋ธ.
  • ์—ฐ๊ด€(๊ด€๊ณ„).
  • Eager&lazy ๊ด€๊ณ„.
  • ๋‹จ๋ฐฉํ–ฅ, ์–‘๋ฐฉํ–ฅ ๋ฐ ์ž์ฒด ์ฐธ์กฐ ๊ด€๊ณ„.
  • ๋‹ค์ค‘ ์ƒ์† ํŒจํ„ด์„ ์ง€์›.
  • cascade.
  • ์ƒ‰์ธ.
  • transaction.
  • ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋ฐ ์ž๋™ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑ.
  • ์—ฐ๊ฒฐ ํ’€๋ง.
  • ๋ณต์ œ.
  • ๋‹ค์ค‘ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์‚ฌ์šฉ.
  • ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์œ ํ˜• ์ž‘์—….
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ„, ์Šคํ‚ค๋งˆ ๊ฐ„์˜ ์ฟผ๋ฆฌ.
  • ์šฐ์•„ํ•œ ๋ฌธ๋ฒ•๊ณผ ์œ ์—ฐํ•˜๊ณ  ๊ฐ•๋ ฅํ•œ ์ฟผ๋ฆฌ ๋นŒ๋”.
  • left join๊ณผ inner join.
  • join์„ ์‚ฌ์šฉํ•˜๋Š” ์ฟผ๋ฆฌ์— ๋Œ€ํ•œ ์ ์ ˆํ•œ ํŽ˜์ด์ง€๋„ค์ด์…˜.
  • ์ฟผ๋ฆฌ ์บ์‹ฑ.
  • ์›์ƒํƒœ์˜ ๊ฒฐ๊ณผ ์ŠคํŠธ๋ฆฌ๋ฐ.
  • ๋กœ๊น….
  • ๋ฆฌ์Šค๋„ˆ ๋ฐ ๊ตฌ๋…์ž(hooks).
  • ํด๋กœ์ € ํ…Œ์ด๋ธ” ํŒจํ„ด ์ง€์›.
  • ๋ชจ๋ธ ๋˜๋Š” ๋ณ„๋„์˜ ์„ค์ • ํŒŒ์ผ์—์„œ ์Šคํ‚ค๋งˆ ์„ ์–ธ.
  • MySQL / MariaDB / Postgres / CockroachDB / SQLite / Microsoft SQL Server / Oracle / SAP Hana / sql.js๋ฅผ ์ง€์›.
  • MongoDB NoSQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ง€์›.
  • NodeJS / Browser / Ionic / Cordova / React Native / NativeScript / Expo / Electron ํ”Œ๋žซํผ์—์„œ ์ž‘๋™.
  • TypeScript ๋ฐ JavaScript ์ง€์›.
  • ์ƒ์„ฑ๋œ ์ฝ”๋“œ๋Š” ์šฐ์ˆ˜ํ•œ ์„ฑ๋Šฅ๊ณผ ์œ ์—ฐํ•จ์„ ๊ฐ€์ง€๋ฉฐ, ํด๋ฆฐํ•˜๊ณ  ์œ ์ง€ ๊ด€๋ฆฌ๊ฐ€ ์šฉ์ด.
  • ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๋ชจ๋ฒ” ์˜ˆ์‹œ๋ฅผ ๋”ฐ๋ฆ„.
  • CLI.

๊ฒŒ๋‹ค๊ฐ€...

TypeORM์„ ์‚ฌ์šฉํ•˜๋ฉด ๋‹น์‹ ์˜ ๋ชจ๋ธ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณด์ธ๋‹ค.

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";

@Entity()
export class User {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    firstName: string;

    @Column()
    lastName: string;

    @Column()
    age: number;

}

๋‹น์‹ ์˜ ๋„๋ฉ”์ธ ๋กœ์ง์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค:

const repository = connection.getRepository(User);

const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.age = 25;
await repository.save(user);

const allUsers = await repository.find();
const firstUser = await repository.findOne(1); // find by id
const timber = await repository.findOne({ firstName: "Timber", lastName: "Saw" });

await repository.remove(timber);

๋˜ํ•œ ActiveRecord๊ตฌํ˜„์„ ์‚ฌ์šฉํ•˜๋Š”๊ฑธ ์„ ํ˜ธํ•˜๋Š” ๊ฒฝ์šฐ, ๋‹น์‹ ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from "typeorm";

@Entity()
export class User extends BaseEntity {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    firstName: string;

    @Column()
    lastName: string;

    @Column()
    age: number;

}

๋‹น์‹ ์˜ ๋„๋ฉ”์ธ ๋กœ์ง์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค:

const user = new User();
user.firstName = "Timber";
user.lastName = "Saw";
user.age = 25;
await user.save();

const allUsers = await User.find();
const firstUser = await User.findOne(1);
const timber = await User.findOne({ firstName: "Timber", lastName: "Saw" });

await timber.remove();

์„ค์น˜

  1. npm ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•œ๋‹ค:

    npm install typeorm --save

  2. reflect-metadata ์‹ฌ(shim)์„ ์„ค์น˜ํ•œ๋‹ค:

    npm install reflect-metadata --save

    ๊ทธ๋ฆฌ๊ณ  ๊ทธ๊ฒƒ์„ app (์˜ˆ: app.ts)์˜ ์ „์—ญ ์œ„์น˜์— ๋ถˆ๋Ÿฌ์™€์•ผ ํ•œ๋‹ค:

    import "reflect-metadata";

  3. ๋…ธ๋“œ ํƒ€์ž…์„ ์„ค์น˜ํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค:

    npm install @types/node --save-dev

  4. DB ๋“œ๋ผ์ด๋ฒ„ ์„ค์น˜๋ฅผ ์„ค์น˜ํ•œ๋‹ค:

    • MySQL ๋˜๋Š” MariaDB์˜ ๊ฒฝ์šฐ

      npm install mysql --save (you can install mysql2 instead as well)

    • for PostgreSQL๋˜๋Š” CockroachDB์˜ ๊ฒฝ์šฐ

      npm install pg --save

    • SQLite์˜ ๊ฒฝ์šฐ

      npm install sqlite3 --save

    • Microsoft SQL Server์˜ ๊ฒฝ์šฐ

      npm install mssql --save

    • sql.js์˜ ๊ฒฝ์šฐ

      npm install sql.js --save

    • Oracle์˜ ๊ฒฝ์šฐ

      npm install oracledb --save

      Oracle ๋“œ๋ผ์ด๋ฒ„๋ฅผ ์ž‘๋™์‹œํ‚ค๋ ค๋ฉด ํ•ด๋‹น ์‚ฌ์ดํŠธ์˜ ์„ค์น˜ ์ง€์นจ์„ ๋”ฐ๋ผ์•ผ ํ•œ๋‹ค.

    • SAP Hana์˜ ๊ฒฝ์šฐ

      npm i @sap/hana-client
      npm i hdb-pool
      

      Neptune Software์˜ ํ›„์›์œผ๋กœ SAP Hana ์ง€์›์ด ๊ฐ€๋Šฅํ•ด์กŒ๋‹ค.

    • MongoDB (experimental)์˜ ๊ฒฝ์šฐ

      npm install mongodb@^5.2.0 --save

    • NativeScript, react-native, Cordova์˜ ๊ฒฝ์šฐ

      ์ง€์›๋˜๋Š” ํ”Œ๋žซํผ ๋ฌธ์„œ ํ™•์ธ

    ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋”ฐ๋ผํ•˜๋‚˜๋งŒ ์„ค์น˜

TypeScript ํ™˜๊ฒฝ ์„ค์ •

๋˜ํ•œ TypeScript ๋ฒ„์ „ 3.3 ์ด์ƒ์„ ์‚ฌ์šฉ ์ค‘์ด์–ด์•ผ ํ•˜๊ณ , tsconfig.json์—์„œ ๋‹ค์Œ ์„ค์ •์„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ–ˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค:

"emitDecoratorMetadata": true,
"experimentalDecorators": true,

์ปดํŒŒ์ผ๋Ÿฌ ์˜ต์…˜์˜ lib ์„น์…˜์—์„œ es6์„ ์‚ฌ์šฉ ์„ค์ •ํ•˜๊ฑฐ๋‚˜, @types์—์„œ es6-shim์„ ์„ค์น˜ํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

๋น ๋ฅธ ์‹œ์ž‘

TypeORM์„ ์‹œ์ž‘ํ•˜๋Š” ๊ฐ€์žฅ ๋น ๋ฅธ ๋ฐฉ๋ฒ•์€ CLI ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜์—ฌ ์‹œ์ž‘ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๋น ๋ฅธ ์‹œ์ž‘์€ NodeJS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ TypeORM์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ๋™์ž‘ํ•œ๋‹ค. ๋‹ค๋ฅธ ํ”Œ๋žซํผ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๋‹จ๊ณ„๋ณ„ ๊ฐ€์ด๋“œ์— ๋”ฐ๋ผ ์ง„ํ–‰ํ•ด์•ผ ํ•œ๋‹ค.

๋จผ์ €, TypeORM์„ ์ „์—ญ ์„ค์น˜ํ•œ๋‹ค.:

npm install typeorm -g

๊ทธ ๋‹ค์Œ ์ƒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๊ณ ์ž ํ•˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™ํ•˜์—ฌ ๋ช…๋ น์„ ์‹คํ–‰ํ•œ๋‹ค:

typeorm init --name MyProject --database mysql

์—ฌ๊ธฐ์„œ name์€ ํ”„๋กœ์ ํŠธ์˜ ์ด๋ฆ„์ด๊ณ  database๋Š” ์‚ฌ์šฉํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์ด๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ๋‹ค์Œ ์ค‘ ํ•˜๋‚˜์ผ ์ˆ˜ ์žˆ๋‹ค: mysql, mariadb, postgres, cockroachdb, sqlite, mssql, oracle, mongodb, cordova, react-native, expo, nativescript.

์ด ๋ช…๋ น์€ MyProject ๋””๋ ‰ํ† ๋ฆฌ์— ๋‹ค์Œ์˜ ํŒŒ์ผ๋“ค์ด ์žˆ๋Š” ์ƒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค:

MyProject
โ”œโ”€โ”€ src              // place of your TypeScript code
โ”‚   โ”œโ”€โ”€ entity       // place where your entities (database models) are stored
โ”‚   โ”‚   โ””โ”€โ”€ User.ts  // sample entity
โ”‚   โ”œโ”€โ”€ migration    // place where your migrations are stored
โ”‚   โ””โ”€โ”€ index.ts     // start point of your application
โ”œโ”€โ”€ .gitignore       // standard gitignore file
โ”œโ”€โ”€ ormconfig.json   // ORM and database connection configuration
โ”œโ”€โ”€ package.json     // node module dependencies
โ”œโ”€โ”€ README.md        // simple readme file
โ””โ”€โ”€ tsconfig.json    // TypeScript compiler options

๊ธฐ์กด Node ํ”„๋กœ์ ํŠธ์—์„œ typeorm init์„ ์‹คํ–‰ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์ด๋ฏธ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํŒŒ์ผ ์ค‘ ์ผ๋ถ€๋ฅผ ๋ฌด์‹œํ•  ์ˆ˜๋„ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์˜ํ•ด์•ผํ•œ๋‹ค.

๋‹ค์Œ ๋‹จ๊ณ„๋Š” ์ƒˆ ํ”„๋กœ์ ํŠธ ์ข…์†์„ฑ์„ ์„ค์น˜ํ•˜๋Š” ๊ฒƒ์ด๋‹ค:

cd MyProject
npm install

์„ค์น˜๊ฐ€ ์ง„ํ–‰๋˜๋Š” ๋™์•ˆ ormconfig.jsonํŒŒ์ผ์„ ํŽธ์ง‘ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์„ค์ • ์˜ต์…˜๋“ค์„ ์ž…๋ ฅํ•œ๋‹ค:

{
   "type": "mysql",
   "host": "localhost",
   "port": 3306,
   "username": "test",
   "password": "test",
   "database": "test",
   "synchronize": true,
   "logging": false,
   "entities": [
      "src/entity/**/*.ts"
   ],
   "migrations": [
      "src/migration/**/*.ts"
   ],
   "subscribers": [
      "src/subscriber/**/*.ts"
   ]
}

ํŠนํžˆ, ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ host, username, password, database๋ฐ port ์˜ต์…˜๋งŒ ์„ค์ •ํ•˜๋ฉด ๋œ๋‹ค.

์„ค์ •์„ ๋งˆ์น˜๊ณ  ๋ชจ๋“  node ๋ชจ๋“ˆ์ด ์„ค์น˜๋˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค:

npm start

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์„ฑ๊ณต์ ์œผ๋กœ ์‹คํ–‰๋˜๊ณ  ์ƒˆ ์‚ฌ์šฉ์ž๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค. ์ด ํ”„๋กœ์ ํŠธ๋กœ ๊ณ„์† ์ž‘์—…ํ•˜๊ฑฐ๋‚˜ ํ•„์š”ํ•œ ๋‹ค๋ฅธ ๋ชจ๋“ˆ์„ ํ†ตํ•ฉํ•˜๊ณ  ๋” ๋งŽ์€ ์—”ํ„ฐํ‹ฐ ์ƒ์„ฑ์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค.

typeorm init --name MyProject --database mysql --express ๋ช…๋ น์„ ์‹คํ–‰ํ•˜์—ฌ Express๊ฐ€ ์„ค์น˜๋œ ๊ณ ๊ธ‰ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

typeorm init --name MyProject --database postgres --docker ๋ช…๋ น์„ ์‹คํ–‰ํ•˜์—ฌ docker ์ž‘์„ฑ ํŒŒ์ผ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‹จ๊ณ„๋ณ„ ๊ฐ€์ด๋“œ

ORM์—์„œ ๋ฌด์—‡์„ ๊ธฐ๋Œ€ํ•˜๋Š”๊ฐ€? ์šฐ์„ , ์œ ์ง€ ๊ด€๋ฆฌ๊ฐ€ ์–ด๋ ค์šด SQL ์ฟผ๋ฆฌ๋ฅผ ๋งŽ์ด ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ ๋„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฒ€์ƒ‰ / ์‚ฝ์ž… / ์—…๋ฐ์ดํŠธ / ์‚ญ์ œ ํ•  ๊ฒƒ์œผ๋กœ ๊ธฐ๋Œ€ํ•œ๋‹ค. ์ด ๊ฐ€์ด๋“œ๋Š” TypeORM์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ์„ค์ •ํ•˜๊ณ  ORM์—์„œ ๊ธฐ๋Œ€ํ•˜๋Š” ๊ฒƒ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด์—ฌ์ค€๋‹ค.

๋ชจ๋ธ ์ƒ์„ฑ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—…์€ ํ…Œ์ด๋ธ” ์ƒ์„ฑ์—์„œ ์‹œ์ž‘๋œ๋‹ค. TypeORM์—๊ฒŒ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•˜๋„๋ก ์ง€์‹œํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ธ๊ฐ€? ๋‹ต์€ '๋ชจ๋ธ์„ ํ†ตํ•ด์„œ'์ด๋‹ค. ์•ฑ์˜ ๋ชจ๋ธ์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์ž…๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, Photo ๋ชจ๋ธ์ด ์žˆ๋‹ค๊ณ  ํ•˜์ž:

export class Photo {
    id: number;
    name: string;
    description: string;
    filename: string;
    views: number;
    isPublished: boolean;
}

๊ทธ๋ฆฌ๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— photo๋ฅผ ์ €์žฅํ•˜๋ ค๊ณ  ํ•œ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์–ด๋–ค ๊ฒƒ์„ ์ €์žฅํ•˜๋ ค๋ฉด ๋จผ์ € ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์ด ํ•„์š”ํ•˜๊ณ  ๋ชจ๋ธ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋œ๋‹ค. ๋ชจ๋“  ๋ชจ๋ธ์ด ์•„๋‹ˆ๋ผ entities๋กœ ์ •์˜ํ•œ ๋ชจ๋ธ๋งŒ ํ•ด๋‹น๋œ๋‹ค.

์—”ํ„ฐํ‹ฐ ์ƒ์„ฑ

Entity๋Š” @Entity ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ ์žฅ์‹(decorated)ํ•œ ๋ชจ๋ธ์ด๋‹ค. ์ด๋Ÿฌํ•œ ๋ชจ๋ธ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋œ๋‹ค. TypeORM์„ ์‚ฌ์šฉํ•˜๋ฉด ์–ด๋””์—์„œ๋‚˜ ์—”ํ„ฐํ‹ฐ๋กœ ๋กœ๋“œ / ์‚ฝ์ž… / ์—…๋ฐ์ดํŠธ / ์ œ๊ฑฐ ๋˜๋Š” ๋‹ค๋ฅธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

Photo ๋ชจ๋ธ์„ ์—”ํ„ฐํ‹ฐ๋กœ ๋งŒ๋“ค์–ด ๋ณด์ž.

import { Entity } from "typeorm";

@Entity()
export class Photo {
    id: number;
    name: string;
    description: string;
    filename: string;
    views: number;
    isPublished: boolean;
}

์ด์ œ Photo ์—”ํ„ฐํ‹ฐ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋˜๊ณ  ์•ฑ์˜ ์–ด๋””์—์„œ๋‚˜ ์ด ํ…Œ์ด๋ธ”๋กœ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์„ ๋งŒ๋“ค์—ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์–ด๋–ค ํ…Œ์ด๋ธ”์ด ์—ด(columns) ์—†์ด ์กด์žฌํ•  ์ˆ˜ ์žˆ์„๊นŒ? ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์— ๋ช‡ ๊ฐœ์˜ ์—ด์„ ์ƒ์„ฑํ•ด ๋ณด์ž.

ํ…Œ์ด๋ธ” ์—ด ์ถ”๊ฐ€

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์—ด์„ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด @Column ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ด๋กœ ๋งŒ๋“ค๊ณ ์ž ํ•˜๋Š” ์—”ํ„ฐํ‹ฐ์˜ ์†์„ฑ์„ ์žฅ์‹ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค.

import { Entity, Column } from "typeorm";

@Entity()
export class Photo {

    @Column()
    id: number;

    @Column()
    name: string;

    @Column()
    description: string;

    @Column()
    filename: string;

    @Column()
    views: number;

    @Column()
    isPublished: boolean;
}

์ด์ œ id, name, description, filename, views ๊ทธ๋ฆฌ๊ณ  isPublished ์—ด์ด photo ํ…Œ์ด๋ธ”์— ์ถ”๊ฐ€๋œ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์—ด ํƒ€์ž…์€ ์‚ฌ์šฉํ•œ ์†์„ฑ ์œ ํ˜•์—์„œ ์œ ์ถ”๋œ๋‹ค(์˜ˆ๋ฅผ ๋“ค์–ด, number๋Š” integer๋กœ, string์€ varchar๋กœ, boolean์€ bool๋กœ, ๋“ฑ). ๊ทธ๋Ÿฌ๋‚˜ @Column ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์— ์—ด ํƒ€์ž…์„ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์ง€์›ํ•˜๋Š” ๋ชจ๋“  ์—ด ํƒ€์ž…์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์—ด์ด ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ–ˆ์ง€๋งŒ ํ•œ ๊ฐ€์ง€๊ฐ€ ๋‚จ์•˜๋‹ค. ๊ฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์—๋Š” ๊ธฐ๋ณธ ํ‚ค๊ฐ€ ์žˆ๋Š” ์—ด์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค.

๊ธฐ๋ณธ ์—ด ์ƒ์„ฑ

๊ฐ ์—”ํ„ฐํ‹ฐ์—๋Š” ๋ฌด์กฐ๊ฑด ํ•˜๋‚˜ ์ด์ƒ ์˜ ๊ธฐ๋ณธ ํ‚ค ์—ด์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค. ์ด๊ฒƒ์€ ํ•„์ˆ˜ ์š”๊ตฌ ์‚ฌํ•ญ์ด๋‹ค. ์—ด์„ ๊ธฐ๋ณธ ํ‚ค๋กœ ๋งŒ๋“œ๋ ค๋ฉด @PrimaryColumn ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

import { Entity, Column, PrimaryColumn } from "typeorm";

@Entity()
export class Photo {

    @PrimaryColumn()
    id: number;

    @Column()
    name: string;

    @Column()
    description: string;

    @Column()
    filename: string;

    @Column()
    views: number;

    @Column()
    isPublished: boolean;
}

์ž๋™ ์ƒ์„ฑ ์—ด ๋งŒ๋“ค๊ธฐ

์ด์ œ id ์—ด์ด ์ž๋™ ์ƒ์„ฑ(์ด๋ฅผ ์ž๋™ ์ฆ๊ฐ€ ์—ด, auto-increment generated identity column ์ด๋ผ๊ณ  ํ•จ)๋˜๊ธฐ๋ฅผ ์›ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž. ๊ทธ๋ ‡๊ฒŒ ํ•˜๋ ค๋ฉด @PrimaryColumn ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ @PrimaryGeneratedColumn๋กœ ๋ณ€๊ฒฝํ•ด์•ผ ํ•œ๋‹ค.

import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class Photo {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @Column()
    description: string;

    @Column()
    filename: string;

    @Column()
    views: number;

    @Column()
    isPublished: boolean;
}

์—ด ๋ฐ์ดํ„ฐ ํƒ€์ž…

๋‹ค์Œ์œผ๋กœ ๋ฐ์ดํ„ฐ ์œ ํ˜•์„ ์ˆ˜์ •ํ•ด๋ณด์ž. ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฌธ์ž์—ด์€ varchar(255)์™€ ์œ ์‚ฌํ•œ ์œ ํ˜•(๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์œ ํ˜•์— ๋”ฐ๋ผ ๋‹ค๋ฆ„)์— ๋งคํ•‘๋˜๊ณ , ์ˆซ์ž๋Š” ์ •์ˆ˜์™€ ๊ฐ™์€ ์œ ํ˜•์œผ๋กœ ๋งคํ•‘๋œ๋‹ค(๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์œ ํ˜•์— ๋”ฐ๋ผ ๋‹ค๋ฆ„). ์šฐ๋ฆฌ๋Š” ๋ชจ๋“  ์—ด์ด varchar ๋˜๋Š” ์ •์ˆ˜๋กœ ์ œํ•œ๋˜๊ธฐ๋ฅผ ์›ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์˜ฌ๋ฐ”๋ฅธ ๋ฐ์ดํ„ฐ ์œ ํ˜•์„ ์„ค์ •ํ•ด๋ณด์ž:

import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class Photo {

    @PrimaryGeneratedColumn()
    id: number;

    @Column({
        length: 100
    })
    name: string;

    @Column("text")
    description: string;

    @Column()
    filename: string;

    @Column("double")
    views: number;

    @Column()
    isPublished: boolean;
}

์—ด ํƒ€์ž…์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋”ฐ๋ผ ๋‹ค๋ฅด๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์ง€์›ํ•˜๋Š” ๋ชจ๋“  ์—ด ํƒ€์ž…์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ง€์› ๋˜๋Š” ์—ด ํƒ€์ž…์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์ •๋ณด๋Š” ์—ฌ๊ธฐ์—์„œ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.

๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค์— ๋Œ€ํ•œ ์—ฐ๊ฒฐ ์ƒ์„ฑ

์ด์ œ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ƒ์„ฑ๋˜๋ฉด index.ts(๋˜๋Š” app.ts์ฒ˜๋Ÿผ ์›ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ถ€๋ฅผ ์ˆ˜ ์žˆ์Œ) ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ  ๊ทธ๊ณณ์—์„œ ์—ฐ๊ฒฐ์„ ์„ค์ •ํ•ด ๋ณด์ž.

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));

์ด ์˜ˆ์‹œ์—์„œ๋Š” MySQL์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์ง€๋งŒ ์ง€์›๋˜๋Š” ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์˜ต์…˜์˜ type์„ ์‚ฌ์šฉ ์ค‘์ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํƒ€์ž…์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค(mysql, mariadb, postgres, cockroachdb, sqlite, mssql, oracle, cordova, nativescript, react-native, expo, or mongodb). ๋˜ํ•œ ํ˜ธ์ŠคํŠธ, ํฌํŠธ, ์‚ฌ์šฉ์ž ์ด๋ฆ„, ์•”ํ˜ธ ๋ฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ •์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

์ด ์—ฐ๊ฒฐ์— ๋Œ€ํ•œ ์—”ํ„ฐํ‹ฐ ๋ชฉ๋ก์— Photo ์—”ํ„ฐํ‹ฐ๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค. ์—ฐ๊ฒฐ์— ์‚ฌ์šฉ ์ค‘์ธ ๊ฐ ์—”ํ„ฐํ‹ฐ๊ฐ€ ์—ฌ๊ธฐ์— ๋‚˜์—ด๋˜์–ด์•ผ ํ•œ๋‹ค.

synchronize๋ฅผ ์„ค์ •ํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•  ๋•Œ๋งˆ๋‹ค ์—”ํ„ฐํ‹ฐ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ๋™๊ธฐํ™”๋œ๋‹ค.

๋””๋ ‰ํ† ๋ฆฌ์—์„œ ๋ชจ๋“  ์—”ํ„ฐํ‹ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

๋‚˜์ค‘์— ๋” ๋งŽ์€ ์—”ํ„ฐํ‹ฐ๋ฅผ ๋งŒ๋“ค ๋•Œ ๊ทธ๊ฒƒ๋“ค์„ ์„ค์ •์— ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค. ์ด๊ฒƒ์€ ๊ทธ๋‹ค์ง€ ํŽธ๋ฆฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€์‹  ๋ชจ๋“  ์—”ํ„ฐํ‹ฐ๊ฐ€ ์—ฐ๊ฒฐ๋˜๊ณ  ์—ฐ๊ฒฐ์— ์‚ฌ์šฉ๋  ์ „์ฒด ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค:

import { createConnection } from "typeorm";

createConnection({
    type: "mysql",
    host: "localhost",
    port: 3306,
    username: "root",
    password: "admin",
    database: "test",
    entities: [
        __dirname + "/entity/*.js"
    ],
    synchronize: true,
}).then(connection => {
    // here you can start to work with your entities
}).catch(error => console.log(error));

๊ทธ๋Ÿฌ๋‚˜ ์ด๋Ÿฌํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์—๋Š” ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ts-node๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š”, .ts ํŒŒ์ผ์— ๋Œ€ํ•œ ๊ฒฝ๋กœ๋ฅผ ์ง€์ •ํ•ด์•ผ ํ•˜๊ณ outDir์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š”, outDir ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์˜ .js ํŒŒ์ผ์— ๋Œ€ํ•œ ๊ฒฝ๋กœ๋ฅผ ์ง€์ •ํ•ด์•ผ ํ•œ๋‹ค. outDir์„ ์‚ฌ์šฉ ์ค‘์ด๊ณ  ์—”ํ„ฐํ‹ฐ๋ฅผ ์ œ๊ฑฐํ•˜๊ฑฐ๋‚˜ ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•  ๋•Œ outDir ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ง€์šฐ๊ณ  ํ”„๋กœ์ ํŠธ๋ฅผ ๋‹ค์‹œ ์ปดํŒŒ์ผํ•ด์•ผ ํ•œ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์†Œ์Šค .ts ํŒŒ์ผ์„ ์ œ๊ฑฐํ•  ๋•Œ ์ปดํŒŒ์ผ๋œ .js ๋ฒ„์ „์€ ์ถœ๋ ฅ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ ์ œ๊ฑฐ๋˜์ง€ ์•Š๊ณ  ์—ฌ์ „ํžˆ outDir ๋””๋ ‰ํ† ๋ฆฌ์— ์กด์žฌํ•˜์—ฌ TypeORM์— ์˜ํ•ด ๋กœ๋“œ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰

์ด์ œ index.ts๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€์˜ ์—ฐ๊ฒฐ์ด ์ดˆ๊ธฐํ™”๋˜๊ณ  photo์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋œ๋‹ค.

+-------------+--------------+----------------------------+
|                         photo                           |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| name        | varchar(100) |                            |
| description | text         |                            |
| filename    | varchar(255) |                            |
| views       | int(11)      |                            |
| isPublished | boolean      |                            |
+-------------+--------------+----------------------------+

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— photo ์ƒ์„ฑ ๋ฐ ์‚ฝ์ž…

์ด์ œ ์ƒˆ photo๋ฅผ ๋งŒ๋“ค์–ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•ด ๋ณด์ž:

import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";

createConnection(/*...*/).then(connection => {

    let photo = new Photo();
    photo.name = "Me and Bears";
    photo.description = "I am near polar bears";
    photo.filename = "photo-with-bears.jpg";
    photo.views = 1;
    photo.isPublished = true;

 ย  ย return connection.manager
            .save(photo)
            .then(photo => {
                console.log("Photo has been saved. Photo id is", photo.id);
            });

}).catch(error => console.log(error));

์—”ํ„ฐํ‹ฐ๊ฐ€ ์ €์žฅ๋˜๋ฉด ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ID๋ฅผ ๊ฐ–๊ฒŒ ๋œ๋‹ค. save ๋ฉ”์†Œ๋“œ๋Š” ์ „๋‹ฌํ•œ ๊ฒƒ๊ณผ ๋™์ผํ•œ ๊ฐ์ฒด์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ด๋Š” ๊ฐ์ฒด์˜ ์ƒˆ ๋ณต์‚ฌ๋ณธ์ด ์•„๋‹ˆ๋ฉฐ "id"๋ฅผ ์ˆ˜์ •ํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•œ๋‹ค.

async/await ๊ตฌ๋ฌธ ์‚ฌ์šฉ

์ตœ์‹  ES8(ES2017) ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜๊ณ  async/await ๊ตฌ๋ฌธ์„ ๋Œ€์‹  ์‚ฌ์šฉํ•ด๋ณด์ž.

import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";

createConnection(/*...*/).then(async connection => {

    let photo = new Photo();
    photo.name = "Me and Bears";
    photo.description = "I am near polar bears";
    photo.filename = "photo-with-bears.jpg";
    photo.views = 1;
    photo.isPublished = true;

    await connection.manager.save(photo);
    console.log("Photo has been saved");

}).catch(error => console.log(error));

์—”ํ„ฐํ‹ฐ ๋งค๋‹ˆ์ € ์‚ฌ์šฉ

๋ฐฉ๊ธˆ ์ƒˆ photo๋ฅผ ๋งŒ๋“ค์–ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ–ˆ์—ˆ๋‹ค. ์ด๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด EntityManager๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค. ์ด์ฒ˜๋Ÿผ ์—”ํ„ฐํ‹ฐ ๋งค๋‹ˆ์ €๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์•ฑ์˜ ๋ชจ๋“  ์—”ํ„ฐํ‹ฐ๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์ €์žฅ๋œ ์—”ํ„ฐํ‹ฐ๋ฅผ ๋กœ๋“œํ•ด๋ณด์ž:

import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";

createConnection(/*...*/).then(async connection => {

    /*...*/
    let savedPhotos = await connection.manager.find(Photo);
    console.log("All photos from the db: ", savedPhotos);

}).catch(error => console.log(error));

savedPhotos๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๋กœ๋“œ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š” Photo ๊ฐ์ฒด์˜ ๋ฐฐ์—ด์ด๋‹ค.

์—ฌ๊ธฐ์—์„œ ์—”ํ„ฐํ‹ฐ ๋งค๋‹ˆ์ €์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

๋ฆฌํฌ์ง€ํ† ๋ฆฌ ์‚ฌ์šฉ

์ด์ œ ์ฝ”๋“œ๋ฅผ ๋ฆฌํŒฉํ† ๋งํ•˜์—ฌ EntityManager ๋Œ€์‹  Repository๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์ž. ๊ฐ ์—”ํ„ฐํ‹ฐ์—๋Š” ์—”ํ„ฐํ‹ฐ์— ๋Œ€ํ•œ ๋ชจ๋“  ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์ž์ฒด ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๊ฐ€ ์žˆ๋‹ค. ์—”ํ„ฐํ‹ฐ๋ฅผ ๋งŽ์ด ๋‹ค๋ฃฐ ๋•Œ๋Š” EntityManager๋ณด๋‹ค Repositories๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ํŽธ๋ฆฌํ•˜๋‹ค.

import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";

createConnection(/*...*/).then(async connection => {

    let photo = new Photo();
    photo.name = "Me and Bears";
    photo.description = "I am near polar bears";
    photo.filename = "photo-with-bears.jpg";
    photo.views = 1;
    photo.isPublished = true;

    let photoRepository = connection.getRepository(Photo);

    await photoRepository.save(photo);
    console.log("Photo has been saved");

    let savedPhotos = await photoRepository.find();
    console.log("All photos from the db: ", savedPhotos);

}).catch(error => console.log(error));

์—ฌ๊ธฐ์—์„œ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๋กœ๋“œ

๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋” ๋งŽ์€ ๋กœ๋“œ ์ž‘์—…์„ ์‹œ๋„ํ•ด๋ณด์ž:

import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";

createConnection(/*...*/).then(async connection => {

    /*...*/
    let allPhotos = await photoRepository.find();
    console.log("All photos from the db: ", allPhotos);

    let firstPhoto = await photoRepository.findOne(1);
    console.log("First photo from the db: ", firstPhoto);

    let meAndBearsPhoto = await photoRepository.findOne({ name: "Me and Bears" });
    console.log("Me and Bears photo from the db: ", meAndBearsPhoto);

    let allViewedPhotos = await photoRepository.find({ views: 1 });
    console.log("All viewed photos: ", allViewedPhotos);

    let allPublishedPhotos = await photoRepository.find({ isPublished: true });
    console.log("All published photos: ", allPublishedPhotos);

    let [allPhotos, photosCount] = await photoRepository.findAndCount();
    console.log("All photos: ", allPhotos);
    console.log("Photos count: ", photosCount);

}).catch(error => console.log(error));

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์—…๋ฐ์ดํŠธ

์ด์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๋‹จ์ผ photo๋ฅผ ๋กœ๋“œํ•˜๊ณ  ์—…๋ฐ์ดํŠธํ•˜๊ณ  ์ €์žฅํ•ด๋ณด์ž:

import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";

createConnection(/*...*/).then(async connection => {

    /*...*/
    let photoToUpdate = await photoRepository.findOne(1);
    photoToUpdate.name = "Me, my friends and polar bears";
    await photoRepository.save(photoToUpdate);

}).catch(error => console.log(error));

์ด์ œ id = 1์ธ photo๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์—…๋ฐ์ดํŠธ ๋  ๊ฒƒ์ด๋‹ค.

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์ œ๊ฑฐ

์ด์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ photo๋ฅผ ์ œ๊ฑฐํ•ด๋ณด์ž:

import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";

createConnection(/*...*/).then(async connection => {

    /*...*/
    let photoToRemove = await photoRepository.findOne(1);
    await photoRepository.remove(photoToRemove);

}).catch(error => console.log(error));

์ด์ œ id = 1์ธ photo๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์ œ๊ฑฐ๋œ๋‹ค.

1:1 ๊ด€๊ณ„ ์ƒ์„ฑ

๋‹ค๋ฅธ ํด๋ž˜์Šค์™€ 1:1 ๊ด€๊ณ„๋ฅผ ๋งŒ๋“ค์–ด ๋ณด์ž. PhotoMetadata.ts์— ์ƒˆ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ด ๋ณด๊ฒ ๋‹ค. ์ด PhotoMetadata ํด๋ž˜์Šค์—๋Š” photo์˜ ์ถ”๊ฐ€ ๋ฉ”ํƒ€ ์ •๋ณด๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•œ๋‹ค.

import { Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn } from "typeorm";
import { Photo } from "./Photo";

@Entity()
export class PhotoMetadata {

    @PrimaryGeneratedColumn()
    id: number;

    @Column("int")
    height: number;

    @Column("int")
    width: number;

    @Column()
    orientation: string;

    @Column()
    compressed: boolean;

    @Column()
    comment: string;

    @OneToOne(type => Photo)
    @JoinColumn()
    photo: Photo;
}

์—ฌ๊ธฐ์—์„œ๋Š” @OneToOne์ด๋ผ๋Š” ์ƒˆ๋กœ์šด ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋‘ ์—”ํ„ฐํ‹ฐ ๊ฐ„์— 1:1 ๊ด€๊ณ„๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. type => Photo๋Š” ์šฐ๋ฆฌ๊ฐ€ ๊ด€๊ณ„๋ฅผ ๋งŒ๋“ค๊ณ ์ž ํ•˜๋Š” ์—”ํ„ฐํ‹ฐ์˜ ํด๋ž˜์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋‹ค. ์–ธ์–ด์  ํŠน์„ฑ ๋•Œ๋ฌธ์— ํด๋ž˜์Šค๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹  ํด๋ž˜์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. () => Photo๋กœ ์“ธ ์ˆ˜๋„ ์žˆ์ง€๋งŒ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด type => Photo๋ฅผ ๊ด€์Šต์ ์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค. ํƒ€์ž… ๋ณ€์ˆ˜ ์ž์ฒด์—๋Š” ์•„๋ฌด ๊ฒƒ๋„ ํฌํ•จ๋˜์ง€ ์•Š๋Š”๋‹ค.

๋˜ํ•œ @JoinColumn ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ๊ด€๊ณ„์˜ ํ•œ ์ชฝ์ด ๊ด€๊ณ„๋ฅผ ์†Œ์œ ํ•˜๊ฒŒ ๋จ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค. ๊ด€๊ณ„๋Š” ๋‹จ๋ฐฉํ–ฅ ๋˜๋Š” ์–‘๋ฐฉํ–ฅ์ผ ์ˆ˜ ์žˆ์ง€๋งŒ ๊ด€๊ณ„์˜ ํ•œ ์ชฝ๋งŒ ์†Œ์œ ๋  ์ˆ˜ ์žˆ๋‹ค. @JoinColumn ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๊ด€๊ณ„์˜ ์†Œ์œ ํ•˜๋Š” ์ชฝ์—์„œ ํ•„์š”๋กœ ํ•œ๋‹ค.

์•ฑ์„ ์‹คํ–‰ํ•˜๋ฉด ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ํ…Œ์ด๋ธ”์ด ํ‘œ์‹œ๋˜๋ฉฐ ์—ฌ๊ธฐ์—๋Š” photo ๊ด€๊ณ„์— ๋Œ€ํ•œ ์™ธ๋ž˜ ํ‚ค๊ฐ€ ์žˆ๋Š” ์—ด์ด ํฌํ•จ๋œ๋‹ค:

+-------------+--------------+----------------------------+
|                     photo_metadata                      |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| height      | int(11)      |                            |
| width       | int(11)      |                            |
| comment     | varchar(255) |                            |
| compressed  | boolean      |                            |
| orientation | varchar(255) |                            |
| photoId     | int(11)      | FOREIGN KEY                |
+-------------+--------------+----------------------------+

1:1 ๊ด€๊ณ„ ์ €์žฅ

์ด์ œ photo์™€ ํ•ด๋‹น metadata๋ฅผ ์ €์žฅํ•˜๊ณ  ์„œ๋กœ ์ฒจ๋ถ€ํ•ด๋ณด์ž.

import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";
import { PhotoMetadata } from "./entity/PhotoMetadata";

createConnection(/*...*/).then(async connection => {

    // create a photo
    let photo = new Photo();
    photo.name = "Me and Bears";
    photo.description = "I am near polar bears";
    photo.filename = "photo-with-bears.jpg";
    photo.views = 1;
    photo.isPublished = true;

    // create a photo metadata
    let metadata = new PhotoMetadata();
    metadata.height = 640;
    metadata.width = 480;
    metadata.compressed = true;
    metadata.comment = "cybershoot";
    metadata.orientation = "portrait";
    metadata.photo = photo; // this way we connect them

    // get entity repositories
    let photoRepository = connection.getRepository(Photo);
    let metadataRepository = connection.getRepository(PhotoMetadata);

    // first we should save a photo
    await photoRepository.save(photo);

    // photo is saved. Now we need to save a photo metadata
    await metadataRepository.save(metadata);

    // done
    console.log("Metadata is saved, and the relation between metadata and photo is created in the database too");

}).catch(error => console.log(error));

๊ด€๊ณ„์˜ ๋ฐ˜๋Œ€์ธก

๊ด€๊ณ„๋Š” ๋‹จ๋ฐฉํ–ฅ ๋˜๋Š” ์–‘๋ฐฉํ–ฅ์ผ ์ˆ˜ ์žˆ๋‹ค. ํ˜„์žฌ PhotoMetadata์™€ Photo ๊ฐ„์˜ ๊ด€๊ณ„๋Š” ๋‹จ๋ฐฉํ–ฅ์ด๋‹ค. ๊ด€๊ณ„์˜ ์†Œ์œ ์ž๋Š” PhotoMetadata์ด๊ณ  Photo๋Š” PhotoMetadata์— ๋Œ€ํ•ด ์•„๋ฌด๊ฒƒ๋„ ๋ชจ๋ฅด๋Š” ์ƒํƒœ๋‹ค. ์ด๋กœ ์ธํ•ด photo ์ธก์—์„œ PhotoMetadata์— ์•ก์„ธ์Šคํ•˜๋Š” ๊ฒƒ์ด ๋ณต์žกํ•ด์ง„๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ์—ญ ๊ด€๊ณ„๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ PhotoMetadata์™€ Photo ๊ฐ„์˜ ๊ด€๊ณ„๋ฅผ ์–‘๋ฐฉํ–ฅ์œผ๋กœ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค. ์—”ํ„ฐํ‹ฐ๋ฅผ ์ˆ˜์ •ํ•ด๋ณด์ž.

import { Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn } from "typeorm";
import { Photo } from "./Photo";

@Entity()
export class PhotoMetadata {

    /* ... other columns */

    @OneToOne(type => Photo, photo => photo.metadata)
    @JoinColumn()
    photo: Photo;
}
import { Entity, Column, PrimaryGeneratedColumn, OneToOne } from "typeorm";
import { PhotoMetadata } from "./PhotoMetadata";

@Entity()
export class Photo {

    /* ... other columns */

    @OneToOne(type => PhotoMetadata, photoMetadata => photoMetadata.photo)
    metadata: PhotoMetadata;
}

photo => photo.metadata๋Š” ๊ด€๊ณ„์˜ ๋ฐ˜๋Œ€์ธก์˜ ์ด๋ฆ„์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋‹ค. ์—ฌ๊ธฐ์—์„œ Photo ํด๋ž˜์Šค์˜ metadata ์†์„ฑ์ด Photo ํด๋ž˜์Šค์—์„œ PhotoMetadata๋ฅผ ์ €์žฅํ•˜๋Š” ์œ„์น˜์ž„์„ ๋ณด์—ฌ์ค€๋‹ค. photo์˜ ์†์„ฑ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋Œ€์‹  "metadata"์™€ ๊ฐ™์€ ๋ฌธ์ž์—ด์„ @OneToOne ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์— ์ „๋‹ฌํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์šฐ๋ฆฌ๋Š” ๋ฆฌํŒฉํ† ๋ง์„ ๋” ์‰ฝ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ํ•จ์ˆ˜ ํƒ€์ž… ์ ‘๊ทผ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ–ˆ๋‹ค.

@JoinColumn ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ๊ด€๊ณ„์˜ ํ•œ ์ชฝ์—์„œ๋งŒ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค. ์ด ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์–ด๋А ์ชฝ์— ๋‘๋“  ๊ทธ ์ชฝ์ด ๊ด€๊ณ„์˜ ์†Œ์œ  ์ธก์ด ๋œ๋‹ค. ๊ด€๊ณ„์˜ ์†Œ์œ  ์ธก์—๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์™ธ๋ž˜ ํ‚ค๊ฐ€ ์žˆ๋Š” ์—ด์ด ์žˆ๋‹ค.

๊ด€๊ณ„์™€ ํ•จ๊ป˜ ๊ฐ์ฒด ๋กœ๋“œ

์ด์ œ ๋‹จ์ผ ์ฟผ๋ฆฌ์—์„œ photo์™€ phto metadata๋ฅผ ๋กœ๋“œํ•ด๋ณด์ž. find* ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ QueryBuilder ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค. ๋จผ์ € find* ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์ž. find* ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด FindOneOptions / FindManyOptions ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๊ฐœ์ฒด๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";
import { PhotoMetadata } from "./entity/PhotoMetadata";

createConnection(/*...*/).then(async connection => {

    /*...*/
    let photoRepository = connection.getRepository(Photo);
    let photos = await photoRepository.find({ relations: ["metadata"] });

}).catch(error => console.log(error));

์—ฌ๊ธฐ์—์„œ photos์—๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ photo ๋ฐฐ์—ด์ด ํฌํ•จ๋˜๊ณ  ๊ฐ photo์—๋Š” photo metadata๊ฐ€ ํฌํ•จ๋œ๋‹ค. ์ด ๋ฌธ์„œ์—์„œ ์ฐพ๊ธฐ ์˜ต์…˜์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณผ ์ˆ˜ ์žˆ๋‹ค.

Using find options is good and dead ์ฐพ๊ธฐ ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ํ›Œ๋ฅญํ•˜๊ณ  ๊ฐ„๋‹จํ•˜์ง€๋งŒ ๋” ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋Š” QueryBuilder๋ฅผ ๋Œ€์‹  ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. QueryBuilder๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ณด๋‹ค ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋ฅผ ์šฐ์•„ํ•œ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";
import { PhotoMetadata } from "./entity/PhotoMetadata";

createConnection(/*...*/).then(async connection => {

    /*...*/
    let photos = await connection
            .getRepository(Photo)
            .createQueryBuilder("photo")
            .innerJoinAndSelect("photo.metadata", "metadata")
            .getMany();


}).catch(error => console.log(error));

QueryBuilder๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฑฐ์˜ ๋ชจ๋“  ๋ณต์žกํ•œ SQL ์ฟผ๋ฆฌ๋ฅผ ๋งŒ๋“ค๊ณ  ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค. QueryBuilder๋กœ ์ž‘์—…ํ•  ๋•Œ SQL ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ์ƒ๊ฐํ•˜์ž. ์ด ์˜ˆ์—์„œ "photo" ๋ฐ "metadata"๋Š” ์„ ํƒํ•œ photo์— ์ ์šฉ๋œ ๋ณ„์นญ์ด๋‹ค. ๋ณ„์นญ์„ ์‚ฌ์šฉํ•˜์—ฌ ์„ ํƒํ•œ ๋ฐ์ดํ„ฐ์˜ ์—ด ๋ฐ ์†์„ฑ์— ์•ก์„ธ์Šคํ•œ๋‹ค.

Casecade๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ด€๋ จ ๊ฐ์ฒด ์ž๋™ ์ €์žฅ

๋‹ค๋ฅธ ๊ฐœ์ฒด๊ฐ€ ์ €์žฅ๋  ๋•Œ๋งˆ๋‹ค ๊ด€๋ จ ๊ฐœ์ฒด๊ฐ€ ์ €์žฅ๋˜๊ธฐ๋ฅผ ์›ํ•˜๋Š” ๊ฒฝ์šฐ ๊ด€๊ณ„์—์„œ cascade ์˜ต์…˜์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. photo์˜ @OneToOne ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์•ฝ๊ฐ„ ๋ณ€๊ฒฝํ•ด ๋ณด์ž.

export class Photo {
    /// ... other columns

    @OneToOne(type => PhotoMetadata, metadata => metadata.photo, {
        cascade: true,
    })
    metadata: PhotoMetadata;
}

cascade๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด photo๋ฅผ ๋”ฐ๋กœ ์ €์žฅํ•˜์ง€ ์•Š๊ณ ๋„ metadata ๊ฐ์ฒด๋ฅผ ๋”ฐ๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค. ์ด์ œ photo ๊ฐ์ฒด๋ฅผ ๊ฐ„๋‹จํžˆ ์ €์žฅํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ metadata ๊ฐ์ฒด๋Š” cascade ์˜ต์…˜์œผ๋กœ ์ธํ•ด ์ž๋™์œผ๋กœ ์ €์žฅ๋œ๋‹ค.

createConnection(options).then(async connection => {

    // create photo object
    let photo = new Photo();
    photo.name = "Me and Bears";
    photo.description = "I am near polar bears";
    photo.filename = "photo-with-bears.jpg";
    photo.isPublished = true;

    // create photo metadata object
    let metadata = new PhotoMetadata();
    metadata.height = 640;
    metadata.width = 480;
    metadata.compressed = true;
    metadata.comment = "cybershoot";
    metadata.orientation = "portrait";

    photo.metadata = metadata; // this way we connect them

    // get repository
    let photoRepository = connection.getRepository(Photo);

    // saving a photo also save the metadata
    await photoRepository.save(photo);

    console.log("Photo is saved, photo metadata is saved too.")

}).catch(error => console.log(error));

์ด์ œ ์ด์ „๊ณผ ๊ฐ™์ด metadata์˜ photo ์†์„ฑ ๋Œ€์‹  photo์˜ metadata ์†์„ฑ์„ ์„ค์ •ํ•œ๋‹ค. cascade ๊ธฐ๋Šฅ์€ photo๋ฅผ photo ์ธก๋ฉด์—์„œ metadata์— ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ž‘๋™ํ•œ๋‹ค. metadata ์ธก๋ฉด์„ ์„ค์ •ํ•˜๋ฉด metadata๊ฐ€ ์ž๋™์œผ๋กœ ์ €์žฅ๋˜์ง€ ์•Š๋Š”๋‹ค.

N:1 ๋˜๋Š” 1:N ๊ด€๊ณ„ ์ƒ์„ฑ

N:1/1:N ๊ด€๊ณ„๋ฅผ ๋งŒ๋“ค์–ด ๋ณด์ž. photo์—๋Š” ํ•œ ๋ช…์˜ author๊ฐ€ ์žˆ๊ณ  ๊ฐ author๋Š” ๋งŽ์€ photo๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ์šฐ์„  Author ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ด ๋ณด์ž:

import { Entity, Column, PrimaryGeneratedColumn, OneToMany, JoinColumn } from "typeorm";
import { Photo } from "./Photo";

@Entity()
export class Author {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @OneToMany(type => Photo, photo => photo.author) // note: we will create author property in the Photo class below
    photos: Photo[];
}

Author๋Š” ๊ด€๊ณ„์˜ ๋ฐ˜๋Œ€ ์ธก๋ฉด์„ ํฌํ•จํ•œ๋‹ค. OneToMany๋Š” ํ•ญ์ƒ ๊ด€๊ณ„์˜ ๋ฐ˜๋Œ€ ์ธก๋ฉด์ด๋ฉฐ ๊ด€๊ณ„์˜ ๋‹ค๋ฅธ ์ธก๋ฉด์— ManyToOne ์—†์ด๋Š” ์กด์žฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

์ด์ œ ๊ด€๊ณ„์˜ ์†Œ์œ ์ž ์ธก์„ Photo ์—”ํ„ฐํ‹ฐ์— ์ถ”๊ฐ€ํ•ด๋ณด์ž:

import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from "typeorm";
import { PhotoMetadata } from "./PhotoMetadata";
import { Author } from "./Author";

@Entity()
export class Photo {

    /* ... other columns */

    @ManyToOne(type => Author, author => author.photos)
    author: Author;
}

N:1/1:N ๊ด€๊ณ„์—์„œ ์†Œ์œ ์ž์ธก์€ ํ•ญ์ƒ ๋‹ค๋Œ€์ผ(ManyToOne)์ด๋‹ค. ์ฆ‰ @ManyToOne์„ ์‚ฌ์šฉํ•˜๋Š” ํด๋ž˜์Šค๊ฐ€ ๊ด€๋ จ ๊ฐ์ฒด์˜ id๋ฅผ ์ €์žฅํ•œ๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•œ ํ›„, ORM์€ author ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•œ๋‹ค:

+-------------+--------------+----------------------------+
|                          author                         |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| name        | varchar(255) |                            |
+-------------+--------------+----------------------------+

๋˜ํ•œ ์ƒˆ author ์—ด์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์ด์— ๋Œ€ํ•œ ์™ธ๋ž˜ ํ‚ค๋ฅผ ์ƒ์„ฑํ•˜์—ฌ photo ํ…Œ์ด๋ธ”์„ ์ˆ˜์ •ํ•œ๋‹ค:

+-------------+--------------+----------------------------+
|                         photo                           |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| name        | varchar(255) |                            |
| description | varchar(255) |                            |
| filename    | varchar(255) |                            |
| isPublished | boolean      |                            |
| authorId    | int(11)      | FOREIGN KEY                |
+-------------+--------------+----------------------------+

M:N ๊ด€๊ณ„ ์ƒ์„ฑ

M:N ๊ด€๊ณ„๋ฅผ ๋งŒ๋“ค์–ด ๋ณด์ž. ์‚ฌ์ง„์ด ์—ฌ๋Ÿฌ album์— ํฌํ•จ๋  ์ˆ˜ ์žˆ๊ณ  ๊ฐ album๋“ค์— ๋งŽ์€ photo๋“ค์ด ํฌํ•จ๋  ์ˆ˜ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์—ฌ Album ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด ๋ณด์ž:

import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from "typeorm";

@Entity()
export class Album {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @ManyToMany(type => Photo, photo => photo.albums)
    @JoinTable()
    photos: Photo[];
}

@JoinTable์€ ์ด๊ฒƒ์ด ๊ด€๊ณ„์˜ ์†Œ์œ ์ž ์ธก์ž„์„ ์ง€์ •ํ•˜๋Š”๋ฐ ํ•„์š”ํ•˜๋‹ค.

์ด์ œ Photo ํด๋ž˜์Šค์— ๊ด€๊ณ„์˜ ๋ฐ˜๋Œ€์ธก์„ ์ถ”๊ฐ€ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค:

export class Photo {
    /// ... other columns

    @ManyToMany(type => Album, album => album.photos)
    albums: Album[];
}

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•œ ํ›„, ORM์€ album_photos_photo_albums๋ผ๋Š” *์ ‘ํ•ฉ ํ…Œ์ด๋ธ”(junction table)*์„ ์ƒ์„ฑํ•œ๋‹ค.:

+-------------+--------------+----------------------------+
|                album_photos_photo_albums                |
+-------------+--------------+----------------------------+
| album_id    | int(11)      | PRIMARY KEY FOREIGN KEY    |
| photo_id    | int(11)      | PRIMARY KEY FOREIGN KEY    |
+-------------+--------------+----------------------------+

ORM์˜ ์—ฐ๊ฒฐ์—์„œ Album ํด๋ž˜์Šค๋ฅผ ๋“ฑ๋กํ•˜๋Š” ๊ฒƒ์„ ์žŠ์œผ๋ฉด ์•ˆ๋œ๋‹ค:

const options: ConnectionOptions = {
    // ... other options
    entities: [Photo, PhotoMetadata, Author, Album]
};

์ด์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— album๊ณผ photo๋ฅผ ์‚ฝ์ž…ํ•ด ๋ณด์ž:

let connection = await createConnection(options);

// create a few albums
let album1 = new Album();
album1.name = "Bears";
await connection.manager.save(album1);

let album2 = new Album();
album2.name = "Me";
await connection.manager.save(album2);

// create a few photos
let photo = new Photo();
photo.name = "Me and Bears";
photo.description = "I am near polar bears";
photo.filename = "photo-with-bears.jpg";
photo.views = 1
photo.isPublished = true
photo.albums = [album1, album2];
await connection.manager.save(photo);

// now our photo is saved and albums are attached to it
// now lets load them:
const loadedPhoto = await connection
    .getRepository(Photo)
    .findOne(1, { relations: ["albums"] });

loadedPhoto๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค:

{
    id: 1,
    name: "Me and Bears",
    description: "I am near polar bears",
    filename: "photo-with-bears.jpg",
    albums: [{
        id: 1,
        name: "Bears"
    }, {
        id: 2,
        name: "Me"
    }]
}

์ฟผ๋ฆฌ ๋นŒ๋” ์‚ฌ์šฉ

์ฟผ๋ฆฌ ๋นŒ๋”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฑฐ์˜ ๋ชจ๋“  ๋ณต์žกํ•œ SQL ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•  ์ˆ˜ ์žˆ๋‹ค:

let photos = await connection
    .getRepository(Photo)
    .createQueryBuilder("photo") // first argument is an alias. Alias is what you are selecting - photos. You must specify it.
    .innerJoinAndSelect("photo.metadata", "metadata")
    .leftJoinAndSelect("photo.albums", "album")
    .where("photo.isPublished = true")
    .andWhere("(photo.name = :photoName OR photo.name = :bearName)")
    .orderBy("photo.id", "DESC")
    .skip(5)
    .take(10)
    .setParameters({ photoName: "My", bearName: "Mishka" })
    .getMany();

์ด ์ฟผ๋ฆฌ๋Š” ์ด๋ฆ„์ด "My" ๋˜๋Š” "Mishka"์ธ ๊ฒŒ์‹œ๋œ ๋ชจ๋“  photo๋ฅผ ์„ ํƒํ•œ๋‹ค. ์œ„์น˜ 5(pagination ์˜คํ”„์…‹)์—์„œ ๊ฒฐ๊ณผ๋ฅผ ์„ ํƒํ•˜๊ณ  10๊ฐœ ๊ฒฐ๊ณผ(pagination ์ œํ•œ)๋งŒ ์„ ํƒํ•œ๋‹ค. ์„ ํƒ ๊ฒฐ๊ณผ๋Š” id์˜ ๋‚ด๋ฆผ์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌ๋œ๋‹ค. photo์˜ album๋“ค์€ ๊ฒฐํ•ฉ๋œ ์ƒํƒœ๋กœ ์œ ์ง€๋˜๊ณ  ํ•ด๋‹น metadata๋Š” ๋‚ด๋ถ€ ๊ฒฐํ•ฉ(inner join)๋œ๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ฟผ๋ฆฌ ๋นŒ๋”๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค. ์—ฌ๊ธฐ์—์„œ ์ฟผ๋ฆฌ ๋นŒ๋”์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

์ƒ˜ํ”Œ๋“ค

์‚ฌ์šฉ ์˜ˆ์‹œ๋Š” sample์˜ ์ƒ˜ํ”Œ์„ ์‚ดํŽด๋ณด์ž.

cloneํ•˜์—ฌ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ๋ช‡ ๊ฐ€์ง€ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๊ฐ€ ์žˆ๋‹ค:

ํ™•์žฅ

TypeORM ์ž‘์—…์„ ๋‹จ์ˆœํ™”ํ•˜๊ณ  ๋‹ค๋ฅธ ๋ชจ๋“ˆ๊ณผ ํ†ตํ•ฉํ•˜๋Š” ๋ช‡ ๊ฐ€์ง€ ํ™•์žฅ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค:

๊ธฐ์—ฌ

์—ฌ๊ธฐ์—์„œ ๊ธฐ์—ฌ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ณ  ์—ฌ๊ธฐ์—์„œ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์„ ์„ค์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

์ด ํ”„๋กœ์ ํŠธ๋Š” ๋ชจ๋“  ๊ธฐ์—ฌ์ž ๋•๋ถ„์— ์กด์žฌํ•œ๋‹ค:

์Šคํฐ์„œ

์˜คํ”ˆ ์†Œ์Šค๋Š” ์–ด๋ ค์šด๋ฐ๋‹ค๊ฐ€ ๋งŽ์€ ์‹œ๊ฐ„์ด ์†Œ์š”๋œ๋‹ค. TypeORM์˜ ๋ฏธ๋ž˜์— ํˆฌ์žํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ํ›„์›์ž๊ฐ€ ๋˜์–ด ํ•ต์‹ฌ ํŒ€์ด TypeORM์˜ ๊ฐœ์„  ์‚ฌํ•ญ๊ณผ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์— ๋” ๋งŽ์€ ์‹œ๊ฐ„์„ ํ• ์• ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์šธ ์ˆ˜ ์žˆ๋‹ค. ํ›„์›์ž๊ฐ€ ๋˜์‹ญ์‹œ์˜ค.

Gold ์Šคํฐ์„œ

Gold ์Šคํฐ์„œ๊ฐ€ ๋˜์–ด ํ•ต์‹ฌ ๊ธฐ์—ฌ์ž๋กœ๋ถ€ํ„ฐ ํ”„๋ฆฌ๋ฏธ์—„ ๊ธฐ์ˆ  ์ง€์›์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. Gold ์Šคํฐ์„œ ๋˜๊ธฐ