前言
大家好!我是 嘟老板。ZimuAdmin
集成了 TypeORM
框架,以更便捷的处理持久数据存储。TypeORM
针对不同的应用场景,为我们提供了两种开发模式 - Active Record
和 Data Mapper
,今天我们就来聊聊两者的差异及 ZimuAdmin
是如何选择的。
ZimuAdmin
项目基于以下技术构建:
- 前端:
Vue3
:JavaScript
框架。Vite4.3
:构建工具。ElementPlus
:UI
框架。TypeScript
:类型系统。Sass
:CSS
预处理框架。
- 服务端 :
express
:Node
框架。pnpm
:包管理工具。TypeScript
:类型系统。routing-controlers
: 优化控制器层。TypeORM
:ORM
框架,操作数据库。MySql
:数据持久化。Redis
:数据缓存。
阅读本文您将收获:
- 了解
Active Record/Data Mapper
两种模式的特点及应用范例。- 了解如何选择合适的开发模式。
Active Record
Active Record
模式可直译为 活动记录 模式,是一种在实体模型内部访问数据库的架构模式,允许我们将所有的操作都写在实体类中,如保存、更新、删除等,通过实体类函数方式调用。
使用 Active Record
模式的实体类必须继承 TypeORM
提供的 BaseEntity
类,以使用 BaseEntity
中提供的数据库操作函数。
标准操作函数
标准操作是指 TypeORM
框架提供的操作,如 save
、remove
、find
等。
直接上代码,定义个 User
实体类:
import { BaseEntity, PrimaryGeneratedColumn, Column } from 'typeorm'
export class User extends BaseEntity {
@PrimaryGeneratedColumn()
id!: number
@Column()
username!: string
@Column()
sex!: string
@Column()
isActive!: boolean
}
后续数据保存、删除等操作,通过以下方式调用:
async function operateWithAR() {
// 创建 UserEntity 实例,并赋值
const user = new User()
user.username = 'dulaoban'
user.sex = 'Male'
user.isActive = true
// 保存
await user.save()
// 删除
await user.remove()
// 查询列表
const users = await User.find({ skip: 2, take: 5 })
}
此时实体类已经包含了大部分数据库操作函数,使用 Active Record
模式,基本不需要使用 Repository
和 EntityManager
。
自定义操作函数
除了标准的数据库操作函数(save
、remove
、find
等),我们可以在实体类中定义自定义操作函数,以满足多样化的需求。
比如创建一个通过 username
查询用户信息的函数 - queryByUsername
:
import { BaseEntity, PrimaryGeneratedColumn, Column } from 'typeorm'
export class User extends BaseEntity {
@PrimaryGeneratedColumn()
id!: number
@Column()
username!: string
@Column()
sex!: string
@Column()
isActive!: boolean
static queryByUsername(username: string) {
return this.createQueryBuilder('user')
.where('user.username = :username', { username })
.getOne()
}
}
调用 queryByUsername
:
const user = await User.queryByUsername('dulaoban')
需要留意一点,queryByUsername
是 静态函数,即属于 User
类,而不是其实例,为什么定义成静态函数呢?主要因为 this 指向 这个经典思考,定义为静态函数,函数内部的 this
指向类的构造函数,就可以访问 BaseEntity
定义的标准处理函数,用以实现定制化的数据处理需求。当然啦,若用不到 BaseEntity
的函数,定义为实例函数也无不可。
Data Mapper
Data Mapper
模式可直译为 数据映射器 模式,用于在持久数据存储(通常是关系数据库)和内存数据表现形式之间进行双向传输。
使用 Data Mapper
模式,需要在存储库中定义数据操作方法,并且通过存储库对象调用执行,如保存、更新、删除等;而 实体类 则仅仅用来定义实体对象结构,如实体属性。
标准操作函数
还是定义 User
实体类:
import { PrimaryGeneratedColumn, Column } from 'typeorm'
export class User {
@PrimaryGeneratedColumn()
id!: number
@Column()
username!: string
@Column()
sex!: string
@Column()
isActive!: boolean
}
与 Active Record
不同之处在于,Data Mapper
模式不需要继承 BaseEntity
类,因为该模式下,不用实体类做太多的事情,只要定义好实体对象就 OK 了。
后续数据操作要通过存储库 (Repository
) 对象执行:
不了解
Repository
的同学,可点击阅读 《TypeORM 知多少?来看看 node 服务端如何基于 typeORM 封装 BaseService》
async function operateWithDM() {
// 获取存储库对象
const userRepository = dataSource.getRepository(UserEntity)
// 创建 UserEntity 实例,并赋值
const user = new User()
user.username = 'dulaoban'
user.sex = 'Male'
user.isActive = true
// 保存
await userRepository.save(user)
// 删除
await userRepository.remove(user)
// 查询列表
const users = await userRepository.find({ skip: 2, take: 5 })
}
自定义操作函数
Repository 对象提供了 extend 方法,允许我们扩展存储库对象,开发自定义功能。还是以 queryByUsername
函数为例:
export const userRepository = dataSource.getRepository(User).extend({
queryByUsername(username: string) {
return this.createQueryBuilder("user")
.where("user.username = :username", { username })
.getOne()
}
})
以上代码在 userRepository
上扩展了 queryByUsername
函数,后续可通过 userRepository
直接调用:
const user = await userRepository.queryByUsername('dulaoban')
更多细节在 《TypeORM 知多少?来看看 node 服务端如何基于 typeORM 封装 BaseService》有介绍,就不过多赘述了,感兴趣的小伙伴可点击阅读。
如何选?
既然存在两种模式,自然就涉及到选择问题。用哪个更好永远是让人头疼的问题。
两种模式各有优劣,但都能实现业务需求,能力方面大可放心。更需要关注的点是 架构风格 和 项目规模。
抛开编码方式的差异不谈,Active Record
模式主打一个简单,快速,一切逻辑都可依靠实体类搞定,在实体类内可以定义任何你想要的处理函数,比较适合业务逻辑相对简单的 小项目;Data Mapper
模式中实体类回归本质,仅用于实体相关属性的定义,加入独立的数据映射器,甚至需要设计独立的服务层,专门处理业务逻辑,将数据处理与实例类定义解耦,更适合业务复杂的 大项目;
ZimuAdmin
采用了 Data Mapper
模式,个人认为 Data Mapper
模式扩展性更强且易维护。
结语
本文重点介绍了 TypeORM
的两种开发模式 - Active Record
和 Data Mapper
,包括各自的编码规则,扩展方式及择优思路,旨在帮助同学们加深对于这两种模式的应用理解。希望对您有所帮助。
如您对文章内容有任何疑问或想深入讨论,欢迎评论区留下您的问题和见解。
技术简而不凡,创新生生不息。我是 嘟老板,咱们下期再会。