README_ko
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 관계.
  • 단방ν–₯, μ–‘λ°©ν–₯ 및 자체 μ°Έμ‘° 관계.
  • 닀쀑 상속 νŒ¨ν„΄μ„ 지원.
  • μΊμŠ€μΌ€μ΄λ“œ.
  • 색인.
  • νŠΈλžœμž­μ…˜.
  • λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ 및 μžλ™ λ§ˆμ΄ν¬λ ˆμ΄μ…˜ 생성.
  • μ—°κ²° 풀링.
  • 볡제.
  • 닀쀑 λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° μ‚¬μš©.
  • μ—¬λŸ¬ λ°μ΄ν„°λ² μ΄μŠ€ μœ ν˜• μž‘μ—….
  • λ°μ΄ν„°λ² μ΄μŠ€ κ°„, μŠ€ν‚€λ§ˆ κ°„μ˜ 쿼리.
  • μš°μ•„ν•œ 문법과 μœ μ—°ν•˜κ³  κ°•λ ₯ν•œ 쿼리 λΉŒλ”.
  • μ™Όμͺ½ joinκ³Ό λ‚΄λΆ€ join.
  • join을 μ‚¬μš©ν•˜λŠ” 쿼리에 λŒ€ν•œ μ μ ˆν•œ νŽ˜μ΄μ§€λ„€μ΄μ…˜
  • 쿼리 캐싱.
  • μ›μ‹œ κ²°κ³Ό 슀트리밍
  • λ‘œκΉ….
  • λ¦¬μŠ€λ„ˆ 및 κ΅¬λ…μž(hooks).
  • ν΄λ‘œμ € ν…Œμ΄λΈ” νŒ¨ν„΄ 지원.
  • λͺ¨λΈ λ˜λŠ” λ³„λ„μ˜ μ„€μ • νŒŒμΌμ—μ„œ μŠ€ν‚€λ§ˆ μ„ μ–Έ
  • json / xml / yml / env ν˜•μ‹μ˜ μ—°κ²° ꡬ성.
  • 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. 1.
    npm νŒ¨ν‚€μ§€λ₯Ό μ„€μΉ˜ν•œλ‹€:
    npm install typeorm --save
  2. 2.
    reflect-metadata 심(shim)을 μ„€μΉ˜ν•œλ‹€:
    npm install reflect-metadata --save
    그리고 그것을 app (예: app.ts)의 μ „μ—­ μœ„μΉ˜μ— λΆˆλŸ¬μ™€μ•Ό ν•œλ‹€:
    import "reflect-metadata";
  3. 3.
    λ…Έλ“œ νƒ€μž…μ„ μ„€μΉ˜ν•΄μ•Ό ν•  μˆ˜λ„ μžˆλ‹€:
    npm install @types/node --save-dev
  4. 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 [email protected]^3.6.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듀이 포함될 수 μžˆλ‹€κ³  κ°€μ •ν•˜μ—¬