Active Record和Data Mapper的区别(typeORM)

896 阅读3分钟

背景

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