Skip to content

第六章 EntityManager 与 Repository 深入

本章目标

  • 说清楚 EntityManager 与 Repository 的关系,并能用源码佐证
  • 掌握 EntityManager 的常用方法(save / find / update / count / transaction / query / createQueryBuilder)
  • 理解 Active Record 与 Data Mapper 两种模式的差别,知道什么时候用哪个
  • 会用 TypeORM 1.1.1 的推荐方式编写自定义 Repository

核心概念

把数据库想象成一家大超市:

  • EntityManager(实体管理器) 是超市的总服务台。它什么柜台都能管——User、Product、Order……任何实体的增删改查都可以找它。代价是每次办事你都得说明"我要办哪个柜台的业务"(方法的第一个参数总是实体类)。
  • Repository(仓库) 是某个柜台的专职管家。它只管一种实体,比如 userRepo 只管 User 表。好处是办事时不用再报实体名,写起来更简洁。
  • 关键事实:Repository 内部并没有自己实现查询,它把所有活都委托给了内部的 EntityManager。 打开 node_modules/typeorm/repository/Repository.d.ts 就能看到:Repository 类有一个 readonly manager: EntityManager 属性,构造函数是 constructor(target, manager, queryRunner?)——创建一个 Repository 时就必须传入一个 EntityManager。

获取方式

ts
import { AppDataSource } from "./data-source";
import { User } from "./entities/User";

AppDataSource.initialize()
  .then(async () => {
    const manager = AppDataSource.manager; // EntityManager:管所有实体
    const userRepo = AppDataSource.getRepository(User); // Repository:只管 User
    console.log(manager);
    console.log(userRepo);
  })
  .catch((error) => console.log(error));

两种写法

ts
// EntityManager 版:每个方法第一个参数都要带上实体类
await manager.save(User, { name: "张三", age: 25 });
const all = await manager.find(User);
await manager.update(User, { name: "李四" }, { age: 31 });
const count = await manager.count(User);

// Repository 版:已经绑定了 User,不用重复写
await userRepo.save({ name: "赵六", age: 28 });
const all2 = await userRepo.find();
const admins = await userRepo.findBy({ role: "admin" });

怎么证明二者是一家?演示代码里打印了:

ts
console.log(userRepo.manager === ds.manager); // true
console.log(userRepo.target === User); // true

EntityManager 常用方法速览

方法作用
manager.save(Entity, data)新增或更新(有主键则更新)
manager.find(Entity, options?)查询多条
manager.findOneBy(Entity, where)查询单条,找不到返回 null
manager.update(Entity, where, partial)按条件更新
manager.delete(Entity, criteria)按条件删除
manager.count(Entity, options?)统计条数
manager.transaction(fn)包裹事务:manager.transaction(async (em) => { ... }),事务内必须用回调给的 em
manager.query("SELECT ...")执行原生 SQL
manager.createQueryBuilder(Entity, "alias")创建查询构造器(第 07 章主角)
manager.getRepository(Entity)拿到某实体的 Repository

Active Record vs Data Mapper

对比项Data Mapper(Repository 模式)Active Record 模式
写法userRepo.find() / userRepo.save(user)User.find() / user.save()
实体类纯数据,只写字段装饰器extends BaseEntity,自带增删改查
数据源绑定通过 ds.getRepository()ds.initialize() 时自动注入(源码:DataSource.js 里对 BaseEntity 子类自动调用 useDataSource(this)
测试性容易 mock Repository,适合分层架构实体和数据库耦合紧,单测稍麻烦
适合场景中大型项目、领域分层小型项目、脚本、快速原型

扩展实体类方法

ts
@Entity()
class ArUser extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  // 还能封装自己的静态查询方法
  static findByNameKeyword(kw: string) {
    return this.findBy({ name: Like(`%${kw}%`) });
  }
}

// 用起来:
const all = await ArUser.find();
const jia = await ArUser.findOneBy({ name: "贾宝玉" });
jia!.age = 20;
await jia!.save(); // 对象自己存自己
await ArUser.delete({ name: "薛宝钗" }); // 静态删除

自定义 Repository方法

写法一

ts
const userRepo = ds.getRepository(User).extend({
  findByNameKeyword(kw: string) {
    // 自定义方法里 this 就是 repository 本体,原生方法随便用
    return this.findBy({ name: Like(`%${kw}%`) });
  },
  findAdults(minAge: number) {
    return this.createQueryBuilder("user")
      .where("user.age >= :minAge", { minAge })
      .orderBy("user.age", "DESC")
      .getMany();
  },
});

const zhangs = await userRepo.findByNameKeyword("张");
const adults = await userRepo.findAdults(25);
await userRepo.count(); // 原生方法照常可用

写法二

  • 适合自定义逻辑多、想收敛成一个独立类的场景:
ts
class UserService extends Repository<User> {
  async findByNameKeyword(kw: string) {
    return this.findBy({ name: Like(`%${kw}%`) });
  }
}
const userService = new UserService(User, ds.manager);
await userService.findByNameKeyword("李");