天天看點

TypeORM 的 Eager 和 Lazy 關系

作者:馬士兵程式員
TypeORM 的 Eager 和 Lazy 關系

前言

存在關聯關系的實體之間,在查詢一個實體時,加載另一個實體的方式有兩種,Eager 和 Lazy,分别是立即加載和延遲加載/懶加載。所謂的加載,就是指查詢資料庫的這個動作。

本文在 Nest 程式中,示範這兩種加載方式的用法和差別。

準備兩個實體

TypeORM 可以友善為實體設定一對一,一對多/多對一,多對多的關系,使用 @OneToOne()、@OneToMany()/@ManyToOne 、@ManyToMany() 裝飾器即可。

作者和文章是典型的一對多的關系,本文以這個一對多的關系來進行示範。 先來定義這兩個實體。Author 實體如下:

import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
import { Article } from './article.entity';

@Entity({ name: 'author' })
export class Author {
  @PrimaryGeneratedColumn()
  id: number;
​
  @Column()
  name: string;
​
  @Column()
  avatar: string;
​
  // 作者和文章是一對多的關系
  @OneToMany(() => Article, (articles) => articles.author, {
    cascade: true,
  })
  articles: Article[];
}
複制代碼           

Article 實體如下:

import {
  Column,
  Entity,
  JoinColumn,
  ManyToOne,
  PrimaryGeneratedColumn,
} from 'typeorm';
import { Author } from './author.entity';
​
@Entity({ name: 'article' })
export class Article {
  @PrimaryGeneratedColumn()
  id: number;
​
  @Column()
  title: string;
​
  @Column()
  banner: string;
​
  // 文章和作者是多對一的關系
  @ManyToOne(() => Author, (author) => author.articles)
  @JoinColumn({ name: 'author_id' })
  author: Author;
}
複制代碼           

eager

Eager 立即加載,當查詢一個實體時,所關聯的實體會立即從資料庫中加載。此模式的開啟方式有兩種。

使用 relations

當使用 find 、findOne 等方法查詢實體時,如果通過 relations 屬性指定了關聯實體,則指定的實體就是 eager 加載。

Author 實體中聲明的關系:

@OneToMany(() => Article, (articles) => articles.author, {
    cascade: true   
})
articles: Article[];
複制代碼           

查詢作者實體的方法:

async findAll() {
    return this.authorRepository.findOne({
      where: {
        id: 1,
      },
      relations: {
        articles: true,
      },
    });
}
複制代碼           

此時生成的 SQL 如下,會生成一個左連接配接的連接配接查詢,當查詢 author 表時會查詢 article 表:

SELECT `Author`.`id` AS `Author_id`, `Author`.`name` AS `Author_name`, `Author`.`avatar` AS `Author_avatar`, `Author__Author_articles`.`id` AS `Author__Author_articles_id`, `Author__Author_articles`.`title` AS `Author__Author_articles_title`, `Author__Author_articles`.`banner` AS `Author__Author_articles_banner`, `Author__Author_articles`.`author_id` AS `Author__Author_articles_author_id` 
FROM `author` `Author` 
LEFT JOIN `article` `Author__Author_articles` 
ON `Author__Author_articles`.`author_id`=`Author`.`id` WHERE ( (`Author`.`id` = ?) ) AND ( `Author`.`id` IN (1) ) 
複制代碼           

設定 eager

使用 @OneToMany() 等裝飾器設定關系時,可通過 eager:true 開啟,此時查詢實體,無論是否指定 relations ,設定了關聯關系的實體都是 eager 加載。

Author 實體中聲明關系時開啟 eager:

@OneToMany(() => Article, (articles) => articles.author, {
   cascade: true,
   eager: true
})
articles: Article[]
複制代碼           

查詢方法,不需要指定 relations:

async findAll() {
    return this.authorRepository.findOne({
      where: {
        id: 1
      },
    });
}
複制代碼           

生成的 SQL 如下,和上面的效果相同,也是生成一個左連接配接 article 表的查詢:

TypeORM 的 Eager 和 Lazy 關系

lazy

Lazy 懶加載,當查詢一個實體時,所關聯的實體不會馬上從資料庫中加載,當通路到時,才去執行加載。懶加載的作用就類似網頁中的圖檔懶加載,路由元件的懶加載,等用到時,才去加載,目的是提高性能。但實際來看,用的并不多。

TypeORM 中使用 lazy 加載也有兩種方式。

使用 Promise

方式一是需要将關聯實體的類型設為 Promise。Author 實體的代碼:

@OneToMany(() => Article, (articles) => articles.author, {
    cascade: true,
})
articles: Promise<Article[]>;
複制代碼           

查詢時,如果沒有沒有通路到關聯實體的屬性(author.articles)時,就不會去查詢 Article 實體:

async findAll() {
return await this.authorRepository.findOne({
      where: { id: 1 }
  });
}
複制代碼           

此時生成的 SQL 語句隻查詢了 author 表:

TypeORM 的 Eager 和 Lazy 關系

并且傳回給前端的資料,也隻有Author 實體:

TypeORM 的 Eager 和 Lazy 關系

當通路 author.articles ,也就是使用到 Author 實體關聯的 Article 實體時,才會去查詢 article 表。查詢方法如下:

async findAll() {
  const author = await this.authorRepository.findOne({
      where: { id: 1 }
  });
  // 通路到關聯的 Article 實體,才會去查詢 article 表
  console.log(await author.articles);
  return author;
}
複制代碼           

生成的 SQL 如下,可以看到執行了兩次查詢:

query: SELECT `Author`.`id` AS `Author_id`, `Author`.`name` AS `Author_name`, `Author`.`avatar` AS `Author_avatar` FROM `author` `Author` WHERE (`Author`.`id` = ?) LIMIT 1 
​
query: SELECT `articles`.`id` AS `articles_id`, `articles`.`title` AS `articles_title`, `articles`.`banner` AS `articles_banner`, `articles`.`author_id` AS `articles_author_id` FROM `article` `articles` WHERE `articles`.`author_id` IN (?) 
複制代碼           

傳回給浏覽器的資料,也包含了 article 表的資料:

TypeORM 的 Eager 和 Lazy 關系

設定 lazy

方式二是在設定關聯關系時,設定 lazy 屬性為 true。Author 實體如下:

@OneToMany(() => Article, (articles) => articles.author, {
    cascade: true,
    lazy: true,
})
articles: Article[];
複制代碼           

此時查詢的效果,和上面那種方式是一緻的,都是懶加載。

需要注意的是,如果設定了 lazy,又在查詢實體時使用了 relations,那麼此時的查詢依然是 eager 的。

總結

實際上,TypeORM 文檔将 eager 和 lazy 定義為關聯實體之間的關系。Eager 關系是在查詢完實體之後,立即查詢所關聯的實體,得到一個完整的資料。而 lazy 關系是當屬性被通路到時才去加載相應實體,也就是才去執行相應的 SQL 查詢語句。

大部分場景下,當我們需要查詢關聯實體的資料時,都會通過 relations 屬性去指定,而非設定 eager,這樣更靈活一些。而 lazy 關系,TypeORM 文檔也指出了,延遲加載是非标準技術,也是實驗性質的,實際上很少使用。

繼續閱讀