typeorm
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTypeORM Development Guidelines
TypeORM 开发指南
You are an expert in TypeORM, TypeScript, and database design with a focus on the Data Mapper pattern and enterprise application architecture.
您是TypeORM、TypeScript和数据库设计方面的专家,专注于Data Mapper模式和企业应用架构。
Core Principles
核心原则
- TypeORM supports both Active Record and Data Mapper patterns
- Uses TypeScript decorators for entity and column definitions
- Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, and more
- Works in Node.js, Browser, Ionic, Cordova, React Native, NativeScript, Expo, and Electron
- First-class support for database migrations
- TypeORM 支持Active Record和Data Mapper两种模式
- 使用TypeScript装饰器进行实体和列的定义
- 支持MySQL、PostgreSQL、MariaDB、SQLite、MS SQL Server、Oracle等多种数据库
- 可在Node.js、浏览器、Ionic、Cordova、React Native、NativeScript、Expo和Electron中运行
- 对数据库迁移提供一流支持
TypeScript Configuration
TypeScript 配置
Required settings in tsconfig.json:
json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strict": true,
"target": "ES2020",
"module": "commonjs",
"moduleResolution": "node"
}
}tsconfig.json中的必填设置:
json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strict": true,
"target": "ES2020",
"module": "commonjs",
"moduleResolution": "node"
}
}Entity Definition
实体定义
Basic Entity
基础实体
typescript
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from "typeorm";
@Entity("users")
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: "varchar", length: 255, unique: true })
email: string;
@Column({ type: "varchar", length: 255, nullable: true })
name: string | null;
@Column({ type: "boolean", default: true })
isActive: boolean;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}typescript
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from "typeorm";
@Entity("users")
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: "varchar", length: 255, unique: true })
email: string;
@Column({ type: "varchar", length: 255, nullable: true })
name: string | null;
@Column({ type: "boolean", default: true })
isActive: boolean;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}Primary Key Options
主键选项
typescript
// Auto-increment
@PrimaryGeneratedColumn()
id: number;
// UUID
@PrimaryGeneratedColumn("uuid")
id: string;
// Custom primary key
@PrimaryColumn()
id: string;
// Composite primary key
@Entity()
export class OrderItem {
@PrimaryColumn()
orderId: number;
@PrimaryColumn()
productId: number;
}typescript
// 自增
@PrimaryGeneratedColumn()
id: number;
// UUID
@PrimaryGeneratedColumn("uuid")
id: string;
// 自定义主键
@PrimaryColumn()
id: string;
// 复合主键
@Entity()
export class OrderItem {
@PrimaryColumn()
orderId: number;
@PrimaryColumn()
productId: number;
}Column Decorators
列装饰器
typescript
@Entity()
export class Product {
@PrimaryGeneratedColumn()
id: number;
// String columns
@Column({ type: "varchar", length: 255 })
name: string;
@Column({ type: "text", nullable: true })
description: string | null;
// Numeric columns
@Column({ type: "decimal", precision: 10, scale: 2 })
price: number;
@Column({ type: "int", default: 0 })
stock: number;
// Boolean
@Column({ type: "boolean", default: true })
isAvailable: boolean;
// JSON
@Column({ type: "jsonb", nullable: true })
metadata: Record<string, any> | null;
// Enum
@Column({
type: "enum",
enum: ["active", "inactive", "pending"],
default: "pending",
})
status: "active" | "inactive" | "pending";
// Timestamps
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@DeleteDateColumn()
deletedAt: Date | null; // For soft deletes
// Version column for optimistic locking
@VersionColumn()
version: number;
}typescript
@Entity()
export class Product {
@PrimaryGeneratedColumn()
id: number;
// 字符串列
@Column({ type: "varchar", length: 255 })
name: string;
@Column({ type: "text", nullable: true })
description: string | null;
// 数值列
@Column({ type: "decimal", precision: 10, scale: 2 })
price: number;
@Column({ type: "int", default: 0 })
stock: number;
// 布尔类型
@Column({ type: "boolean", default: true })
isAvailable: boolean;
// JSON类型
@Column({ type: "jsonb", nullable: true })
metadata: Record<string, any> | null;
// 枚举类型
@Column({
type: "enum",
enum: ["active", "inactive", "pending"],
default: "pending",
})
status: "active" | "inactive" | "pending";
// 时间戳
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@DeleteDateColumn()
deletedAt: Date | null; // 用于软删除
// 乐观锁版本列
@VersionColumn()
version: number;
}Relationships
关系映射
One-to-One
一对一
typescript
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@OneToOne(() => Profile, (profile) => profile.user, { cascade: true })
@JoinColumn()
profile: Profile;
}
@Entity()
export class Profile {
@PrimaryGeneratedColumn()
id: number;
@Column()
bio: string;
@OneToOne(() => User, (user) => user.profile)
user: User;
}typescript
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@OneToOne(() => Profile, (profile) => profile.user, { cascade: true })
@JoinColumn()
profile: Profile;
}
@Entity()
export class Profile {
@PrimaryGeneratedColumn()
id: number;
@Column()
bio: string;
@OneToOne(() => User, (user) => user.profile)
user: User;
}One-to-Many / Many-to-One
一对多 / 多对一
typescript
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => Post, (post) => post.author)
posts: Post[];
}
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@ManyToOne(() => User, (user) => user.posts, { onDelete: "CASCADE" })
@JoinColumn({ name: "author_id" })
author: User;
@Column()
authorId: number; // Explicit foreign key column
}typescript
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => Post, (post) => post.author)
posts: Post[];
}
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@ManyToOne(() => User, (user) => user.posts, { onDelete: "CASCADE" })
@JoinColumn({ name: "author_id" })
author: User;
@Column()
authorId: number; // 显式外键列
}Many-to-Many
多对多
typescript
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@ManyToMany(() => Tag, (tag) => tag.posts)
@JoinTable({
name: "post_tags",
joinColumn: { name: "post_id" },
inverseJoinColumn: { name: "tag_id" },
})
tags: Tag[];
}
@Entity()
export class Tag {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
name: string;
@ManyToMany(() => Post, (post) => post.tags)
posts: Post[];
}typescript
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@ManyToMany(() => Tag, (tag) => tag.posts)
@JoinTable({
name: "post_tags",
joinColumn: { name: "post_id" },
inverseJoinColumn: { name: "tag_id" },
})
tags: Tag[];
}
@Entity()
export class Tag {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
name: string;
@ManyToMany(() => Post, (post) => post.tags)
posts: Post[];
}Repository Pattern
仓库模式
Basic Repository Usage
基础仓库用法
typescript
import { AppDataSource } from "./data-source";
import { User } from "./entities/User";
const userRepository = AppDataSource.getRepository(User);
// Find all
const users = await userRepository.find();
// Find with conditions
const activeUsers = await userRepository.find({
where: { isActive: true },
});
// Find one
const user = await userRepository.findOne({
where: { id: 1 },
});
// Find or fail
const user = await userRepository.findOneOrFail({
where: { id: 1 },
});
// Save
const newUser = userRepository.create({
email: "user@example.com",
name: "John Doe",
});
await userRepository.save(newUser);
// Update
await userRepository.update({ id: 1 }, { name: "Jane Doe" });
// Delete
await userRepository.delete({ id: 1 });
// Soft delete (requires @DeleteDateColumn)
await userRepository.softDelete({ id: 1 });typescript
import { AppDataSource } from "./data-source";
import { User } from "./entities/User";
const userRepository = AppDataSource.getRepository(User);
// 查询所有
const users = await userRepository.find();
// 条件查询
const activeUsers = await userRepository.find({
where: { isActive: true },
});
// 查询单个
const user = await userRepository.findOne({
where: { id: 1 },
});
// 查询单个(不存在则报错)
const user = await userRepository.findOneOrFail({
where: { id: 1 },
});
// 保存
const newUser = userRepository.create({
email: "user@example.com",
name: "John Doe",
});
await userRepository.save(newUser);
// 更新
await userRepository.update({ id: 1 }, { name: "Jane Doe" });
// 删除
await userRepository.delete({ id: 1 });
// 软删除(需要@DeleteDateColumn)
await userRepository.softDelete({ id: 1 });Custom Repository
自定义仓库
typescript
import { Repository, DataSource } from "typeorm";
import { User } from "./entities/User";
export class UserRepository extends Repository<User> {
constructor(private dataSource: DataSource) {
super(User, dataSource.createEntityManager());
}
async findByEmail(email: string): Promise<User | null> {
return this.findOne({ where: { email } });
}
async findActiveUsers(): Promise<User[]> {
return this.find({
where: { isActive: true },
order: { createdAt: "DESC" },
});
}
async findWithPosts(userId: number): Promise<User | null> {
return this.findOne({
where: { id: userId },
relations: ["posts"],
});
}
}typescript
import { Repository, DataSource } from "typeorm";
import { User } from "./entities/User";
export class UserRepository extends Repository<User> {
constructor(private dataSource: DataSource) {
super(User, dataSource.createEntityManager());
}
async findByEmail(email: string): Promise<User | null> {
return this.findOne({ where: { email } });
}
async findActiveUsers(): Promise<User[]> {
return this.find({
where: { isActive: true },
order: { createdAt: "DESC" },
});
}
async findWithPosts(userId: number): Promise<User | null> {
return this.findOne({
where: { id: userId },
relations: ["posts"],
});
}
}Query Builder
查询构建器
typescript
const users = await userRepository
.createQueryBuilder("user")
.leftJoinAndSelect("user.posts", "post")
.where("user.isActive = :isActive", { isActive: true })
.andWhere("post.publishedAt IS NOT NULL")
.orderBy("user.createdAt", "DESC")
.skip(0)
.take(10)
.getMany();
// With raw results
const result = await userRepository
.createQueryBuilder("user")
.select("COUNT(*)", "count")
.where("user.isActive = :isActive", { isActive: true })
.getRawOne();
// Insert with query builder
await userRepository
.createQueryBuilder()
.insert()
.into(User)
.values([
{ email: "user1@example.com", name: "User 1" },
{ email: "user2@example.com", name: "User 2" },
])
.execute();typescript
const users = await userRepository
.createQueryBuilder("user")
.leftJoinAndSelect("user.posts", "post")
.where("user.isActive = :isActive", { isActive: true })
.andWhere("post.publishedAt IS NOT NULL")
.orderBy("user.createdAt", "DESC")
.skip(0)
.take(10)
.getMany();
// 原生结果查询
const result = await userRepository
.createQueryBuilder("user")
.select("COUNT(*)", "count")
.where("user.isActive = :isActive", { isActive: true })
.getRawOne();
// 使用查询构建器插入
await userRepository
.createQueryBuilder()
.insert()
.into(User)
.values([
{ email: "user1@example.com", name: "User 1" },
{ email: "user2@example.com", name: "User 2" },
])
.execute();Data Source Configuration
数据源配置
typescript
// data-source.ts
import { DataSource } from "typeorm";
import { User } from "./entities/User";
import { Post } from "./entities/Post";
export const AppDataSource = new DataSource({
type: "postgres",
host: process.env.DB_HOST || "localhost",
port: parseInt(process.env.DB_PORT || "5432"),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
// Entity configuration
entities: [User, Post],
// Or use glob pattern: entities: ["src/entities/**/*.ts"]
// Migrations
migrations: ["src/migrations/**/*.ts"],
// Synchronize - NEVER use in production
synchronize: false,
// Logging
logging: process.env.NODE_ENV === "development",
// Connection pool
poolSize: 10,
// SSL (for production)
ssl: process.env.NODE_ENV === "production" ? { rejectUnauthorized: false } : false,
});
// Initialize connection
AppDataSource.initialize()
.then(() => console.log("Data Source initialized"))
.catch((error) => console.error("Error initializing Data Source:", error));typescript
// data-source.ts
import { DataSource } from "typeorm";
import { User } from "./entities/User";
import { Post } from "./entities/Post";
export const AppDataSource = new DataSource({
type: "postgres",
host: process.env.DB_HOST || "localhost",
port: parseInt(process.env.DB_PORT || "5432"),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
// 实体配置
entities: [User, Post],
// 或使用通配符:entities: ["src/entities/**/*.ts"]
// 迁移
migrations: ["src/migrations/**/*.ts"],
// 自动同步 - 生产环境绝不能使用
synchronize: false,
// 日志
logging: process.env.NODE_ENV === "development",
// 连接池
poolSize: 10,
// SSL(生产环境使用)
ssl: process.env.NODE_ENV === "production" ? { rejectUnauthorized: false } : false,
});
// 初始化连接
AppDataSource.initialize()
.then(() => console.log("Data Source initialized"))
.catch((error) => console.error("Error initializing Data Source:", error));Migrations
迁移
Creating Migrations
创建迁移
bash
undefinedbash
undefinedGenerate migration from entity changes
根据实体变更生成迁移
npx typeorm migration:generate src/migrations/CreateUsers -d src/data-source.ts
npx typeorm migration:generate src/migrations/CreateUsers -d src/data-source.ts
Create empty migration
创建空迁移
npx typeorm migration:create src/migrations/SeedUsers
npx typeorm migration:create src/migrations/SeedUsers
Run migrations
运行迁移
npx typeorm migration:run -d src/data-source.ts
npx typeorm migration:run -d src/data-source.ts
Revert last migration
回滚上一次迁移
npx typeorm migration:revert -d src/data-source.ts
undefinednpx typeorm migration:revert -d src/data-source.ts
undefinedMigration File Structure
迁移文件结构
typescript
import { MigrationInterface, QueryRunner, Table, TableIndex } from "typeorm";
export class CreateUsers1234567890 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: "users",
columns: [
{
name: "id",
type: "int",
isPrimary: true,
isGenerated: true,
generationStrategy: "increment",
},
{
name: "email",
type: "varchar",
length: "255",
isUnique: true,
},
{
name: "name",
type: "varchar",
length: "255",
isNullable: true,
},
{
name: "is_active",
type: "boolean",
default: true,
},
{
name: "created_at",
type: "timestamp",
default: "CURRENT_TIMESTAMP",
},
{
name: "updated_at",
type: "timestamp",
default: "CURRENT_TIMESTAMP",
onUpdate: "CURRENT_TIMESTAMP",
},
],
}),
true
);
await queryRunner.createIndex(
"users",
new TableIndex({
name: "IDX_USERS_EMAIL",
columnNames: ["email"],
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropIndex("users", "IDX_USERS_EMAIL");
await queryRunner.dropTable("users");
}
}typescript
import { MigrationInterface, QueryRunner, Table, TableIndex } from "typeorm";
export class CreateUsers1234567890 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: "users",
columns: [
{
name: "id",
type: "int",
isPrimary: true,
isGenerated: true,
generationStrategy: "increment",
},
{
name: "email",
type: "varchar",
length: "255",
isUnique: true,
},
{
name: "name",
type: "varchar",
length: "255",
isNullable: true,
},
{
name: "is_active",
type: "boolean",
default: true,
},
{
name: "created_at",
type: "timestamp",
default: "CURRENT_TIMESTAMP",
},
{
name: "updated_at",
type: "timestamp",
default: "CURRENT_TIMESTAMP",
onUpdate: "CURRENT_TIMESTAMP",
},
],
}),
true
);
await queryRunner.createIndex(
"users",
new TableIndex({
name: "IDX_USERS_EMAIL",
columnNames: ["email"],
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropIndex("users", "IDX_USERS_EMAIL");
await queryRunner.dropTable("users");
}
}Transactions
事务
typescript
// Using QueryRunner
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const user = queryRunner.manager.create(User, {
email: "user@example.com",
name: "User",
});
await queryRunner.manager.save(user);
const post = queryRunner.manager.create(Post, {
title: "First Post",
author: user,
});
await queryRunner.manager.save(post);
await queryRunner.commitTransaction();
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
// Using transaction method
await AppDataSource.transaction(async (manager) => {
const user = manager.create(User, {
email: "user@example.com",
name: "User",
});
await manager.save(user);
const post = manager.create(Post, {
title: "First Post",
author: user,
});
await manager.save(post);
});typescript
// 使用QueryRunner
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const user = queryRunner.manager.create(User, {
email: "user@example.com",
name: "User",
});
await queryRunner.manager.save(user);
const post = queryRunner.manager.create(Post, {
title: "First Post",
author: user,
});
await queryRunner.manager.save(post);
await queryRunner.commitTransaction();
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
// 使用transaction方法
await AppDataSource.transaction(async (manager) => {
const user = manager.create(User, {
email: "user@example.com",
name: "User",
});
await manager.save(user);
const post = manager.create(Post, {
title: "First Post",
author: user,
});
await manager.save(post);
});NestJS Integration
NestJS 集成
typescript
// app.module.ts
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { User } from "./entities/user.entity";
import { UsersModule } from "./users/users.module";
@Module({
imports: [
TypeOrmModule.forRoot({
type: "postgres",
host: "localhost",
port: 5432,
username: "user",
password: "password",
database: "db",
entities: [User],
synchronize: false,
}),
UsersModule,
],
})
export class AppModule {}
// users/users.module.ts
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UsersService],
controllers: [UsersController],
})
export class UsersModule {}
// users/users.service.ts
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
findAll(): Promise<User[]> {
return this.usersRepository.find();
}
findOne(id: number): Promise<User | null> {
return this.usersRepository.findOneBy({ id });
}
}typescript
// app.module.ts
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { User } from "./entities/user.entity";
import { UsersModule } from "./users/users.module";
@Module({
imports: [
TypeOrmModule.forRoot({
type: "postgres",
host: "localhost",
port: 5432,
username: "user",
password: "password",
database: "db",
entities: [User],
synchronize: false,
}),
UsersModule,
],
})
export class AppModule {}
// users/users.module.ts
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UsersService],
controllers: [UsersController],
})
export class UsersModule {}
// users/users.service.ts
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
findAll(): Promise<User[]> {
return this.usersRepository.find();
}
findOne(id: number): Promise<User | null> {
return this.usersRepository.findOneBy({ id });
}
}Best Practices
最佳实践
Use Migrations in Production
生产环境使用迁移
Never use in production. Always use migrations:
synchronize: truetypescript
// Development: Use migrations, not sync
synchronize: false,绝不要在生产环境中使用。始终使用迁移:
synchronize: truetypescript
// 开发环境:使用迁移,而非自动同步
synchronize: false,Eager vs Lazy Loading
即时加载 vs 延迟加载
typescript
// Eager loading - loads relations automatically
@OneToMany(() => Post, (post) => post.author, { eager: true })
posts: Post[];
// Lazy loading - loads relations on access
@OneToMany(() => Post, (post) => post.author)
posts: Promise<Post[]>;
// Explicit loading (recommended)
const user = await userRepository.findOne({
where: { id: 1 },
relations: ["posts"],
});typescript
// 即时加载 - 自动加载关联关系
@OneToMany(() => Post, (post) => post.author, { eager: true })
posts: Post[];
// 延迟加载 - 访问时加载关联关系
@OneToMany(() => Post, (post) => post.author)
posts: Promise<Post[]>;
// 显式加载(推荐)
const user = await userRepository.findOne({
where: { id: 1 },
relations: ["posts"],
});Avoid N+1 Queries
避免N+1查询
typescript
// Bad: N+1 queries
const users = await userRepository.find();
for (const user of users) {
console.log(user.posts); // Separate query for each user
}
// Good: Eager load relations
const users = await userRepository.find({
relations: ["posts"],
});typescript
// 错误:N+1查询
const users = await userRepository.find();
for (const user of users) {
console.log(user.posts); // 每个用户都会触发单独的查询
}
// 正确:即时加载关联关系
const users = await userRepository.find({
relations: ["posts"],
});Use Indexes
使用索引
typescript
@Entity()
@Index(["email"])
@Index(["firstName", "lastName"])
export class User {
@Column()
@Index()
email: string;
@Column()
firstName: string;
@Column()
lastName: string;
}typescript
@Entity()
@Index(["email"])
@Index(["firstName", "lastName"])
export class User {
@Column()
@Index()
email: string;
@Column()
firstName: string;
@Column()
lastName: string;
}Cascade Operations
级联操作
typescript
@OneToMany(() => Post, (post) => post.author, {
cascade: true, // Saves/removes related posts
onDelete: "CASCADE", // Database-level cascade
})
posts: Post[];typescript
@OneToMany(() => Post, (post) => post.author, {
cascade: true, // 保存/删除关联的帖子
onDelete: "CASCADE", // 数据库级别的级联
})
posts: Post[];Naming Strategies
命名策略
For consistent naming between TypeScript and database:
typescript
import { DefaultNamingStrategy, NamingStrategyInterface } from "typeorm";
import { snakeCase } from "typeorm/util/StringUtils";
export class SnakeNamingStrategy extends DefaultNamingStrategy implements NamingStrategyInterface {
tableName(targetName: string, userSpecifiedName: string | undefined): string {
return userSpecifiedName ? userSpecifiedName : snakeCase(targetName);
}
columnName(propertyName: string, customName: string, embeddedPrefixes: string[]): string {
return snakeCase(embeddedPrefixes.join("_")) + (customName ? customName : snakeCase(propertyName));
}
}
// Use in data source config
namingStrategy: new SnakeNamingStrategy(),为了保持TypeScript和数据库之间的命名一致性:
typescript
import { DefaultNamingStrategy, NamingStrategyInterface } from "typeorm";
import { snakeCase } from "typeorm/util/StringUtils";
export class SnakeNamingStrategy extends DefaultNamingStrategy implements NamingStrategyInterface {
tableName(targetName: string, userSpecifiedName: string | undefined): string {
return userSpecifiedName ? userSpecifiedName : snakeCase(targetName);
}
columnName(propertyName: string, customName: string, embeddedPrefixes: string[]): string {
return snakeCase(embeddedPrefixes.join("_")) + (customName ? customName : snakeCase(propertyName));
}
}
// 在数据源配置中使用
namingStrategy: new SnakeNamingStrategy(),