Skip to content
第 178 / 250 章Node⏱ 10 分钟阅读

第 178 章:Repository 与查询构建器

学习目标

  • 掌握 Repository 高级用法
  • 深入 QueryBuilder
  • 学会事务处理
  • 理解原始 SQL

一、Repository 方法速查

1.1 查询类

typescript
// 查全部
const all = await repo.find();

// 按主键查
const one = await repo.findOneBy({ id: 1 });
const byIds = await repo.findByIds([1, 2, 3]);

// 条件
const users = await repo.findBy({ role: 'admin', status: 'active' });

// 复杂条件
const users = await repo.find({
  where: { role: 'admin' },
  order: { id: 'DESC' },
  skip: 0,
  take: 10,
});

// 分页
const [items, total] = await repo.findAndCount({
  where: { role: 'admin' },
  skip: 0,
  take: 10,
});

// 计数
const count = await repo.count({ where: { role: 'admin' } });

// 存在?
const exists = await repo.exists({ where: { email: 'a@b.com' } });

// 单字段求和 / 平均
const sum = await repo.sum('age', { role: 'admin' });
const avg = await repo.average('age', { role: 'admin' });
const min = await repo.min('age', { role: 'admin' });
const max = await repo.max('age', { role: 'admin' });

1.2 写入类

typescript
// 创建 + 保存
const user = repo.create({ name: 'Tom' });
const saved = await repo.save(user);

// 批量保存
await repo.save([user1, user2]);

// 更新(不查)
await repo.update({ id: 1 }, { name: 'Tom' });
await repo.update({ role: 'user' }, { status: 'banned' });

// 新增或更新
await repo.upsert([{ id: 1, name: 'Tom' }], ['id']);

// 软删除
await repo.softDelete({ id: 1 });

// 硬删除
await repo.delete({ id: 1 });
await repo.remove(user);

// 恢复软删
await repo.restore({ id: 1 });

1.3 Where 高级语法

typescript
import { Like, Between, In, IsNull, MoreThan, LessThan, Not } from 'typeorm';

// 模糊
await repo.find({ where: { name: Like('%Tom%') } });

// 区间
await repo.find({ where: { age: Between(18, 30) } });

// IN
await repo.find({ where: { role: In(['admin', 'user']) } });

// NULL
await repo.find({ where: { avatar: IsNull() } });

// 大于 / 小于
await repo.find({ where: { age: MoreThan(18) } });
await repo.find({ where: { age: LessThan(60) } });

// NOT
await repo.find({ where: { role: Not('admin') } });

// AND / OR(数组里逗号是 AND,嵌套数组是 OR)
await repo.find({
  where: [
    { name: 'Tom', age: MoreThan(18) },
    { role: 'admin' },
  ],
});

二、QueryBuilder 入门

2.1 创建

typescript
const qb = repo.createQueryBuilder('alias');

2.2 WHERE

typescript
// 等值
qb.where('user.name = :name', { name: 'Tom' })

// 比较
qb.andWhere('user.age > :age', { age: 18 })
qb.orWhere('user.role = :role', { role: 'admin' })

// IN
qb.andWhere('user.id IN (:...ids)', { ids: [1, 2, 3] })

// BETWEEN
qb.andWhere('user.createdAt BETWEEN :start AND :end', {
  start: '2024-01-01',
  end: '2024-12-31',
})

// LIKE
qb.andWhere('user.name LIKE :name', { name: '%Tom%' })

2.3 JOIN

typescript
// LEFT JOIN
qb.leftJoin('user.posts', 'post')

// 关联查询(SELECT)
qb.leftJoinAndSelect('user.posts', 'post')

// INNER JOIN
qb.innerJoin('post.tags', 'tag')

// 带条件 JOIN
qb.leftJoin('user.posts', 'post', 'post.published = :pub', { pub: true })

2.4 ORDER / GROUP / LIMIT

typescript
qb.orderBy('user.createdAt', 'DESC')
qb.addOrderBy('user.id', 'ASC')

qb.groupBy('user.role')
qb.having('COUNT(user.id) > :count', { count: 5 })

qb.limit(10)
qb.offset(20)

// 取一条
qb.limit(1).getOne()

2.5 结果获取

typescript
qb.getMany();           // 数组
qb.getOne();            // 单个
qb.getManyAndCount();   // [items, total]
qb.getCount();          // 数量
qb.getExists();         // boolean
qb.getRawMany();        // 原始行
qb.getRawOne();         // 原始行单条
qb.execute();           // 用于 INSERT/UPDATE/DELETE

三、QueryBuilder 实战

3.1 动态条件查询

typescript
async search(filter: UserFilterDto) {
  const qb = this.repo.createQueryBuilder('user');

  if (filter.name) {
    qb.andWhere('user.name LIKE :name', { name: `%${filter.name}%` });
  }
  if (filter.role) {
    qb.andWhere('user.role = :role', { role: filter.role });
  }
  if (filter.startDate) {
    qb.andWhere('user.createdAt >= :start', { start: filter.startDate });
  }
  if (filter.endDate) {
    qb.andWhere('user.createdAt <= :end', { end: filter.endDate });
  }

  qb.orderBy('user.id', 'DESC');

  const [items, total] = await qb
    .skip((filter.page - 1) * filter.size)
    .take(filter.size)
    .getManyAndCount();

  return { items, total };
}

3.2 关联查询 + 过滤

typescript
async findUserWithPosts(userId: number) {
  return this.repo
    .createQueryBuilder('user')
    .leftJoinAndSelect('user.posts', 'post', 'post.published = :pub', { pub: true })
    .leftJoinAndSelect('post.tags', 'tag')
    .where('user.id = :id', { id: userId })
    .getOne();
}

3.3 子查询

typescript
async findActiveUsersWithPostCount() {
  return this.repo
    .createQueryBuilder('user')
    .loadRelationCountAndMap('user.postCount', 'user.posts')
    .where('user.status = :status', { status: 'active' })
    .getMany();
}

四、聚合查询

4.1 SELECT 子句

typescript
const stats = await userRepo
  .createQueryBuilder('user')
  .select('user.role', 'role')
  .addSelect('COUNT(*)', 'count')
  .addSelect('AVG(user.age)', 'avgAge')
  .groupBy('user.role')
  .getRawMany();

// [{ role: 'admin', count: '5', avgAge: '28' }, ...]

4.2 子查询

typescript
const users = await userRepo
  .createQueryBuilder('user')
  .where(qb => {
    const subQuery = qb
      .subQuery()
      .select('post.authorId')
      .from(Post, 'post')
      .where('post.published = :pub', { pub: true });
    return 'user.id IN ' + subQuery.getQuery();
  })
  .getMany();

五、事务

5.1 自动事务

typescript
await userRepo.manager.transaction(async manager => {
  const user = await manager.save(User, { name: 'Tom' });
  await manager.save(Post, { authorId: user.id, title: 'Hi' });
});

5.2 手动事务

typescript
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();

try {
  await queryRunner.manager.save(User, { name: 'Tom' });
  await queryRunner.manager.save(Post, { title: 'Hello' });
  await queryRunner.commitTransaction();
} catch (err) {
  await queryRunner.rollbackTransaction();
  throw err;
} finally {
  await queryRunner.release();
}

5.3 隔离级别

typescript
await dataSource.transaction('SERIALIZABLE', async manager => {
  // ...
});

六、原始 SQL

typescript
// 查询
const users = await dataSource.query(
  'SELECT * FROM users WHERE role = ?',
  ['admin'],
);

// 写操作
await dataSource.query(
  'DELETE FROM users WHERE createdAt < ?',
  ['2024-01-01'],
);

安全

直接拼接字符串会有 SQL 注入风险,务必使用参数化(?:name)!

七、软删除

7.1 配置

typescript
import { DeleteDateColumn } from 'typeorm';

@Entity()
export class User {
  @DeleteDateColumn()
  deletedAt?: Date;
}

7.2 查询

typescript
// 默认不查已删除
await repo.find();   // 不返回软删数据

// 查全部(含软删)
await repo.find({ withDeleted: true });

// 只查已删除
await repo.find({ withDeleted: true, where: { deletedAt: Not(IsNull()) } });

7.3 软删 / 恢复

typescript
await repo.softDelete({ id: 1 });   // 设置 deletedAt
await repo.restore({ id: 1 });      // 清空 deletedAt

八、Repository API 速查表

方法用途
find / findBy / findOneBy查询
findAndCount分页查询
count / exists统计
save保存(新增/更新)
create创建实例(不保存)
update更新(不查)
delete / remove删除
softDelete软删
restore恢复
upsert新增或更新
increment / decrement增减
createQueryBuilder构建器

九、QueryBuilder vs Repository 对比

typescript
// Repository 简单场景
const users = await repo.find({ where: { role: 'admin' } });

// QueryBuilder 复杂场景
const users = await repo
  .createQueryBuilder('user')
  .leftJoinAndSelect('user.posts', 'post')
  .where('user.role = :role', { role: 'admin' })
  .andWhere('post.published = :pub', { pub: true })
  .orderBy('user.id', 'DESC')
  .getMany();

十、本章小结

方法用途
find简单查询
findOneBy查一条
findAndCount分页
createQueryBuilder复杂查询
transaction事务
query原始 SQL
softDelete软删除
manager.save事务中保存

动手练习

  1. 写一个 findUsers API,支持多条件 + 分页
  2. 用 QueryBuilder 实现联表查询(用户 + 文章 + 标签)
  3. 用事务实现转账功能(A 减钱,B 加钱)
  4. 实现软删除 + 恢复

推荐阅读


下一章:第 179 章:Migration 数据迁移

本站基于 VitePress 构建 · 由 Codebook 团队维护