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();-
npm ํจํค์ง๋ฅผ ์ค์นํ๋ค:
npm install typeorm --save -
reflect-metadata์ฌ(shim)์ ์ค์นํ๋ค:npm install reflect-metadata --save๊ทธ๋ฆฌ๊ณ ๊ทธ๊ฒ์ app (์:
app.ts)์ ์ ์ญ ์์น์ ๋ถ๋ฌ์์ผ ํ๋ค:import "reflect-metadata"; -
๋ ธ๋ ํ์ ์ ์ค์นํด์ผ ํ ์๋ ์๋ค:
npm install @types/node --save-dev -
DB ๋๋ผ์ด๋ฒ ์ค์น๋ฅผ ์ค์นํ๋ค:
-
MySQL ๋๋ MariaDB์ ๊ฒฝ์ฐ
npm install mysql --save(you can installmysql2instead 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 --saveOracle ๋๋ผ์ด๋ฒ๋ฅผ ์๋์ํค๋ ค๋ฉด ํด๋น ์ฌ์ดํธ์ ์ค์น ์ง์นจ์ ๋ฐ๋ผ์ผ ํ๋ค.
-
SAP Hana์ ๊ฒฝ์ฐ
npm i @sap/hana-client npm i hdb-poolNeptune Software์ ํ์์ผ๋ก SAP Hana ์ง์์ด ๊ฐ๋ฅํด์ก๋ค.
-
MongoDB (experimental)์ ๊ฒฝ์ฐ
npm install mongodb@^5.2.0 --save -
NativeScript, react-native, Cordova์ ๊ฒฝ์ฐ
์ฌ์ฉํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ๋ผํ๋๋ง ์ค์น
-
๋ํ 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๋ฅผ ๋ง๋ค์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํด ๋ณด์:
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"๋ฅผ ์์ ํ๊ณ ๋ฐํํ๋ค.
์ต์ 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 ๊ด๊ณ๋ฅผ ๋ง๋ค์ด ๋ณด์. 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 |
+-------------+--------------+----------------------------+์ด์ 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์ ์ ์ฉ๋ ๋ณ์นญ์ด๋ค. ๋ณ์นญ์ ์ฌ์ฉํ์ฌ ์ ํํ ๋ฐ์ดํฐ์ ์ด ๋ฐ ์์ฑ์ ์ก์ธ์คํ๋ค.
๋ค๋ฅธ ๊ฐ์ฒด๊ฐ ์ ์ฅ๋ ๋๋ง๋ค ๊ด๋ จ ๊ฐ์ฒด๊ฐ ์ ์ฅ๋๊ธฐ๋ฅผ ์ํ๋ ๊ฒฝ์ฐ ๊ด๊ณ์์ 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 ๊ด๊ณ๋ฅผ ๋ง๋ค์ด ๋ณด์. 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 ๊ด๊ณ๋ฅผ ๋ง๋ค์ด ๋ณด์. ์ฌ์ง์ด ์ฌ๋ฌ 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ํ์ฌ ์์ํ ์ ์๋ ๋ช ๊ฐ์ง ๋ฆฌํฌ์งํ ๋ฆฌ๊ฐ ์๋ค:
- TypeScript์ ํจ๊ป TypeORM์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์
- JavaScript์์ TypeORM์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์
- JavaScript ๋ฐ Babel๊ณผ ํจ๊ป TypeORM์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์
- ๋ธ๋ผ์ฐ์ ์์ TypeScript ๋ฐ SystemJS์ ํจ๊ป TypeORM์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์
- ๋ธ๋ผ์ฐ์ ์์ TypeScript ๋ฐ React์ ํจ๊ป TypeORM์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์
- Express ๋ฐ TypeORM ์ฌ์ฉ ๋ฐฉ๋ฒ์ ์์
- Koa ๋ฐ TypeORM ์ฌ์ฉ ์์
- MongoDB์์ TypeORM์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์
- Cordova/PhoneGap ์ฑ์์ TypeORM์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์
- Ionic ์ฑ์์ TypeORM์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์
- React Native์์ TypeORM์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์
- Nativescript-Vue์ ํจ๊ป TypeORM์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์
- Nativescript-Angular์ ํจ๊ป TypeORM์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์
- JavaScript๋ฅผ ์ฌ์ฉํ์ฌ Electron์์ TypeORM์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์
- TypeScript๋ฅผ ์ฌ์ฉํ์ฌ Electron๊ณผ ํจ๊ป TypeORM์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์
TypeORM ์์ ์ ๋จ์ํํ๊ณ ๋ค๋ฅธ ๋ชจ๋๊ณผ ํตํฉํ๋ ๋ช ๊ฐ์ง ํ์ฅ ๋ฐฉ๋ฒ์ด ์๋ค:
- TypeORM + GraphQL ํ๋ ์์ํฌ
- TypeORM๊ณผ TypeDI ํตํฉ
- ๋ผ์ฐํ ์ปจํธ๋กค๋ฌ์ TypeORM ํตํฉ
- ๊ธฐ์กด ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ชจ๋ธ ์์ฑ - typeorm-model-generator
- Fixtures loader - typeorm-fixtures-cli
- ER ๋ค์ด์ด๊ทธ๋จ ์์ฑ๊ธฐ - typeorm-uml
- ๋ค๋ฅธ ER ๋ค์ด์ด๊ทธ๋จ ์์ฑ๊ธฐ - erdia
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ฑ(create)/์ญ์ (drop) - typeorm-extension
์ฌ๊ธฐ์์ ๊ธฐ์ฌ์ ๋ํด ์์๋ณด๊ณ ์ฌ๊ธฐ์์ ๊ฐ๋ฐ ํ๊ฒฝ์ ์ค์ ํ๋ ๋ฐฉ๋ฒ์ ์ ์ ์๋ค.
์ด ํ๋ก์ ํธ๋ ๋ชจ๋ ๊ธฐ์ฌ์ ๋๋ถ์ ์กด์ฌํ๋ค:
์คํ ์์ค๋ ์ด๋ ค์ด๋ฐ๋ค๊ฐ ๋ง์ ์๊ฐ์ด ์์๋๋ค. TypeORM์ ๋ฏธ๋์ ํฌ์ํ๊ณ ์ถ๋ค๋ฉด ํ์์๊ฐ ๋์ด ํต์ฌ ํ์ด TypeORM์ ๊ฐ์ ์ฌํญ๊ณผ ์๋ก์ด ๊ธฐ๋ฅ์ ๋ ๋ง์ ์๊ฐ์ ํ ์ ํ ์ ์๋๋ก ๋์ธ ์ ์๋ค. ํ์์๊ฐ ๋์ญ์์ค.
Gold ์คํฐ์๊ฐ ๋์ด ํต์ฌ ๊ธฐ์ฌ์๋ก๋ถํฐ ํ๋ฆฌ๋ฏธ์ ๊ธฐ์ ์ง์์ ๋ฐ์ ์ ์๋ค. Gold ์คํฐ์ ๋๊ธฐ