前言
大家好!我是 嘟老板。目前 ZimuAdmin 服务端集成了 TypeORM
框架,用以处理数据库相关操作。今天我们来聊聊 TypeORM
比较常用的数据库操作方法以及项目中的实际应用场景。
ZimuAdmin 服务端基于以下技术构建:
阅读本文您将收获:
TypeORM
存储库(Repository)
TypeORM
提供两种操作实体数据的方式:
- EntityManager:管理所有实体,相当于实体存储库的集合。
- Repository:某个特定实体的存储库。
本文内容内容重点关注 Repository 的介绍及应用。
Repository 可以对指定实体数据进行管理,比如 查询、新增、修改、删除 等操作。
通过 DataSource 实例获取 Repository 对象。
import db from '../tools/data-source'
import { User } from '../entities/user.entity'
const repository = db.getRepository(User)
以上代码摘自 ZimuAdmin,引入的 db 就是 DataSource 实例,可理解为 new DataSource({ // 配置项... })
。
常用 api
TypeORM
Repository 为我们提供了丰富的实用 api,方便调用。
查询类
- find:获取匹配查询条件(Find Options)的数据列表。
const users = await repository.find({ where: { username: "dulaoban" } })
- findBy:作用与 find 相同,参数是 Find Options 中 where 条件对象。
const users = await repository.findBy({ username: "dulaoban" })
- exists:检查否存在匹配查询条件(Find Options)的数据。
const exists = await repository.exists({ where: { username: "dulaoban" } })
- existsBy:作用与 exists 相同,参数是 Find Options 中 where 条件对象。
const exists = await repository.existsBy({ username: "dulaoban" })
- count:获取匹配查询条件(Find Options) 的数据量。
const count = await repository.count({ where: { username: "dulaoban" } })
- countBy:作用与 count 相同,参数是 Find Options 中 where 条件对象。
const exists = await repository.countBy({ username: "dulaoban" })
- findAndCount:获取匹配查询条件(Find Options)的 数据列表 和 数据总量,其中 数据总量 的计算会忽略分页相关配置(skip 和 take)。
const [users, count] = await repository.findAndCount({ where: { username: "dulaoban" }, skip: 10, take: 10 })
- findAndCountBy:作用与 findAndCount 相同,参数是 Find Options 中 where 条件对象。
const [users, count] = await repository.findAndCountBy({ username: "dulaoban" })
- findOne:获取匹配查询条件(Find Options)的第一条数据。
const user = await repository.findOne({ where: { username: "dulaoban" } })
- findOneBy:作用与 findOne 相同,参数是 Find Options 中 where 条件对象。
const user = await repository.findOneBy({ username: "dulaoban" })
- query:直接执行 SQL 语句。
const users = await repository.query(`SELECT * FROM USERS`)
对象处理类
- create:创建实体类的实例对象,接收一个可选的对象参数,参数属性与实体类属性一致。若传递参数,参数的属性值会写入生成的实例对象。
// 等价于 const user = new User();
const user = repository.create()
// 等价于 const user = new User(); user.firstName = "Timber"; user.lastName = "Saw";
const user = repository.create({ id: 1, username: "dulaoban" })
- merge:将多个实体对象合并为一个。
const user = new User()
// 等价于 user.username = "dulaoban"; user.age = 18;
repository.merge(user, { username: "dulaoban" }, { age: 18 })
- preload:基于参数对象,生成新的实体对象。参数对象必须包含 主键,若该主键在数据库中存在数据,则获取该实体数据,并将参数对象中的所有值覆盖到该实体对象上。
const partialUser = { id: 1, username: "dulaoban", age: 18 }
// 假设数据库中存在对应用户数据 { id: 1, username: "zhagnsan", sex: "M", age: 18 }
// user 对象:{ id: 1, username: "dulaoban", sex: "M", age: 18 }
const user = await repository.preload(partialUser)
数据操作类
-
save:保存参数传递的实体数据到数据库,并返回保存的实体对象或实体数组。若待保存实体在数据库中已存在,则更新,否则新增一条数据。支持部分更新,所有值为 undefined 的属性都会被跳过。
参数:实体对象/实体对象数组
await repository.save(user) await repository.save([user1, user2, user3])
-
insert:数据库插入实体数据。
参数:实体对象 / 实体对象数组
await repository.insert(user) await repository.insert([user1, user2, user3])
-
update:更新数据库中符合条件的实体数据。
参数:1. 条件对象,用以筛选需要更新的数据。2. 实体对象,用以更新数据库实体。
// UPDATE user SET category = ADULT WHERE age = 18 await repository.update({ age: 18 }, { category: "ADULT" }) // UPDATE user SET username = dulaoban WHERE id = 1 await repository.update(1, { username: "dulaoban" })
-
delete:删除数据库中符合条件的实体数据。
参数:id / id 数组 / 条件对象
// DELETE from user WHERE id = 1 await repository.delete(1) // DELETE from user WHERE id in (1, 2, 3) await repository.delete([1, 2, 3]) // DELETE from user WHERE username = dulaoban await repository.delete({ username: "dulaoban" })
-
softDelete:软删除数据库指定 id 对应的实体数据。
参数:id
await repository.softDelete(1)
-
restore:重新保存 softDelete 软删除的数据。
参数:id
await repository.restore(1)
-
softRemove:功能与 softDelete 相同。
参数:待删除的实体数组。
await repository.softRemove([user1, user2])
-
recover:重新保存 softRemove 软删除的数据。
参数:待保存的实体数组。
await repository.recover([user1, user2])
自定义 Repository
若 Repository 提供的 api 无法满足需求场景,TypeORM
允许我们创建自定义 Repository,以扩展 Repository 能力。
可以通过 Repository 实例方法 extend 扩展自定义功能,比如为 UserRepository 添加 queryByUsername 方法,用来根据 username 查询实体数据。
过程如下:
- 创建 Repository 并导出
如新增 user.responsitory.ts
import db from '../tools/data-source'
import { User } from '../entities/user.entity'
export const UserRepository = db.getRepository(User).extend({
queryByUsername(username: string) {
return this.createQueryBuilder("user")
.where("user.username = :username", { username })
.getMany()
}
})
- 引用 UserRepository 并调用 queryByUsername
如在 UserController 中使用:
import { UserRepository } from './user.responsitory'
export class UserController {
queryByUsername() {
return UserRepository.queryByUsername("dulaoban")
}
}
BaseService
BaseService 定位是基础服务类,用于封装基础的,通用的业务数据处理过程,其他业务类服务,如 UserService,都可继承 BaseService。
若非特殊场景,业务类服务不需要重复编写 BaseService 中已有的通用函数;若需求特殊,业务类服务可自行编写,覆盖通用函数。
实现
BaseService 中包含以下内容:
- 全列表查询 - queryList
- 分页列表查询 - queryByPage
- 详情查询 - queryById
- 新增记录 - insert
- 修改记录 - update
- 删除记录 - delete
- 软删除记录 - softDelete
在 services 目录下新建 base-service.ts 文件,来编写 BaseService 代码:
- 创建并导出 BaseService 类,构造函数结构一个实体类,用来获取对应的实体存储库对象,以执行数据操作。
import db from '../tools/data-source'
export class BaseService {
repository
constructor(entityClass: any) {
this.repository = db.getRepository(entityClass)
}
}
- 全列表查询 - queryList,调用 repository.findAndCountBy api,获取满足条件的所有数据及总数。
async queryList(params?: any) {
const [rows, total] = await this.repository.findAndCountBy(params)
return {
rows,
total
}
}
- 分页列表查询 - queryByPage,通过 repository.countBy api 获取满足条件的数据总量,然后调用 repository.find api 获取满足条件及分页要求的数据列表。
此处借助 skip 和 take 配置项实现分页需求。
async queryByPage(
params: any = {},
pageSize: number = 10,
pageNum: number = 1
) {
// 总数
const total = await this.repository.countBy(params)
// 数据列表
const skip = pageSize * (pageNum - 1)
const rows = await this.repository.find({
where: params,
skip,
take: pageSize
})
return {
total,
pageSize,
pageNum,
rows
}
}
- 详情查询 - queryById,调用 repository.findOneBy 通过主键 id 查询实体明细数据。
async queryById(id: string) {
return await this.repository.findOneBy({
id
})
}
- 新增记录 - insert,调用 repository.insert 将客户端传递的实体数据新增到数据库。
async insert(entity: any) {
if (!entity) {
return Promise.reject(new Error('不存在待插入的实体对象'))
}
const { identifiers } = await this.repository.insert(entity)
// 获取主键
const id = identifiers[0].id
if (!id) {
return Promise.reject(new Error('保存失败, repository.insert 未生成 id'))
}
return await this.queryById(id)
}
- 修改记录 - update,调用 repository.update 将客户端传递的实体数据更新到数据库。
async update(entity: any = {}) {
if (!entity.id) {
return Promise.reject(new Error('id 不存在'))
}
return await this.repository.update(entity, { id: entity.id })
}
- 删除记录 - delete,调用 repository.delete 删除指定 id 对应的数据库数据。
async delete(id: string) {
if (!id) {
return Promise.reject(new Error('id 不存在'))
}
return await this.repository.delete(id)
}
- 软删除记录 - softDelete,即逻辑删除,调用 repository.softDelete 为指定 id 对应的数据库数据打上删除标识。
async softDelete(id: string) {
if (!id) {
return Promise.reject(new Error('id 不存在'))
}
return await this.repository.softDelete(id)
}
目前 BaseService 仅涵盖基础的数据操作逻辑,后续会随着业务模块的深入应用,逐步完善。
应用
现在我们在用户模块应用一下。
services/user-service.ts 文件中包含用户模块的业务处理逻辑,我们先简单声明一下用户服务类:
/**
* 用户服务类
*/
import { User } from '../entities/user.entity'
import { BaseService } from './base-service'
export class UserService extends BaseService {
constructor() {
super(User)
}
}
因为 UserService 继承了 BaseService,所以 UserService 的实例可用 BaseService 的实例函数。
然后在用户模块控制器文件 controllers/user-controller.ts 中,编写业务接口,为节约篇幅,我们仅实现 插入 和 全列表查询 接口。
/**
* 用户 controller
*/
import { Controller, Get, Post, Body } from 'routing-controllers'
import { UserService } from '../services/user.service'
@Controller('/users')
export class UserController {
service = new UserService()
@Get('/list')
async queryList() {
return this.service.queryList()
}
@Post('/insert')
async insert(@Body() body: any) {
return this.service.insert(body)
}
}
测试
用户模块接口已搞定,我们使用 Postman 工具来测试一下。
- 调用 insert 接口向数据库插入一条用户数据:
接口调用成功,查看数据库如下:
zhangsan 的用户数据已成功插入。
- 调用 list 接口查询用户数据列表:
OK,用户数据列表获取正常。
结语
本文重点介绍了 TypeORM
存储库(Repository) 相关内容及常用方法,并结合 ZimuAdmin 中的实际应用场景,旨在帮助同学们加深对于 TypeORM
实现数据操作的应用理解。希望对您有所帮助!相关代码已上传至 GitHub,欢迎 star。
如您对文章内容有任何疑问或想深入讨论,欢迎评论区留下您的问题和见解。
技术简而不凡,创新生生不息。我是 嘟老板,咱们下期再会。