前言
存在關聯關系的實體之間,在查詢一個實體時,加載另一個實體的方式有兩種,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 表的查詢:
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 表:
并且傳回給前端的資料,也隻有Author 實體:
當通路 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 表的資料:
設定 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 文檔也指出了,延遲加載是非标準技術,也是實驗性質的,實際上很少使用。