From 6da0911a0b61ece270341f1e103b11cd6580ad65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=B6ck?= Date: Fri, 14 Nov 2025 12:16:38 +0100 Subject: [PATCH] docs: expand sponsors section and remove outdated translations (#11771) --- README-zh_CN.md | 1208 ----------------------------------------------- README.md | 26 +- README_ko.md | 1205 ---------------------------------------------- 3 files changed, 21 insertions(+), 2418 deletions(-) delete mode 100644 README-zh_CN.md delete mode 100644 README_ko.md diff --git a/README-zh_CN.md b/README-zh_CN.md deleted file mode 100644 index 90dbbe622..000000000 --- a/README-zh_CN.md +++ /dev/null @@ -1,1208 +0,0 @@ -
- - - - - TypeORM Logo - - -
-
- NPM Version - NPM Downloads - Commit Validation - Coverage Status - MIT License -
-
-
- -TypeORM 是一个 [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping) 框架,它可以运行在 NodeJS、Browser、Cordova、Ionic、React Native、Expo 和 Electron 平台上,可以与 TypeScript 和 JavaScript (ES2021)一起使用。 它的目标是始终支持最新的 JavaScript 特性并提供额外的特性以帮助你开发任何使用数据库的(不管是只有几张表的小型应用还是拥有多数据库的大型企业应用)应用程序。 - -不同于现有的所有其他 JavaScript ORM 框架,TypeORM 支持 [Active Record](./docs/docs/guides/1-active-record-data-mapper.md#what-is-the-active-record-pattern) 和 [Data Mapper](./docs/docs/guides/1-active-record-data-mapper.md#what-is-the-data-mapper-pattern) 模式,这意味着你可以以最高效的方式编写高质量的、松耦合的、可扩展的、可维护的应用程序。 - -TypeORM 参考了很多其他优秀 ORM 的实现, 比如 [Hibernate](http://hibernate.org/orm/), [Doctrine](http://www.doctrine-project.org/) 和 [Entity Framework](https://www.asp.net/entity-framework)。 - -TypeORM 的一些特性: - -- 同时支持 [Active Record](./docs/docs/guides/1-active-record-data-mapper.md#what-is-the-active-record-pattern) 和 [Data Mapper](./docs/docs/guides/1-active-record-data-mapper.md#what-is-the-data-mapper-pattern) (随你选择) -- 实体和列 -- 数据库特性列类型 -- 实体管理 -- 存储库和自定义存储库 -- 清晰的对象关系模型 -- 关联(关系) -- 贪婪和延迟关系 -- 单向的,双向的和自引用的关系 -- 支持多重继承模式 -- 级联 -- 索引 -- 事务 -- 迁移和自动迁移 -- 连接池 -- 主从复制 -- 使用多个数据库连接 -- 使用多个数据库类型 -- 跨数据库和跨模式查询 -- 优雅的语法,灵活而强大的 QueryBuilder -- 左联接和内联接 -- 使用联查查询的适当分页 -- 查询缓存 -- 原始结果流 -- 日志 -- 监听者和订阅者(钩子) -- 支持闭包表模式 -- 在模型或者分离的配置文件中声明模式 -- 支持 MySQL / MariaDB / Postgres / SQLite / Microsoft SQL Server / Oracle / SAP Hana / sql.js -- 支持 MongoDB NoSQL 数据库 -- 可在 NodeJS / 浏览器 / Ionic / Cordova / React Native / Expo / Electron 平台上使用 -- 支持 TypeScript 和 JavaScript -- 生成高性能、灵活、清晰和可维护的代码 -- 遵循所有可能的最佳实践 -- 命令行工具 - -还有更多... - -通过使用 `TypeORM` 你的 `models` 看起来如下: - -```ts -import { Entity, PrimaryGeneratedColumn, Column } from "typeorm" - -@Entity() -export class User { - @PrimaryGeneratedColumn() - id: number - - @Column() - firstName: string - - @Column() - lastName: string - - @Column() - age: number -} -``` - -逻辑操作如下: - -```ts -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) // 根据id查找 -const timber = await repository.findOne({ - firstName: "Timber", - lastName: "Saw", -}) - -await repository.remove(timber) -``` - -或者,如果你更喜欢使用 `ActiveRecord` 模式,也可以这样用: - -```ts -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 -} -``` - -逻辑操作如下所示: - -```ts -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` - -2. 你还需要安装 `reflect-metadata`: - - `npm install reflect-metadata` - - 并且需要在应用程序的全局位置导入(例如在`app.ts`中) - - `import "reflect-metadata";` - -3. 你可能还需要安装 node typings(以此来使用 Node 的智能提示): - - `npm install @types/node --save-dev` - -4. 安装数据库驱动: - - - **MySQL** 或者 **MariaDB** - - `npm install mysql` (也可以安装 `mysql2`) - - - **PostgreSQL** - - `npm install pg` - - - **SQLite** - - `npm install sqlite3` - - - **Better SQLite** - - `npm install better-sqlite3` - - - **Microsoft SQL Server** - - `npm install mssql` - - - **sql.js** - - `npm install sql.js` - - - **Oracle** - - `npm install oracledb` - - 根据你使用的数据库,仅安装其中*一个*即可。 - 要使 Oracle 驱动程序正常工作,需要按照其[站点](https://github.com/oracle/node-oracledb)中的安装说明进行操作。 - - - **SAP Hana** - - `npm i @sap/hana-client` - - - **MongoDB** (试验性) - - `npm install mongodb` - - - **NativeScript**, **React Native**, **Cordova** 和 **Expo** - - 查看 [支持的平台](/supported-platforms.md) - -### TypeScript 配置 - -此外,请确保你使用的 TypeScript 编译器版本是**3.3**或更高版本,并且已经在 `tsconfig.json` 中启用了以下设置: - -```json -"emitDecoratorMetadata": true, -"experimentalDecorators": true, -``` - -## 快速开始 - -快速上手 TypeORM 的方法是使用其 CLI 命令生成启动项目。 -但是只有在 NodeJS 应用程序中使用 TypeORM 时,此操作才有效。如果你使用的是其他平台,请查看[分步指南](#分步指南)。 - -首先全局安装 TypeORM: - -```shell -npm install typeorm -g -``` - -然后转到要创建新项目的目录并运行命令: - -```shell -typeorm init --name MyProject --database mysql -``` - -其中 `name` 是项目的名称,`database` 是将使用的数据库。 - -数据库可以是以下值之一: `mysql`、 `mariadb`、 `postgres`、 `sqlite`、 `mssql`、 `oracle`、 `mongodb`、 -`cordova`、 `react-native`、 `expo`、 `nativescript`. - -此命令将在 `MyProject` 目录中生成一个包含以下文件的新项目: - -```sh -MyProject -├── src // TypeScript 代码 -│ ├── entity // 存储实体(数据库模型)的位置 -│ │ └── User.ts // 示例 entity -│ ├── migration // 存储迁移的目录 -│ └── index.ts // 程序执行主文件 -├── .gitignore // gitignore文件 -├── ormconfig.json // ORM和数据库连接配置 -├── package.json // node module 依赖 -├── README.md // 简单的 readme 文件 -└── tsconfig.json // TypeScript 编译选项 -``` - -> 你还可以在现有 node 项目上运行 `typeorm init`,但要注意,此操作可能会覆盖已有的某些文件。 - -接下来安装项目依赖项: - -```shell -cd MyProject -npm install -``` - -在安装过程中,编辑 `ormconfig.json` 文件并在其中编辑自己的数据库连接配置选项: - -```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 modules 后,即可运行应用程序: - -```shell -npm start -``` - -至此你的应用程序应该成功运行并将新用户插入数据库。你可以继续使用此项目并集成所需的其他模块并创建更多实体。 - -> 你可以通过运行 `typeorm init --name MyProject --database mysql --express` 来生成一个更高级的 Express 项目 - -## 分步指南 - -你对 ORM 有何期待?期望它将为你创建数据库表,并且无需编写大量难以维护的 SQL 语句来查找/插入/更新/删除数据。本指南将向你展示如何从头开始设置 TypeORM 并实现这些操作。 - -### 创建一个模型 - -使用数据库从创建表开始。如何告诉 TypeORM 创建数据库表?答案是 - 通过模型。 -应用程序中的模型即是数据库中的表。 - -举个例子, 你有一个 `Photo` 模型: - -```ts -export class Photo { - id: number - name: string - description: string - filename: string - views: number -} -``` - -并且希望将 photos 存储在数据库中。要在数据库中存储内容,首先需要一个数据库表,并从模型中创建数据库表。但是并非所有模型,只有定义为*entities*的模型。 - -### 创建一个实体 - -*实体*是由 `@Entity` 装饰器装饰的模型。将为此类模型创建数据库表。你可以使用 TypeORM 处理各处的实体,可以使用它们 load/insert/update/remove 并执行其他操作。 - -让我们将 `Photo` 模型作为一个实体 - -```typescript -import { Entity } from "typeorm" - -@Entity() -export class Photo { - id: number - name: string - description: string - filename: string - views: number - isPublished: boolean -} -``` - -现在,将为 `Photo` 实体创建一个数据库表,我们将能够在应用程序中的任何位置使用它。 -我们已经创建了一个数据库表,但是没有指明哪个字段属于哪一列,下面让我们在数据库表中创建列。 - -### 添加表列 - -要添加数据库列,你只需要将要生成的实体属性加上 `@Column` 装饰器。 - -```typescript -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` 装饰器。 - -```typescript -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/sequence/serial/generated identity column)。为此你需要将`@PrimaryColumn` 装饰器更改为 `@PrimaryGeneratedColumn` 装饰器: - -```ts -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) 类型(取决于数据库类型)。 -数字被映射到一个类似 integer 类型(取决于数据库类型)。但是我们不希望所有的列都是有限的 varchars 或 integer,让我们修改下代码以设置想要的数据类型: - -```ts -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 -} -``` - -列类型是特定于数据库的。你可以设置数据库支持的任何列类型。有关支持的列类型的更多信息,请参见[此处](./docs/docs/entity/1-entities.md#column-types)。 - -### 创建数据库的连接 - -当实体被创建后,让我们创建一个`index.ts`(或`app.ts`,无论你怎么命名)文件,并配置数据库连接:: - -```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) => { - // 这里可以写实体操作相关的代码 - }) - .catch((error) => console.log(error)) -``` - -我们在此示例中使用 MySQL,你可以使用任何其他受支持的数据库。要使用其他数据库,只需将选项中的 `type` 更改为希望使用的数据库类型:`mysql`,`mariadb`,`postgres`,`sqlite`,`mssql`,`oracle`,`cordova`,`nativescript`,`react-native`,`expo` 或 `mongodb`。同时还要确保 `host`, `port`, `username`, `password` 和 `database` 正确设置。 - -我们将 Photo 实体添加到此连接的实体列表中,并且所有需要使用的实体都必须加进来。 - -设置 `synchronize` 可确保每次运行应用程序时实体都将与数据库同步。 - -### 加载目录中所有实体 - -之后当我们创建更多实体时,都需要一一将它们添加到配置中的实体中,但是这不是很方便,所以我们可以设置加载整个目录,从中连接所有实体并使用: - -```ts -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) => { - // 这里可以写实体操作相关的代码 - }) - .catch((error) => console.log(error)) -``` - -但要小心使用这种方法。 -如果使用的是 `ts-node`,则需要指定 `.ts` 文件的路径。 -如果使用的是 `outDir`,那么需要在 `outDir` 目录中指定 `.js` 文件的路径。 -如果使用 `outDir`,当你删除或重命名实体时,请确保清除 `outDir` 目录并再次重新编译项目,因为当你删除 `.ts` 源文件时,其编译的 `.js` 文件不会从输出目录中删除,并且 TypeORM 依然会从 `outDir` 中加载这些文件,从而导致异常。 - -### 启动应用 - -现在可以启动 `app.ts`,启动后可以发现数据库自动被初始化,并且 Photo 这个表也会创建出来。 - -```text -+-------------+--------------+----------------------------+ -| photo | -+-------------+--------------+----------------------------+ -| id | int | PRIMARY KEY AUTO_INCREMENT | -| name | varchar(100) | | -| description | text | | -| filename | varchar(255) | | -| views | int | | -| isPublished | boolean | | -+-------------+--------------+----------------------------+ -``` - -### 添加和插入 photo - -现在创建一个新的 photo 存到数据库: - -```ts -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 语法代替: - -```ts -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)) -``` - -### 使用 Entity Manager - -我们刚创建了一张新 photo 表并将其保存在数据库中。通过使用 `EntityManager` 你可以操纵应用中的任何实体。 - -例如,加载已经保存的实体: - -```ts -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](./docs/docs/working-with-entity-manager/2-working-with-repository.md) 的信息。 - -### 使用 Repositories - -现在让我们重构之前的代码,并使用 `Repository` 替代 `EntityManager`。每个实体都有自己的 repository,可以处理其实体的所有操作。当你经常处理实体时,Repositories 比 EntityManagers 更方便使用: - -```ts -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)) -``` - -了解更多有关 [Repository](./docs/docs/working-with-entity-manager/2-working-with-repository.md) 的信息。 - -### 从数据库加载 - -让我们使用 Repository 尝试更多的加载操作: - -```ts -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,更新并保存到数据库: - -```ts -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: - -```ts -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 在数据库中被移除了。 - -### 创建一对一的关系 - -要与另一个类创建一对一的关系。先在 `PhotoMetadata.ts` 中创建一个新类。此 PhotoMetadata 类应包含 photo 的其他元信息: - -```ts -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` 的新装饰器,它允许我们在两个实体之间创建一对一的关系。 -`type => Photo` 是一个函数,返回我们想要与之建立关系的实体的类。由于特定于语言的关系,我们只能使用一个返回类的函数,而不是直接使用该类。 -同时也可以把它写成 `()=> Photo`,但是 `type => Photo` 显得代码更有可读性。type 变量本身不包含任何内容。 - -我们还添加了一个 `@JoinColumn` 装饰器,表明实体键的对应关系。关系可以是单向的或双向的。但是只有一方可以拥有。在关系的所有者方需要使用 `@JoinColumn` 装饰器。 - -如果运行该应用程序,你将看到一个新生成的表,它将包含一个带有外键的列: - -```text -+-------------+--------------+----------------------------+ -| photo_metadata | -+-------------+--------------+----------------------------+ -| id | int | PRIMARY KEY AUTO_INCREMENT | -| height | int | | -| width | int | | -| comment | varchar(255) | | -| compressed | boolean | | -| orientation | varchar(255) | | -| photoId | int | FOREIGN KEY | -+-------------+--------------+----------------------------+ -``` - -### 保存一对一的关系 - -现在让我们来创建一个 photo,它的元信息将它们互相连接起来。 - -```ts -import { createConnection } from "typeorm" -import { Photo } from "./entity/Photo" -import { PhotoMetadata } from "./entity/PhotoMetadata" - -createConnection(/*...*/) - .then(async (connection) => { - // 创建 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 - - // 创建 photo metadata - let metadata = new PhotoMetadata() - metadata.height = 640 - metadata.width = 480 - metadata.compressed = true - metadata.comment = "cybershoot" - metadata.orientation = "portait" - metadata.photo = photo // 联接两者 - - // 获取实体 repositories - let photoRepository = connection.getRepository(Photo) - let metadataRepository = connection.getRepository(PhotoMetadata) - - // 先保存photo - await photoRepository.save(photo) - - // 然后保存photo的metadata - await metadataRepository.save(metadata) - - // 完成 - console.log( - "Metadata is saved, and 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 之间建立双向关系。让我们来修改一下实体: - -```ts -import { - Entity, - Column, - PrimaryGeneratedColumn, - OneToOne, - JoinColumn, -} from "typeorm" -import { Photo } from "./Photo" - -@Entity() -export class PhotoMetadata { - /* ... 其他列 */ - - @OneToOne((type) => Photo, (photo) => photo.metadata) - @JoinColumn() - photo: Photo -} -``` - -```typescript -import { Entity, Column, PrimaryGeneratedColumn, OneToOne } from "typeorm" -import { PhotoMetadata } from "./PhotoMetadata" - -@Entity() -export class Photo { - /* ... 其他列 */ - - @OneToOne((type) => PhotoMetadata, (photoMetadata) => photoMetadata.photo) - metadata: PhotoMetadata -} -``` - -`photo => photo.metadata` 是用来指定反向关系的名称。Photo 类的元数据属性是在 Photo 类中存储 PhotoMetadata 的地方。你可以选择简单地将字符串传递给 `@OneToOne` 装饰器,而不是传递返回 photo 属性的函数,例如 `"metadata"`。这种函数类型的方法使我们的重构更容易。 - -注意,我们应该仅在关系的一侧使用 `@JoinColumn` 装饰器。你把这个装饰者放在哪一方将是这段关系的拥有方。关系的拥有方包含数据库中具有外键的列。 - -### 取出关系对象的数据 - -在一个查询中加载 photo 及 photo metadata 有两种方法。使用 `find *` 或使用 `QueryBuilder`。我们先使用 `find *` 方法。 `find *` 方法允许你使用 `FindOneOptions` / `FindManyOptions` 接口指定对象。 - -```ts -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 包含来自数据库的 photos 数组,每个 photo 包含其 photo metadata。详细了解本文档中的[Find 选项](./docs/docs/working-with-entity-manager/3-find-options.md)。 - -使用 find 选项很简单,但是如果你需要更复杂的查询,则应该使用 `QueryBuilder`。 `QueryBuilder` 使用更优雅的方式执行更复杂的查询: - -```ts -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"是应用于所选 photos 的别名。你可以使用别名来访问所选数据的列和属性。 - -### 使用 cascades 自动保存相关对象 - -我们可以在关系中设置 `cascade` 选项,这时就可以在保存其他对象的同时保存相关对象。让我们更改一下的 photo 的 `@OneToOne` 装饰器: - -```ts -export class Photo { - /// ... 其他列 - - @OneToOne((type) => PhotoMetadata, (metadata) => metadata.photo, { - cascade: true, - }) - metadata: PhotoMetadata -} -``` - -使用 `cascade` 允许就不需要边存 photo 边存元数据对象。我们可以简单地保存一个 photo 对象,由于使用了 cascade,metadata 也将自动保存。 - -```ts -createConnection(options) - .then(async (connection) => { - // 创建 photo 对象 - 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 - - // 创建 photo metadata 对象 - let metadata = new PhotoMetadata() - metadata.height = 640 - metadata.width = 480 - metadata.compressed = true - metadata.comment = "cybershoot" - metadata.orientation = "portait" - - photo.metadata = metadata // this way we connect them - - // 获取 repository - let photoRepository = connection.getRepository(Photo) - - // 保存photo的同时保存metadata - await photoRepository.save(photo) - - console.log("Photo is saved, photo metadata is saved too.") - }) - .catch((error) => console.log(error)) -``` - -### 创建多对一/一对多关系 - -让我们创建一个多对一/一对多的关系。假设一个 photo 有一个 author,每个 author 都可以有多个 photos。首先让我们创建一个`Author`类: - -```typescript -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) // 注意:我们将在下面的Photo类中创建author属性 - photos: Photo[] -} -``` - -`Author` 包含反向关系。 -`OneToMany` 总是反向的, 并且总是与 `ManyToOne`一起出现。 - -现在让我们将关系的所有者方添加到 Photo 实体中: - -```ts -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 -} -``` - -在多对一/一对多的关系中,拥有方总是多对一的。这意味着使用`@ManyToOne`的类将存储相关对象的 id。 -运行应用程序后,ORM 将创建`author`表: - -```text -+-------------+--------------+----------------------------+ -| author | -+-------------+--------------+----------------------------+ -| id | int | PRIMARY KEY AUTO_INCREMENT | -| name | varchar(255) | | -+-------------+--------------+----------------------------+ -``` - -它还将修改`photo`表,添加新的`author`列并为其创建外键: - -```text -+-------------+--------------+----------------------------+ -| photo | -+-------------+--------------+----------------------------+ -| id | int | PRIMARY KEY AUTO_INCREMENT | -| name | varchar(255) | | -| description | varchar(255) | | -| filename | varchar(255) | | -| isPublished | boolean | | -| authorId | int | FOREIGN KEY | -+-------------+--------------+----------------------------+ -``` - -### 创建多对多关系 - -假设一个 photo 可以放在多个 albums 中,每个 albums 可以包含多个 photo。让我们创建一个`Album`类: - -```ts -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`类: - -```ts -export class Photo { - /// ... 其他列 - - @ManyToMany((type) => Album, (album) => album.photos) - albums: Album[] -} -``` - -运行后,ORM 将创建**album_photos_photo_albums**\_联结表。 - -```text -+-------------+--------------+----------------------------+ -| album_photos_photo_albums | -+-------------+--------------+----------------------------+ -| album_id | int | PRIMARY KEY FOREIGN KEY | -| photo_id | int | PRIMARY KEY FOREIGN KEY | -+-------------+--------------+----------------------------+ -``` - -记得在 ORM 中使用 ConnectionOptions 注册`Album`类: - -```ts -const options: ConnectionOptions = { - // ... 其他选项 - entities: [Photo, PhotoMetadata, Author, Album], -} -``` - -现在让我们将 albums 和 photos 插入我们的数据库: - -```ts -let connection = await createConnection(options) - -// 创建一些 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) - -// 创建一些 photos -let photo = new Photo() -photo.name = "Me and Bears" -photo.description = "I am near polar bears" -photo.filename = "photo-with-bears.jpg" -photo.albums = [album1, album2] -await connection.manager.save(photo) - -// 现在我们的`photo`被保存了,并且'albums`被附加到它上面 -// 然后加载它们 -const loadedPhoto = await connection - .getRepository(Photo) - .findOne(1, { relations: ["albums"] }) -``` - -`loadedPhoto` 如下所示: - -```ts -{ - 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" - }] -} -``` - -### 使用 QueryBuilder - -你可以使用 QueryBuilder 构建几乎任何复杂性的 SQL 查询。例如,可以这样做: - -```ts -let photos = await connection - .getRepository(Photo) - .createQueryBuilder("photo") // 第一个参数是别名。即photos。 该参数必须指定。 - .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() -``` - -此查询选择所有 published 的 name 等于"My"或"Mishka"的 photos。它将从结果中的第 5 个(分页偏移)开始,并且仅选择 10 个结果(分页限制)。得到的结果将按 ID 降序排序。photo 的 albums 将被 left-joined,其元数据将被 inner joined。 - -由于 QueryBuilder 的自由度更高,因此在项目中可能会大量的使用它。 -更多关于 QueryBuilder 的信息,[可查看](./docs/docs/query-builder/1-select-query-builder.md)。 - -## 示例 - -查看[示例](https://github.com/typeorm/typeorm/tree/master/sample)用法。 - -下面这些 repositories 可以帮助你快速开始: - -- [Example how to use TypeORM with TypeScript](https://github.com/typeorm/typescript-example) -- [Example how to use TypeORM with JavaScript](https://github.com/typeorm/javascript-example) -- [Example how to use TypeORM with JavaScript and Babel](https://github.com/typeorm/babel-example) -- [Example how to use TypeORM with TypeScript and SystemJS in Browser](https://github.com/typeorm/browser-example) -- [Example how to use Express and TypeORM](https://github.com/typeorm/typescript-express-example) -- [Example how to use Koa and TypeORM](https://github.com/typeorm/typescript-koa-example) -- [Example how to use TypeORM with MongoDB](https://github.com/typeorm/mongo-typescript-example) -- [Example how to use TypeORM in a Cordova app](https://github.com/typeorm/cordova-example) -- [Example how to use TypeORM with an Ionic app](https://github.com/typeorm/ionic-example) -- [Example how to use TypeORM with React Native](https://github.com/typeorm/react-native-example) -- [Example how to use TypeORM with Electron using JavaScript](https://github.com/typeorm/electron-javascript-example) -- [Example how to use TypeORM with Electron using TypeScript](https://github.com/typeorm/electron-typescript-example) - -## 扩展 - -这几个扩展可以简化 TypeORM 的使用,并将其与其他模块集成: - -- [TypeORM integration](https://github.com/typeorm/typeorm-typedi-extensions) with [TypeDI](https://github.com/pleerock/typedi) -- [TypeORM integration](https://github.com/typeorm/typeorm-routing-controllers-extensions) with [routing-controllers](https://github.com/pleerock/routing-controllers) -- 从现有数据库生成模型 - [typeorm-model-generator](https://github.com/Kononnable/typeorm-model-generator) - -## 贡献 - -了解如何贡献[这里](https://github.com/typeorm/typeorm/blob/master/CONTRIBUTING.md)以及如何设置开发环境[这里](https://github.com/typeorm/typeorm/blob/master/DEVELOPER.md)。 - -感谢所有贡献者: - - - -## 赞助商 - -开源既困难又耗时。 如果你想投资 TypeORM 的未来,你可以成为赞助商,让我们的核心团队花更多时间在 TypeORM 的改进和新功能上。[成为赞助商](https://opencollective.com/typeorm) - - - -## 金牌赞助商 - -成为金牌赞助商,并从我们的核心贡献者那里获得高级技术支持。 [成为金牌赞助商](https://opencollective.com/typeorm) - - diff --git a/README.md b/README.md index 5aa5537e2..592800c22 100644 --- a/README.md +++ b/README.md @@ -207,12 +207,28 @@ This project exists thanks to all the people who contribute: ## Sponsors -Open source is hard and time-consuming. If you want to invest in TypeORM's future, you can become a sponsor and allow our core team to spend more time on TypeORM's improvements and new features. [Become a sponsor](https://opencollective.com/typeorm) +Open source is hard and time-consuming. If you want to invest in TypeORM's future, you can become a sponsor and allow our core team to spend more time on TypeORM's improvements and new features. - +### Champion -## Gold Sponsors +Become a champion sponsor and get premium technical support from our core contributors. [Become a champion](https://opencollective.com/typeorm) -Become a gold sponsor and get premium technical support from our core contributors. [Become a gold sponsor](https://opencollective.com/typeorm) + - +### Supporter + +Support TypeORM's development with a monthly contribution. [Become a supporter](https://opencollective.com/typeorm) + + + +### Community + +Join our community of supporters and help sustain TypeORM. [Become a community supporter](https://opencollective.com/typeorm) + + + +### Sponsor + +Make a one-time or recurring contribution of your choice. [Become a sponsor](https://opencollective.com/typeorm) + + diff --git a/README_ko.md b/README_ko.md deleted file mode 100644 index 8ae8c24fd..000000000 --- a/README_ko.md +++ /dev/null @@ -1,1205 +0,0 @@ -
- - - - - TypeORM Logo - - -
-
- NPM Version - NPM Downloads - Commit Validation - Coverage Status - MIT License -
-
-
- -TypeORM은 NodeJS, Browser, Cordova, Ionic, React Native, NativeScript, Expo 및 Electron 플랫폼에서 실행할 수 있는 [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping)이며 TypeScript 및 JavaScript(ES2021)와 함께 사용할 수 있다. TypeORM의 목표는 항상 최신 JavaScript 기능을 지원하고 몇 개의 테이블이 있는 작은 응용 프로그램에서 여러 데이터베이스가 있는 대규모 엔터프라이즈 응용 프로그램에 이르기까지 데이터베이스를 사용하는 모든 종류의 응용 프로그램을 개발하는 데 도움이 되는 추가 기능을 제공하는 것이다. - -TypeORM은 현재 존재하는 다른 모든 JavaScript ORM과 달리 [Active Record](./docs/docs/guides/1-active-record-data-mapper.md#what-is-the-active-record-pattern) 및 [Data Mapper](./docs/docs/guides/1-active-record-data-mapper.md#what-is-the-data-mapper-pattern) 패턴을 모두 지원한다. 즉, 고품질의 느슨하게 결합된 확장 가능하고 유지 관리 가능한 애플리케이션을 가장 생산적인 방식으로 작성할 수 있다. - -TypeORM은 [Hibernate](http://hibernate.org/orm/), [Doctrine](http://www.doctrine-project.org/) 및 [Entity Framework](https://www.asp.net/entity-framework)와 같은 다른 ORM의 영향을 많이 받는다. - -## 특징 - -- [DataMapper](./docs/docs/guides/1-active-record-data-mapper.md#what-is-the-data-mapper-pattern)와 [ActiveRecord](./docs/docs/guides/1-active-record-data-mapper.md#what-is-the-active-record-pattern)을 모두 지원. -- 항목 및 열. -- 데이터베이스 별 열 유형. -- 엔터티 관리자. -- 레포지토리 및 사용자 지정 레포지토리. -- 명확한 객체 관계형 모델. -- 연관(관계). -- 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을 사용하면 당신의 모델은 다음과 같이 보인다. - -```typescript -import { Entity, PrimaryGeneratedColumn, Column } from "typeorm" - -@Entity() -export class User { - @PrimaryGeneratedColumn() - id: number - - @Column() - firstName: string - - @Column() - lastName: string - - @Column() - age: number -} -``` - -당신의 도메인 로직은 다음과 같다: - -```typescript -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`구현을 사용하는걸 선호하는 경우, 당신은 다음과 같이 사용할 수도 있다. - -```typescript -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 -} -``` - -당신의 도메인 로직은 다음과 같다: - -```typescript -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` - -2. `reflect-metadata` 심(shim)을 설치한다: - - `npm install reflect-metadata` - - 그리고 그것을 app (예: `app.ts`)의 전역 위치에 불러와야 한다: - - `import "reflect-metadata";` - -3. 노드 타입을 설치해야 할 수도 있다: - - `npm install @types/node --save-dev` - -4. DB 드라이버 설치를 설치한다: - - - **MySQL** 또는 **MariaDB**의 경우 - - `npm install mysql` / `npm install mysql2` - - - for **PostgreSQL**또는 **CockroachDB**의 경우 - - `npm install pg` - - - **SQLite**의 경우 - - `npm install sqlite3` - - - **Better SQLite**의 경우 - - `npm install better-sqlite3` - - - **Microsoft SQL Server**의 경우 - - `npm install mssql` - - - **sql.js**의 경우 - - `npm install sql.js` - - - **Oracle**의 경우 - - `npm install oracledb` - - Oracle 드라이버를 작동시키려면 [해당](https://github.com/oracle/node-oracledb) 사이트의 설치 지침을 따라야 한다. - - - **SAP Hana**의 경우 - - `npm i @sap/hana-client` - - - **MongoDB** (experimental)의 경우 - - `npm install mongodb` - - - **NativeScript**, **React Native**, **Cordova**, **Expo**의 경우 - - [지원되는 플랫폼 문서](./docs/docs/help/2-supported-platforms.md) 확인 - -### TypeScript 환경 설정 - -또한 TypeScript 버전 **3.3** 이상을 사용 중이어야 하고, `tsconfig.json`에서 다음 설정을 사용 가능하게 했는지 확인해야 한다: - -```json -"emitDecoratorMetadata": true, -"experimentalDecorators": true, -``` - -## 빠른 시작 - -TypeORM을 시작하는 가장 빠른 방법은 CLI 명령을 사용하여 시작 프로젝트를 생성하는 것이다. 빠른 시작은 NodeJS 애플리케이션에서 TypeORM을 사용하는 경우에만 동작한다. 다른 플랫폼을 사용하는 경우 [단계별 가이드](#step-by-step-guide)에 따라 진행해야 한다. - -먼저, 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` 디렉토리에 다음의 파일들이 있는 새 프로젝트를 생성한다: - -```text -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`을 실행할 수도 있지만, 이미 가지고 있는 파일 중 일부를 무시할 수도 있기 때문에 주의해야한다. - -다음 단계는 새 프로젝트 종속성을 설치하는 것이다: - -```shell -cd MyProject -npm install -``` - -설치가 진행되는 동안 `ormconfig.json`파일을 편집하여 데이터베이스 연결 설정 옵션들을 입력한다: - -```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 모듈이 설치되면 애플리케이션을 실행할 수 있다: - -```shell -npm start -``` - -애플리케이션이 성공적으로 실행되고 새 사용자를 데이터베이스에 추가해야 한다. 이 프로젝트로 계속 작업하거나 필요한 다른 모듈을 통합하고 더 많은 엔터티 생성을 시작할 수 있다. - -> `typeorm init --name MyProject --database mysql --express` 명령을 실행하여 Express가 설치된 고급 프로젝트를 생성할 수 있다. - -> `typeorm init --name MyProject --database postgres --docker` 명령을 실행하여 docker 작성 파일을 생성할 수 있다. - -## 단계별 가이드 - -ORM에서 무엇을 기대하는가? 우선, 유지 관리가 어려운 SQL 쿼리를 많이 작성하지 않고도 데이터베이스 테이블을 생성하고 데이터를 검색 / 삽입 / 업데이트 / 삭제 할 것으로 기대한다. 이 가이드는 TypeORM을 처음부터 설정하고 ORM에서 기대하는 것을 수행하는 방법을 보여준다. - -### 모델 생성 - -데이터베이스 작업은 테이블 생성에서 시작된다. TypeORM에게 데이터베이스 테이블을 생성하도록 지시하는 방법은 무엇인가? 답은 '모델을 통해서'이다. 앱의 모델은 데이터베이스 테이블입니다. - -예를 들어, `Photo` 모델이 있다고 하자: - -```typescript -export class Photo { - id: number - name: string - description: string - filename: string - views: number - isPublished: boolean -} -``` - -그리고 데이터베이스에 photo를 저장하려고 한다. 데이터베이스에 어떤 것을 저장하려면 먼저 데이터베이스 테이블이 필요하고 모델에서 데이터베이스 테이블이 생성된다. 모든 모델이 아니라 *entities*로 정의한 모델만 해당된다. - -### 엔터티 생성 - -*Entity*는 `@Entity` 데코레이터로 장식(decorated)한 모델이다. 이러한 모델에 대한 데이터베이스 테이블이 생성된다. TypeORM을 사용하면 어디에서나 엔터티로 로드 / 삽입 / 업데이트 / 제거 또는 다른 작업을 수행할 수 있다. - -`Photo` 모델을 엔터티로 만들어 보자. - -```typescript -import { Entity } from "typeorm" - -@Entity() -export class Photo { - id: number - name: string - description: string - filename: string - views: number - isPublished: boolean -} -``` - -이제 `Photo` 엔터티에 대한 데이터베이스 테이블이 생성되고 앱의 어디에서나 이 테이블로 작업할 수 있다. 우리는 데이터베이스 테이블을 만들었다. 그런데 어떤 테이블이 열(columns) 없이 존재할 수 있을까? 데이터베이스 테이블에 몇 개의 열을 생성해 보자. - -### 테이블 열 추가 - -데이터베이스에 열을 추가하려면 `@Column` 데코레이터를 사용하여 열로 만들고자 하는 엔터티의 속성을 장식하기만 하면 된다. - -```typescript -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` 데코레이터를 사용해야 한다. - -```typescript -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`로 변경해야 한다. - -```typescript -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 또는 정수로 제한되기를 원하지 않는다. 올바른 데이터 유형을 설정해보자: - -```typescript -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 -} -``` - -열 타입은 데이터베이스에 따라 다르다. 데이터베이스가 지원하는 모든 열 타입을 설정할 수 있다. 지원 되는 열 타입에 대한 자세한 정보는 [여기](./docs/docs/entity/1-entities.md#column-types)에서 찾을 수 있다. - -### 데이터 베이스에 대한 연결 생성 - -이제 엔티티가 생성되면 `index.ts`(또는 `app.ts`처럼 원하는 것으로 부를 수 있음) 파일을 만들고 그곳에서 연결을 설정해 보자. - -```typescript -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`를 설정하면 애플리케이션을 실행할 때마다 엔터티가 데이터베이스와 동기화된다. - -### 디렉토리에서 모든 엔터티 불러오기 - -나중에 더 많은 엔터티를 만들 때 그것들을 설정에 추가해야 한다. 이것은 그다지 편리하지 않기 때문에 대신 모든 엔터티가 연결되고 연결에 사용될 전체 디렉토리를 설정할 수 있다: - -```typescript -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에 대한 데이터베이스 테이블이 생성된다. - -```text -+-------------+--------------+----------------------------+ -| photo | -+-------------+--------------+----------------------------+ -| id | int | PRIMARY KEY AUTO_INCREMENT | -| name | varchar(100) | | -| description | text | | -| filename | varchar(255) | | -| views | int | | -| isPublished | boolean | | -+-------------+--------------+----------------------------+ -``` - -### 데이터베이스에 photo 생성 및 삽입 - -이제 새 photo를 만들어 데이터베이스에 저장해 보자: - -```typescript -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 구문을 대신 사용해보자. - -```typescript -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를 사용하였다. 이처럼 엔터티 매니저를 사용하여 앱의 모든 엔터티를 조작할 수 있다. 예를 들어 저장된 엔터티를 로드해보자: - -```typescript -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 객체의 배열이다. - -[여기](./docs/docs/working-with-entity-manager/2-working-with-repository.md)에서 엔터티 매니저에 대해 자세히 알 수 있다. - -### 리포지토리 사용 - -이제 코드를 리팩토링하여 `EntityManager` 대신 `Repository`를 사용해보자. 각 엔터티에는 엔터티에 대한 모든 작업을 처리하는 자체 리포지토리가 있다. 엔터티를 많이 다룰 때는 EntityManager보다 Repositories를 사용하는 것이 더 편리하다. - -```typescript -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)) -``` - -[여기](./docs/docs/working-with-entity-manager/2-working-with-repository.md)에서 리포지토리에 대해 자세히 알 수 있다. - -### 데이터베이스에서 로드 - -리포지토리를 사용하여 더 많은 로드 작업을 시도해보자: - -```typescript -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를 로드하고 업데이트하고 저장해보자: - -```typescript -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를 제거해보자: - -```typescript -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의 추가 메타 정보가 포함되어야 한다. - -```typescript -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 관계에 대한 외래 키가 있는 열이 포함된다: - -```text -+-------------+--------------+----------------------------+ -| photo_metadata | -+-------------+--------------+----------------------------+ -| id | int | PRIMARY KEY AUTO_INCREMENT | -| height | int | | -| width | int | | -| comment | varchar(255) | | -| compressed | boolean | | -| orientation | varchar(255) | | -| photoId | int | FOREIGN KEY | -+-------------+--------------+----------------------------+ -``` - -### 1:1 관계 저장 - -이제 photo와 해당 metadata를 저장하고 서로 첨부해보자. - -```typescript -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 간의 관계를 양방향으로 만들어야 한다. 엔터티를 수정해보자. - -```typescript -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 -} -``` - -```typescript -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` 인터페이스로 개체를 지정할 수 있게 된다. - -```typescript -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가 포함된다. [이 문서](./docs/docs/working-with-entity-manager/3-find-options.md)에서 찾기 옵션에 대해 자세히 알아볼 수 있다. - -Using find options is good and dead 찾기 옵션을 사용하는 것은 훌륭하고 간단하지만 더 복잡한 쿼리가 필요한 경우에는 `QueryBuilder`를 대신 사용해야 한다. `QueryBuilder`를 사용하면 보다 복잡한 쿼리를 우아한 방식으로 사용할 수 있다. - -```typescript -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` 데코레이터를 약간 변경해 보자. - -```typescript -export class Photo { - /// ... other columns - - @OneToOne((type) => PhotoMetadata, (metadata) => metadata.photo, { - cascade: true, - }) - metadata: PhotoMetadata -} -``` - -`cascade`를 사용하면 photo를 따로 저장하지 않고도 metadata 객체를 따로 저장할 수 있게 된다. 이제 photo 객체를 간단히 저장할 수 있으며 metadata 객체는 cascade 옵션으로 인해 자동으로 저장된다. - -```typescript -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 클래스를 생성해 보자: - -```typescript -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 엔터티에 추가해보자: - -```typescript -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` 테이블을 생성한다: - -```text -+-------------+--------------+----------------------------+ -| author | -+-------------+--------------+----------------------------+ -| id | int | PRIMARY KEY AUTO_INCREMENT | -| name | varchar(255) | | -+-------------+--------------+----------------------------+ -``` - -또한 새 `author` 열을 추가하고 이에 대한 외래 키를 생성하여 `photo` 테이블을 수정한다: - -```text -+-------------+--------------+----------------------------+ -| photo | -+-------------+--------------+----------------------------+ -| id | int | PRIMARY KEY AUTO_INCREMENT | -| name | varchar(255) | | -| description | varchar(255) | | -| filename | varchar(255) | | -| isPublished | boolean | | -| authorId | int | FOREIGN KEY | -+-------------+--------------+----------------------------+ -``` - -### M:N 관계 생성 - -M:N 관계를 만들어 보자. 사진이 여러 album에 포함될 수 있고 각 album들에 많은 photo들이 포함될 수 있다고 가정하여 `Album` 클래스를 만들어 보자: - -```typescript -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` 클래스에 관계의 반대측을 추가해 보겠습니다: - -```typescript -export class Photo { - /// ... other columns - - @ManyToMany((type) => Album, (album) => album.photos) - albums: Album[] -} -``` - -애플리케이션을 실행한 후, ORM은 **album_photos_photo_albums**라는 *접합 테이블(junction table)*을 생성한다.: - -```text -+-------------+--------------+----------------------------+ -| album_photos_photo_albums | -+-------------+--------------+----------------------------+ -| album_id | int | PRIMARY KEY FOREIGN KEY | -| photo_id | int | PRIMARY KEY FOREIGN KEY | -+-------------+--------------+----------------------------+ -``` - -ORM의 연결에서 `Album` 클래스를 등록하는 것을 잊으면 안된다: - -```typescript -const options: ConnectionOptions = { - // ... other options - entities: [Photo, PhotoMetadata, Author, Album], -} -``` - -이제 데이터베이스에 album과 photo를 삽입해 보자: - -```typescript -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`는 다음과 같다: - -```typescript -{ - 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 쿼리를 작성할 수 있다. 예를 들어 다음과 같이 할 수 있다: - -```typescript -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)된다. - -애플리케이션에서 쿼리 빌더를 많이 사용할 것이다. [여기](./docs/docs/query-builder/1-select-query-builder.md)에서 쿼리 빌더에 대해 자세히 알 수 있다. - -## 샘플들 - -사용 예시는 [sample](https://github.com/typeorm/typeorm/tree/master/sample)의 샘플을 살펴보자. - -clone하여 시작할 수 있는 몇 가지 리포지토리가 있다: - -- [TypeScript와 함께 TypeORM을 사용하는 방법의 예시](https://github.com/typeorm/typescript-example) -- [JavaScript에서 TypeORM을 사용하는 방법의 예시](https://github.com/typeorm/javascript-example) -- [JavaScript 및 Babel과 함께 TypeORM을 사용하는 방법의 예시](https://github.com/typeorm/babel-example) -- [브라우저에서 TypeScript 및 SystemJS와 함께 TypeORM을 사용하는 방법의 예시](https://github.com/typeorm/browser-example) -- [브라우저에서 TypeScript 및 React와 함께 TypeORM을 사용하는 방법의 예시](https://github.com/ItayGarin/typeorm-react-swc) -- [Express 및 TypeORM 사용 방법의 예시](https://github.com/typeorm/typescript-express-example) -- [Koa 및 TypeORM 사용 예시](https://github.com/typeorm/typescript-koa-example) -- [MongoDB에서 TypeORM을 사용하는 방법의 예시](https://github.com/typeorm/mongo-typescript-example) -- [Cordova 앱에서 TypeORM을 사용하는 방법의 예시](https://github.com/typeorm/cordova-example) -- [Ionic 앱에서 TypeORM을 사용하는 방법의 예시](https://github.com/typeorm/ionic-example) -- [React Native에서 TypeORM을 사용하는 방법의 예시](https://github.com/typeorm/react-native-example) -- [Nativescript-Vue와 함께 TypeORM을 사용하는 방법의 예시](https://github.com/typeorm/nativescript-vue-typeorm-sample) -- [Nativescript-Angular와 함께 TypeORM을 사용하는 방법의 예시](https://github.com/betov18x/nativescript-angular-typeorm-example) -- [JavaScript를 사용하여 Electron에서 TypeORM을 사용하는 방법의 예시](https://github.com/typeorm/electron-javascript-example) -- [TypeScript를 사용하여 Electron과 함께 TypeORM을 사용하는 방법의 예시](https://github.com/typeorm/electron-typescript-example) - -## 확장 - -TypeORM 작업을 단순화하고 다른 모듈과 통합하는 몇 가지 확장 방법이 있다: - -- [TypeORM](https://github.com/typeorm/typeorm-typedi-extensions)과 [TypeDI](https://github.com/pleerock/typedi) 통합 -- [라우팅 컨트롤러](https://github.com/typeorm/typeorm-routing-controllers-extensions)와 [TypeORM 통합](https://github.com/pleerock/routing-controllers) -- 기존 데이터베이스에서 모델 생성 - [typeorm-model-generator](https://github.com/Kononnable/typeorm-model-generator) -- Fixtures loader - [typeorm-fixtures-cli](https://github.com/RobinCK/typeorm-fixtures) -- ER 다이어그램 생성기 - [typeorm-uml](https://github.com/eugene-manuilov/typeorm-uml/) -- 다른 ER 다이어그램 생성기 - [erdia](https://www.npmjs.com/package/erdia/) -- 데이터베이스 생성(create)/삭제(drop) - [typeorm-extension](https://github.com/Tada5hi/typeorm-extension) - -## 기여 - -[여기](https://github.com/typeorm/typeorm/blob/master/CONTRIBUTING.md)에서 기여에 대해 알아보고 [여기](https://github.com/typeorm/typeorm/blob/master/DEVELOPER.md)에서 개발 환경을 설정하는 방법을 알 수 있다. - -이 프로젝트는 모든 기여자 덕분에 존재한다: - - - -## 스폰서 - -오픈 소스는 어려운데다가 많은 시간이 소요된다. TypeORM의 미래에 투자하고 싶다면 후원자가 되어 핵심 팀이 TypeORM의 개선 사항과 새로운 기능에 더 많은 시간을 할애할 수 있도록 도울 수 있다. 후원자가 되십시오. - - - -## Gold 스폰서 - -Gold 스폰서가 되어 핵심 기여자로부터 프리미엄 기술 지원을 받을 수 있다. [Gold 스폰서 되기](https://opencollective.com/typeorm) - -