跳至主内容区

常见问题解答

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

如何更新数据库表结构?

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 的职责范围,因为验证是与 ORM 功能无关的独立流程。如需验证功能,请使用 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 中使用 @JoinColumnphoto 表创建 userId
  • User 中使用 @JoinColumnuser 表创建 photoId

使用 @JoinColumn 的一侧称为“关系的拥有方”,而另一侧(未使用 @JoinColumn)则称为“关系的反向方(非拥有方)”。

多对多(@ManyToMany)关系同理,使用 @JoinTable 标注关系的拥有方。

@ManyToOne@OneToMany 关系中,不需要 @JoinColumn,因为:

  • 这两个装饰器功能不同
  • 添加 @ManyToOne 装饰器的表会自动创建关系列

@JoinColumn@JoinTable 装饰器还可用于指定额外的联结列/联结表配置,如列名或表名。

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

无法直接在多对多关系自动生成的联结表中添加额外列。解决方案是:

  1. 创建独立实体
  2. 通过两个多对一关系连接目标实体(效果等同于多对多表)
  3. 在该实体中添加额外列

详见多对多关系文档

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

当使用 outDir 编译器选项时,请确保将应用程序使用的静态资源和依赖文件复制到输出目录。否则,请确保为这些资源设置了正确的访问路径。

需要特别注意:当你删除或移动实体文件时,旧实体文件仍会保留在输出目录中。例如,将 Post 实体重命名为 Blog 后,项目中不再有 Post.ts 文件,但输出目录中仍会存在 Post.js。当 TypeORM 从输出目录读取实体时,会同时识别 PostBlog 两个实体,这可能导致错误。因此,在启用 outDir 的情况下修改实体时,强烈建议先删除整个输出目录再重新编译项目。

如何结合 ts-node 使用 TypeORM?

使用 ts-node 可以避免每次编译文件。在数据源配置中指定 ts 格式的实体路径即可:

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

另外,若将编译后的 js 文件输出到与 TypeScript 源文件相同的目录,请务必配置 outDir 编译器选项以避免此问题

若要通过 ts-node CLI 运行 TypeORM,可使用以下命令:

npx typeorm-ts-node-commonjs schema:sync

对于 ESM 项目,请改用以下命令:

npx typeorm-ts-node-esm schema:sync

对于 ESM 项目请改用:

Webpack 会因检测到未使用的 TypeORM 驱动程序而生成警告。要消除这些未使用驱动程序的警告,需要修改 webpack 配置文件。

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

module.exports = {
...
plugins: [
// ignore the drivers you don't want. This is the complete list of all drivers -- remove the suppressions for drivers you want to use.
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 会尝试将所有文件打包成单个文件。当项目包含迁移文件(这些文件需要在打包代码部署生产环境后执行)时,这会导致问题。为确保所有migrations都能被 TypeORM 识别并执行,您可能需要为迁移文件单独配置 entry 的"对象语法"。

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

module.exports = {
// ... your webpack configurations here...
// Dynamically generate a `{ [name]: sourceFileName }` map for the `entry` option
// change `src/db/migrations` to the relative path to your migrations folder
entry: globSync(path.resolve("src/db/migrations/*.ts")).reduce(
(entries, filename) => {
const migrationName = path.basename(filename, ".ts")
return Object.assign({}, entries, {
[migrationName]: filename,
})
},
{},
),
resolve: {
// assuming all your migration files are written in TypeScript
extensions: [".ts"],
},
output: {
// change `path` to where you want to put transpiled migration files.
path: __dirname + "/dist/db/migrations",
// this is important - we want UMD (Universal Module Definition) for migration files.
libraryTarget: "umd",
filename: "[name].js",
},
}

此外,从 Webpack 4 开始,当使用 mode: 'production' 时默认会启用文件优化(包括代码混淆以减小文件体积)。这将导致migrations失效,因为 TypeORM 依赖迁移文件名判断执行状态。您可通过添加以下配置完全禁用压缩优化:

module.exports = {
// ... other Webpack configurations here
optimization: {
minimize: false,
},
}

另外,如果您正在使用 UglifyJsPlugin,可以按如下方式配置使其不更改类名或函数名:

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

module.exports = {
// ... other Webpack configurations here
optimization: {
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
keep_classnames: true,
keep_fnames: true,
},
}),
],
},
}

最后,请确保在你的数据源选项中包含转译后的迁移文件:

// TypeORM Configurations
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 包装