TypeORM Node ORM

FreeGuideOnline 最新 2026-07-12

bash mkdir typeorm-tutorial cd typeorm-tutorial npm init -y npm install typeorm reflect-metadata @types/node --save npm install typescript ts-node --save-dev

初始化 TypeScript 配置文件:
```bash
npx tsc --init

确保 tsconfig.json 中开启 experimentalDecoratorsemitDecoratorMetadata

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

安装数据库驱动

npm install sqlite3 --save

配置数据库连接

创建数据源

在项目根目录创建 src/data-source.ts

import "reflect-metadata"
import { DataSource } from "typeorm"

export const AppDataSource = new DataSource({
    type: "sqlite",
    database: "database.sqlite",
    synchronize: true,      // 开发环境可开启,自动同步实体到数据库
    logging: false,
    entities: [__dirname + "/entity/*.ts"],
    migrations: [],
    subscribers: [],
})

需要手动修改 ormconfig.json 或使用如上代码形式。之后在应用入口初始化连接:

import { AppDataSource } from "./data-source"

AppDataSource.initialize()
    .then(() => console.log("数据库连接成功"))
    .catch((error) => console.log(error))

连接选项详解

  • type:数据库类型
  • host / port:数据库地址和端口(SQLite 不需要)
  • username / password:认证信息
  • database:数据库名或文件路径
  • synchronize:是否自动根据实体创建表结构(生产环境禁用)
  • logging:是否打印 SQL
  • entities:实体类路径

定义实体

实体概念

实体是映射到数据库表的类,使用装饰器声明列和关系。

第一个实体

创建 src/entity/User.ts

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

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

    @Column()
    name: string

    @Column()
    email: string
}

实体必须由 @Entity() 装饰,至少包含一个主键列。

常用装饰器

装饰器 作用
@Entity() 标记为数据库表
@Column() 普通列
@PrimaryColumn() 手动赋值主键
@PrimaryGeneratedColumn() 自增主键
@CreateDateColumn() 自动记录创建时间
@UpdateDateColumn() 自动记录更新时间

主键与自动生成列

@PrimaryGeneratedColumn("uuid")
id: string

可指定主键生成策略:"increment", "uuid" 等。

枚举与默认值

@Column({
    type: "varchar",
    default: "active"
})
status: string

@Column({
    type: "enum",
    enum: ["admin", "user"],
    default: "user"
})
role: string

实体的CRUD操作

使用EntityManager

import { AppDataSource } from "./data-source"
import { User } from "./entity/User"

const manager = AppDataSource.manager

// 新增
const user = new User()
user.name = "Alice"
user.email = "alice@example.com"
await manager.save(user)

// 查询
const users = await manager.find(User)

// 更新
user.name = "Alice Updated"
await manager.save(user)

// 删除
await manager.remove(user)

使用Repository

const userRepository = AppDataSource.getRepository(User)

const newUser = userRepository.create({ name: "Bob", email: "bob@test.com" })
await userRepository.save(newUser)

const bob = await userRepository.findOneBy({ name: "Bob" })
if (bob) {
    bob.email = "newbob@test.com"
    await userRepository.save(bob)
}

await userRepository.delete({ id: 1 })

高级查询

// 使用条件对象
const users = await userRepository.findBy({ role: "admin" })

// 使用find选项
const result = await userRepository.find({
    select: ["name", "email"],
    where: { name: "Alice" },
    order: { id: "DESC" },
    skip: 0,
    take: 10,
})

实体关系

一对一关系

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

    @Column()
    gender: string

    @OneToOne(() => User, user => user.profile)
    user: User
}

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

    @Column()
    name: string

    @OneToOne(() => Profile, profile => profile.user)
    @JoinColumn()
    profile: Profile
}

一对多 / 多对一关系

// 多对一(多个文章属于一个用户)
@Entity()
export class Post {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    title: string

    @ManyToOne(() => User, user => user.posts)
    user: User
}

// 一对多(一个用户有多篇文章)
@Entity()
export class User {
    // ...
    @OneToMany(() => Post, post => post.user)
    posts: Post[]
}

多对多关系

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

    @Column()
    name: string

    @ManyToMany(() => Question, question => question.categories)
    questions: Question[]
}

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

    @Column()
    text: string

    @ManyToMany(() => Category, category => category.questions)
    @JoinTable()
    categories: Category[]
}

级联操作

在关系装饰器中设置 cascade: true 可以在保存父实体时自动保存关联的实体。

@OneToMany(() => Post, post => post.user, { cascade: true })
posts: Post[]

查询构建器

基本用法

QueryBuilder 允许创建复杂 SQL 查询。

const posts = await AppDataSource.getRepository(Post)
    .createQueryBuilder("post")
    .leftJoinAndSelect("post.user", "user")
    .where("user.name = :name", { name: "Alice" })
    .getMany()

复杂查询示例

const users = await AppDataSource
    .getRepository(User)
    .createQueryBuilder("user")
    .select(["user.name", "COUNT(post.id)", "count"])
    .leftJoin("user.posts", "post")
    .groupBy("user.id")
    .having("count > 2")
    .getRawMany()

迁移

迁移简介

迁移用于版本化数据库架构变更,方便团队协作。TypeORM 可以自动根据实体变化生成迁移文件。

生成迁移

data-source 配置中指定迁移目录:

migrations: ["src/migration/*.ts"]

然后运行命令生成迁移:

npx typeorm migration:generate src/migration/Init -d src/data-source.ts

-d 指定数据源路径,Init 是迁移名称。

运行与回滚迁移

# 运行所有待处理的迁移
npx typeorm migration:run -d src/data-source.ts

# 回滚最近一次迁移
npx typeorm migration:revert -d src/data-source.ts

高级特性

监听器与订阅者

使用 @AfterInsert@BeforeUpdate 等装饰器添加生命周期钩子。

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

    @Column()
    name: string

    @BeforeInsert()
    beforeInsert() {
        console.log("即将插入用户:", this.name)
    }
}

事务

await AppDataSource.transaction(async (manager) => {
    await manager.save(user1)
    await manager.save(user2)
    // 任何错误都会自动回滚
})

也可使用 QueryRunner 手动管理。

索引与唯一约束

@Entity()
@Index(["name", "email"], { unique: true })
export class User {
    @Column({ unique: true })
    email: string
}

实战示例:构建博客系统

项目结构

src/
├── entity/
│   ├── User.ts
│   ├── Post.ts
│   └── Comment.ts
├── data-source.ts
└── index.ts

定义三个实体:User、Post、Comment。User 与 Post 是一对多,Post 与 Comment 是一对多。

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

    @Column()
    username: string

    @OneToMany(() => Post, post => post.author)
    posts: Post[]
}

// Post.ts
@Entity()
export class Post {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    title: string

    @Column("text")
    content: string

    @ManyToOne(() => User, user => user.posts)
    author: User

    @OneToMany(() => Comment, comment => comment.post)
    comments: Comment[]
}

// Comment.ts
@Entity()
export class Comment {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    text: string

    @ManyToOne(() => Post, post => post.comments)
    post: Post
}