多数据源、数据库、模式和复制设置
使用多个数据源
要使用多个连接到不同数据库的数据源,只需创建多个 DataSource 实例:
import { DataSource } from "typeorm"
const db1DataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "admin",
database: "db1",
entities: [__dirname + "/entities/*{.js,.ts}"],
synchronize: true,
})
const db2DataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "admin",
database: "db2",
entities: [__dirname + "/entities/*{.js,.ts}"],
synchronize: true,
})
在单个数据源中使用多个数据库
要在单个数据源中使用多个数据库, 可以在每个实体中指定数据库名称:
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity({ database: "secondDB" })
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
firstName: string
@Column()
lastName: string
}
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity({ database: "thirdDB" })
export class Photo {
@PrimaryGeneratedColumn()
id: number
@Column()
url: string
}
User 实体会创建在 secondDB 数据库中,Photo 实体会创建在 thirdDB 数据库中。
所有其他实体会在数据源选项中定义的默认数据库中创建。
如果想从不同的数据库中查询数据,只需提供实体即可:
const users = await dataSource
.createQueryBuilder()
.select()
.from(User, "user")
.addFrom(Photo, "photo")
.andWhere("photo.userId = user.id")
.getMany() // userId 不是外键,因为这是跨数据库请求
此代码会生成如下 SQL 查询(根据数据库类型不同):
SELECT * FROM "secondDB"."user" "user", "thirdDB"."photo" "photo"
WHERE "photo"."userId" = "user"."id"
你也可以指定表路径而不是实体:
const users = await dataSource
.createQueryBuilder()
.select()
.from("secondDB.user", "user")
.addFrom("thirdDB.photo", "photo")
.andWhere("photo.userId = user.id")
.getMany() // userId 不是外键,因为这是跨数据库请求
此功能仅在 MySQL 和 MSSQL 数据库中支持。
在单个数据源中使用多个模式(schema)
要在应用中使用多个模式,只需在每个实体上设置 schema:
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity({ schema: "secondSchema" })
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
firstName: string
@Column()
lastName: string
}
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity({ schema: "thirdSchema" })
export class Photo {
@PrimaryGeneratedColumn()
id: number
@Column()
url: string
}
User 实体将创建在 secondSchema 模式中,Photo 实体将创建在 thirdSchema 模式中。
其他所有实体会在数据源选项中定义的默认数据库中创建。
如果想从不同模式中查询数据,只需提供实体:
const users = await dataSource
.createQueryBuilder()
.select()
.from(User, "user")
.addFrom(Photo, "photo")
.andWhere("photo.userId = user.id")
.getMany() // userId 不是外键,因为这是跨数据库请求
此代码会生成如下 SQL 查询(根据数据库类型不同):
SELECT * FROM "secondSchema"."question" "question", "thirdSchema"."photo" "photo"
WHERE "photo"."userId" = "user"."id"
你也可以指定表路径而非实体:
const users = await dataSource
.createQueryBuilder()
.select()
.from("secondSchema.user", "user") // 在mssql中你甚至可以指定数据库:secondDB.secondSchema.user
.addFrom("thirdSchema.photo", "photo") // 在mssql中你甚至可以指定数据库:thirdDB.thirdSchema.photo
.andWhere("photo.userId = user.id")
.getMany()
此功能仅在 Postgres 和 MSSQL 数据库中支持。 在 MSSQL 中,你也可以结合使用模式和数据库,例如:
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity({ database: "secondDB", schema: "public" })
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
firstName: string
@Column()
lastName: string
}
复制(Replication)
你可以使用 TypeORM 设置读写复制。 复制配置示例:
const datasource = new DataSource({
type: "mysql",
logging: true,
replication: {
master: {
host: "server1",
port: 3306,
username: "test",
password: "test",
database: "test",
},
slaves: [
{
host: "server2",
port: 3306,
username: "test",
password: "test",
database: "test",
},
{
host: "server3",
port: 3306,
username: "test",
password: "test",
database: "test",
},
],
},
})
定义复制从库(slaves)后,TypeORM 默认会将所有可能的查询发送到从库。
- 通过
find方法或SelectQueryBuilder执行的所有查询将使用随机的slave实例 - 所有通过
update、create、InsertQueryBuilder、UpdateQueryBuilder等执行的写操作将使用master实例 - 通过调用
.query()执行的所有原始查询将使用master实例 - 所有模式更新操作都使用
master实例执行
明确选择查询目标
默认情况下,TypeORM 会将所有读取查询都发送到随机的读从库,写入操作发送到主库。这意味着当你首次在配置中添加 replication 设置后,任何现有的不显式指定复制模式的读取查询都会开始访问从库。这有利于扩展,但如果某些查询必须返回最新数据,则需要在创建查询运行器时显式传入复制模式。
如果你想明确使用 master 进行读取查询,创建 QueryRunner 时传入明确的 ReplicationMode:
const masterQueryRunner = dataSource.createQueryRunner("master")
try {
const postsFromMaster = await dataSource
.createQueryBuilder(Post, "post", masterQueryRunner) // 你可以作为可选参数传递 QueryRunner 给查询构造器
.setQueryRunner(masterQueryRunner) // 或使用 setQueryRunner 设置或覆盖查询构造器的 QueryRunner
.getMany()
} finally {
await masterQueryRunner.release()
}
如果你想在原始查询中使用从库,创建 QueryRunner 时传入 "slave":
const slaveQueryRunner = dataSource.createQueryRunner("slave")
try {
const userFromSlave = await slaveQueryRunner.query(
"SELECT * FROM users WHERE id = $1",
[userId],
slaveQueryRunner,
)
} finally {
return slaveQueryRunner.release()
}
注意:手动创建的 QueryRunner 实例必须显式释放。如果不释放,查询运行器会一直占用一个数据库连接,阻止其他查询使用该连接。
调整读取查询默认目标
如果不想让所有读取都默认发送到 slave 实例,可以在复制配置中传入 defaultMode: "master" 来更改默认读取目标:
const datasource = new DataSource({
type: "mysql",
logging: true,
replication: {
// 设置读取查询默认目标为主库
defaultMode: "master",
master: {
host: "server1",
port: 3306,
username: "test",
password: "test",
database: "test",
},
slaves: [
{
host: "server2",
port: 3306,
username: "test",
password: "test",
database: "test",
},
],
},
})
在此模式下,默认不会将查询发送给读从库,你必须显式使用 .createQueryRunner("slave") 调用,才能访问从库。
如果你是首次在现有应用中添加复制配置,这是保证初始行为不变的好选项,之后可以逐步在每个查询运行器上使用读副本。
支持的驱动
复制支持 MySQL、PostgreSQL、SQL Server、Cockroach、Oracle 和 Spanner 连接驱动。
MySQL 复制支持附加配置选项:
{
replication: {
master: {
host: "server1",
port: 3306,
username: "test",
password: "test",
database: "test"
},
slaves: [{
host: "server2",
port: 3306,
username: "test",
password: "test",
database: "test"
}, {
host: "server3",
port: 3306,
username: "test",
password: "test",
database: "test"
}],
/**
* 如果为 true,在连接失败时 PoolCluster 会尝试重新连接。(默认:true)
*/
canRetry: true,
/**
* 如果连接失败,node 的 errorCount 增加。
* 当 errorCount 大于 removeNodeErrorCount 时,会移除 PoolCluster 中的该节点。(默认:5)
*/
removeNodeErrorCount: 5,
/**
* 如果连接失败,指定毫秒数后尝试重新连接。
* 如果设置为 0,则节点将被移除且不会再重用。(默认:0)
*/
restoreNodeTimeout: 0,
/**
* 确定如何选择从库:
* RR: 轮询选择。
* RANDOM: 随机选择节点。
* ORDER: 无条件选择第一个可用节点。
*/
selector: "RR"
}
}