美文网首页mysql数据模型
【译】关系对象模型 typeorm 上

【译】关系对象模型 typeorm 上

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

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

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

    准备工作

    初始化工程

    $ mkdir test-typeorm
    $ cd test-typeorm
    $ npm init -y
    

    安装依赖包

    $ npm install typeorm --save
    $ npm install reflect-metadata --save
    $ npm install mysql --save
    $ npm install typescript --save-dev
    $ npm install @types/node --save-dev
    
    • typeormreflect-metadata 是使用 typeorm 必备依赖;
    • 这里使用 mysql 数据库,所以还要安装 mysql 依赖;
    • 要用到装饰器,typescript@types/node 是使用 typescript 必备依赖。

    搭建 ts 环境

    创建 tsconfig.json 配置文件

    $ npx tsc --init
    

    修改 tsconfig.json 文件中部分属性

    "outDir": "./dist",
    "rootDir": "./src",
    "strictPropertyInitialization": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    

    创建 src 目录,源码都写在该目录中,之后目录结构如下:

    |-- test-typeorm
        |-- src
        |-- package.json
        |-- tsconfig.json
    

    修改 package.json 文件:

    "scripts": {
        "dev": "tsc -w"
    }
    

    测试

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

    const greet: string = 'hello typeorm'
    console.log(greet)
    

    打开终端,执行 $ npm run dev,可以看到根目录下生成了 dist 目录,里面包含编译后的 js 文件。

    另打开一个终端,执行 $ node dist/test.js,可以看到控制台打印出问候 "hello typeorm"。

    至此,依赖包和环境搭建完成,下面开始进入正题,先来学习如何使用 typeorm 创建数据库表。

    Typeorm 生成数据库表

    创建 model

    与数据库打交道的第一步是需要创建一张数据库表,TypeORM 怎么知道你要创建什么样的表呢?答案是通过 model 类,你的程序代码中的 model 类就是你的数据库表。

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

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

    你想往数据库里存一些照片,为了做到这个,你首先需要建一张数据库表,typeorm 会根据你的 model 类自动创建表,但并非所有的 models 类都可以创建表,只有那些实现了 entity 的 model 类才有这个特性。

    此时您的目录结构如下:

    |-- test-typeorm
        |-- src
            |-- entity
                |-- Photo.ts
        |-- package.json
        |-- tsconfig.json
    

    创建 entity

    为你的 model 类添加 @Entity 装饰器,只有符合这种规则的 model 类,typeorm 才会帮助你建表。

    将我们的 Photo model 改造成 Photo entity。

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

    现在,表已经创立了,但是表中还没有字段,让我们为数据库表创建一些字段。

    创建表字段

    要为表添加字段,您只需要给 entity 类的属性值添加 @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;
    }
    

    现在,idnamedescriptionfilenameviewsisPublished 列已经被添加到 photo 表中了。 数据库里的列类型会根据 entity 类中字段类型自动转换,例如:类中的 number 类型会转换为数据库列类型为 integer,string 转换为 varchar,boolean 转换为 bool 等等。您还可以设置 @Column() 装饰器的参数明确设置数据库列类型。

    @Column('varchar2')
    filename: string;
    

    我们已经为数据库表添加了字段,但还有件事没有做,每张表应该有一个主键字段。

    创建主键列

    每个 Entity 类至少有一个主键字段,这是 typeorm 规定的,并且是不可避免的。使用 @PrimaryColumn 装饰器创建主键列。

    import { Entity, Column, PrimaryColumn } from "typeorm";
    
    @Entity()
    export class Photo {
    
        @PrimaryColumn()
        id: number;
    
        @Column()
        name: string;
    
        @Column()
        description: string;
    
        @Column()
        filename: string;
    
        @Column()
        views: number;
    
        @Column()
        isPublished: boolean;
    }
    

    主键列只能保证 id 的值不能重复。

    创建主键自生成字段

    您已经创建了主键列,每次存数据时需要手动添加 id 字段的值,如果每次插值时想要自动生成主键值,使用 @PrimaryGeneratedColumn 装饰器代替 @PrimaryColumn 装饰器。

    import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";
    
    @Entity()
    export class Photo {
    
        @PrimaryGeneratedColumn('increment')    // 主键自增长
        // @PrimaryGeneratedColumn('uuid')      // 自动生成 uuid 作为主键:'0ae5e5c8-36f3-4b7b-84e4-c0b79cdfb1d1'
        id: number;
    
        @Column()
        name: string;
    
        @Column()
        description: string;
    
        @Column()
        filename: string;
    
        @Column()
        views: number;
    
        @Column()
        isPublished: boolean;
    }
    

    完善类类型

    接下来,让我们完善数据库类类型。默认情况,string 被转换为 varchar 类型,长度自动设置为 255,number 被转换为 integer,我们不想要所有的列类型都被限制的太死板,让我们按实际情况来设置正确的列类型。

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

    创建数据库连接

    准备就绪,让我们尝试连接数据库。

    创建 src/testConnect.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    // <==== 将需要操作的 Entity 导入进来
        ],
        synchronize: true,
        logging: false
    }).then(connection => {
        // here you can start to work with your entities
        console.log('数据库连接成功,做点什么吧~')
    }).catch(error => console.log(error));
    

    将数据库连接信息改成您自己数据库的相关值:host、port、username、password、database

    在这个例子中我们使用 MySQL 数据库,您也可以自由选择使用其它数据库。要用其它数据库,只需要简单的修改 type 属性,值可以是:mysqlmariadbsqlitemssqloraclemongodb

    我们将 Photo Entity 类添加到此次连接中,您需要将使用的所有 Entity 都列出来。

    设置 synchronize 属性为 true,确保每次运行应用时,您的 Entity 实体类将与数据库同步。
    (译者注:Entity 中新增了一个字段或修改了字段的数据类型,每次启动应用,数据库表也会同步这些修改)

    加载所有的 Entities

    当我们创建更多 Entity 实体类时,需要我们每次都手动将 Entity 文件添加到配置中的 entities 属性,这很不方便,您可以直接导入整个 entity 目录。

    import { createConnection } from "typeorm";
    
    createConnection({
        type: "mysql",
        host: "localhost",
        port: 3306,
        username: "root",
        password: "admin",
        database: "test",
        entities: [
            __dirname + "/entity/*.js"   // <=== 将 entity 目录下所有文件全导进来
        ],
        synchronize: true,
    }).then(connection => {
        // here you can start to work with your entities
        console.log('数据库连接成功,做点什么吧~')
    }).catch(error => console.log(error));
    

    (译者注:如果 Vscode 报错 __dirname 未定义,安装 @types/node 包即可解决。 )

    运行应用

    执行 $ node dist/testConnect.js,数据库连接会被初始化,去查看您的数据库,photo 表格已经被创建了。

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

    Typeorm 增删改查

    新增数据

    让我们创建一张图片并保存到数据库中,创建 src/testPhotoCurd.ts 文件,内容如下:
    (createConnection() 方法的连接数据库的参数和上面是一样样的,这里用 /.../ 替代)**

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

    执行 $ node dist/testPhotoCurd.js,查看数据库,已经生成一张照片数据了。

    新增操作会自动生成 id,save() 方法返回的对象和你传给它的对象是同一个对象,唯一的区别是为 photo 对象设置了 id 字段值并返回。

    使用 async/await 语法

    使用 es8(es2017)的新语法 async/await 可以让代码更易读。

    import { createConnection } from "typeorm";
    import { Photo } from "./entity/Photo";
    
    createConnection(/*...*/).then(async connection => {
    
        let photo = new Photo();
        photo.name = "Me and Bears";
        photo.description = "I am near polar bears";
        photo.filename = "photo-with-bears.jpg";
        photo.views = 1;
        photo.isPublished = true;
    
        await connection.manager.save(photo);
        console.log("Photo has been saved");
    
    }).catch(error => console.log(error));
    

    执行 $ node dist/testPhotoCurd.js,查看数据库又生成一条数据。
    (下面如没有特别说明,代码都是写在 src/testPhotoCurd.ts 文件中)

    使用 Entity Manager

    前面使用 EntityManager 我们已经创建了两张图片并保存到数据库中。
    使用 EntityManager,您可以操作应用中的任何 Entity 实体类,如下示例:

    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 对象组成的数组。

    使用 Repositories

    Repository 也可以做到 EntityManager 做的事,让我们使用 Repository 重构上面的代码。
    当你要对同一个 Entity 做多次处理时,Repositories 会更加方便些。

    import { createConnection } from "typeorm";
    import { Photo } from "./entity/Photo";
    
    createConnection(/*...*/).then(async connection => {
    
        let photo = new Photo();
        photo.name = "Me and Bears";
        photo.description = "I am near polar bears";
        photo.filename = "photo-with-bears.jpg";
        photo.views = 1;
        photo.isPublished = true;
    
        let photoRepository = connection.getRepository(Photo);
    
        await photoRepository.save(photo);     // <=== 操作1:保存数据
        console.log("Photo has been saved");
    
        let savedPhotos = await photoRepository.find();   // <=== 操作2:查询数据
        console.log("All photos from the db: ", savedPhotos);  
    
    }).catch(error => console.log(error));
    

    更新数据

    首先从数据库把数据查出来,然后修改它的值。

    import { createConnection } from "typeorm";
    import { Photo } from "./entity/Photo";
    
    createConnection(/*...*/).then(async connection => {
    
        /*...*/
        let photoRepository = connection.getRepository(Photo); 
    
        let photoToUpdate = await photoRepository.findOne(1);   // <=== 操作1:查询数据
        if (photoToUpdate !== undefined) {
            photoToUpdate.name = "Me, my friends and polar bears";
            await photoRepository.save(photoToUpdate);      // <=== 操作2:修改数据
        }
    
    }).catch(error => console.log(error));
    

    现在,id = 1 的数据被修改了。

    删除数据

    import { createConnection } from "typeorm";
    import { Photo } from "./entity/Photo";
    
    createConnection(/*...*/).then(async connection => {
    
        /*...*/
        let photoRepository = connection.getRepository(Photo);
    
        let photoToRemove = await photoRepository.findOne(1);   // <=== 操作1:查询数据
        if (photoToRemove !== undefined) {
            await photoRepository.remove(photoToRemove);        // <=== 操作2:修改数据
        }
    
    }).catch(error => console.log(error));
    

    现在,id = 1 的数据被删除了。

    查询数据

    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);
    
        /* 查询 id = 1 的第一条数据 */
        let firstPhoto = await photoRepository.findOne(1);
        console.log("First photo from the db: ", firstPhoto);
    
        /* 查询 name = 'Me and Bears' 的第一条数据 */
        let meAndBearsPhoto = await photoRepository.findOne({ name: "Me and Bears" });
        console.log("Me and Bears photo from the db: ", meAndBearsPhoto);
    
        /* 查询 views = 1 的所有数据 */
        let allViewedPhotos = await photoRepository.find({ views: 1 });
        console.log("All viewed photos: ", allViewedPhotos);
    
        /* 查询 isPublished = true 的所有数据 */
        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));
    

    更复杂的查询数据

    您可以使用 QueryBuilder 构建复杂的 sql 查询:

    let photos = await connection
        .getRepository(Photo)
        .createQueryBuilder("photo") // first argument is an alias. Alias is what you are selecting - photos. You must specify it.
        .innerJoinAndSelect("photo.metadata", "metadata")
        .leftJoinAndSelect("photo.albums", "album")
        .where("photo.isPublished = true")
        .andWhere("(photo.name = :photoName OR photo.name = :bearName)")
        .orderBy("photo.id", "DESC")
        .skip(5)
        .take(10)
        .setParameters({ photoName: "My", bearName: "Mishka" })
        .getMany();
    

    这条查询语句会检索已经发布的,照片名字是 "My" 或 "Mishka" 的照片。从数据库中下标为 5 的数据开始取(pagination offset),只取 10 条数据(pagination limit),检索到的数据按照 photo.id 倒序排列,photo 表左连接 albums 表,内连接 metadata 表查询。

    相关文章

      网友评论

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

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