第六章 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); // trueEntityManager 常用方法速览
| 方法 | 作用 |
|---|---|
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("李");