美文网首页
【译】关系对象模型 typeorm 下

【译】关系对象模型 typeorm 下

作者: dkvirus | 来源:发表于2020-03-11 14:44 被阅读0次

    typeorm 有什么用?typeorm 以操作对象方法的方式与数据库交互,而不是像传统库(mysql)那样需要写 sql 语句。

    本文主要说什么?Typeorm README.md 写的很长,往下拉可以看到 "Step-by-Step Guide" 标题,本文翻译这部分内容(非直译),帮助新手一步步熟悉并使用 typeorm。(翻译时 typeorm 版本为 0.2.24。)

    接上文.......

    表与表之间的关系

    创建一对一的关系

    PhotoMetadata 类,描述照片的详细信息,如长度、宽度、朝向、评论等信息,该类与 Photo 类的关系是一对一的关系:一张照片有且仅有一份详细信息,一份详细信息仅属于一张照片。

    创建 src/entity/PhotoMetadata.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。它允许我们为两个 Entity 设置一对一的关系,type => Photo 是一个函数,指向该类要关联的那个类。

    我们强制使用一个函数来返回关联类 @OneToOne(type => Photo),而不是直接使用该类的名字 @OneToOne(Photo),这样做是由 js 语言特性决定的。(译者问:具体啥特性?)

    我们也可以将函数写成 () => Photo,但是推荐使用 type => Photo,因为这样更易读,type 参数本身没有任何意义,即它的值是 undefined。

    我们还使用了 @JoinColumn 装饰器,这个装饰器可以指定一对一关系的拥有者。
    Photo 拥有 PhotoMetadata,PhotoMetadata 属于 Photo,在 PhotoMetadata 上加 @JoinColumn,在建表时体现在 photo_metadata 表多一个 photoId 这个外键。

    运行应用 $ node dist/testConnect.js ,可以看到数据库里又新增了一张 photo_metadata 表,并且包含了关联 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                |
    +-------------+--------------+----------------------------+
    

    保存一对一的关系

    现在让我们保存一张照片及其元数据,并将它们彼此连接起来。

    创建 src/testRelation.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.views = 2;
        photo.description = "I am near polar bears";
        photo.filename = "photo-with-bears.jpg";
        photo.isPublished = true;
    
        // 创建 PhotoMetadata 对象
        let metadata = new PhotoMetadata();
        metadata.height = 640;
        metadata.width = 480;
        metadata.compressed = true;
        metadata.comment = "cybershoot";
        metadata.orientation = "portait";
        metadata.photo = photo; // <=== 将 Photo 和 PhotoMetadata 关联起来
    
        // 获取 repository
        let photoRepository = connection.getRepository(Photo);
        let metadataRepository = connection.getRepository(PhotoMetadata);
    
        // 首先保存照片
        await photoRepository.save(photo);
    
        // 照片保存之后,再保存元数据,这是因为元数据中包含了照片数据的外键
        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));
    

    执行程序 $ node dist/testRelation.js,查看数据库新增了一条 photo 数据,一条 photo_metadata 数据,并且后者包含前者的外键值。

    双向关系

    上面的关系是单向的,PhotoMetadata 中包含 Photo 的外键,因此查询 PhotoMetadata 时,可以顺带查出 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));
    

    查询结果,可以看到 PhotoMetadata 中确实包含了 photo 的数据。

    photometadatas ==================== 
     [
      PhotoMetadata {
        id: 1,
        height: 640,
        width: 480,
        orientation: 'portait',
        compressed: true,
        comment: 'cybershoot',
        photo: Photo {      # <====== 只是查询 metadata 数据,会顺带把 photo 的数据也查出来
          id: 3,
          name: 'Me and Bears',
          description: 'I am near polar bears',
          filename: 'photo-with-bears.jpg',
          views: 2,
          isPublished: true
        }
      },
      // .....
     ]
    

    上面的代码,Photo 和 PhotoMetadata 的关系是单向的,关系的拥有者是 PhotoMetadata,但 Photo 类不知道任何 PhotoMetadata 的信息,这使得从 Photo 类访问 PhotoMetadata 变得复杂。

    为了解决这个问题,我们应该为 Photo 也添加一个关系,使得 Photo 和 PhotoMetadata 变成双向关系。
    (改成双向关系后,查询 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;
    }
    

    查询关系对象数据

    现在我们可以用一条查询语句得到照片信息和它的元数据信息,有两种方式可以做到这点:

    • 使用 find* 方法
    • 使用 QueryBuilder 函数

    使用 find* 方法,该方法允许您指定关联关系的对象。(下面👇代码关联关系的对象是 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 是从数据库中查询的照片数组,每张照片都包含元数据信息。

    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 数据查出来 
          id: 1,
          height: 640,
          width: 480,
          orientation: 'portait',
          compressed: true,
          comment: 'cybershoot'
        }
      },
    ]
    

    使用 find() 是个简单有效的方式,但有时您需要更加复杂的查询,此时可以使用 QueryBuilder(),QueryBuilder() 允许用优雅的方式 处理复杂的查询

    import {createConnection} from "typeorm";
    import {Photo} from "./entity/Photo";
    import {PhotoMetadata} from "./entity/PhotoMetadata";
    
    createConnection(/*...*/).then(async connection => {
    
        /*...*/
        let photos = await connection
            .getRepository(Photo)
            .createQueryBuilder("photo")
            .innerJoinAndSelect("photo.metadata", "metadata")
            .getMany();
        console.log('photos', photos);
    
    }).catch(error => console.log(error));
    

    当你使用 QueryBuilder() 时,看起来就像创建 sql 查询语句一样。在这个例子中,photo 和 metadata 都是别名,你可以使用别名访问列。

    使用 cascade 选项来自动保存关系对象

    上面 保存 Photo 和 PhotoMetadata,我们分别做了两次保存操作。

    // ......
    // 首先保存照片
    await photoRepository.save(photo);
    
    // 照片保存之后,再保存元数据,这是因为元数据中包含了照片数据的外键
    await metadataRepository.save(metadata);
    

    我们希望只做一次保存操作就可以完成保存 Photo 和 PhotoMetadata,可以使用 cascade 选项。

    修改 Photo 类中 @OneToOne 装饰器:

    export class Photo {
        /// ... other columns
    
        @OneToOne(type => PhotoMetadata, metadata => metadata.photo, {
            cascade: true,     // <=== 加上 cascade 选项
        })
        metadata: PhotoMetadata;
    }
    

    因为设置了级联选项(cascade: true),现在保存 photo 时,会自动保存 metadata 数据。

    createConnection(options).then(async connection => {
    
        // 创建 photo 对象
        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;
    
        // 创建 metadata 对象
        let metadata = new PhotoMetadata();
        metadata.height = 640;
        metadata.width = 480;
        metadata.compressed = true;
        metadata.comment = "cybershoot";
        metadata.orientation = "portait";
    
        // 将 photo 和 metadata 联系起来
        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));
    

    注意这里我们设置了 photo 对象的 metadata 属性,而不是像之前那样设置 metadata 的 photo 属性。因为你是在 photo 类中设置的 cascade 配置,因此保存 photo,会自动保存 metadata,但是保存 metadata,并不会自动保存 photo。

    创建(多对一)/(一对多)关系

    让我们创建一个(一对多)/(多对一)的关系,每张照片都有唯一一个作者,每个作者可以同时拥有很多张照片

    创建 src/entity/Author.ts 文件,内容如下:

    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 类中添加另一种关系
        photos: Photo[];
    }
    

    @OneToMany 不能单独存在,需要在另一个类中添加 @ManyToOne。

    修改 Photo 类:

    import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from "typeorm";
    import { PhotoMetadata } from "./PhotoMetadata";
    import { Author } from "./Author";
    
    @Entity()
    export class Photo {
    
        /* ... other columns */
    
        @ManyToOne(type => Author, author => author.photos)
        author: Author;
    }
    

    @ManyToOne 装饰器和 @JoinColumn 装饰器类似,会为 photo 表添加一个 authorId 的外键。

    运行应用 $ node dist/testConnect.js,数据库会自动创建 Author 表:

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

    我们发现 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                |
    +-------------+--------------+----------------------------+
    

    创建多对多关系

    让我们创建多对多的关系:一张照片可以保存在多个相册中(同一张照片可以打印很多张,但还是同一张照片),一个相册中可以存很多张照片

    创建 src/entity/Album.ts 文件,内容如下:

    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 必须被明确指定这是关系的所有者。

    修改 Photo 类添加关系:

    import { ..., ManyToMany } from "typeorm";
    import { Album } from './Album'
    
    export class Photo {
        /// ... other columns
    
        @ManyToMany(type => Album, album => album.photos)
        albums: Album[];
    }
    

    运行应用 $ node dist/testConnect.js,数据库会自动创建 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    |
    +-------------+--------------+----------------------------+
    

    多对多的关系会创建一张中间表。

    现在让我们往数据库中插入一些相册和照片:

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

    查询结果:

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

    总结:

    • 双向关系的目的是做一次查询操作,可以同时查出 Photo 以及它的 metadata 数据;
    • cascade 的目的是只要做一次保存操作,就可以完成 photo 和 metadata 的保存;
    • 多对多的关系会创建一张中间表。

    相关文章

      网友评论

          本文标题:【译】关系对象模型 typeorm 下

          本文链接:https://www.haomeiwen.com/subject/furcjhtx.html