跳到主要内容

常见问题解答

如何更新数据库模式?

TypeORM 的主要职责之一是保持数据库表与实体同步。 有两种方式可以实现:

  • 在数据源选项中使用 synchronize: true

    import { DataSource } from "typeorm"

    const myDataSource = new DataSource({
    // ...
    synchronize: true,
    })

    该选项会在每次运行此代码时自动将数据库表与给定实体同步。 该选项非常适合开发环境,但在生产环境中通常不建议启用。

  • 使用命令行工具,手动执行模式同步:

    typeorm schema:sync

    此命令将执行模式同步。

模式同步非常快速。 如果你考虑在开发时因为性能问题禁用 synchronize 选项, 请先检查它的执行速度。

如何更改数据库中的列名?

默认情况下,列名是从属性名生成的。 你可以通过指定 name 列选项来轻松更改:

@Column({ name: "is_active" })
isActive: boolean;

如何将默认值设置为某个函数,例如 NOW()

default 列选项支持函数。 如果你传入一个返回字符串的函数, 将直接使用该字符串作为默认值,不会对其进行转义。 例如:

@Column({ default: () => "NOW()" })
date: Date;

如何进行验证?

验证不是 TypeORM 的一部分,因为验证是一个独立的过程, 并不直接关联 TypeORM 的职责。 如果需要使用验证,可以使用 class-validator —— 它与 TypeORM 完美配合。

关系中的“拥有方”是什么意思?为什么需要使用 @JoinColumn@JoinTable

one-to-one 关系开始。 假设有两个实体:UserPhoto

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

@Column()
name: string

@OneToOne()
photo: Photo
}
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number

@Column()
url: string

@OneToOne()
user: User
}

这个例子中没有 @JoinColumn,这是不正确的。 为什么?因为为了实现真正的关联,我们需要在数据库中创建一个列。 需要在 photo 表中创建 userId 列,或者在 user 表中创建 photoId 列。 但是应该创建哪个列呢 - userId 还是 photoId? TypeORM 无法自动决定。 要做出决定,必须在其中一方使用 @JoinColumn。 如果你在 Photo 中使用 @JoinColumn,则会在 photo 表中创建 userId 列。 如果在 User 中使用 @JoinColumn,则会在 user 表中创建 photoId 列。 带有 @JoinColumn 的一方被称为“拥有方(owner side)”。 另一方没有 @JoinColumn,称为“反向方(inverse,非拥有方)”。

@ManyToMany 关系中也是类似的。使用 @JoinTable 来标识关系的拥有方。

@ManyToOne@OneToMany 关系中,不需要 @JoinColumn, 因为两个装饰器本质不同,带有 @ManyToOne 装饰器的表会自动拥有关联列。

@JoinColumn@JoinTable 装饰器也可用于指定额外的 连接列或连接表的设置,比如连接列名或连接表名。

如何在多对多(关联)表中添加额外列?

无法在由多对多关系自动创建的表中添加额外列。 你需要创建一个单独的实体,并通过两个多对一关系与目标实体关联 (效果等同于创建一个多对多表), 然后在该实体中添加额外列。详情请参阅多对多关系

如何处理 TypeScript 编译器的 outDir 选项?

使用 outDir 编译选项时,别忘了将应用使用的资源和静态文件复制到输出目录, 否则需要确保这些资源的路径设置正确。

还有一点很重要:当你删除或移动实体时,输出目录中的旧实体文件不会被自动删除。 例如,你创建了一个 Post 实体并重命名为 Blog, 项目中不再存在 Post.ts,但 Post.js 文件依然留在输出目录里。 当 TypeORM 读取输出目录中的实体时,会同时看到 PostBlog 两个实体, 这可能导致错误。 因此,启用 outDir 时,建议在删除或移动实体后删除输出目录并重新编译项目。

如何在 ts-node 中使用 TypeORM?

你可以使用 ts-node 防止每次都编译文件。 使用 ts-node 时,可以在数据源配置中指定 ts 实体:

{
entities: [__dirname + "/entities/**/*{.js,.ts}"],
subscribers: [__dirname + "/subscribers/**/*{.js,.ts}"]
}

另外,如果你将 JavaScript 文件编译到和 TypeScript 文件同一目录, 请务必使用 outDir 选项以避免 这个问题

如果想使用 ts-node CLI,可以这样执行 TypeORM:

npx typeorm-ts-node-commonjs schema:sync

对于 ESM 项目,使用:

npx typeorm-ts-node-esm schema:sync

如何为后端使用 Webpack?

Webpack 可能会因缺失某些 require 语句(如 TypeORM 支持的所有驱动)而产生警告。 为抑制这些未用驱动的警告,你需要编辑 webpack 配置:

const FilterWarningsPlugin = require('webpack-filter-warnings-plugin');

module.exports = {
...
plugins: [
// 忽略不想使用的驱动。这是所有驱动的完整列表——想用哪个就移除对应的屏蔽。
new FilterWarningsPlugin({
exclude: [/mongodb/, /mssql/, /mysql2/, /oracledb/, /pg/, /pg-native/, /pg-query-stream/, /react-native-sqlite-storage/, /redis/, /sqlite3/, /sql.js/, /typeorm-aurora-data-api-driver/]
})
]
};

打包迁移文件

Webpack 默认会把所有东西打包成一个文件。 如果项目中有迁移文件,须在发布后执行,它们不能被全部打包进单一文件。 为确保所有 迁移文件 能被 TypeORM 识别和执行,需要对迁移文件单独使用“对象语法”配置 entry

const { globSync } = require("node:fs")
const path = require("node:path")

module.exports = {
// ... 你的webpack配置 ...
// 动态生成 { [name]: sourceFileName } 键值对用于 entry 选项
// 将 `src/db/migrations` 改为你迁移文件夹的相对路径
entry: globSync(path.resolve("src/db/migrations/*.ts")).reduce(
(entries, filename) => {
const migrationName = path.basename(filename, ".ts")
return Object.assign({}, entries, {
[migrationName]: filename,
})
},
{},
),
resolve: {
// 假设所有迁移文件都是 TypeScript
extensions: [".ts"],
},
output: {
// 改成你想放置编译后迁移文件的路径
path: __dirname + "/dist/db/migrations",
// 重点是使用 UMD(通用模块定义)格式
libraryTarget: "umd",
filename: "[name].js",
},
}

此外,自 Webpack 4 起,使用 mode: 'production' 时,会默认开启代码优化,包括混淆压缩,这会破坏 迁移文件,因为 TypeORM 依赖迁移文件名来判断执行状态。 你可以通过配置停用最小化:

module.exports = {
// ... 其他配置 ...
optimization: {
minimize: false,
},
}

或者,如果你使用 UglifyJsPlugin,可以让它保留类名和函数名:

const UglifyJsPlugin = require("uglifyjs-webpack-plugin")

module.exports = {
// ... 其他配置 ...
optimization: {
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
keep_classnames: true,
keep_fnames: true,
},
}),
],
},
}

最后,确保你的数据源配置包含了编译后的迁移文件路径:

// TypeORM 配置
module.exports = {
// ...
migrations: [__dirname + "/migrations/**/*{.js,.ts}"],
}

如何在 ESM 项目中使用 TypeORM?

确保在项目的 package.json 中添加 "type": "module",这样 TypeORM 才会使用 import(...) 语法导入文件。

为避免循环依赖问题,实体中关系属性的类型定义请使用 Relation 包装类型:

@Entity()
export class User {
@OneToOne(() => Profile, (profile) => profile.user)
profile: Relation<Profile>
}

这样可以防止属性类型在编译后的代码元数据中被保存,从而避免循环依赖。

由于关系类型已经通过 @OneToOne 装饰器定义,TypeScript 附加保存的类型元数据变得没有必要。

重要提示:不要在非关系列类型上使用 Relation