「现在,请搜索微信小程序「奥科领航信息技术 」,让我们一起深入代码的世界,探索无限可能!
」
TypeORM 是一个非常流行的 ORM(对象关系映射)框架,它可以在 TypeScript 和 JavaScript (ES6+) 环境中使用。TypeORM 旨在与 TypeScript 结合得天衣无缝,利用 TypeScript 的高级特性如装饰器和异步函数来简化数据库交互操作。
主要特性:

开始使用 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 ManagerEntity 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 ReadRead操作,即查询数据表中的记录。
先往数据表插入一些测试数据:
INSERT INTO `users` VALUES(NULL, '刘德华', '', 48),(NULL, '张学友', '', 50),(NULL, '郭富城', '', 42),(NULL, '刘青云', '', 46),(NULL, '刘德华', '', 20),(NULL, '关之琳', '', 45),(NULL, '张曼玉', '', 42);
mysql> use mydbDatabase changedmysql> 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不等于(!=):Notlet 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 DeleteDelete,即删除操作。
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,并使用数据库迁移工具来管理数据库结构的变更。