最近在学习神光大神的《Nest通关秘籍》,该小册主要包含下面这些内容:
接下来的日子里,我将更新一系列的学习笔记。感兴趣的可以关注我的专栏《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
}