背景
typeorm查询语句比较简单,就算记不住稍微查下文档就可以,但在使用typeorm框架过程中曾让我比较困惑的主要是两个问题:
1、Data Mapper 和 Active Record 的区别和使用场景;
2、Entity Manager,Repository api,connection api等都可以作为查询语句的载体,到底什么场景使用哪个api进行查询;
其实1/2两个问题是关联的。本文主要是整理下他们之间的区别和关联,希望给一些为此困惑的人一些帮助;
Active Record VS Data Mapper
Active Record
Active Record模式有两种方式进行sql查询:
一、继承 BaseEntity 的基础crud语句(BaseEntity的crud也分两种,实例方法/静态方法);
// user 类
@Entity()
export class User extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
age: number;
}
// crud-增删, 实例方法(save, remove, hasId, softRemove, recover, reload)
const user = new User();
user.name = 'vb'
user.age = 17
await user.save() // 增
await user.remove() // 删
// crud, 静态方法(save, remove, update, find, etc.)
await User.update({id: 3}, {name: 'fish'})
await User.find({id: 1})
await User.save(user, {data: user}) // save 其实也是要通过实例方法进行保存
await User.remove(user, {data: user}) // delete 其实也需要通过实例方法进行删除
二、其实就是拓展entity的方法;
@Entity()
export class User extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
age: number;
// 实例上的 save
saveUser() {
return (this.constructor as any).getRepository().save(this, {}) // 用法跟实例方法save一样
}
// 静态方法save
static saveStatic(name: string, age: number) {
return this.createQueryBuilder("user")
.insert()
.values({name, age})
.execute()
}
}
其实心细的童鞋可以发现,即使在Active Record这个模式下,crud也分是类的静态方法或实例方法(暂没有查询到原因,感觉可能是历史原因导致。如果有了解的童鞋麻烦留言说下)。其实实现上的区别可以通过上面拓展的方法看到:
实例上的拓展方法是通过repository api去实现的(repository也是静态方式),而静态方式的因为无法访问到实例,所以是通过 createQueryBuilder(alias) 实现的(也可以通过传入实例的方式,例如例子一中的静态方法save)。
再往 BaseEntity 上溯源,repository其实是由 Connection api 提供的,但无法指定connection(使用默认连接)。
对于拓展方式不必过于纠结,建议拓展的情况下直接使用静态方法进行拓展就好了(因为实例方式是在太绕,增加了很多额外负担);
其实写完几个demo后可以发现,Active Record是永远通过Entity或拓展Entity进行数据库的crud的。我自己尝试总结一下 Active Record 的特性就是:
1、所有的方法都由 Entity 提供,或拓展 Entity 提供;
2、因为局限于 Entity,所以当项目复杂后,需要在里面再抽象就会变得比较麻烦和难以理解;
所以当项目比较简单时可以尝试用 Active Record 模式进行开发(但我自己看法,统一用 Data Mapper 更利于规范和拓展);
Data Mapper
Data Mapper的使用方式是把数据库crud和 Entity 解耦,e.g.
import { getRepository } from "typeorm"
function getUser() {
return getRepository('user')
.createQueryBuilder()
.where({
id: 3
})
.execute()
}
// 也可以尝试混合 Active Record 的写法(但感觉没什么必要, 简单的sql用 BaseEntity 也没方便多少,复杂的sql都要拓展)
import { getRepository } from "typeorm"
import { User } from './src/entity/user'
function getUserMixin() {
let user = new User()
user.name = 'vb'
user.age = 17
return getRepository('user').save(user)
}
可以看到其实 Data Mapper 的方式让sql和 Entity 完全解耦,更利于拓展。
总结
不管是 Active Record 还是 Data Mapper 都是通过 Repository api 来进行数据库操作的,两者的区别是 Data Mapper 直接调用 Repository api,而 Active Record 是通过继承拓展 BaseEntity 再调用的;
而他们默认调用的都是default connection,可以通过 Connection api 进行连接管理,调用流程图如下;
graph LR
A(Active Record) --> B(BaseEntity)
B --> D(Repository api) --> E(Connection Api)
C(Data Mapper)
C --> D