常见问题解答
- 如何更改数据库中的列名?
- 如何将某个函数(如
NOW()
)设置为默认值? - 如何进行验证?
- 关系中的 "owner side" 是什么意思?为什么我们需要使用
@JoinColumn
和@JoinTable
装饰器? - 如何将额外的列添加到多对多(交叉)表中?
- 如何处理 TypeScript 编译器选项中的 outDir?
- 如何在 ts-node 中使用 TypeORM?
- 如何在后端使用 Webpack?
- 如何在 ESM 项目中使用 TypeORM?
如何更新数据库模式?
TypeORM 的主要职责之一是使您的数据库表与实体保持同步。 有两种方法可以帮助您实现这一点:
在数据源选项中使用
synchronize: true
:import { DataSource } from "typeorm"
const myDataSource = new DataSource({
// ...
synchronize: true,
})每次运行此代码时,此选项会自动将数据库表与给定实体同步。 这个选项在开发过程中非常完美,但在生产环境中,您可能不希望启用此选项。
使用命令行工具并在命令行中手动运行模式同步:
typeorm schema:sync
该命令将执行模式同步。
模式同步非常快。 如果您考虑在开发过程中因性能问题而禁用同步选项, 请首先检查它的速度如何。
如何更改数据库中的列名?
默认情况下,列名是根据属性名生成的。
您可以通过指定 name
列选项简单地更改它:
@Column({ name: "is_active" })
isActive: boolean;
如何将某个函数(如 NOW()
)设置为默认值?
default
列选项支持函数。
如果您传递一个返回字符串的函数,
它将在不转义的情况下将该字符串用作默认值。
例如:
@Column({ default: () => "NOW()"})
date: Date;
如何进行验证?
验证不是 TypeORM 的一部分,因为验证是一个单独的过程,与 TypeORM 所做的事情没有太大关系。如果您想使用验证,可以使用 class-validator - 它与 TypeORM 完美配合。
关系中的 "owner side" 是什么意思?为什么我们需要使用 @JoinColumn
和 @JoinTable
?
让我们从 one-to-one
关系开始。假设我们有两个实体:User
和 Photo
:
@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
。
如果将@JoinColumn
放在Photo
中,则会在photo
表中创建一个名为userId
的列。
如果将@JoinColumn
放在User
中,则会在user
表中创建一个名为photoId
的列。
具有@JoinColumn
的一方将被称为“关系的拥有者一方”。
另一方没有@JoinColumn
,被称为“关系的反向(非拥有者)一方”。
在@ManyToMany
关系中也是如此。您使用@JoinTable
来显示关系的拥有者一方。
在@ManyToOne
或@OneToMany
关系中,由于两个修饰符不同,不需要@JoinColumn
,放置@ManyToOne
修饰符的表将具有关系列。
@JoinColumn
和@JoinTable
修饰符还可以用于指定附加的联接列/交叉表设置,如联接列名称或交叉表名称。
如何在多对多(交叉)表中添加额外的列?
无法在由多对多关系创建的表中添加额外的列。 您需要创建一个单独的实体,并使用两个多对一关系将其与目标实体绑定(效果与创建多对多表相同),然后在其中添加额外的列。您可以在多对多关系中了解更多信息。
如何处理outDir
TypeScript编译器选项?
当您使用outDir
编译器选项时,请不要忘记将应用程序使用的资源和资产复制到输出目录。
否则,请确保为这些资产设置正确的路径。
需要了解的一点是,当您删除或移动实体时,旧实体在输出目录中保持不变。
例如,您创建一个Post
实体并将其重命名为Blog
,项目中不再有Post.ts
。然而,Post.js
保留在输出目录中。
现在,当TypeORM从输出目录中读取实体时,它看到了两个实体——Post
和Blog
。
这可能是错误的来源。
这就是为什么在启用outDir
时删除和移动实体时强烈建议删除输出目录并重新编译项目。
如何在ts-node中使用TypeORM?
您可以使用ts-node避免每次编译文件。如果您使用ts-node,可以在数据源选项中指定ts
实体:
{
entities: ["src/entity/*.ts"],
subscribers: ["src/subscriber/*.ts"]
}
此外,如果您将js文件编译到与TypeScript文件相同的文件夹中,请确保使用outDir
编译器选项以防止此问题。
如果您想使用ts-node CLI,可以通过以下方式执行TypeORM:
npx typeorm-ts-node-commonjs schema:sync
对于ESM项目,请使用以下命令:
npx typeorm-ts-node-esm schema:sync
如何为后端使用Webpack?
由于Webpack认为所有TypeORM支持的驱动程序都缺少require语句,因此会产生警告。要消除对未使用驱动程序的警告,您需要编辑Webpack配置文件。
const FilterWarningsPlugin = require('webpack-filter-warnings-plugin');
module.exports = {
...
plugins: [
// 忽略您不想要的驱动程序。这是所有驱动程序的完整列表--删除您想要使用的驱动程序的抑制。
new FilterWarningsPlugin({
exclude: [/mongodb/, /mssql/, /mysql/, /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 glob = require("glob")
const path = require("path")
module.exports = {
// ... 您的Webpack配置在这里...
// 使用`entry`选项为`{ [name]: sourceFileName }`映射动态生成
// 将`src/db/migrations`更改为您的迁移文件夹的相对路径
entry: glob
.sync(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`以将转换后的迁移文件放在哪里。
path: __dirname + "/dist/db/migrations",
// 这很重要-我们希望迁移文件的UMD(通用模块定义)。
libraryTarget: "umd",
filename: "[name].js",
},
}
此外,自Webpack 4起,当使用mode: 'production'
时,默认优化文件,包括为了最小化文件大小而混淆代码。这会破坏迁移,因为TypeORM依赖它们的名称来确定已经执行了哪些操作。您可以通过添加以下内容来完全禁用最小化:
module.exports = {
// ... 其他Webpack配置在这里
optimization: {
minimize: false,
},
}
或者,如果您正在使用UglifyJsPlugin
,可以通过以下方式告诉它不更改类或函数名称:
const UglifyJsPlugin = require("uglifyjs-webpack-plugin")
module.exports = {
// ... 其他Webpack配置在这里
optimization: {
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
keep_classnames: true,
keep_fnames: true,
},
}),
],
},
}
最后,请确保在您的数据源选项中包含已转换的迁移文件:
// TypeORM配置
module.exports = {
// ...
migrations: [
// 这是生产模式下转换的迁移文件的相对路径
"db/migrations/**/*.js",
// 您的源迁移文件,在开发模式下使用
"src/db/migrations/**/*.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
。