6月2日 23:04

TypeORM Active Record 和 Data Mapper 有什么区别?怎么选?

TypeORM 支持两种数据访问模式:Active Record(实体自带 CRUD 方法)和 Data Mapper(通过 Repository 操作实体)。选哪种影响代码组织方式,但不影响功能。

Active Record 模式

实体继承 BaseEntity,直接调用静态方法操作数据:

typescript
@Entity() export class User extends BaseEntity { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column({ unique: true }) email: string; } // 使用:实体自带 find/save/remove const users = await User.find(); const user = await User.findOne({ where: { email: 'test@test.com' } }); user.name = 'Updated'; await user.save(); await user.remove();

优点:代码简洁,一个对象既承载数据又负责持久化。适合简单 CRUD 和小项目。

缺点:实体和数据库操作耦合——同一个类既定义数据结构又包含查询逻辑,业务复杂后类会膨胀。

Data Mapper 模式

实体是纯数据对象,通过 Repository 操作:

typescript
@Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column({ unique: true }) email: string; } // 使用:通过 Repository 操作 const userRepo = dataSource.getRepository(User); const users = await userRepo.find(); const user = await userRepo.findOne({ where: { email: 'test@test.com' } }); user.name = 'Updated'; await userRepo.save(user); await userRepo.remove(user);

优点:关注点分离——实体只管数据结构,Repository 负责持久化逻辑。业务代码不依赖数据库 API,方便单元测试(mock Repository)。

缺点:多一层间接,简单操作代码量稍多。

怎么选

Active Record 适合

  • 小项目,实体少,查询逻辑简单
  • 快速原型,不想写太多样板代码
  • Rails/Django 背景,习惯模型自带操作

Data Mapper 适合

  • 中大型项目,业务逻辑复杂
  • 需要单元测试,要 mock 数据层
  • 领域驱动设计(DDD)风格,实体只表达业务概念

NestJS 项目推荐 Data Mapper——NestJS 的 @InjectRepository() 天然就是 Data Mapper 风格,依赖注入也让测试更方便。

两种模式可以混用

TypeORM 不强制二选一。同一个实体可以同时用两种方式:

typescript
@Entity() export class User extends BaseEntity { @PrimaryGeneratedColumn() id: number; // Active Record 方式 static findActive() { return this.find({ where: { active: true } }); } } // Data Mapper 方式也可以 const repo = dataSource.getRepository(User); const users = await repo.find({ where: { active: true } });

但不建议混用——团队应该统一风格,避免同一项目里两种模式交叉出现。

自定义查询放哪里

Active Record 模式的常见问题:复杂查询方法堆积在实体类上。

typescript
// 不推荐:实体类越来越胖 export class User extends BaseEntity { static findWithStats() { ... } static findActiveUsers() { ... } static findByDepartment() { ... } static searchByName() { ... } } // 推荐:抽取到单独的 Service 或自定义 Repository export class UserService { constructor(@InjectRepository(User) private repo: Repository<User>) {} async findWithStats() { return this.repo.createQueryBuilder('user') .leftJoinAndSelect('user.posts', 'post') .loadRelationCountAndMap('user.postCount', 'user.posts') .getMany(); } }

Data Mapper 模式自然避免这个问题——查询逻辑在 Service 里,实体保持干净。

标签:TypeORM