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

2023年 7月 19日 54.5k 0

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

image.png

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

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

本章我们来学习一下TypeORM。

1. 创建项目

新建项目

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

该命令可以创建一个基础的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

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

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

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

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的表:

看一下它的表字段:

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

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表中已经被添加了数据了。就是在这里添加的。

如果指定了 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的数据被修改了:

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

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

批量修改是这样写:

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

这就是 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,我们可以做一些复杂的联表查询:

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

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

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

现在 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
}

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

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

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

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

接下来,试试增删改查:

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,也就是建立关联。

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

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 里,然后运行项目,

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

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

通过 @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
}

相关文章

Oracle如何使用授予和撤销权限的语法和示例
Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
社区版oceanbase安装
Oracle 导出CSV工具-sqluldr2
ETL数据集成丨快速将MySQL数据迁移至Doris数据库

发布评论