TypeORM框架中的QueryBuilder

856 阅读8分钟

QueryBuilder是 TypeORM 最强大的功能之一 ,它允许你使用优雅便捷的语法构建 SQL 查询,执行并获得自动转换的实体。

在实际应用中,是最多的一个

示例:

获取数据库表中的数据

const firstUser = await connection
  .getRepository(User)
  .createQueryBuilder("user")
  .where("user.id = :id", { id: 1 })
  .getOne();

内部转化成SQL语言

SELECT
    user.id as userId,
    user.firstName as userFirstName,
    user.lastName as userLastName
FROM users user
WHERE user.id = 1

返回查询结果

User {
    id: 1,
    firstName: "Timber",
    lastName: "Saw"
}

创建QueryBuilder

  • 使用 connection
import { getConnection } from "typeorm";

// 获取一个数据库连接
const user = await getConnection()
  //  创建一个查询构建器
  .createQueryBuilder()
  // 指定查询的字段为 `user`
  .select("user")
  // 指定查询的表为 `User` 实体,并使用 `"user"` 别名来引用该实体
  .from(User, "user")
  // 指定查询的条件为 `id = 1`,其中 `:id` 是一个占位符,表示要查询的 `User` 实体的 `id` 属性为 `1`
  .where("user.id = :id", { id: 1 })
  // 获取查询的数据
  .getOne();
  • 使用 entity manager
import { getManager } from "typeorm";

const user = await getManager()
  .createQueryBuilder(User, "user")
  .where("user.id = :id", { id: 1 })
  .getOne();
  • 使用 repository
import { getRepository } from "typeorm";

const user = await getRepository(User)
  .createQueryBuilder("user")
  .where("user.id = :id", { id: 1 })
  .getOne();

使用QueryBuilder获取值

const timber = await getRepository(User)
  .createQueryBuilder("user")
  // 查询 id 为 1 或者 name 为 Timber 的数据
  .where("user.id = :id OR user.name = :name", { id: 1, name: "Timber" })
  .getOne();
const photosSums = await getRepository(User)
  .createQueryBuilder("user")
  .select("user.id")
  // 第一个参数是返回的数据,第二个参数为别名
  .addSelect("SUM(user.photosCount)", "sum")
  .where("user.id = :id", { id: 1 })
  .getRawMany();

// 结果会像这样: [{ id: 1, sum: 25 }, { id: 2, sum: 13 }, ...]

getRawOnegetRawMany的用法,与getOnegetMany方法用法一样,区别在于获取的数据为原始数据

取别名

处理选定的数据,都可以用别名

createQueryBuilder("user")
// 相当于
createQueryBuilder()
  .select("user")
  .from(User, "user");

这会生成以下 sql 查询语句:

SELECT ... FROM users user

下面的表达式方法,一个查询构造器中不能使用多个,后面的查询条件会覆盖前面的;如果要使用多个,用提供的特殊表达式(特殊表达式一般是该表达式的衍生表达式)


添加where表达式

andor两种逻辑,可以添加到表达式里面

where只有一个,而 andWhereorWhere可以无限叠加

// 逻辑与
createQueryBuilder("user")
  .where("user.firstName = :firstName", { firstName: "Timber" })
  .andWhere("user.lastName = :lastName", { lastName: "Saw" });
  
// 逻辑或
createQueryBuilder("user")
  .where("user.firstName = :firstName", { firstName: "Timber" })
  .orWhere("user.lastName = :lastName", { lastName: "Saw" });

综合示例:

使用Brackets将复杂的WHERE表达式添加到现有的WHERE中(用Brackets类来创建一个子查询)

createQueryBuilder("user")
    .where("user.registered = :registered", { registered: true })
    .andWhere(new Brackets(qb => {
        qb.where("user.firstName = :firstName", { firstName: "Timber" })
          .orWhere("user.lastName = :lastName", { lastName: "Saw" })

分析代码:

查询user表中registered字段等于true,且firstName字段等于"Timber"或者lastName字段等于"Saw"的数据。

添加having表达式

having查询通常用于对查询结果进行分组、聚合统计等操作,而不是用于单独的数据筛选。

andor两种逻辑,可以添加到表达式里面

having只有一个,而 andHavingorHaving可以无限叠加

// 逻辑与
createQueryBuilder("user")
  .having("user.firstName = :firstName", { firstName: "Timber" })
  .andHaving("user.lastName = :lastName", { lastName: "Saw" });

// 逻辑或
createQueryBuilder("user")
  .having("user.firstName = :firstName", { firstName: "Timber" })
  .orHaving("user.lastName = :lastName", { lastName: "Saw" });

添加orderBy表达式

orderBy表达式用于排序

// 根据`user.id`数值,进行降序排列
createQueryBuilder("user").orderBy("user.id", "DESC");

可以用addOrderBy添加多个排序条件

createQueryBuilder("user")
  .orderBy("user.name")
  .addOrderBy("user.id");

添加groupBy表达式

groupBy方法查询,又称为分组查询

分组查询通常用于对查询结果进行聚合统计等操作,而不是用于单独的数据筛选。

// 查询结果按照`user.id`字段进行分组
createQueryBuilder("user").groupBy("user.id");

可以使用 addGroupBy,添加多个分组查询

createQueryBuilder("user")
  .groupBy("user.name")
  .addGroupBy("user.id");

添加limit表达式

limit指定查询结果的最大返回数量

// 查询 user 表中数据,最多返回 10 条数据,如果超过 10 条,就返回最前面的 10 条
createQueryBuilder("user").limit(10);

limit方法通常需要和orderBy方法一起使用

// 按照`id`字段进行降序排列
createQueryBuilder("user")
  .orderBy("user.id", "DESC")
  .limit(10);

添加offset表达式

offset表示查询结果的偏移量

// 查询`user`表中的数据,并从查询结果的第`11`条数据开始返回
createQueryBuilder("user").offset(10);
// 指定最多返回`10`条结果,并从查询结果的第`11`条数据开始返回
createQueryBuilder("user")
  .limit(10)
  .offset(10);

联查

假设有以下实体:

import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
import { Photo } from "./Photo";

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @OneToMany(type => Photo, photo => photo.user)
  photos: Photo[];
}
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm";
import { User } from "./User";

@Entity()
export class Photo {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  url: string;

  @ManyToOne(type => User, user => user.photos)
  user: User;
}

现在让我们假设你要用用户"Timber"加载他所有的 photos:

const user = await createQueryBuilder("user")
  .leftJoinAndSelect("user.photos", "photo")
  .where("user.name = :name", { name: "Timber" })
  .getOne();

你将会得到以下结果:

// 在 user 中获得一条用户名为 Timber 的所有 photos 数据,
{
    id: 1,
    name: "Timber",
    photos: [{
        id: 1,
        url: "me-with-chakram.jpg"
    }, {
        id: 2,
        url: "me-with-trees.jpg"
    }]
}

可以看到leftJoinAndSelect自动加载了所有 Timber 的 photos。 第一个参数是你要加载的关系,第二个参数是你为此关系的表分配的别名。 你可以在查询构建器中的任何位置使用此别名。

还可以向连接表达式添加条件,而不是使用"where":

    const user = await createQueryBuilder("user")
      .leftJoinAndSelect("user.photos", "photo", "photo.isRemoved = :isRemoved", { isRemoved: false })
      .where("user.name = :name", { name: "Timber" })
      .getOne();

这将生成以下 sql 查询:

    SELECT user.*, photo.* FROM users user
        LEFT JOIN photos photo ON photo.user = user.id AND photo.isRemoved = FALSE
        WHERE user.name = 'Timber'

内联和左联

内联用innerJoinAndSelect

外联用leftJoinAndSelect

两者的区别:

内联如果查询的数据没有,不会在执行后面的查询并返回数据;外联会执行后面的查询并返回数据

例如:

const user = await createQueryBuilder("user")
  .innerJoinAndSelect("user.photos", "photo", "photo.isRemoved = :isRemoved", { isRemoved: false })
  .where("user.name = :name", { name: "Timber" })
  .getOne();

内联如果没有任何 photos,INNER JOIN将不会返回 user。 外联即使没有 photos,LEFT JOIN也会返回 user

不使用条件的联查

const user = await createQueryBuilder("user")
  // `innerJoin`方法用于进行内连接查询,可以将多个数据表进行关联查询
  .innerJoin("user.photos", "photo")
  .where("user.name = :name", { name: "Timber" })
  .getOne();

查询user表中name字段等于"Timber"的数据,并关联查询名为photos的另一个数据表。如果查询结果存在,则返回结果集中的第一条数据,否则返回undefined

流数据stream

将获取到的数据,以数据流的方式返回

const stream = await getRepository(User)
  .createQueryBuilder("user")
  .where("user.id = :id", { id: 1 })
  .stream();

使用分页

const users = await getRepository(User)
  .createQueryBuilder("user")
  .leftJoinAndSelect("user.photos", "photo")
  .skip(5)
  .take(10)
  .getMany();

跳过前 5 个 users,并获取他们之后的 10 个 user 数据

使用子查询

formwhereselectjoin表达式都支持子查询

子查询的结果可以使用别名来引用,并在后续的查询操作中进行使用

// form表达式
const posts = await connection
  .createQueryBuilder()
  .select("user.name", "name")
  .from(subQuery => {
    return subQuery
      .select("user.name", "name")
      .from(User, "user")
      .where("user.registered = :registered", { registered: true });
  }, "user")
  .getMany();

使用子查询查询User表中registered字段等于true的数据,并将name字段重命名为name,最终返回所有查询结果集中的数据

// where 表达式
const posts = await connection
  .getRepository(Post)
  .createQueryBuilder("post")
  .where(qb => {
    const subQuery = qb
      .subQuery()
      .select("user.name")
      .from(User, "user")
      .where("user.registered = :registered")
      .getQuery();
    return "post.title IN " + subQuery;
  })
  .setParameter("registered", true)
  .getMany();

查询Post实体中title字段的值在User表中registered字段等于true的数据的name字段中出现过的所有数据,并返回所有查询结果集中的数据

// select 表达式
const posts = await connection
  .createQueryBuilder()
  .select("post.id", "id")
  .addSelect(subQuery => {
    return subQuery
      .select("user.name", "name")
      .from(User, "user")
      .limit(1);
  }, "name")
  .from(Post, "post")
  .getRawMany();

使用子查询查询User表中第一条数据的name字段,并将查询结果与Post实体的id字段进行组合,最终返回所有查询结果集中的数据

使用 Query Builder 插入

import { getConnection } from "typeorm";

await getConnection()
  .createQueryBuilder()
  // 擦插入
  .insert()
  // 插入表
  .into(User)
  // 插入数据
  .values([{ firstName: "Timber", lastName: "Saw" }, { firstName: "Phantom", lastName: "Lancer" }])
  .execute();

使用 Query Builder 更新

import { getConnection } from "typeorm";

await getConnection()
  .createQueryBuilder()
  .update(User)
  .set({ firstName: "Timber", lastName: "Saw" })
  .where("id = :id", { id: 1 })
  .execute();

使用 Query Builder 删除

import { getConnection } from "typeorm";

await getConnection()
  .createQueryBuilder()
  .delete()
  .from(User)
  .where("id = :id", { id: 1 })
  .execute();

缓存查询

cache 表达式用来做查询结果缓存,可以对其进行属性配置

const users = await connection
  .createQueryBuilder(User, "user")
  .where("user.isAdmin = :isAdmin", { isAdmin: true })
  .cache(true)
  .getMany();

// or
const users = await connection.getRepository(User).find({
  where: { isAdmin: true },
  cache: true
});

可以设置时间,来做到控制缓存时间的控制,例如:cache(1000),表示1 秒内缓存无效,实际效果是用户在 1 秒内打开用户页面 150 次,则在此期间只会执行一次查询

// 两种查询方式
const users = await connection
  .createQueryBuilder(User, "user")
  .where("user.isAdmin = :isAdmin", { isAdmin: true })
  .cache(60000) // 1 分钟
  .getMany();
  
const users = await connection.getRepository(User).find({
  where: { isAdmin: true },
  cache: 60000
});

可以给缓存设置id,就是标识,方便精准查询和删除

const users = await connection
  .createQueryBuilder(User, "user")
  .where("user.isAdmin = :isAdmin", { isAdmin: true })
  .cache("users_admins", 25000)
  .getMany();

const users = await connection.getRepository(User).find({
  where: { isAdmin: true },
  cache: {
    id: "users_admins",
    milisseconds: 25000
  }
});

// 删除可以根据 id ,来精准删除缓存
await connection.queryResultCache.remove(["users_admins"]);

学习完查询,可以看看如何连接数据并缓存数据的

Connection数据库连接

点击Connection数据库连接,查看数据缓存