typeorm數(shù)據(jù)庫(kù)ORM框架中文文檔 js豆村,node,typescript

TypeORM是一個(gè)ORM框架骂删,它可以運(yùn)行在NodeJS掌动、瀏覽器、Cordova宁玫、PhoneGap坏匪、Ionic、React Native撬统、Expo和Electron平臺(tái)上适滓,可以與TypeScript和JavaScript (ES5, ES6, ES7)一起使用。
它的目標(biāo)是始終支持最新的JavaScript特性并提供額外的特性以幫助你開發(fā)任何使用數(shù)據(jù)庫(kù)的應(yīng)用程序 —— 不管是只有幾張表的小型應(yīng)用還是擁有多數(shù)據(jù)庫(kù)的大型企業(yè)應(yīng)用恋追。

不同于現(xiàn)有的所有其他JavaScript ORM框架凭迹,TypeORM支持Active Record和Data Mapper模式,這意味著你用最有效的方法編寫高質(zhì)量的苦囱、松耦合的嗅绸、可擴(kuò)展的、可維護(hù)的應(yīng)用程序撕彤。

TypeORM參考了很多其他優(yōu)秀ORM的實(shí)現(xiàn), 比如 Hibernate, DoctrineEntity Framework.

TypeORM 的一些特性:

  • 支持Active Record和Data Mapper(你可以自由選擇)
  • 實(shí)體和列
  • 數(shù)據(jù)庫(kù)特性列類型
  • 實(shí)體管理
  • 存儲(chǔ)庫(kù)和自定義存儲(chǔ)庫(kù)
  • 清潔對(duì)象關(guān)系模型
  • 關(guān)聯(lián)(關(guān)系)
  • 貪婪和延遲關(guān)系
  • 單向的鱼鸠,雙向的和自引用的關(guān)系
  • 支持多重繼承模式
  • 級(jí)聯(lián)
  • 索引
  • 事務(wù)
  • 遷移和自動(dòng)遷移
  • 連接池
  • 復(fù)制
  • 使用多個(gè)數(shù)據(jù)庫(kù)連接
  • 使用多個(gè)數(shù)據(jù)庫(kù)類型
  • 跨數(shù)據(jù)庫(kù)和跨模式查詢
  • 優(yōu)雅的語(yǔ)法,靈活而強(qiáng)大的QueryBuilder
  • 左聯(lián)接和內(nèi)聯(lián)接
  • 準(zhǔn)確的分頁(yè)連接查詢
  • 查詢緩存
  • 原始結(jié)果流
  • 日志
  • 監(jiān)聽者和訂閱者(鉤子)
  • 支持閉包表模式
  • 在模型或者分離的配置文件中聲明模式
  • json / xml / yml / env格式的連接配置
  • 支持 MySQL / MariaDB / Postgres / SQLite / Microsoft SQL Server / Oracle / sql.js
  • 支持 MongoDB NoSQL 數(shù)據(jù)庫(kù)
  • 在NodeJS / 瀏覽器 / Ionic / Cordova / React Native / Expo / Electron平臺(tái)上工作
  • 支持 TypeScript 和 JavaScript
  • 產(chǎn)生出高性能羹铅、靈活蚀狰、清晰和可維護(hù)的代碼
  • 遵循所有可能的最佳實(shí)踐
  • 命令行工具

還有更多...

使用TypeORM你的模型是這樣的:

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

@Entity()
export class User {

    @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 repository.save(user);

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

await repository.remove(timber);

或者,你如果喜歡使用“ActiveRecord”實(shí)現(xiàn)职员,你也可以使用它:

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

請(qǐng)注意

這個(gè)文檔可能不是最新的麻蹋。
可以去官網(wǎng)查看最新的英文文檔。
非常歡迎你的貢獻(xiàn)焊切。

安裝

  1. 安裝TypeORM:

    npm install typeorm --save

  2. 需要安裝依賴模塊 reflect-metadata :

    npm install reflect-metadata --save

    在應(yīng)用里全局引用一下:

    • 比如在app.ts的入口處 require("reflect-metadata")
  3. 你可能需要安裝node類型:

    npm install @types/node --save

  4. 安裝數(shù)據(jù)庫(kù)驅(qū)動(dòng):

    • MySQLMariaDB

      npm install mysql --save

    • PostgreSQL

      npm install pg --save

    • SQLite

      npm install sqlite3 --save

    • Microsoft SQL Server

      npm install mssql --save

    • sql.js

      npm install sql.js --save

    • Oracle (experimental)

      npm install oracledb --save

    可以根據(jù)你的數(shù)據(jù)庫(kù)選擇安裝上面的任意一個(gè).

    使用oracle驅(qū)動(dòng)需要參考安裝說明:地址.

TypeScript配置

確保你的TypeScript編譯器的版本大于2.3扮授,并且在tsconfig.json開啟下面設(shè)置:

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

同時(shí)需要開啟編譯選項(xiàng)里的lib下的es6或者從@typings安裝es6-shim

快速開始

開始使用TypeORM的最快方法是使用它的CLI命令生成一個(gè)初始項(xiàng)目。
快速開始只有在NodeJS應(yīng)用程序中使用TypeORM才可以使用专肪。
如果你正在使用其他平臺(tái)刹勃,請(qǐng)看分步指南

首先全局安裝TypeORM:

npm install typeorm -g

然后轉(zhuǎn)到新項(xiàng)目的目錄并運(yùn)行該命令:

typeorm init --name MyProject --database mysql

name即項(xiàng)目的名稱嚎尤,database是你將使用的數(shù)據(jù)庫(kù)荔仁。數(shù)據(jù)庫(kù)可以是下列值之一:mysqlmariadbpostgres咕晋、sqlitemssql收奔、oracle掌呜、mongodbcordova坪哄、react-native质蕉、expo

該命令將在MyProject目錄中生成一個(gè)新項(xiàng)目翩肌,其中包含以下文件:

MyProject
├── src              // 放你的 TypeScript 代碼
│   ├── entity       // 放實(shí)體(數(shù)據(jù)庫(kù)模型)的目錄
│   │   └── User.ts  // 實(shí)體的案例
│   ├── migration    // 遷移文件目錄
│   └── index.ts     // 應(yīng)用程序入口
├── .gitignore       // 標(biāo)準(zhǔn)git忽略文件
├── ormconfig.json   // ORM和數(shù)據(jù)連接配置
├── package.json     // node模塊依賴
├── README.md        // 簡(jiǎn)單的說明文件
└── tsconfig.json    // TypeScript編譯配置

你也可以在現(xiàn)有的node項(xiàng)目目錄執(zhí)行typeorm init模暗,但是一定要小心 - 它可能會(huì)覆蓋你已經(jīng)有的一些文件。

下一步是安裝項(xiàng)目依賴

cd MyProject
npm install

在安裝過程中念祭,修改 ormconfig.json 文件將自己的數(shù)據(jù)庫(kù)連接配置選項(xiàng)放在其中:

{
   "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"
   ]
}

通常來說兑宇,大多數(shù)時(shí)候你只需要配置hostusername粱坤,password隶糕,database 或者 port 選項(xiàng)。

配置和模塊安裝都完成之后站玄,就可以運(yùn)行應(yīng)用程序了:

npm start

就是這樣枚驻,你的應(yīng)用程序應(yīng)該成功地運(yùn)行并將一個(gè)新用戶插入到數(shù)據(jù)庫(kù)中。
你可以繼續(xù)這個(gè)項(xiàng)目株旷,集成你需要的其他模塊再登,并創(chuàng)建更多的實(shí)體。

運(yùn)行typeorm init --name MyProject --database mysql --express命令可以安裝express晾剖,生成一個(gè)更高級(jí)的項(xiàng)目锉矢。

分步指南

你對(duì)ORM的期望是什么?
首先齿尽,你預(yù)期它將為你創(chuàng)建數(shù)據(jù)庫(kù)表沈撞,并查找/插入/更新/刪除你的數(shù)據(jù),而不必編寫大量難以維護(hù)的SQL查詢雕什。
本指南將向你展示如何從頭開始設(shè)置TypeORM缠俺,并讓它按照你所期望的ORM進(jìn)行。

創(chuàng)建模型

與數(shù)據(jù)庫(kù)一起工作從創(chuàng)建表開始贷岸。
如何告訴TypeORM創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)表壹士?
答案是 - 通過模型。
你的應(yīng)用程序中的模型就是你的數(shù)據(jù)庫(kù)中的表偿警。

例如你有一個(gè) Photo 模型:

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

你想在你的數(shù)據(jù)庫(kù)中存儲(chǔ)照片躏救。
要在數(shù)據(jù)庫(kù)中存儲(chǔ)東西,首先需要一個(gè)數(shù)據(jù)庫(kù)表,并從模型創(chuàng)建數(shù)據(jù)庫(kù)表盒使。
不是所有的模型崩掘,而僅僅是那些你定義為實(shí)體

創(chuàng)建實(shí)體

實(shí)體是你用 @Entity 裝飾的模型少办。
將為這些模型創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)表苞慢。
使用TypeORM你將在任何地方使用實(shí)體。
你可以使用他們加載/插入/更新/刪除并執(zhí)行其他操作英妓。

讓我們把Photo模型變成一個(gè)實(shí)體:

import {Entity} from "typeorm";

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

現(xiàn)在挽放,將會(huì)為 Photo 實(shí)體創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)表,我們能夠在應(yīng)用程序的任何地方使用它蔓纠。
我們已經(jīng)創(chuàng)建了一個(gè)數(shù)據(jù)庫(kù)表辑畦,然而沒有列的表示不存在的。
讓我們?cè)跀?shù)據(jù)庫(kù)表中創(chuàng)建一些列吧腿倚。

添加數(shù)據(jù)庫(kù)表列

要添加數(shù)據(jù)庫(kù)列纯出,只需要將生成的實(shí)體的屬性用 @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;
}

現(xiàn)在 id敷燎,name潦刃,descriptionfilename懈叹,viewsisPublished 列將會(huì)被添加 photo 表乖杠。
數(shù)據(jù)庫(kù)中的列類型是從你使用的屬性類型推斷出來的,例如:number 將會(huì)被轉(zhuǎn)成 integer澄成,string 轉(zhuǎn)為 varchar胧洒,boolean 轉(zhuǎn)為 bool,等墨状。
但是你可以通過隱式在 @Column 裝飾器傳入類型將列類型指定為任何你數(shù)據(jù)庫(kù)支持的類型卫漫。

我們生成了一個(gè)帶有列的數(shù)據(jù)庫(kù)表,但是還剩下一件事肾砂。
每個(gè)數(shù)據(jù)庫(kù)表必須有一個(gè)帶有主鍵的列列赎。

創(chuàng)建一個(gè)主鍵列

每個(gè)表都必須至少有一個(gè)主鍵列。這是一個(gè)要求,你不能避免。要使列成為主鍵旺韭,你需要使用 @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;
}

創(chuàng)建一個(gè)自動(dòng)生成的列

現(xiàn)在回论,假設(shè)你希望將id列自動(dòng)生成(這就是所謂的自動(dòng)遞增/按順序/連續(xù)的/生成唯一標(biāo)識(shí)列)。
要做到這一點(diǎn),你需要將 @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;
}

列數(shù)據(jù)類型

接下來,讓我們修復(fù)數(shù)據(jù)類型嚷狞。默認(rèn)情況下块促,字符串被映射到一個(gè)varchar(255)類型(取決于數(shù)據(jù)庫(kù)類型)。
數(shù)字被映射到一個(gè)integer類型(取決于數(shù)據(jù)庫(kù)類型)床未。
我們不希望所有的列都是有限的varchars或整數(shù)竭翠。
讓我們?cè)O(shè)置正確的數(shù)據(jù)類型:

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

列類型取決于數(shù)據(jù)庫(kù)支持的類型。
可以設(shè)置數(shù)據(jù)庫(kù)支持的任何列類型薇搁。
更多關(guān)于支持的列類型信息可以在這里找到這里斋扰。

創(chuàng)建數(shù)據(jù)庫(kù)連接

現(xiàn)在實(shí)體已經(jīng)有了,讓我們新建一個(gè) index.ts (或 app.ts 不管你叫它什么)的文件只酥,并配置數(shù)據(jù)庫(kù)連接:

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 => {
    // 這里可以寫實(shí)體操作相關(guān)的代碼 
}).catch(error => console.log(error));

在例子里使用的是mysql,你也可以選擇其他數(shù)據(jù)庫(kù)呀狼,只需要簡(jiǎn)單修改driver選項(xiàng)里的數(shù)據(jù)庫(kù)的類型就可以了裂允,比如:mysql、mariadb哥艇、postgres绝编、sqlite、mssql貌踏、oracle十饥、cordova、react-native祖乳、expo或mongodb
同樣可以修改host, port, username, password 以及database等設(shè)置逗堵。

把Photo實(shí)體加到數(shù)據(jù)連接的實(shí)體列表中,所有需要在這個(gè)連接下使用的實(shí)體都必須加到這個(gè)列表中眷昆。

synchronize選項(xiàng)可以在應(yīng)用啟動(dòng)時(shí)確保你的實(shí)體和數(shù)據(jù)庫(kù)保持同步蜒秤。

引用目錄下的所有實(shí)體

接下來我們可能會(huì)創(chuàng)建更多的實(shí)體并把它們一一加到配置當(dāng)中。
不過這樣會(huì)比較麻煩亚斋,好在可以直接寫上實(shí)體的目錄作媚,這樣這個(gè)目錄下的所有實(shí)體都可以在當(dāng)前連接中被使用:

import {createConnection} from "typeorm";

createConnection({
    driver: {
        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));

啟動(dòng)應(yīng)用

現(xiàn)在可以啟動(dòng)app.ts,啟動(dòng)后可以發(fā)現(xiàn)數(shù)據(jù)庫(kù)自動(dòng)被初始化帅刊,并且Photo這個(gè)表也會(huì)創(chuàng)建出來纸泡。

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

添加和插入photo

現(xiàn)在創(chuàng)建一個(gè)新的photo然后存到數(shù)據(jù)庫(kù):

import {createConnection} from "typeorm";

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;

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

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

使用async/await語(yǔ)法

現(xiàn)在利用TypeScript的async/await語(yǔ)法來實(shí)現(xiàn)同樣的功能:

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

使用EntityManager

剛剛我們創(chuàng)建了一個(gè)新的photo并且存進(jìn)數(shù)據(jù)庫(kù)。使用EntityManager可以操作實(shí)體赖瞒,現(xiàn)在用EntityManager來把photo從數(shù)據(jù)庫(kù)中取出來女揭。

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 會(huì)從數(shù)據(jù)庫(kù)中取到的是一個(gè)Photo對(duì)象的數(shù)組

使用Repositories

現(xiàn)在重構(gòu)下代碼,使用Repository來代替EntityManage栏饮。每個(gè)實(shí)體都有自己的repository田绑,可以對(duì)這個(gè)實(shí)體進(jìn)行任何操作。
如果要對(duì)實(shí)體做很多操作抡爹,Repositories會(huì)比EntityManager更加方便掩驱。

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

從數(shù)據(jù)庫(kù)中取photos

現(xiàn)在來嘗試用Repository做一些取數(shù)據(jù)方面的操作:

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

現(xiàn)在來從數(shù)據(jù)庫(kù)中取出一個(gè)photo,修改并更新到數(shù)據(jù)庫(kù)。

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

這個(gè)id = 1的photo在數(shù)據(jù)庫(kù)中就成功更新了.

刪除photo

再來欧穴,從數(shù)據(jù)庫(kù)中刪除我們的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));

這個(gè)id = 1的photo就在數(shù)據(jù)庫(kù)中被移除了民逼。

一對(duì)一關(guān)系

來創(chuàng)建與另一個(gè)類的一對(duì)一關(guān)系。
新建PhotoMetadata.ts用來存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;
}

這里我們用到了一個(gè)新的裝飾器@OneToOne拼苍,它可以用來在兩個(gè)實(shí)體之間創(chuàng)建一對(duì)一關(guān)系。
type => Photo指示了我們想要連接的實(shí)體類名调缨,這里因?yàn)門ypeScript語(yǔ)言的支持原因不能直接用類名疮鲫。
當(dāng)然也可以使用() => Photo,但是type => Photo顯得更有可讀性弦叶。
Type變量本身并不包含任何東西俊犯。

我們同樣使用了@JoinColumn裝飾器,這個(gè)裝飾器可以指定一對(duì)一關(guān)系的擁有者伤哺。
關(guān)系可以是單向的或雙向的燕侠,但是只有一方是擁有者,加個(gè)這個(gè)裝飾器就表示關(guān)系是給這個(gè)表服務(wù)的立莉。

現(xiàn)在運(yùn)行app绢彤,會(huì)新創(chuàng)建一個(gè)table,這個(gè)table有一個(gè)連接photo的外鍵:

+-------------+--------------+----------------------------+
|                      photo `譯者注:應(yīng)該是PhotoMetadata` |
+-------------+--------------+----------------------------+
| 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                |
+-------------+--------------+----------------------------+

存一個(gè)有一對(duì)一關(guān)系的對(duì)象

現(xiàn)在來創(chuàng)建一個(gè)photo蜓耻,一個(gè)photo的元信息茫舶,并把它們已經(jīng)連接起來。

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

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

    // 創(chuàng)建一個(gè)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;

    // 創(chuàng)建一個(gè)photo的元信息
    let  metadata = new PhotoMetadata();
    metadata.height = 640;
    metadata.width = 480;
    metadata.compressed = true;
    metadata.comment = "cybershoot";
    metadata.orientation = "portait";
    metadata.photo = photo; // 這里把兩者連起來

    // 獲取實(shí)體repositories
    let photoRepository = connection.getRepository(Photo);
    let metadataRepository = connection.getRepository(PhotoMetadata);

    // 先來把photo存到數(shù)據(jù)庫(kù)
    await photoRepository.save(photo);

    // photo存完了刹淌,再存下photo的元信息
    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));

雙向關(guān)系

關(guān)系可以是單向的或是雙向的.
現(xiàn)在PhotoMetadata和Photo的關(guān)系是單向的奇适,關(guān)系擁有者是PhotoMetadata,Photo并不知道PhotoMetadata芦鳍,這樣如果要想從Photo里得到PhotoMetadata的數(shù)據(jù)會(huì)比較麻煩嚷往。
現(xiàn)在來改變一下,把單向改成雙向:

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;
}
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 是用來指定反向關(guān)系的字段名字柠衅,photo.metadata就指出了Photo里的metadata字段名字皮仁。
當(dāng)然也可以使用@OneToOne('metadata')來達(dá)到同樣的目的,不過這種對(duì)于以后的代碼重構(gòu)不友好菲宴。

按上面說的贷祈,@JoinColumn只能在關(guān)系的一邊使用來使這邊做為關(guān)系的擁有者,關(guān)系擁有者在數(shù)據(jù)庫(kù)里的表現(xiàn)就是擁有一個(gè)外鍵列喝峦。

取出關(guān)系對(duì)象的數(shù)據(jù)

現(xiàn)在來用一個(gè)查詢來取出photo以及它的元信息势誊。
有兩種方式,一是用FindOptions谣蠢,另一個(gè)是使用QueryBuilder粟耻。
先試下FindOptions查近,通過指定FindOptions接口作為參數(shù)來使用Repository.find方法可以完成非常復(fù)雜的查詢。

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是從數(shù)據(jù)庫(kù)里取回的photo的數(shù)組挤忙,每個(gè)photo都包含它的元信息霜威。

alias 是FindOptions的一個(gè)必需選項(xiàng),這是你自己在select里定義的別名册烈,然后需要用在接下來的 where, order by, group by, join 以及其他表達(dá)式.

這里還用到了innerJoinAndSelect戈泼,表示內(nèi)聯(lián)查詢photo.metadata的數(shù)據(jù)。
"photo.metadata"里"photo"是一個(gè)別名赏僧,"metadata"則是你想查詢的那個(gè)對(duì)象的屬性名大猛。
"metadata": 是內(nèi)聯(lián)返回?cái)?shù)據(jù)的新的別名.

下面來嘗試第二種方式:QueryBuilder來達(dá)到同樣的目的. 使用QueryBuilder可以優(yōu)雅完成復(fù)雜的查詢:

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

使用 cascade 選項(xiàng)來自動(dòng)保存關(guān)系著的對(duì)象

上面要保存關(guān)系對(duì)象需要一個(gè)一個(gè)來保存,略顯麻煩淀零。
如果我們需要當(dāng)關(guān)系對(duì)象中的一個(gè)被保存后挽绩,另一個(gè)也同樣被保存,則可以使用cascade選項(xiàng)來做到窑滞。
稍微改下@OneToOne裝飾:

export class Photo {
    /// ... 其他列

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

使用cascade就可以不需要像上面那邊先存photo再存metadata了琼牧。
現(xiàn)在我們來單單存photo對(duì)象恢筝,由于cascade的作用哀卫,metadata也會(huì)自動(dòng)存上。

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

    // 創(chuàng)建photo對(duì)象
    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;

    // 創(chuàng)建photo metadata 對(duì)象
    let metadata = new PhotoMetadata();
    metadata.height = 640;
    metadata.width = 480;
    metadata.compressed = true;
    metadata.comment = "cybershoot";
    metadata.orientation = "portait";
    
    photo.metadata = metadata; // 連接起來

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

    // 存photo
    await photoRepository.save(photo);
    // photo metadata也自動(dòng)存上了
    console.log("Photo is saved, photo metadata is saved too.")

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

多對(duì)一/一對(duì)多關(guān)系

接下來顯示多對(duì)一/一對(duì)多關(guān)系撬槽。
假設(shè)一個(gè)photo會(huì)有一個(gè)author此改,并且每個(gè)author可以有很多photo。
先創(chuàng)建Author實(shí)體:

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) // 備注:下面會(huì)為Photo創(chuàng)建author屬性
    photos: Photo[];
}

Author包含一個(gè)反向的關(guān)系侄柔,OneToMany總是反向的共啃,并且總是與ManyToOne成對(duì)出現(xiàn)。

現(xiàn)在來為Photo加上關(guān)系擁有者暂题。

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

@Entity()
export class Photo {

    /* ... 其他列 */

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

ManyToOne/OneToMany關(guān)系中移剪,擁有者一邊總是ManyToOne譯者注:擁有外鍵者即關(guān)系擁有者
也就是ManyToOne的那個(gè)字段存的是另一個(gè)對(duì)象的id薪者。譯者注:也就是上面的author雖然屬性是Author纵苛,但在數(shù)據(jù)庫(kù)中類型是Author id的類型,存的也是id

執(zhí)行上面的代碼將會(huì)自動(dòng)創(chuàng)建author表言津,如下:

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

因?yàn)閜hoto表已經(jīng)存在攻人,所以不是增加而是修改photo表 - 添加一個(gè)新外鍵列author:

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

多對(duì)多關(guān)系

假設(shè)photo可以存在多個(gè)相冊(cè)中,并且相冊(cè)里可以包含多個(gè)photo悬槽。
先創(chuàng)建一個(gè)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多對(duì)多關(guān)系擁有者必須指定的怀吻。

接著給Photo實(shí)體加個(gè)反向關(guān)系:

export class Photo {
    /// ... 其他列

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

執(zhí)行上面的代碼后會(huì)自動(dòng)創(chuàng)建一個(gè)叫 album_photos_photo_albums聯(lián)接表:

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

記得把Album實(shí)體加到ConnectionOptions中:

const options: ConnectionOptions = {
    // ... 其他配置
    entities: [Photo, PhotoMetadata, Author, Album]
};

現(xiàn)在來往數(shù)據(jù)庫(kù)里插入albums和photos

let connection = await createConnection(options);

// 創(chuàng)建幾張相冊(cè)
let album1 = new Album();
album1.name = "Bears";
await connection.manager.save(album1);

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

// 創(chuàng)建幾個(gè)相片
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);

// 現(xiàn)在我們的相片已經(jīng)保存,并且添加到相冊(cè)里面了
// 讓我們開始加載它們:
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"
    }]
}

使用QueryBuilder

可以利用QueryBuilder來構(gòu)建一個(gè)非常復(fù)雜的查詢初婆,例如:

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

這個(gè)查詢會(huì)查找已經(jīng)published的蓬坡,并且name是"My"或"Mishka"猿棉,
得到的結(jié)果會(huì)從第5個(gè)開始(分頁(yè)偏移決定的),
并且只會(huì)得到10個(gè)結(jié)果(分頁(yè)每頁(yè)個(gè)數(shù)決定的)渣窜,
所得結(jié)果是以id的倒序排序的铺根,
Photo的albums是左聯(lián)接,photo的metadata是內(nèi)聯(lián)接乔宿。

你將在應(yīng)用程序中大量使用QueryBuilder位迂。
了解更多QueryBuilder這里.

樣例

看看樣例里這些例子的用法

這些倉(cāng)庫(kù),你可以克隆下來幫助你開始:

擴(kuò)展

這幾個(gè)擴(kuò)展可以簡(jiǎn)化TypeORM的使用详瑞,并將其與其他模塊集成:

轉(zhuǎn)載自:點(diǎn)擊查看文檔來源

留言

歡迎查看使用TypeORM 和 nestjs 實(shí)現(xiàn)的Mock Server 前端Mock接口和數(shù)據(jù)方案

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末掂林,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子坝橡,更是在濱河造成了極大的恐慌泻帮,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件计寇,死亡現(xiàn)場(chǎng)離奇詭異锣杂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)番宁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門元莫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蝶押,你說我怎么就攤上這事踱蠢。” “怎么了棋电?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵茎截,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我赶盔,道長(zhǎng)企锌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任于未,我火速辦了婚禮撕攒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沉眶。我一直安慰自己打却,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布谎倔。 她就那樣靜靜地躺著柳击,像睡著了一般。 火紅的嫁衣襯著肌膚如雪片习。 梳的紋絲不亂的頭發(fā)上捌肴,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天蹬叭,我揣著相機(jī)與錄音,去河邊找鬼状知。 笑死秽五,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的饥悴。 我是一名探鬼主播坦喘,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼西设!你這毒婦竟也來了瓣铣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤贷揽,失蹤者是張志新(化名)和其女友劉穎棠笑,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體禽绪,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蓖救,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了印屁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片循捺。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖库车,靈堂內(nèi)的尸體忽然破棺而出巨柒,到底是詐尸還是另有隱情樱拴,我是刑警寧澤柠衍,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站晶乔,受9級(jí)特大地震影響珍坊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜正罢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一阵漏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧翻具,春花似錦履怯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至工禾,卻和暖如春运提,著一層夾襖步出監(jiān)牢的瞬間蝗柔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工民泵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留癣丧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓栈妆,卻偏偏與公主長(zhǎng)得像胁编,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鳞尔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354