【譯】關(guān)系對(duì)象模型 typeorm 下

typeorm 有什么用?typeorm 以操作對(duì)象方法的方式與數(shù)據(jù)庫交互,而不是像傳統(tǒng)庫(mysql)那樣需要寫 sql 語句。

本文主要說什么?Typeorm README.md 寫的很長,往下拉可以看到 "Step-by-Step Guide" 標(biāo)題,本文翻譯這部分內(nèi)容(非直譯),幫助新手一步步熟悉并使用 typeorm。(翻譯時(shí) typeorm 版本為 0.2.24。)

接上文.......

表與表之間的關(guān)系

創(chuàng)建一對(duì)一的關(guān)系

PhotoMetadata 類,描述照片的詳細(xì)信息,如長度、寬度、朝向、評(píng)論等信息,該類與 Photo 類的關(guān)系是一對(duì)一的關(guān)系:一張照片有且僅有一份詳細(xì)信息,一份詳細(xì)信息僅屬于一張照片。

創(chuàng)建 src/entity/PhotoMetadata.ts 文件,內(nèi)容如下:

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。它允許我們?yōu)閮蓚€(gè) Entity 設(shè)置一對(duì)一的關(guān)系,type => Photo 是一個(gè)函數(shù),指向該類要關(guān)聯(lián)的那個(gè)類。

我們強(qiáng)制使用一個(gè)函數(shù)來返回關(guān)聯(lián)類 @OneToOne(type => Photo),而不是直接使用該類的名字 @OneToOne(Photo),這樣做是由 js 語言特性決定的。(譯者問:具體啥特性?)

我們也可以將函數(shù)寫成 () => Photo,但是推薦使用 type => Photo,因?yàn)檫@樣更易讀,type 參數(shù)本身沒有任何意義,即它的值是 undefined。

我們還使用了 @JoinColumn 裝飾器,這個(gè)裝飾器可以指定一對(duì)一關(guān)系的擁有者。
Photo 擁有 PhotoMetadata,PhotoMetadata 屬于 Photo,在 PhotoMetadata 上加 @JoinColumn,在建表時(shí)體現(xiàn)在 photo_metadata 表多一個(gè) photoId 這個(gè)外鍵。

運(yùn)行應(yīng)用 $ node dist/testConnect.js ,可以看到數(shù)據(jù)庫里又新增了一張 photo_metadata 表,并且包含了關(guān)聯(lián) photo 表的外鍵。

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

保存一對(duì)一的關(guān)系

現(xiàn)在讓我們保存一張照片及其元數(shù)據(jù),并將它們彼此連接起來。

創(chuàng)建 src/testRelation.ts 文件,內(nèi)容如下:

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

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

    // 創(chuàng)建 Photo 對(duì)象
    let photo = new Photo();
    photo.name = "Me and Bears";
    photo.views = 2;
    photo.description = "I am near polar bears";
    photo.filename = "photo-with-bears.jpg";
    photo.isPublished = true;

    // 創(chuàng)建 PhotoMetadata 對(duì)象
    let metadata = new PhotoMetadata();
    metadata.height = 640;
    metadata.width = 480;
    metadata.compressed = true;
    metadata.comment = "cybershoot";
    metadata.orientation = "portait";
    metadata.photo = photo; // <=== 將 Photo 和 PhotoMetadata 關(guān)聯(lián)起來

    // 獲取 repository
    let photoRepository = connection.getRepository(Photo);
    let metadataRepository = connection.getRepository(PhotoMetadata);

    // 首先保存照片
    await photoRepository.save(photo);

    // 照片保存之后,再保存元數(shù)據(jù),這是因?yàn)樵獢?shù)據(jù)中包含了照片數(shù)據(jù)的外鍵
    await metadataRepository.save(metadata);

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

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

執(zhí)行程序 $ node dist/testRelation.js,查看數(shù)據(jù)庫新增了一條 photo 數(shù)據(jù),一條 photo_metadata 數(shù)據(jù),并且后者包含前者的外鍵值。

雙向關(guān)系

上面的關(guān)系是單向的,PhotoMetadata 中包含 Photo 的外鍵,因此查詢 PhotoMetadata 時(shí),可以順帶查出 Photo 的信息;

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

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

    let photometadataRepository = connection.getRepository(PhotoMetadata);
    let photometadatas = await photometadataRepository.find({ relations: ["photo"] });
    console.log('photometadatas ==================== \n', photometadatas)

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

查詢結(jié)果,可以看到 PhotoMetadata 中確實(shí)包含了 photo 的數(shù)據(jù)。

photometadatas ==================== 
 [
  PhotoMetadata {
    id: 1,
    height: 640,
    width: 480,
    orientation: 'portait',
    compressed: true,
    comment: 'cybershoot',
    photo: Photo {      # <====== 只是查詢 metadata 數(shù)據(jù),會(huì)順帶把 photo 的數(shù)據(jù)也查出來
      id: 3,
      name: 'Me and Bears',
      description: 'I am near polar bears',
      filename: 'photo-with-bears.jpg',
      views: 2,
      isPublished: true
    }
  },
  // .....
 ]

上面的代碼,Photo 和 PhotoMetadata 的關(guān)系是單向的,關(guān)系的擁有者是 PhotoMetadata,但 Photo 類不知道任何 PhotoMetadata 的信息,這使得從 Photo 類訪問 PhotoMetadata 變得復(fù)雜。

為了解決這個(gè)問題,我們應(yīng)該為 Photo 也添加一個(gè)關(guān)系,使得 Photo 和 PhotoMetadata 變成雙向關(guān)系。
(改成雙向關(guān)系后,查詢 Photo 信息,也可以順帶查詢出 PhotoMetadata 的信息)

修改 PhotoMetadata 類:

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

修改 Photo 類:

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

@Entity()
export class Photo {

    /* ... other columns */

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

查詢關(guān)系對(duì)象數(shù)據(jù)

現(xiàn)在我們可以用一條查詢語句得到照片信息和它的元數(shù)據(jù)信息,有兩種方式可以做到這點(diǎn):

  • 使用 find* 方法
  • 使用 QueryBuilder 函數(shù)

使用 find* 方法,該方法允許您指定關(guān)聯(lián)關(guān)系的對(duì)象。(下面??代碼關(guān)聯(lián)關(guān)系的對(duì)象是 Photo 類中的 metadata 屬性)

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"] });
    console.log('photos', photos)

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

返回的 photos 是從數(shù)據(jù)庫中查詢的照片數(shù)組,每張照片都包含元數(shù)據(jù)信息。

photos [
  Photo {
    id: 3,
    name: 'Me and Bears',
    description: 'I am near polar bears',
    filename: 'photo-with-bears.jpg',
    views: 2,
    isPublished: true,
    metadata: PhotoMetadata {   # <=== 查 photo 也能順帶把 metadata 數(shù)據(jù)查出來 
      id: 1,
      height: 640,
      width: 480,
      orientation: 'portait',
      compressed: true,
      comment: 'cybershoot'
    }
  },
]

使用 find() 是個(gè)簡單有效的方式,但有時(shí)您需要更加復(fù)雜的查詢,此時(shí)可以使用 QueryBuilder(),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();
    console.log('photos', photos);

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

當(dāng)你使用 QueryBuilder() 時(shí),看起來就像創(chuàng)建 sql 查詢語句一樣。在這個(gè)例子中,photo 和 metadata 都是別名,你可以使用別名訪問列。

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

上面 保存 Photo 和 PhotoMetadata,我們分別做了兩次保存操作。

// ......
// 首先保存照片
await photoRepository.save(photo);

// 照片保存之后,再保存元數(shù)據(jù),這是因?yàn)樵獢?shù)據(jù)中包含了照片數(shù)據(jù)的外鍵
await metadataRepository.save(metadata);

我們希望只做一次保存操作就可以完成保存 Photo 和 PhotoMetadata,可以使用 cascade 選項(xiàng)。

修改 Photo 類中 @OneToOne 裝飾器:

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

    @OneToOne(type => PhotoMetadata, metadata => metadata.photo, {
        cascade: true,     // <=== 加上 cascade 選項(xiàng)
    })
    metadata: PhotoMetadata;
}

因?yàn)樵O(shè)置了級(jí)聯(lián)選項(xiàng)(cascade: true),現(xiàn)在保存 photo 時(shí),會(huì)自動(dòng)保存 metadata 數(shù)據(jù)。

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

    // 創(chuàng)建 photo 對(duì)象
    let photo = new Photo();
    photo.name = "Me and Bears";
    photo.views = 3;
    photo.description = "I am near polar bears";
    photo.filename = "photo-with-bears.jpg";
    photo.isPublished = true;

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

    // 將 photo 和 metadata 聯(lián)系起來
    photo.metadata = metadata;

    // 獲取 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));

注意這里我們?cè)O(shè)置了 photo 對(duì)象的 metadata 屬性,而不是像之前那樣設(shè)置 metadata 的 photo 屬性。因?yàn)槟闶窃?photo 類中設(shè)置的 cascade 配置,因此保存 photo,會(huì)自動(dòng)保存 metadata,但是保存 metadata,并不會(huì)自動(dòng)保存 photo。

創(chuàng)建(多對(duì)一)/(一對(duì)多)關(guān)系

讓我們創(chuàng)建一個(gè)(一對(duì)多)/(多對(duì)一)的關(guān)系,每張照片都有唯一一個(gè)作者,每個(gè)作者可以同時(shí)擁有很多張照片

創(chuàng)建 src/entity/Author.ts 文件,內(nèi)容如下:

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

@Entity()
export class Author {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @OneToMany(type => Photo, photo => photo.author) // note: 我們還需要在 Photo 類中添加另一種關(guān)系
    photos: Photo[];
}

@OneToMany 不能單獨(dú)存在,需要在另一個(gè)類中添加 @ManyToOne。

修改 Photo 類:

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

@Entity()
export class Photo {

    /* ... other columns */

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

@ManyToOne 裝飾器和 @JoinColumn 裝飾器類似,會(huì)為 photo 表添加一個(gè) authorId 的外鍵。

運(yùn)行應(yīng)用 $ node dist/testConnect.js,數(shù)據(jù)庫會(huì)自動(dòng)創(chuàng)建 Author 表:

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

我們發(fā)現(xiàn) Photo 表也被修改了,新增了 authorId 列,作為 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                |
+-------------+--------------+----------------------------+

創(chuàng)建多對(duì)多關(guān)系

讓我們創(chuàng)建多對(duì)多的關(guān)系:一張照片可以保存在多個(gè)相冊(cè)中(同一張照片可以打印很多張,但還是同一張照片),一個(gè)相冊(cè)中可以存很多張照片

創(chuàng)建 src/entity/Album.ts 文件,內(nèi)容如下:

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

@Entity()
export class Album {

    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

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

@JoinTable 必須被明確指定這是關(guān)系的所有者。

修改 Photo 類添加關(guān)系:

import { ..., ManyToMany } from "typeorm";
import { Album } from './Album'

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

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

運(yùn)行應(yīng)用 $ node dist/testConnect.js,數(shù)據(jù)庫會(huì)自動(dòng)創(chuàng)建 album 表和 album_photos_photo 表:

+-------------+--------------+----------------------------+
|                       album                     |
+-------------+--------------+----------------------------+
|      id     | int(11)           | PRIMARY KEY FOREIGN KEY    |
|     name    | varchar(255)      | PRIMARY KEY FOREIGN KEY    |
+-------------+--------------+----------------------------+
+-------------+--------------+----------------------------+
|                album_photos_photo               |
+-------------+--------------+----------------------------+
| album_id    | int(11)      | PRIMARY KEY FOREIGN KEY    |
| photo_id    | int(11)      | PRIMARY KEY FOREIGN KEY    |
+-------------+--------------+----------------------------+

多對(duì)多的關(guān)系會(huì)創(chuàng)建一張中間表。

現(xiàn)在讓我們往數(shù)據(jù)庫中插入一些相冊(cè)和照片:

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

    // 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.views = 2;
    photo.description = "I am near polar bears";
    photo.filename = "photo-with-bears.jpg";
    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"] });

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

查詢結(jié)果:

loadedPhoto Photo {
  id: 1,
  name: 'Me and Bears',
  description: 'I am near polar bears',
  filename: 'photo-with-bears.jpg',
  views: 1,
  isPublished: true,
  albums: []
}

總結(jié):

  • 雙向關(guān)系的目的是做一次查詢操作,可以同時(shí)查出 Photo 以及它的 metadata 數(shù)據(jù);
  • cascade 的目的是只要做一次保存操作,就可以完成 photo 和 metadata 的保存;
  • 多對(duì)多的關(guān)系會(huì)創(chuàng)建一張中間表。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容