likes
comments
collection
share

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

作者站长头像
站长
· 阅读数 12

最近在学习神光大神的《Nest通关秘籍》,该小册主要包含下面这些内容:

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

接下来的日子里,我将更新一系列的学习笔记。感兴趣的可以关注我的专栏《Nest 通关秘籍》学习总结

特别申明:本系列文章已经经过作者本人的允许。 大家也不要想着白嫖,我的笔记只是个人边学习边记录的,不是很完整,大家想要深入学习还是要自己去购买原版小册,购买链接点击《传送门》。

本章我们来学习一下TypeORM。

1. 创建项目

新建项目

npx typeorm@latest init --name typeorm-all-feature --database mysql

该命令可以创建一个基础的typeorm示例。

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

2. 配置项

然后改下用户名密码数据库,把连接 msyql 的驱动包改为 mysql2,并修改加密密码的方式:

import "reflect-metadata"
import { DataSource } from "typeorm"
import { User } from "./entity/User"

export const AppDataSource = new DataSource({
    type: "mysql",
    host: "localhost",
    port: 3306,
    username: "root",
    password: "xxxxxx",
    database: "practice",
    synchronize: true,
    logging: false,
    entities: [User],
    migrations: [],
    subscribers: [],
    poolSize: 10,
    connectorPackage: 'mysql2',
    extra: {
        authPlugin: 'sha256_password',
    }
})

注意:需要提前创建好一个数据库practice

安装 mysql2:

npm install --save mysql2

上面的配置我们来简单解释一下:

  1. type 是数据库的类型,typeorm还支持 postgres、oracle、sqllite 。
  2. host、port 是指定数据库服务器的主机和端口号。
  3. user、password 是登录数据库的用户名和密码。
  4. database 是要指定操作的 database,因为 mysql 是可以有多个 database 或者叫 schema 的。
  5. synchronize 是根据同步建表,也就是当 database 里没有和 Entity 对应的表的时候,会自动生成建表 sql 语句并执行。
  6. logging 是打印生成的 sql 语句。
  7. entities 是指定有哪些和数据库的表对应的 Entity。
  8. migrations 是修改表结构之类的 sql。
  9. subscribers 是一些 Entity 生命周期的订阅者,比如 insert、update、remove 前后,可以加入一些逻辑
  10. poolSize 是指定数据库连接池中连接的最大数量。
  11. connectorPackage 是指定用什么驱动包。
  12. extra 是额外发送给驱动包的一些选项。

以上这些配置都在data-source.ts里面。

DataSource 会根据你传入的连接配置、驱动包,来创建数据库连接,并且如果制定了 synchronize 的话,会同步创建表。

而创建表的依据就是 Entity:

//  src/entity/User.ts
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class User {

  @PrimaryGeneratedColumn()
  id: number

  @Column()
  firstName: string

  @Column()
  lastName: string

  @Column()
  age: number

}

当执行npm run start的时候,就会自动创建一个user表,并且生成表字段。

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

刷新一下数据库,就可以看到生成的user表了。

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

3. 映射关系

在我们上面创建的数据库中,主键为 INT 自增、firstName 和 lastName 是 VARCHAR(255),age 是 INT。这是默认的映射关系。

那如果我 number 不是想映射到 INT 而是 DOUBLE 呢?

或者如果 string 不是想映射到 VARCHAR(255),而是 TEXT (长文本)呢?

我们就可以使用下面的方式来写了:

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

@Entity({
    name: 't_aaa'
})
export class Aaa {

    @PrimaryGeneratedColumn({
        comment: '这是 id'
    })
    id: number

    @Column({
        name: 'a_aa',
        type: 'text',
        comment: '这是 aaa'
    })
    aaa: string

    @Column({
        unique: true,
        nullable: false,
        length: 10,
        type: 'varchar',
        default: 'bbb'
    })
    bbb: string

    @Column({
        type: 'double',
    })
    ccc: number
}

我们新增了一个 Entity Aaa。

@Entity 指定它是一个 Entity,name 指定表名为 t_aaa。

@PrimaryGeneratedColumn 指定它是一个自增的主键,通过 comment 指定注释。

@Column 映射属性和字段的对应关系。

通过 name 指定字段名,type 指定映射的类型,length 指定长度,default 指定默认值。

nullable 设置 NOT NULL 约束,unique 设置 UNIQUE 唯一索引。

type 这里指定的都是数据库里的数据类型。

然后在 DataSource 的 entities 里引入下:

import "reflect-metadata"
import { DataSource } from "typeorm"
import { User } from "./entity/User"

import { Aaa } from "./entity/Aaa"
export const AppDataSource = new DataSource({
    type: "mysql",
    host: "localhost",
    port: 3306,
    username: "root",
    password: "xxxxxx",
    database: "practice",
    synchronize: true,
    logging: false,
    entities: [User, Aaa],
    migrations: [],
    subscribers: [],
    poolSize: 10,
    connectorPackage: 'mysql2',
    extra: {
        authPlugin: 'sha256_password',
    }
})

重新跑 npm run start。

又生成了一个t_aaa的表:

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

看一下它的表字段:

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

表有了,接下来就是对表的增删改查操作。

4. 增删改查

我们来看看src/index.ts文件中:

import { AppDataSource } from "./data-source"
import { User } from "./entity/User"

AppDataSource.initialize().then(async () => {

    console.log("Inserting a new user into the database...")
    const user = new User()
    user.firstName = "Timber"
    user.lastName = "Saw"
    user.age = 24
    await AppDataSource.manager.save(user, )
    console.log("Saved a new user with id: " + user.id)

    console.log("Loading users from the database...")
    const users = await AppDataSource.manager.find(User)
    console.log("Loaded users: ", users)

    console.log("Here you can setup and run express / fastify / any other framework.")

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

4.1. 添加/修改

使用AppDataSource.manager.save来操作数据。我们在前面测试的时候,user表中已经被添加了数据了。就是在这里添加的。

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

如果指定了 id,就会变成修改操作:

import { AppDataSource } from "./data-source"
import { User } from "./entity/User"

AppDataSource.initialize().then(async () => {

    const user = new User()
    user.id = 1;
    user.firstName = "aaa111"
    user.lastName = "bbb"
    user.age = 25

    await AppDataSource.manager.save(user)

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

我们修改id=1的数据,重新npm run start,可以看到id=1的数据被修改了:

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

那如果想批量插入和修改呢?可以这样写:

import { AppDataSource } from "./data-source"
import { User } from "./entity/User"

AppDataSource.initialize().then(async () => {

    await AppDataSource.manager.save(User, [
        { firstName: 'ccc', lastName: 'ccc', age: 21},
        { firstName: 'ddd', lastName: 'ddd', age: 22},
        { firstName: 'eee', lastName: 'eee', age: 23}
    ]);


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

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

批量修改是这样写:

import { AppDataSource } from "./data-source"
import { User } from "./entity/User"

AppDataSource.initialize().then(async () => {

    await AppDataSource.manager.save(User, [
        { id: 2 ,firstName: 'ccc111', lastName: 'ccc', age: 21},
        { id: 3 ,firstName: 'ddd222', lastName: 'ddd', age: 22},
        { id: 4, firstName: 'eee333', lastName: 'eee', age: 23}
    ]);

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

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

这就是 typeorm 里新增和修改的方式,使用 save 方法。

其实 EntityManager 还有 update 和 insert 方法,分别是修改和插入的,但是它们不会先查询一次。而 save 方法会先查询一次数据库来确定是插入还是修改。

4.2. 删除

删除和批量删除用 delete 方法:

import { AppDataSource } from "./data-source"
import { User } from "./entity/User"

AppDataSource.initialize().then(async () => {

    await AppDataSource.manager.delete(User, 1);
    await AppDataSource.manager.delete(User, [2,3]);

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

这里也可以用 remove 方法:

import { AppDataSource } from "./data-source"
import { User } from "./entity/User"

AppDataSource.initialize().then(async () => {

    const user = new User();
    user.id = 1;

    await AppDataSource.manager.remove(User, user);

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

delete 和 remove 的区别是,delete 直接传 id、而 remove 则是传入 entity 对象。

4.3. 查询

使用 find 方法查询:

const users = await AppDataSource.manager.find(User)

使用 findBy方法条件查询:

const users = await AppDataSource.manager.findBy(User, {
    id: 6
});

使用 findAndCount 来拿到有多少条记录:

const { count } = await AppDataSource.manager.findAndCount(User)

findAndCountBy可以传入指定条件:

const users = await AppDataSource.manager.findAndCountBy(User, { "age": 23})

使用 findOne 查询一条:

import { AppDataSource } from "./data-source"
import { User } from "./entity/User"

AppDataSource.initialize().then(async () => {
    const user = await await AppDataSource.manager.findOne(User, {
        select: {
            firstName: true,
            age: true
        },
        where: {
            id: 4
        },
        order: {
            age: 'ASC'
        }
    });
    console.log(user);

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

findOne 只是比 find 多加了个 LIMIT 1,其余的都一样:

import { In } from "typeorm";
import { AppDataSource } from "./data-source"
import { User } from "./entity/User"

AppDataSource.initialize().then(async () => {

    const users = await await AppDataSource.manager.find(User, {
        select: {
            firstName: true,
            age: true
        },
        where: {
            id: In([4, 8])
        },
        order: {
            age: 'ASC'
        }
    });
    console.log(users);

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

通过 findOneBy 也可以查询:

import { AppDataSource } from "./data-source"
import { User } from "./entity/User"

AppDataSource.initialize().then(async () => {
    const user = await AppDataSource.manager.findOneBy(User, {
        age: 23
    });
    console.log(user);

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

此外,findOne 还有两个特殊的方法:

import { AppDataSource } from "./data-source"
import { User } from "./entity/User"

AppDataSource.initialize().then(async () => {
    try {
        const user = await AppDataSource.manager.findOneOrFail(User, {
            where: {
                id: 666
            }
        });
        console.log(user);
    }catch(e) {
        console.log(e);
        console.log('没找到该用户');
    }

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

findOneOrFail 或者 findOneByOrFail,如果没找到,会抛一个 EntityNotFoundError 的异常。

此外,你还可以用 query 方法直接执行 sql 语句:

import { AppDataSource } from "./data-source"

AppDataSource.initialize().then(async () => {

    const users = await AppDataSource.manager.query('select * from user where age in(?, ?)', [21, 22]);
    console.log(users);

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

但复杂 sql 语句不会直接写,而是会用 query builder:

const queryBuilder = await AppDataSource.manager.createQueryBuilder();

const user = await queryBuilder.select("user")
    .from(User, "user")
    .where("user.age = :age", { age: 21 })
    .getOne();

console.log(user);

使用 query builder,我们可以做一些复杂的联表查询:

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

我们现在调用每个方法的时候都要先传入实体类,比较麻烦,我们先调用 getRepository 传入 Entity,拿到专门处理这个 Entity 的增删改查的类,再调用这些方法:

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

5. 一对一映射

在数据库里,表和表之间是存在关系的。比如用户和身份证是一对一的关系,我们是通过外键来存储这种关系的。那么在typeorm中是如何映射这种关系的呢?

下面来看看。

之前我们已经有了user表了。再创建个身份证表。

通过 typeorm entity:create 命令创建:

npx typeorm entity:create src/entity/IdCard

填入属性和映射信息:

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

@Entity({
    name: 'id_card'
})
export class IdCard {
    @PrimaryGeneratedColumn()
    id: number

    @Column({
        length: 50,
        comment: '身份证号'
    })
    cardName: string
}

在dataSource中引入:

import "reflect-metadata"
import { DataSource } from "typeorm"
import { User } from "./entity/User"

import { IdCard } from "./entity/IdCard"
export const AppDataSource = new DataSource({
    type: "mysql",
    host: "localhost",
    port: 3306,
    username: "root",
    password: "xiumubai",
    database: "practice",
    synchronize: true,
    logging: false,
    entities: [User, IdCard],
    migrations: [],
    subscribers: [],
    poolSize: 10,
    connectorPackage: 'mysql2',
    extra: {
        authPlugin: 'sha256_password',
    }
})

运行npm run start

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

可以看到,表已经被创建完成了。

现在 user 和 id_card 表都有了,怎么让它们建立一对一的关联呢?

需要在 IdCard 的 Entity 添加一个 user 列,指定它和 User 是 @OneToTone 一对一的关系。

还要指定 @JoinColum 也就是外键列在 IdCard 对应的表里维护:

import { Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from "typeorm"
import { User } from "./User"

@Entity({
    name: 'id_card'
})
export class IdCard {
    @PrimaryGeneratedColumn()
    id: number

    @Column({
        length: 50,
        comment: '身份证号'
    })
    cardName: string

    @JoinColumn()
    @OneToOne(() => User)
    user: User
}

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

可以看到已经创建了一个外健userId,但是级联关系还没改过来。

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

如果我们想设置 CASCADE 应该这么写:

@JoinColumn()
@OneToOne(() => User, {
  onDelete: 'CASCADE',
  onUpdate: 'CASCADE'
})
user: User

重新运行项目,此时就会变成级联的方式了:

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

接下来,试试增删改查:

import { AppDataSource } from "./data-source"
import { User } from "./entity/User"
import { IdCard } from "./entity/IdCard"

AppDataSource.initialize().then(async () => {

    const user = new User();
    user.firstName = 'xiumubai';
    user.lastName = 'xiumubai';
    user.age = 18;
    
    const idCard = new IdCard();
    idCard.cardName = '1111111';
    idCard.user = user;
    
    await AppDataSource.manager.save(user);
    await AppDataSource.manager.save(idCard);

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

创建 user 和 idCard 对象,设置 idCard.user 为 user,也就是建立关联。

可以看到,数据都已经保存了:

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

6. 一对多映射

一对多,拿员工和部门来举例:一个部门可以有多个员工。

创建 Department 和 Employee 两个实体:

npx typeorm entity:create src/entity/Department
npx typeorm entity:create src/entity/Employee

然后添加 Department 和 Employee 的映射信息:

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

@Entity()
export class Employee {
    @PrimaryGeneratedColumn()
    id: number;

    @Column({
        length: 50
    })
    name: string;
}
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"

@Entity()
export class Department {

    @PrimaryGeneratedColumn()
    id: number;

    @Column({
        length: 50
    })
    name: string;
}

把这俩 Entity 添加到 DataSource 的 entities 里,然后运行项目,

神光《Nest 通关秘籍》学习总结-快速上手TypeORM

可以看到,这两个表都创建成功了。

如何给它们添加一对多的映射呢?

通过 @ManyToOne 的装饰器,在多的一方使用 @ManyToOne 装饰器:

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

@Entity()
export class Employee {
    @PrimaryGeneratedColumn()
    id: number;

    @Column({
        length: 50
    })
    name: string;

    @ManyToOne(() => Department)
    department: Department
}