「遇见周大侠(网名:程序猿),长沙奥科领航信息技术公司的资深软件开发工程师。他的软件开发系列课件和项目,凝聚了多年的专业积累与热情。每套课件都包含丰富的案例和深入的知识点解析,每一讲都配备了精心设计的课后作业,旨在为热爱编程的你提供最佳编程实践。是教师备课的得力助手,是学生学习的贴心伴侣,亦是毕业设计的灵感源泉。」
「现在,请搜索微信小程序「奥科领航信息技术 」,让我们一起深入代码的世界,探索无限可能!」
TypeORM 是一个非常流行的 ORM(对象关系映射)框架,它可以在 TypeScript 和 JavaScript (ES6+) 环境中使用。TypeORM 旨在与 TypeScript 结合得天衣无缝,利用 TypeScript 的高级特性如装饰器和异步函数来简化数据库交互操作。
主要特性:
- 支持多种数据库:TypeORM 支持多种 SQL 数据库,包括 MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, 以及更多。这为开发者提供了灵活性,可以根据项目需求选择合适的数据库。
- Active Record 和 Data Mapper 模式:TypeORM 提供了两种模式来管理数据实体。Active Record 模式允许实体自己保存、加载和删除等操作;Data Mapper 模式则通过仓库(Repository)或管理器(Manager)来处理这些操作。
- 自动数据库模式同步:TypeORM 可以自动创建数据库表和字段,根据实体类的变更自动同步数据库模式,这使得数据库维护变得简单。
- 高级查询能力:通过QueryBuilder,开发者可以构建复杂的SQL查询,同时保持代码的可读性和可维护性。
- 事务支持:TypeORM 提供了事务支持,可以执行多个数据库操作作为一个单一的工作单元来处理,确保数据的一致性和完整性。
- 装饰器和实体关系:TypeORM 使用装饰器来定义实体和数据库表之间的关系,如一对一、一对多、多对一和多对多关系。
开始使用 TypeORM 的最快方法是使用其 CLI 命令生成启动项目。 只有在 Node.JS 应用程序中使用 TypeORM 时,此操作才有效。
一、快速开始
1.1 安装TypeORM
全局安装 TypeORM:
cnpm install typeorm -g
1.2 创建项目
切换到要创建新项目的目录并运行命令:
typeorm init --name ch01 --database mysql
其中:
- name是项目的名称,
- database是你将使用的数据库。数据库可以是以下值之一:mysql, mariadb, postgres, sqlite, mssql, oracle, mongodb, cordova, react-native, expo, nativescript。
此命令将在ch01目录中生成一个包含以下文件的新项目:
MyProject
├── src // TypeScript 代码
│ ├── entity // 存储实体(数据库模型)的位置
│ │ └── User.ts // 示例 entity
│ ├── migration // 存储迁移的目录
│ └── index.ts // 程序执行主文件
├── .gitignore // gitignore文件
├── ormconfig.json // ORM和数据库连接配置
├── package.json // node module 依赖
├── README.md // 简单的 readme 文件
└── tsconfig.json // TypeScript 编译选项
1.3 修改数据源配置
修改src/data-source.ts数据源配置文件,主要是修改username、password、database三个属性:
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: "123456",
database: "mydb",
synchronize: true,
logging: false,
entities: [User],
})
注意:需要确保database属性指定的数据库是存在的,即先要创建好这个数据库。
1.4 运行项目
在VS Code的终端执行以下指令运行项目:
npm start
如果没有错误的话,是这个样子的:
PS E:\我的程序\S1\TypeORM\ch01> npm start
> ch01@0.0.1 start
> ts-node src/index.ts
切换到数据库,可以看到数据表已经自动创建好,并且成功的插入了两条记录:
打开浏览器,输入:
http://localhost:3001/users
是不是so easy?
如果用npm start指令来运行项目的话,不能实现热加载,即每次代码做了变动,都要ctrl + c停止服务,然后再重新启动,挻麻烦的。可以用nodemon进行简化。
先全局安装nodemon:
cnpm i nodemon -g
再用nodemon运行入口文件src/index.ts:
nodemon src/index.ts
二、代码解读
2.1 创建模型
使用数据库从创建表开始。如何告诉 TypeORM 创建数据库表?答案是 - 通过模型。 应用程序中的模型即是数据库中的表。模型也称为"实体类"。
所有的模型类存放在src/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
}
2.1.1 @Entity
@Entity装饰器用于将一个实体类映射成一个数据表。默认情况下,实体类的类名就是数据表的表名(在MySQL中,表名自动变成了小写字母)。我们知道,类名通常用名词,且是单数,但在数据库中,表名一般用复数。那如何将单数形式的类名映射成复数形式的表名呢?
@Entity({name: "users"})
2.1.2 @PrimaryGeneratedColumn
每个实体必须至少有一个主键列。这是必须的,你无法避免。要使列成为主键,你需要使用@PrimaryColumn装饰器。假设你希望 id 列自动生成,是自增长的,那么需要使用@PrimaryGeneratedColumn装饰器:
@PrimaryGeneratedColumn()
id: number
2.1.3 Column
要添加数据库列,你只需要将要生成的实体属性加上@Column装饰器。数据库中的列类型是根据你使用的属性类型推断的,例如: number将被转换为int,string将转换为varchar(255),boolean转换为bool等。但你也可以通过在@Column装饰器中显式指定列类型来使用数据库支持的任何列类型:
@Column({length: 100})
firstName: string
@Column({length: 50})
lastName: string
@Column("double")
age: number
2.1.4 初始值
前面所定义的属性都没有初始值,如果TypeScript的编译模式设置为严格模式的话,会报错。
(1)修改tsconfig.json,将编译模式设置为严格(第14行代码):
{
"compilerOptions": {
"lib": [
"es5",
"es6"
],
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./build",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"strict": true
}
}
在严格模式下,每一个属性都需要有一个初始值。有两种方式设置属性的初始值。
(2)在构造方法中赋初始值
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity({ name: "users" })
export class User {
constructor(){
this.id = 0;
this.firstName = "";
this.lastName = "";
this.age = 0
}
@PrimaryGeneratedColumn()
id: number
@Column({ length: 100 })
firstName: string
@Column({ length: 50 })
lastName: string
@Column("double")
age: number
}
(3)直接在属性声明时赋初始值
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity({ name: "users" })
export class User {
@PrimaryGeneratedColumn()
id: number = 0
@Column({ length: 100 })
firstName: string = ""
@Column({ length: 50 })
lastName: string = ""
@Column("double")
age: number = 0
}
可是,一旦开启严格模式,项目中好多地方都报错了,比如:
在开启严格模式后,TypeScript会更加严格地检查你的代码,包括模块的导入和类型声明。错误描述中显示无法找到模块"express"的声明文件,这意味着TypeScript无法找到与该模块对应的类型定义文件(通常是以.d.ts为扩展名的文件)。解决方法:安装类型声明文件。
cnpm install @types/express --save-dev
现在错误消失了。
2.2 配置数据源
配置数据源,主要就是配置数据库的连接信息,这项工作是在src/data-source.ts文件中完成的:
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: "123456",
database: "mydb",
synchronize: true,
logging: false,
entities: [User],
migrations: [],
subscribers: [],
})
这里说一下synchronize和entities两个属性。
- synchronize:设置synchronize的值为true,可确保同步数据库结构(开启后会自动创建或更新数据库表结构,仅在开发环境中使用)。
- entities:所有需要在连接中使用的每个实体都必须加到这个属性中。之后当我们创建更多实体时,都需要将一一它们添加到配置中的实体中,但是这不是很方便,所以我们可以设置整个目录,从中连接所有实体并在连接中使用:
- entities: [__dirname + "/entity/*.js"]
现在我们来测试一下。再定义一个新的模型:
import { Entity, Column, PrimaryGeneratedColumn } from "typeorm"
@Entity({ name: "departments" })
export class Department {
@PrimaryGeneratedColumn()
id: number = 0;
@Column({ length: 100 })
name: string = "";
@Column({ length: 1000 })
description: string = "";
@Column("datetime")
createTime: Date = new Date();
}
2.3 CRUD操作
2.3.1 Create
Create操作,即往表中插入记录。
创建一个新的 Department 存到数据库中。
下面的操作都是在src/index.ts文件中进行。
2.3.1.1 使得Entity Manager
Entity Manager,实体管理器,顾名思义,它可以对实体进行任何操作。
(1)方法一
import { AppDataSource } from "./data-source"
import { Department } from "./entity/Department"
AppDataSource.initialize().then(async () => {
await AppDataSource.manager.save(
AppDataSource.manager.create(Department, {
name: "研发部",
description: "工资很高的部门",
createTime: new Date()
})
);
console.log("ok");
}).catch(error => console.log(error))
(2)方法2
let item = new Department();
item.name = "财务部";
item.description = "油水很多的部门";
item.createTime = new Date("2001-1-1");
await AppDataSource.manager.save(item);
2.3.1.2 使用Repository
现在让我们重构之前的代码,并使用Repository而不是EntityManager。每个实体都有自己的存储库,可以处理其实体的所有操作。当你经常处理实体时,Repository 比 EntityManager 更方便使用。
let item = new Department();
item.name = "行政部";
item.description = "很悠闲的部门";
item.createTime = new Date("2001-1-1");
let departmentRepository = AppDataSource.getRepository(Department);
departmentRepository.save(item);
console.log("ok");
2.3.2 Read
Read操作,即查询数据表中的记录。
先往数据表插入一些测试数据:
INSERT INTO `users` VALUES
(NULL, '刘德华', '', 48),
(NULL, '张学友', '', 50),
(NULL, '郭富城', '', 42),
(NULL, '刘青云', '', 46),
(NULL, '刘德华', '', 20),
(NULL, '关之琳', '', 45),
(NULL, '张曼玉', '', 42);
mysql> use mydb
Database changed
mysql> select * from `users`;
+----+-----------+----------+-----+
| id | firstName | lastName | age |
+----+-----------+----------+-----+
| 1 | 刘德华 | | 48 |
| 2 | 张学友 | | 50 |
| 3 | 郭富城 | | 42 |
| 4 | 刘青云 | | 46 |
| 5 | 刘德华 | | 20 |
| 6 | 关之琳 | | 45 |
| 7 | 张曼玉 | | 42 |
+----+-----------+----------+-----+
7 rows in set (0.00 sec)
mysql>
(1)查询出所有的记录
调用find()方法,不传入任何参数,即表示查询出所有记录。
let userRepository = AppDataSource.getRepository(User);
let users = await userRepository.find();
console.log(users);
console.log("ok");
(2)根据主键值查询
调用findOneBy()方法。
let userRepository = AppDataSource.getRepository(User);
let user = await userRepository.findOneBy({ id: 1 })
console.log(user);
console.log("ok");
(3)查询符合条件的第一条记录
调用findOne()方法,并传入一个where条件。
let userRepository = AppDataSource.getRepository(User);
let user = await userRepository.findOne({ where: { firstName: "刘德华" } });
console.log(user);
console.log("ok");
可以看到,只查询出了第1个刘德华。
(4)模糊查询
类似于MySQL数据库中like。
用find和Like方法。Like区分大小写,ILike忽略大小写。
let userRepository = AppDataSource.getRepository(User);
let users = await userRepository.find({ where: { firstName: ILike("张%") } });
console.log(users);
console.log("ok");
(5)数字大小比较
- 大于:MoreThan
- 大于等于(>=):MoreThanOrEqual
- 小于(<):LessThan
- 小于等于(<=):LessThanOrEqual
- 等于(==):Equal
- 不等于(!=):Not
let userRepository = AppDataSource.getRepository(User);
let users = await userRepository.find({ where: { age: MoreThan(40) } });
console.log(users);
(6)多个条件的查询
示例1:查询"刘"姓且年龄大于等于48岁的用户。这是逻辑与的查询。
let userRepository = AppDataSource.getRepository(User);
let users = await userRepository.find({
where: {
firstName: Like("刘%"),
age: MoreThanOrEqual(48)
}
});
console.log(users);
示例2:查询"张"姓或年龄大于等于48岁的用户。这是逻辑或的查询。
let userRepository = AppDataSource.getRepository(User);
let users = await userRepository.find({
where: [
{ firstName: Like("张%") },
{ age: MoreThanOrEqual(48) }
]
});
console.log(users);
(7)对结果进行排序
- asc:升序
- desc:降序
let userRepository = AppDataSource.getRepository(User);
let users = await userRepository.find({
order: {
age: "desc"
}
})
console.log(users);
(8)查询出某个区间的记录
- skip:跳过多少条
- take:抓取多少条
let userRepository = AppDataSource.getRepository(User);
let users = await userRepository.find({
order: {
age: "desc"
},
skip: 2,
take: 3
})
console.log(users);
2.3.3 Update
Update,即更新操作。
让我们从数据库加载出User,更新并保存到数据库:
let userRepository = AppDataSource.getRepository(User);
let user = await userRepository.findOneBy({ id: 1 });
if (user != null) {
user.age = 18;
userRepository.save(user);
} else {
console.log("未找到");
}
console.log("ok");
查询数据库,可以发现ID为1的用户的年龄变成了18。
2.3.4 Delete
Delete,即删除操作。
let userRepository = AppDataSource.getRepository(User);
let user = await userRepository.findOneBy({ id: 1 });
if (user != null) {
userRepository.remove(user);
} else {
console.log("未找到");
}
console.log("ok");
查询数据库,可以发现ID为1的用户已经被删除了。
三、其他事项
再次审视一下数据源配置文件(src/data-source.ts):
import "reflect-metadata"
import { DataSource } from "typeorm"
export const AppDataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "123456",
database: "mydb",
synchronize: false,
logging: false,
entities: [__dirname + "/entity/*.ts"],
})
其中synchronize属性的作用是什么呢?设置为true和false有何区别?
在 TypeORM 中,synchronize 属性用于指定在应用程序启动时是否自动同步数据库结构。它的作用如下:
- 当 synchronize 设置为 true 时,TypeORM 会在应用程序启动时自动检查数据库结构和实体类的差异,并尝试自动同步它们。如果数据库中不存在指定的表或列,TypeORM 会尝试创建它们。这个过程被称为自动同步(automatic synchronization)。如果数据库中存在与实体类不匹配的表或列,自动同步可能会删除或修改数据库中的数据,带来意外的数据丢失或改动。因此,在生产环境中,通常不推荐使用 synchronize: true。
- 当 synchronize 设置为 false 时,TypeORM 不会自动检查数据库结构。它会假定数据库结构与实体类定义一致,并且不会对数据库做任何更改。你需要手动执行数据库迁移(migrations)来同步数据库结构或修改数据库。
因此,synchronize 的设置会影响 TypeORM 在启动应用程序时是否自动创建或修改数据库结构。在开发阶段,使用 synchronize: true 可以方便地创建和更新表结构,但在生产环境中建议将其设置为 false,并使用数据库迁移工具来管理数据库结构的变更。
本文暂时没有评论,来添加一个吧(●'◡'●)