TypeORM
TypeORM 是一个面向对象的关系型数据库ORM框架,用于在 Node.js 应用程序中操作数据库。它支持多种数据库,包括 MySQL,PostgreSQL,SQLite,以及 Microsoft SQL Server 等。TypeORM 提供了使用 TypeScript 的完整ORM解决方案,它的主要目标是简化数据库操作,提高开发效率。

查看更多相关内容
TypeORM 与 Prisma、Sequelize、MikroORM 等其他 ORM 框架的对比分析选择合适的 ORM 框架对于项目成功至关重要。本文将详细对比 TypeORM 与其他主流 ORM 框架的异同,帮助开发者做出明智的选择。
## 主流 ORM 框架对比
### 1. TypeORM vs Prisma
#### TypeORM 特点
```typescript
// TypeORM 使用装饰器和类
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => Post, post => post.author)
posts: Post[];
}
// 使用 Repository 模式
const userRepository = dataSource.getRepository(User);
const users = await userRepository.find({ relations: ['posts'] });
```
**优点:**
* 完全支持 TypeScript,类型安全
* 支持 Active Record 和 Data Mapper 两种模式
* 灵活的查询构建器
* 强大的迁移系统
* 支持多种数据库
**缺点:**
* 学习曲线较陡
* 性能相对较低
* 查询复杂度较高
#### Prisma 特点
```typescript
// Prisma 使用 schema 文件
// schema.prisma
model User {
id Int @id @default(autoincrement())
name String
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id])
authorId Int
}
// 使用生成的客户端
const users = await prisma.user.findMany({
include: { posts: true }
});
```
**优点:**
* 类型安全,自动生成类型
* 查询语法简洁直观
* 性能优秀
* 强大的类型推断
* 良好的开发体验
**缺点:**
* 不支持 Active Record 模式
* 自定义查询能力有限
* 迁移系统相对简单
### 2. TypeORM vs Sequelize
#### TypeORM 特点
```typescript
// TypeORM 装饰器方式
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column({ unique: true })
email: string;
}
// 查询构建器
const users = await dataSource
.getRepository(User)
.createQueryBuilder('user')
.where('user.name LIKE :name', { name: '%John%' })
.getMany();
```
**优点:**
* TypeScript 原生支持
* 装饰器语法清晰
* 强类型系统
* 现代化设计
**缺点:**
* 相对较新,生态不如 Sequelize 成熟
* 文档和社区资源较少
#### Sequelize 特点
```typescript
// Sequelize 定义模型
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING
},
email: {
type: DataTypes.STRING,
unique: true
}
});
// 查询
const users = await User.findAll({
where: {
name: {
[Op.like]: '%John%'
}
}
});
```
**优点:**
* 成熟稳定,生态丰富
* 文档完善,社区活跃
* 支持多种数据库
* 功能全面
**缺点:**
* TypeScript 支持需要额外配置
* 类型安全性不如 TypeORM
* API 设计相对老旧
### 3. TypeORM vs MikroORM
#### TypeORM 特点
```typescript
// TypeORM 使用装饰器
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => Post, post => post.author)
posts: Post[];
}
// Data Mapper 模式
const userRepository = dataSource.getRepository(User);
const users = await userRepository.find({ relations: ['posts'] });
```
**优点:**
* 灵活的模式选择
* 强大的查询能力
* 良好的 TypeScript 支持
**缺点:**
* 性能不如 MikroORM
* 单元测试(Unit of Work)实现不够完善
#### MikroORM 特点
```typescript
// MikroORM 使用装饰器
@Entity()
export class User {
@PrimaryKey()
id!: number;
@Property()
name!: string;
@OneToMany(() => Post, post => post.author)
posts = new Collection<Post>(this);
}
// Unit of Work 模式
const users = await em.find(User, {}, {
populate: ['posts']
});
```
**优点:**
* 性能优秀
* 真正的 Unit of Work 模式
* 身份映射(Identity Map)
* 延迟加载(Lazy Loading)性能好
* 更好的类型推断
**缺点:**
* 相对较新,生态较小
* 学习曲线较陡
* 文档相对较少
### 4. TypeORM vs Mongoose (MongoDB)
#### TypeORM (MongoDB)
```typescript
// TypeORM 支持 MongoDB
@Entity()
export class User {
@ObjectIdColumn()
id: string;
@Column()
name: string;
@Column()
email: string;
}
// 查询
const users = await dataSource.getRepository(User).find();
```
**优点:**
* 统一的 API,支持多种数据库
* TypeScript 原生支持
* 迁移系统
**缺点:**
* MongoDB 支持不如 Mongoose 完善
* 性能相对较低
#### Mongoose
```typescript
// Mongoose Schema
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, unique: true, required: true }
});
const User = mongoose.model('User', userSchema);
// 查询
const users = await User.find();
```
**优点:**
* MongoDB 生态最佳选择
* 功能丰富,性能优秀
* 中间件和插件系统强大
* 文档完善
**缺点:**
* TypeScript 支持需要额外配置
* 不支持关系型数据库
## 性能对比
### 查询性能
```typescript
// 性能测试示例
import { performance } from 'perf_hooks';
async function benchmarkORMs() {
const iterations = 1000;
// TypeORM
const typeormStart = performance.now();
for (let i = 0; i < iterations; i++) {
await typeormRepository.find({ take: 10 });
}
const typeormEnd = performance.now();
// Prisma
const prismaStart = performance.now();
for (let i = 0; i < iterations; i++) {
await prisma.user.findMany({ take: 10 });
}
const prismaEnd = performance.now();
console.log(`TypeORM: ${typeormEnd - typeormStart}ms`);
console.log(`Prisma: ${prismaEnd - prismaStart}ms`);
}
```
**性能排名(从快到慢):**
1. MikroORM
2. Prisma
3. TypeORM
4. Sequelize
### 内存使用
```typescript
// 内存使用监控
function monitorMemoryUsage() {
const used = process.memoryUsage();
console.log(`Memory Usage:`);
console.log(` RSS: ${Math.round(used.rss / 1024 / 1024)} MB`);
console.log(` Heap Total: ${Math.round(used.heapTotal / 1024 / 1024)} MB`);
console.log(` Heap Used: ${Math.round(used.heapUsed / 1024 / 1024)} MB`);
}
```
**内存使用排名(从低到高):**
1. Prisma
2. MikroORM
3. TypeORM
4. Sequelize
## 功能对比表
| 特性 | TypeORM | Prisma | Sequelize | MikroORM | Mongoose |
| ------------- | ------- | ------ | --------- | -------- | -------- |
| TypeScript 支持 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 性能 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 学习曲线 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| 文档质量 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 社区活跃度 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 迁移系统 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | N/A |
| 查询构建器 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 关系处理 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | N/A |
| 数据库支持 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | MongoDB |
## 选择建议
### 选择 TypeORM 的场景
1. **需要 TypeScript 原生支持**
```typescript
// 完全的类型安全
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}
const user: User = await userRepository.findOne({ where: { id: 1 } });
```
1. **需要灵活的设计模式**
```typescript
// Active Record 模式
@Entity()
export class User extends BaseEntity {
static async findActive() {
return this.find({ where: { isActive: true } });
}
}
// Data Mapper 模式
const userRepository = dataSource.getRepository(User);
const users = await userRepository.find({ where: { isActive: true } });
```
1. **需要强大的迁移系统**
```typescript
// 复杂的迁移操作
export class CreateUserTable1234567890123 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(/* ... */);
await queryRunner.createForeignKey(/* ... */);
await queryRunner.createIndex(/* ... */);
}
}
```
1. **需要支持多种数据库**
```typescript
// 支持多种数据库类型
const dataSource = new DataSource({
type: 'mysql', // 或 'postgres', 'sqlite', 'mssql', 'oracle'
host: 'localhost',
// ...
});
```
### 选择 Prisma 的场景
1. **追求最佳开发体验**
```typescript
// 简洁的查询语法
const users = await prisma.user.findMany({
where: {
posts: {
some: {
published: true
}
}
},
include: {
posts: true
}
});
```
1. **需要最佳性能**
```typescript
// Prisma 的性能通常优于 TypeORM
const users = await prisma.user.findMany({
take: 100
});
```
1. **项目规模较小到中等**
```typescript
// Prisma 适合中小型项目
const user = await prisma.user.create({
data: {
name: 'John',
email: 'john@example.com'
}
});
```
### 选择 Sequelize 的场景
1. **需要成熟的生态系统**
```typescript
// 丰富的插件和中间件
User.addHook('beforeCreate', (user, options) => {
// 自定义逻辑
});
```
1. **团队已经熟悉 Sequelize**
```typescript
// Sequelize 的 API 设计相对传统
const users = await User.findAll({
where: {
status: 'active'
}
});
```
1. **需要支持旧版 Node.js**
```typescript
// Sequelize 支持更旧的 Node.js 版本
const sequelize = new Sequelize(/* ... */);
```
### 选择 MikroORM 的场景
1. **需要最佳性能**
```typescript
// MikroORM 的性能通常是最好的
const users = await em.find(User, {}, {
populate: ['posts']
});
```
1. **需要真正的 Unit of Work 模式**
```typescript
// Unit of Work 模式
const user = em.create(User, { name: 'John' });
em.persist(user);
await em.flush(); // 批量提交
```
1. **需要高级特性**
```typescript
// 身份映射和延迟加载
const user = await em.findOne(User, 1);
// 不会立即加载关联数据
const posts = await user.posts.init(); // 按需加载
```
### 选择 Mongoose 的场景
1. **使用 MongoDB 数据库**
```typescript
// Mongoose 是 MongoDB 的最佳选择
const user = await User.findOne({ email: 'john@example.com' });
```
1. **需要丰富的 MongoDB 特性**
```typescript
// MongoDB 特有的查询
const users = await User.find({
$or: [
{ name: 'John' },
{ email: 'john@example.com' }
]
});
```
## 迁移指南
### 从 Sequelize 迁移到 TypeORM
```typescript
// Sequelize 模型
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING
}
});
// TypeORM 实体
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}
```
### 从 Mongoose 迁移到 TypeORM (MongoDB)
```typescript
// Mongoose Schema
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, unique: true }
});
// TypeORM 实体 (MongoDB)
@Entity()
export class User {
@ObjectIdColumn()
id: string;
@Column()
name: string;
@Column({ unique: true })
email: string;
}
```
## 总结
选择 ORM 框架时需要考虑:
1. **项目需求**: 功能需求、性能要求
2. **团队技能**: 团队对框架的熟悉程度
3. **生态系统**: 社区支持、插件生态
4. **学习曲线**: 框架的学习难度
5. **长期维护**: 框架的维护状态和更新频率
TypeORM 是一个功能强大、类型安全的 ORM 框架,特别适合需要 TypeScript 支持和灵活设计模式的项目。但根据具体需求,其他框架可能更适合某些场景。
服务端 · 2月18日 22:21
TypeORM 如何处理事务?包括事务隔离级别、锁机制和分布式事务的详细说明事务是数据库操作的核心概念,它确保一组数据库操作要么全部成功,要么全部失败。TypeORM 提供了多种事务处理方式,让开发者能够轻松管理数据库事务。
## 事务基础概念
### 什么是事务
事务是数据库操作的逻辑单元,具有 ACID 特性:
* **原子性 (Atomicity)**: 事务中的操作要么全部执行,要么全部不执行
* **一致性 (Consistency)**: 事务执行前后数据库保持一致状态
* **隔离性 (Isolation)**: 并发事务之间相互隔离
* **持久性 (Durability)**: 事务提交后,修改永久保存
### 事务的使用场景
* 银行转账(从一个账户扣款,另一个账户入账)
* 订单处理(创建订单、扣减库存、更新用户余额)
* 多表关联操作
* 需要数据一致性的复杂业务逻辑
## TypeORM 事务处理方式
### 1. 使用 DataSource.transaction()
这是最常用的事务处理方式,自动管理事务的提交和回滚。
```typescript
import { DataSource } from 'typeorm';
async function transferFunds(
dataSource: DataSource,
fromUserId: number,
toUserId: number,
amount: number
) {
await dataSource.transaction(async transactionalEntityManager => {
// 查询转出账户
const fromUser = await transactionalEntityManager.findOne(User, {
where: { id: fromUserId },
lock: { mode: 'pessimistic_write' }
});
if (!fromUser || fromUser.balance < amount) {
throw new Error('Insufficient balance');
}
// 查询转入账户
const toUser = await transactionalEntityManager.findOne(User, {
where: { id: toUserId },
lock: { mode: 'pessimistic_write' }
});
if (!toUser) {
throw new Error('Recipient not found');
}
// 扣减转出账户余额
fromUser.balance -= amount;
await transactionalEntityManager.save(fromUser);
// 增加转入账户余额
toUser.balance += amount;
await transactionalEntityManager.save(toUser);
// 记录交易日志
const transaction = transactionalEntityManager.create(Transaction, {
fromUserId,
toUserId,
amount,
type: 'transfer'
});
await transactionalEntityManager.save(transaction);
});
}
```
### 2. 使用 QueryRunner
QueryRunner 提供了更细粒度的事务控制。
```typescript
import { DataSource } from 'typeorm';
async function transferWithQueryRunner(
dataSource: DataSource,
fromUserId: number,
toUserId: number,
amount: number
) {
const queryRunner = dataSource.createQueryRunner();
try {
// 开启事务
await queryRunner.connect();
await queryRunner.startTransaction();
// 查询转出账户
const fromUser = await queryRunner.manager.findOne(User, {
where: { id: fromUserId },
lock: { mode: 'pessimistic_write' }
});
if (!fromUser || fromUser.balance < amount) {
throw new Error('Insufficient balance');
}
// 查询转入账户
const toUser = await queryRunner.manager.findOne(User, {
where: { id: toUserId },
lock: { mode: 'pessimistic_write' }
});
if (!toUser) {
throw new Error('Recipient not found');
}
// 执行转账操作
await queryRunner.manager.update(User, fromUserId, {
balance: fromUser.balance - amount
});
await queryRunner.manager.update(User, toUserId, {
balance: toUser.balance + amount
});
// 记录交易日志
await queryRunner.manager.insert(Transaction, {
fromUserId,
toUserId,
amount,
type: 'transfer'
});
// 提交事务
await queryRunner.commitTransaction();
} catch (error) {
// 回滚事务
await queryRunner.rollbackTransaction();
throw error;
} finally {
// 释放 QueryRunner
await queryRunner.release();
}
}
```
### 3. 使用装饰器 [Transaction](mention:Transaction)
在类方法上使用装饰器声明事务。
```typescript
import { Transaction, TransactionManager, EntityManager } from 'typeorm';
class PaymentService {
@Transaction()
async processPayment(
userId: number,
amount: number,
@TransactionManager() manager?: EntityManager
) {
const user = await manager.findOne(User, {
where: { id: userId },
lock: { mode: 'pessimistic_write' }
});
if (!user || user.balance < amount) {
throw new Error('Insufficient balance');
}
user.balance -= amount;
await manager.save(user);
const payment = manager.create(Payment, {
userId,
amount,
status: 'completed'
});
await manager.save(payment);
}
}
```
## 事务隔离级别
### 设置隔离级别
```typescript
import { DataSource } from 'typeorm';
const dataSource = new DataSource({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'myapp',
entities: [User, Transaction],
// 设置默认隔离级别
extra: {
connectionLimit: 10,
},
});
// 在事务中设置隔离级别
await dataSource.transaction(async transactionalEntityManager => {
const queryRunner = transactionalEntityManager.queryRunner;
// 设置隔离级别
await queryRunner.query('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
// 执行业务逻辑
// ...
});
```
### 隔离级别说明
1. **READ UNCOMMITTED (读未提交)**
* 可以读取未提交的数据
* 可能出现脏读、不可重复读、幻读
* 性能最好,但数据一致性最差
2. **READ COMMITTED (读已提交)**
* 只能读取已提交的数据
* 避免脏读,但可能出现不可重复读、幻读
* 大多数数据库的默认隔离级别
3. **REPEATABLE READ (可重复读)**
* 在同一事务中多次读取同一数据结果一致
* 避免脏读、不可重复读,但可能出现幻读
* MySQL 的默认隔离级别
4. **SERIALIZABLE (串行化)**
* 最高隔离级别,完全避免并发问题
* 性能最差,但数据一致性最好
```typescript
// 设置不同隔离级别的示例
await dataSource.transaction(async transactionalEntityManager => {
const queryRunner = transactionalEntityManager.queryRunner;
// READ COMMITTED
await queryRunner.query('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
// REPEATABLE READ
await queryRunner.query('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ');
// SERIALIZABLE
await queryRunner.query('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE');
});
```
## 锁机制
### 悲观锁
悲观锁假设会发生并发冲突,在读取数据时就锁定。
```typescript
// SELECT ... FOR UPDATE
const user = await dataSource.transaction(async transactionalEntityManager => {
const user = await transactionalEntityManager.findOne(User, {
where: { id: userId },
lock: { mode: 'pessimistic_write' }
});
// 此时该行被锁定,其他事务无法修改
user.balance -= amount;
await transactionalEntityManager.save(user);
return user;
});
// SELECT ... FOR SHARE
const user = await dataSource.transaction(async transactionalEntityManager => {
const user = await transactionalEntityManager.findOne(User, {
where: { id: userId },
lock: { mode: 'pessimistic_read' }
});
// 其他事务可以读取但不能修改
return user;
});
```
### 乐观锁
乐观锁假设不会发生并发冲突,通过版本号或时间戳检测冲突。
```typescript
@Entity()
export class Product {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
stock: number;
@VersionColumn()
version: number;
}
// 使用乐观锁
async function updateProductStock(productId: number, quantity: number) {
const product = await dataSource.manager.findOne(Product, {
where: { id: productId }
});
if (!product || product.stock < quantity) {
throw new Error('Insufficient stock');
}
product.stock -= quantity;
try {
await dataSource.manager.save(product);
} catch (error) {
// 版本冲突,说明其他事务已经修改了数据
throw new Error('Concurrent modification detected');
}
}
```
## 嵌套事务
### 使用 Savepoint
```typescript
async function complexOperation(dataSource: DataSource) {
await dataSource.transaction(async transactionalEntityManager => {
const queryRunner = transactionalEntityManager.queryRunner;
try {
// 主事务操作
await transactionalEntityManager.save(/* ... */);
// 创建保存点
await queryRunner.query('SAVEPOINT savepoint1');
try {
// 子事务操作
await transactionalEntityManager.save(/* ... */);
// 如果成功,释放保存点
await queryRunner.query('RELEASE SAVEPOINT savepoint1');
} catch (error) {
// 如果失败,回滚到保存点
await queryRunner.query('ROLLBACK TO SAVEPOINT savepoint1');
// 继续执行其他操作
}
// 继续主事务操作
await transactionalEntityManager.save(/* ... */);
} catch (error) {
throw error; // 整个事务回滚
}
});
}
```
## 事务超时和重试
### 设置事务超时
```typescript
async function transactionWithTimeout(
dataSource: DataSource,
timeout: number = 30000
) {
const queryRunner = dataSource.createQueryRunner();
try {
await queryRunner.connect();
await queryRunner.startTransaction();
// 设置事务超时(MySQL)
await queryRunner.query(`SET max_execution_time = ${timeout}`);
// 执行业务逻辑
// ...
await queryRunner.commitTransaction();
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
```
### 事务重试机制
```typescript
async function transactionWithRetry(
dataSource: DataSource,
maxRetries: number = 3,
operation: (manager: EntityManager) => Promise<void>
) {
let lastError: Error;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await dataSource.transaction(async transactionalEntityManager => {
await operation(transactionalEntityManager);
});
return; // 成功,退出重试循环
} catch (error) {
lastError = error;
// 如果是死锁错误,可以重试
if (error.code === 'ER_LOCK_DEADLOCK' && attempt < maxRetries) {
await new Promise(resolve => setTimeout(resolve, 100 * attempt));
continue;
}
throw error; // 其他错误直接抛出
}
}
throw lastError;
}
// 使用示例
await transactionWithRetry(dataSource, 3, async manager => {
const user = await manager.findOne(User, { where: { id: 1 } });
user.balance += 100;
await manager.save(user);
});
```
## 分布式事务
### 两阶段提交 (2PC)
TypeORM 本身不支持分布式事务,但可以通过以下方式实现:
```typescript
async function distributedTransaction(
dataSource1: DataSource,
dataSource2: DataSource
) {
const queryRunner1 = dataSource1.createQueryRunner();
const queryRunner2 = dataSource2.createQueryRunner();
try {
await queryRunner1.connect();
await queryRunner2.connect();
await queryRunner1.startTransaction();
await queryRunner2.startTransaction();
// 第一阶段:准备
await queryRunner1.manager.save(/* ... */);
await queryRunner2.manager.save(/* ... */);
// 第二阶段:提交
await queryRunner1.commitTransaction();
await queryRunner2.commitTransaction();
} catch (error) {
// 回滚所有事务
await queryRunner1.rollbackTransaction();
await queryRunner2.rollbackTransaction();
throw error;
} finally {
await queryRunner1.release();
await queryRunner2.release();
}
}
```
## 最佳实践
### 1. 保持事务简短
```typescript
// ❌ 不好的做法:事务时间过长
await dataSource.transaction(async manager => {
const user = await manager.findOne(User, { where: { id: 1 } });
// 执行耗时操作(不应该在事务中)
await sendEmail(user.email);
await processPayment(user);
user.balance += 100;
await manager.save(user);
});
// ✅ 好的做法:只包含数据库操作
const user = await dataSource.manager.findOne(User, { where: { id: 1 } });
// 在事务外执行耗时操作
await sendEmail(user.email);
await processPayment(user);
// 在事务中只执行数据库操作
await dataSource.transaction(async manager => {
user.balance += 100;
await manager.save(user);
});
```
### 2. 正确处理异常
```typescript
await dataSource.transaction(async manager => {
try {
const user = await manager.findOne(User, { where: { id: 1 } });
if (!user) {
throw new Error('User not found');
}
user.balance += 100;
await manager.save(user);
} catch (error) {
// 记录错误日志
console.error('Transaction failed:', error);
// 抛出错误以触发回滚
throw error;
}
});
```
### 3. 避免嵌套事务
```typescript
// ❌ 不好的做法:嵌套事务
await dataSource.transaction(async manager1 => {
await dataSource.transaction(async manager2 => {
// 嵌套事务可能导致问题
});
});
// ✅ 好的做法:使用单一事务
await dataSource.transaction(async manager => {
// 所有操作在一个事务中
});
```
### 4. 使用适当的隔离级别
```typescript
// 根据业务需求选择合适的隔离级别
await dataSource.transaction(async manager => {
const queryRunner = manager.queryRunner;
// 读多写少的场景使用 READ COMMITTED
await queryRunner.query('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
// 需要强一致性的场景使用 SERIALIZABLE
// await queryRunner.query('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE');
// 执行业务逻辑
});
```
TypeORM 的事务处理功能强大而灵活,掌握事务的使用对于构建可靠的数据驱动应用至关重要。正确使用事务可以确保数据的一致性和完整性,避免并发问题。
服务端 · 2月18日 22:21
TypeORM 的事件系统如何工作?包括实体监听器和订阅者TypeORM 的事件系统允许开发者在实体操作的生命周期中执行自定义逻辑,提供了强大的扩展能力。
## 事件类型
### 1. 实体生命周期事件
TypeORM 提供了以下实体生命周期事件:
* `BeforeInsert` - 在实体插入之前触发
* `AfterInsert` - 在实体插入之后触发
* `BeforeUpdate` - 在实体更新之前触发
* `AfterUpdate` - 在实体更新之后触发
* `BeforeRemove` - 在实体删除之前触发
* `AfterRemove` - 在实体删除之后触发
* `BeforeSoftRemove` - 在实体软删除之前触发
* `AfterSoftRemove` - 在实体软删除之后触发
* `BeforeRecover` - 在实体恢复之前触发
* `AfterRecover` - 在实体恢复之后触发
### 2. 订阅者事件
订阅者可以监听所有实体的特定事件。
## 使用实体监听器
### 基本用法
```typescript
import { Entity, PrimaryGeneratedColumn, Column, BeforeInsert, BeforeUpdate, AfterInsert, AfterUpdate } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
email: string;
@Column({ type: 'timestamp' })
createdAt: Date;
@Column({ type: 'timestamp' })
updatedAt: Date;
@Column({ default: 0 })
version: number;
@BeforeInsert()
beforeInsert() {
this.createdAt = new Date();
this.updatedAt = new Date();
this.version = 1;
}
@BeforeUpdate()
beforeUpdate() {
this.updatedAt = new Date();
this.version++;
}
@AfterInsert()
afterInsert() {
console.log(`User ${this.name} inserted with ID ${this.id}`);
}
@AfterUpdate()
afterUpdate() {
console.log(`User ${this.name} updated to version ${this.version}`);
}
}
```
### 复杂逻辑处理
```typescript
import { Entity, PrimaryGeneratedColumn, Column, BeforeInsert, BeforeUpdate } from 'typeorm';
import { hash } from 'bcrypt';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
email: string;
@Column()
password: string;
@Column({ default: false })
emailVerified: boolean;
@Column({ type: 'timestamp', nullable: true })
emailVerifiedAt: Date;
@Column({ type: 'timestamp' })
createdAt: Date;
@Column({ type: 'timestamp' })
updatedAt: Date;
@BeforeInsert()
async beforeInsert() {
this.createdAt = new Date();
this.updatedAt = new Date();
// 加密密码
if (this.password) {
this.password = await hash(this.password, 10);
}
// 验证邮箱格式
if (!this.validateEmail(this.email)) {
throw new Error('Invalid email format');
}
}
@BeforeUpdate()
async beforeUpdate() {
this.updatedAt = new Date();
// 如果密码被修改,重新加密
if (this.password && this.isPasswordModified()) {
this.password = await hash(this.password, 10);
}
// 如果邮箱被验证,记录验证时间
if (this.emailVerified && !this.emailVerifiedAt) {
this.emailVerifiedAt = new Date();
}
}
private validateEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
private isPasswordModified(): boolean {
// 实现密码修改检测逻辑
return true;
}
}
```
## 使用订阅者
### 基本订阅者
```typescript
import { EntitySubscriberInterface, EventSubscriber, InsertEvent, UpdateEvent, RemoveEvent } from 'typeorm';
import { User } from '../entity/User';
@EventSubscriber()
export class UserSubscriber implements EntitySubscriberInterface<User> {
// 指定监听的实体
listenTo() {
return User;
}
// 插入前
beforeInsert(event: InsertEvent<User>) {
console.log(`Before inserting user: ${event.entity.name}`);
// 可以修改实体
event.entity.createdAt = new Date();
}
// 插入后
afterInsert(event: InsertEvent<User>) {
console.log(`After inserting user with ID: ${event.entity.id}`);
// 发送欢迎邮件
this.sendWelcomeEmail(event.entity);
}
// 更新前
beforeUpdate(event: UpdateEvent<User>) {
console.log(`Before updating user: ${event.entity.name}`);
// 记录变更
this.logChanges(event);
}
// 更新后
afterUpdate(event: UpdateEvent<User>) {
console.log(`After updating user: ${event.entity.name}`);
// 发送通知
this.sendUpdateNotification(event.entity);
}
// 删除前
beforeRemove(event: RemoveEvent<User>) {
console.log(`Before removing user: ${event.entity.name}`);
// 检查是否可以删除
if (event.entity.posts && event.entity.posts.length > 0) {
throw new Error('Cannot delete user with posts');
}
}
// 删除后
afterRemove(event: RemoveEvent<User>) {
console.log(`After removing user: ${event.entity.name}`);
// 清理相关数据
this.cleanupUserData(event.entity.id);
}
private sendWelcomeEmail(user: User) {
// 发送欢迎邮件逻辑
console.log(`Sending welcome email to ${user.email}`);
}
private sendUpdateNotification(user: User) {
// 发送更新通知逻辑
console.log(`Sending update notification to ${user.email}`);
}
private logChanges(event: UpdateEvent<User>) {
// 记录变更逻辑
console.log('Changes:', event.updatedColumns);
}
private cleanupUserData(userId: number) {
// 清理用户数据逻辑
console.log(`Cleaning up data for user ${userId}`);
}
}
```
### 全局订阅者
```typescript
import { EntitySubscriberInterface, EventSubscriber, InsertEvent } from 'typeorm';
@EventSubscriber()
export class AuditSubscriber implements EntitySubscriberInterface {
// 监听所有实体
listenTo() {
return Object;
}
// 所有实体的插入操作
afterInsert(event: InsertEvent<any>) {
console.log(`Entity ${event.metadata.name} inserted with ID ${event.entity.id}`);
// 记录审计日志
this.logAudit({
action: 'INSERT',
entity: event.metadata.name,
entityId: event.entity.id,
timestamp: new Date(),
});
}
private logAudit(log: any) {
// 记录审计日志逻辑
console.log('Audit log:', log);
}
}
```
## 注册订阅者
### 在 DataSource 中注册
```typescript
import { DataSource } from 'typeorm';
import { UserSubscriber } from './subscriber/UserSubscriber';
import { AuditSubscriber } from './subscriber/AuditSubscriber';
const dataSource = new DataSource({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'myapp',
entities: [User, Post],
synchronize: false,
logging: true,
// 注册订阅者
subscribers: [UserSubscriber, AuditSubscriber],
});
```
### 动态注册订阅者
```typescript
import { DataSource } from 'typeorm';
const dataSource = new DataSource({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'myapp',
entities: [User, Post],
synchronize: false,
logging: true,
});
// 初始化后动态注册订阅者
dataSource.initialize().then(() => {
const userSubscriber = new UserSubscriber();
dataSource.subscribers.push(userSubscriber);
});
```
## 高级事件处理
### 事务中的事件
```typescript
@EventSubscriber()
export class TransactionSubscriber implements EntitySubscriberInterface<User> {
listenTo() {
return User;
}
afterInsert(event: InsertEvent<User>) {
// 检查是否在事务中
if (event.queryRunner?.isTransactionActive) {
console.log('Insert operation is part of a transaction');
}
// 使用事务执行器
if (event.queryRunner) {
event.queryRunner.manager.getRepository(AuditLog).save({
action: 'USER_INSERT',
userId: event.entity.id,
timestamp: new Date(),
});
}
}
}
```
### 异步事件处理
```typescript
@EventSubscriber()
export class AsyncSubscriber implements EntitySubscriberInterface<User> {
listenTo() {
return User;
}
async afterInsert(event: InsertEvent<User>) {
// 异步发送邮件
await this.sendEmailAsync(event.entity);
// 异步生成用户资料
await this.generateUserProfileAsync(event.entity);
}
private async sendEmailAsync(user: User) {
// 模拟异步邮件发送
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Email sent to ${user.email}`);
resolve(null);
}, 1000);
});
}
private async generateUserProfileAsync(user: User) {
// 模拟异步用户资料生成
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Profile generated for user ${user.id}`);
resolve(null);
}, 500);
});
}
}
```
### 条件事件处理
```typescript
@EventSubscriber()
export class ConditionalSubscriber implements EntitySubscriberInterface<User> {
listenTo() {
return User;
}
beforeUpdate(event: UpdateEvent<User>) {
// 只在特定条件下执行
if (this.shouldProcessUpdate(event)) {
this.processUpdate(event);
}
}
private shouldProcessUpdate(event: UpdateEvent<User>): boolean {
// 检查是否更新了特定字段
const updatedFields = event.updatedColumns.map(col => col.propertyName);
return updatedFields.includes('email') || updatedFields.includes('password');
}
private processUpdate(event: UpdateEvent<User>) {
// 处理更新逻辑
console.log('Processing critical update:', event.entity);
}
}
```
## 事件最佳实践
### 1. 保持事件处理简单
```typescript
// ✅ 好的做法:事件处理简单直接
@EventSubscriber()
export class SimpleSubscriber implements EntitySubscriberInterface<User> {
listenTo() {
return User;
}
afterInsert(event: InsertEvent<User>) {
// 简单的日志记录
console.log(`User created: ${event.entity.name}`);
}
}
// ❌ 不好的做法:事件处理过于复杂
@EventSubscriber()
export class ComplexSubscriber implements EntitySubscriberInterface<User> {
listenTo() {
return User;
}
async afterInsert(event: InsertEvent<User>) {
// 复杂的业务逻辑
const user = event.entity;
// 发送邮件
await this.sendEmail(user);
// 创建用户资料
await this.createProfile(user);
// 初始化用户设置
await this.initializeSettings(user);
// 发送欢迎消息
await this.sendWelcomeMessage(user);
// 记录统计
await this.recordStatistics(user);
// 更新缓存
await this.updateCache(user);
// 触发其他事件
await this.triggerEvents(user);
}
}
```
### 2. 避免循环事件
```typescript
// ✅ 好的做法:避免循环事件
@EventSubscriber()
export class SafeSubscriber implements EntitySubscriberInterface<User> {
listenTo() {
return User;
}
async afterInsert(event: InsertEvent<User>) {
// 使用标志位避免循环
if (event.entity.processed) {
return;
}
// 处理逻辑
await this.processUser(event.entity);
// 标记为已处理
event.entity.processed = true;
}
}
// ❌ 不好的做法:可能导致循环事件
@EventSubscriber()
export class CircularSubscriber implements EntitySubscriberInterface<User> {
listenTo() {
return User;
}
async afterInsert(event: InsertEvent<User>) {
// 更新用户,可能触发 afterUpdate 事件
await event.manager.save(User, {
id: event.entity.id,
processed: true,
});
}
}
```
### 3. 错误处理
```typescript
// ✅ 好的做法:适当的错误处理
@EventSubscriber()
export class ErrorHandlingSubscriber implements EntitySubscriberInterface<User> {
listenTo() {
return User;
}
async afterInsert(event: InsertEvent<User>) {
try {
await this.sendWelcomeEmail(event.entity);
} catch (error) {
console.error('Failed to send welcome email:', error);
// 记录错误,但不影响主流程
await this.logError(error, event.entity);
}
}
private async logError(error: any, user: User) {
// 记录错误到数据库
await event.manager.getRepository(ErrorLog).save({
error: error.message,
userId: user.id,
timestamp: new Date(),
});
}
}
```
### 4. 性能考虑
```typescript
// ✅ 好的做法:批量处理
@EventSubscriber()
export class BatchSubscriber implements EntitySubscriberInterface<User> {
private batch: User[] = [];
private timer: NodeJS.Timeout | null = null;
listenTo() {
return User;
}
afterInsert(event: InsertEvent<User>) {
// 添加到批次
this.batch.push(event.entity);
// 设置定时器
if (!this.timer) {
this.timer = setTimeout(() => {
this.processBatch();
}, 1000); // 1 秒后处理
}
}
private async processBatch() {
if (this.batch.length === 0) {
return;
}
const usersToProcess = [...this.batch];
this.batch = [];
this.timer = null;
// 批量处理
await this.sendBatchNotifications(usersToProcess);
}
private async sendBatchNotifications(users: User[]) {
console.log(`Sending notifications to ${users.length} users`);
// 批量发送通知逻辑
}
}
```
## 实际应用场景
### 1. 审计日志
```typescript
@EventSubscriber()
export class AuditLogSubscriber implements EntitySubscriberInterface {
listenTo() {
return Object;
}
afterInsert(event: InsertEvent<any>) {
this.logAudit('INSERT', event.entity);
}
afterUpdate(event: UpdateEvent<any>) {
this.logAudit('UPDATE', event.entity, event.updatedColumns);
}
afterRemove(event: RemoveEvent<any>) {
this.logAudit('DELETE', event.entity);
}
private async logAudit(action: string, entity: any, columns?: any[]) {
const auditLog = {
action,
entityName: entity.constructor.name,
entityId: entity.id,
changes: columns ? columns.map(col => col.propertyName) : null,
timestamp: new Date(),
};
await event.manager.getRepository(AuditLog).save(auditLog);
}
}
```
### 2. 缓存失效
```typescript
@EventSubscriber()
export class CacheInvalidationSubscriber implements EntitySubscriberInterface<User> {
listenTo() {
return User;
}
afterUpdate(event: UpdateEvent<User>) {
// 清除用户缓存
this.clearUserCache(event.entity.id);
// 清除相关缓存
this.clearRelatedCache(event.entity.id);
}
afterRemove(event: RemoveEvent<User>) {
// 清除所有相关缓存
this.clearAllUserCache(event.entity.id);
}
private clearUserCache(userId: number) {
// 清除用户缓存逻辑
console.log(`Clearing cache for user ${userId}`);
}
private clearRelatedCache(userId: number) {
// 清除相关缓存逻辑
console.log(`Clearing related cache for user ${userId}`);
}
private clearAllUserCache(userId: number) {
// 清除所有用户缓存逻辑
console.log(`Clearing all cache for user ${userId}`);
}
}
```
### 3. 通知系统
```typescript
@EventSubscriber()
export class NotificationSubscriber implements EntitySubscriberInterface<Post> {
listenTo() {
return Post;
}
afterInsert(event: InsertEvent<Post>) {
// 通知关注者
this.notifyFollowers(event.entity);
// 通知作者
this.notifyAuthor(event.entity);
}
afterUpdate(event: UpdateEvent<Post>) {
// 如果文章被发布,通知关注者
if (this.isPublished(event)) {
this.notifyFollowers(event.entity);
}
}
private isPublished(event: UpdateEvent<Post>): boolean {
const updatedFields = event.updatedColumns.map(col => col.propertyName);
return updatedFields.includes('status') && event.entity.status === 'published';
}
private async notifyFollowers(post: Post) {
// 通知关注者逻辑
console.log(`Notifying followers of post ${post.id}`);
}
private async notifyAuthor(post: Post) {
// 通知作者逻辑
console.log(`Notifying author of post ${post.id}`);
}
}
```
TypeORM 的事件系统提供了强大的扩展能力,合理使用事件可以简化业务逻辑,提高代码的可维护性。
服务端 · 2月18日 22:20
TypeORM 中如何定义和使用关系映射?包括一对一、一对多、多对多关系的详细配置TypeORM 提供了四种主要的关系映射类型,每种关系都有其特定的使用场景和配置方式。理解这些关系映射对于构建复杂的数据模型至关重要。
## 四种关系类型
### 1. One-to-One (一对一)
一对一关系表示两个实体之间存在唯一的对应关系。例如,一个用户只能有一个个人资料。
```typescript
@Entity()
export class Profile {
@PrimaryGeneratedColumn()
id: number;
@Column()
gender: string;
@Column()
bio: string;
@OneToOne(() => User, user => user.profile)
@JoinColumn()
user: User;
}
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToOne(() => Profile, profile => profile.user, { cascade: true })
profile: Profile;
}
```
**关键点**:
* 使用 `@OneToOne()` 装饰器定义关系
* 在拥有方使用 `@JoinColumn()` 指定外键列
* `cascade: true` 允许级联操作(保存、删除等)
### 2. One-to-Many / Many-to-One (一对多/多对一)
一对多关系是最常见的关系类型。例如,一个用户可以发表多篇文章。
```typescript
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
content: string;
@ManyToOne(() => User, user => user.posts)
author: User;
}
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(() => Post, post => post.author)
posts: Post[];
}
```
**关键点**:
* `@ManyToOne()` 放在"多"的一方,包含外键
* `@OneToMany()` 放在"一"的一方,不需要 `@JoinColumn()`
* 外键自动添加到"多"的一方表中
### 3. Many-to-Many (多对多)
多对多关系需要中间表来连接两个实体。例如,一篇文章可以有多个标签,一个标签也可以属于多篇文章。
```typescript
@Entity()
export class Tag {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@ManyToMany(() => Post, post => post.tags)
posts: Post[];
}
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@ManyToMany(() => Tag, tag => tag.posts, { cascade: true })
@JoinTable()
tags: Tag[];
}
```
**关键点**:
* 使用 `@JoinTable()` 在关系的一方定义中间表
* 中间表自动创建,包含两个外键
* 可以自定义中间表名称和列名
## 关系配置选项
### Eager 和 Lazy 加载
```typescript
@Entity()
export class User {
@OneToMany(() => Post, post => post.author, {
eager: true // 立即加载关联数据
})
posts: Post[];
}
// 或者使用懒加载
@Entity()
export class User {
@OneToMany(() => Post, post => post.author)
posts: Promise<Post[]>;
}
```
### 级联操作 (Cascade)
```typescript
@OneToMany(() => Post, post => post.author, {
cascade: ['insert', 'update', 'remove', 'soft-remove', 'recover']
})
posts: Post[];
```
级联操作选项:
* `insert`: 保存父实体时自动保存子实体
* `update`: 更新父实体时自动更新子实体
* `remove`: 删除父实体时自动删除子实体
* `soft-remove`: 软删除
* `recover`: 恢复软删除的实体
### OnDelete 和 OnUpdate
```typescript
@ManyToOne(() => User, user => user.posts, {
onDelete: 'CASCADE', // 删除用户时级联删除文章
onUpdate: 'CASCADE' // 更新用户ID时级联更新文章
})
author: User;
```
## 关系查询
### 使用 FindOptions 查询关联数据
```typescript
const userRepository = dataSource.getRepository(User);
// 加载关联数据
const users = await userRepository.find({
relations: ['posts', 'profile']
});
// 条件查询关联数据
const usersWithPosts = await userRepository.find({
relations: {
posts: true
},
where: {
posts: {
title: Like('%TypeORM%')
}
}
});
```
### 使用 QueryBuilder
```typescript
const users = await dataSource
.getRepository(User)
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.leftJoinAndSelect('user.profile', 'profile')
.where('post.title = :title', { title: 'TypeORM Guide' })
.getMany();
```
## 自定义关系
### 自定义 JoinTable
```typescript
@ManyToMany(() => Tag, tag => tag.posts)
@JoinTable({
name: 'post_tags',
joinColumn: {
name: 'postId',
referencedColumnName: 'id'
},
inverseJoinColumn: {
name: 'tagId',
referencedColumnName: 'id'
}
})
tags: Tag[];
```
### 自定义 JoinColumn
```typescript
@ManyToOne(() => User, user => user.posts)
@JoinColumn({
name: 'author_id',
referencedColumnName: 'id'
})
author: User;
```
## 最佳实践
1. **合理选择关系类型**: 根据业务需求选择最合适的关系类型
2. **避免过度使用 Eager 加载**: 可能导致 N+1 查询问题
3. **谨慎使用级联删除**: 确保不会意外删除重要数据
4. **使用索引优化查询**: 为外键列添加索引
5. **考虑性能影响**: 复杂关系查询可能影响性能
TypeORM 的关系映射系统提供了强大而灵活的方式来处理实体之间的关系,掌握这些概念对于构建高效、可维护的应用程序至关重要。
服务端 · 2月18日 22:20
TypeORM 的 QueryBuilder 如何使用?包括复杂查询、关联查询、分页排序等高级功能QueryBuilder 是 TypeORM 中最强大、最灵活的查询工具,它允许开发者构建复杂的 SQL 查询,同时保持类型安全和可读性。
## QueryBuilder 基础用法
### 创建 QueryBuilder
```typescript
import { DataSource } from 'typeorm';
const dataSource = new DataSource(/* 配置 */);
// 方式1: 通过 Repository 创建
const userRepository = dataSource.getRepository(User);
const queryBuilder = userRepository.createQueryBuilder('user');
// 方式2: 通过 DataSource 创建
const queryBuilder = dataSource.createQueryBuilder(User, 'user');
```
### 基本查询操作
```typescript
// 查询所有用户
const users = await dataSource
.createQueryBuilder('user')
.getMany();
// 查询单个用户
const user = await dataSource
.createQueryBuilder('user')
.where('user.id = :id', { id: 1 })
.getOne();
// 查询并计数
const count = await dataSource
.createQueryBuilder('user')
.getCount();
```
## 条件查询
### Where 子句
```typescript
// 简单条件
const users = await dataSource
.createQueryBuilder('user')
.where('user.age > :age', { age: 18 })
.getMany();
// 多个条件 (AND)
const users = await dataSource
.createQueryBuilder('user')
.where('user.age > :age', { age: 18 })
.andWhere('user.isActive = :isActive', { isActive: true })
.getMany();
// OR 条件
const users = await dataSource
.createQueryBuilder('user')
.where('user.age > :age', { age: 18 })
.orWhere('user.role = :role', { role: 'admin' })
.getMany();
// 复杂条件组合
const users = await dataSource
.createQueryBuilder('user')
.where(
new Brackets(qb => {
qb.where('user.age > :age', { age: 18 })
.orWhere('user.role = :role', { role: 'admin' });
})
)
.andWhere('user.isActive = :isActive', { isActive: true })
.getMany();
```
### 操作符
```typescript
import { Like, Between, In, MoreThan, LessThan } from 'typeorm';
// LIKE 查询
const users = await dataSource
.createQueryBuilder('user')
.where('user.name LIKE :name', { name: '%John%' })
.getMany();
// 或者使用 Like 操作符
const users = await dataSource
.createQueryBuilder('user')
.where('user.name = :name', { name: Like('%John%') })
.getMany();
// BETWEEN 查询
const users = await dataSource
.createQueryBuilder('user')
.where('user.age = :age', { age: Between(18, 30) })
.getMany();
// IN 查询
const users = await dataSource
.createQueryBuilder('user')
.where('user.id IN :ids', { ids: [1, 2, 3] })
.getMany();
// 或者使用 In 操作符
const users = await dataSource
.createQueryBuilder('user')
.where('user.id = :ids', { ids: In([1, 2, 3]) })
.getMany();
// 比较操作符
const users = await dataSource
.createQueryBuilder('user')
.where('user.age = :age', { age: MoreThan(18) })
.andWhere('user.score = :score', { score: LessThan(100) })
.getMany();
```
## 关联查询
### Left Join 和 Inner Join
```typescript
// Left Join (包含没有关联数据的记录)
const users = await dataSource
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.where('user.id = :id', { id: 1 })
.getMany();
// Inner Join (只包含有关联数据的记录)
const users = await dataSource
.createQueryBuilder('user')
.innerJoinAndSelect('user.posts', 'post')
.where('user.id = :id', { id: 1 })
.getMany();
// 多层关联
const users = await dataSource
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.leftJoinAndSelect('post.comments', 'comment')
.leftJoinAndSelect('comment.author', 'commentAuthor')
.getMany();
```
### Join 条件
```typescript
const users = await dataSource
.createQueryBuilder('user')
.leftJoin('user.posts', 'post', 'post.status = :status', { status: 'published' })
.addSelect(['post.title', 'post.createdAt'])
.getMany();
```
## 排序和分页
### 排序
```typescript
// 单字段排序
const users = await dataSource
.createQueryBuilder('user')
.orderBy('user.createdAt', 'DESC')
.getMany();
// 多字段排序
const users = await dataSource
.createQueryBuilder('user')
.orderBy('user.createdAt', 'DESC')
.addOrderBy('user.name', 'ASC')
.getMany();
// 随机排序 (MySQL)
const users = await dataSource
.createQueryBuilder('user')
.orderBy('RAND()')
.getMany();
```
### 分页
```typescript
// 基本分页
const page = 1;
const pageSize = 10;
const users = await dataSource
.createQueryBuilder('user')
.skip((page - 1) * pageSize)
.take(pageSize)
.getMany();
// 获取总数和分页数据
const [users, total] = await dataSource
.createQueryBuilder('user')
.skip((page - 1) * pageSize)
.take(pageSize)
.getManyAndCount();
console.log(`Total: ${total}, Page: ${page}, PageSize: ${pageSize}`);
```
## 聚合查询
### Group By 和 Having
```typescript
// 按角色分组统计用户数
const result = await dataSource
.createQueryBuilder('user')
.select('user.role', 'role')
.addSelect('COUNT(*)', 'count')
.groupBy('user.role')
.getRawMany();
// 使用 Having 过滤分组
const result = await dataSource
.createQueryBuilder('user')
.select('user.role', 'role')
.addSelect('COUNT(*)', 'count')
.groupBy('user.role')
.having('COUNT(*) > :minCount', { minCount: 5 })
.getRawMany();
```
### 聚合函数
```typescript
// 统计总数
const count = await dataSource
.createQueryBuilder('user')
.select('COUNT(*)', 'count')
.getRawOne();
// 计算平均值
const avgAge = await dataSource
.createQueryBuilder('user')
.select('AVG(user.age)', 'avgAge')
.getRawOne();
// 求和
const totalScore = await dataSource
.createQueryBuilder('user')
.select('SUM(user.score)', 'totalScore')
.getRawOne();
// 最大值和最小值
const result = await dataSource
.createQueryBuilder('user')
.select('MAX(user.age)', 'maxAge')
.addSelect('MIN(user.age)', 'minAge')
.getRawOne();
```
## 子查询
### 使用 SubQueryFactory
```typescript
import { SubQueryFactory } from 'typeorm';
const users = await dataSource
.createQueryBuilder('user')
.where((qb: SelectQueryBuilder<User>) => {
const subQuery = qb
.subQuery()
.select('post.userId')
.from(Post, 'post')
.where('post.title LIKE :title', { title: '%TypeORM%' })
.getQuery();
return 'user.id IN ' + subQuery;
})
.setParameter('title', '%TypeORM%')
.getMany();
```
### 使用 EXISTS
```typescript
const users = await dataSource
.createQueryBuilder('user')
.where((qb: SelectQueryBuilder<User>) => {
const subQuery = qb
.subQuery()
.select('1')
.from(Post, 'post')
.where('post.userId = user.id')
.getQuery();
return 'EXISTS ' + subQuery;
})
.getMany();
```
## 更新和删除
### 更新操作
```typescript
// 简单更新
await dataSource
.createQueryBuilder(User, 'user')
.update()
.set({ name: 'Updated Name' })
.where('id = :id', { id: 1 })
.execute();
// 条件更新
await dataSource
.createQueryBuilder(User, 'user')
.update()
.set({ isActive: false })
.where('user.lastLoginAt < :date', { date: new Date('2024-01-01') })
.execute();
// 基于子查询的更新
await dataSource
.createQueryBuilder(User, 'user')
.update()
.set({ score: () => 'score + 10' })
.where('user.id IN :ids', { ids: [1, 2, 3] })
.execute();
```
### 删除操作
```typescript
// 简单删除
await dataSource
.createQueryBuilder(User, 'user')
.delete()
.where('id = :id', { id: 1 })
.execute();
// 条件删除
await dataSource
.createQueryBuilder(User, 'user')
.delete()
.where('user.createdAt < :date', { date: new Date('2023-01-01') })
.andWhere('user.isActive = :isActive', { isActive: false })
.execute();
```
## 高级特性
### 原生 SQL
```typescript
const users = await dataSource
.createQueryBuilder(User, 'user')
.where('user.id = :id', { id: 1 })
.andWhere('JSON_CONTAINS(user.preferences, :preferences)', {
preferences: JSON.stringify({ theme: 'dark' })
})
.getMany();
```
### 缓存
```typescript
const users = await dataSource
.createQueryBuilder('user')
.where('user.isActive = :isActive', { isActive: true })
.cache(60000) // 缓存 60 秒
.getMany();
```
### 事务
```typescript
await dataSource.transaction(async transactionalEntityManager => {
const queryRunner = transactionalEntityManager.queryRunner;
await queryRunner.manager
.createQueryBuilder(User, 'user')
.insert()
.values({ name: 'John', email: 'john@example.com' })
.execute();
await queryRunner.manager
.createQueryBuilder(Post, 'post')
.insert()
.values({ title: 'New Post', authorId: 1 })
.execute();
});
```
## 性能优化建议
1. **避免 N+1 查询**: 使用 `leftJoinAndSelect` 一次性加载关联数据
2. **只选择需要的字段**: 使用 `select()` 明确指定需要的列
3. **合理使用索引**: 为常用查询条件添加数据库索引
4. **使用缓存**: 对不常变化的数据启用查询缓存
5. **限制返回结果**: 使用 `take()` 和 `skip()` 实现分页
6. **监控查询性能**: 使用 `getQuery()` 和 `getSql()` 查看生成的 SQL
QueryBuilder 是 TypeORM 中最强大的查询工具,掌握它的使用可以让你构建出高效、灵活的数据库查询。
服务端 · 2月18日 22:19
TypeORM 的核心概念是什么?包括 Entity、Repository、DataSource 等主要组件的详细说明TypeORM 是一个基于 TypeScript 和 JavaScript 的 ORM 框架,它采用 Active Record 和 Data Mapper 两种设计模式,让开发者能够使用面向对象的方式来操作关系型数据库。
## 核心概念
### 1. Entity (实体)
Entity 是 TypeORM 的核心概念,它对应数据库中的表。每个 Entity 类都使用 `@Entity()` 装饰器标记,并映射到数据库表。
```typescript
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column({ unique: true })
email: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
```
### 2. Column (列)
Column 装饰器用于定义实体属性如何映射到数据库列:
* `@Column()`: 基本列定义
* `@PrimaryGeneratedColumn()`: 自增主键
* `@CreateDateColumn()`: 自动创建时间
* `@UpdateDateColumn()`: 自动更新时间
* `@Generated()`: 自动生成值
### 3. Repository (仓储)
Repository 是用于操作实体的主要接口,提供了 CRUD 操作:
```typescript
const userRepository = dataSource.getRepository(User);
// 创建
const user = userRepository.create({ name: 'John', email: 'john@example.com' });
await userRepository.save(user);
// 查询
const users = await userRepository.find();
const user = await userRepository.findOne({ where: { id: 1 } });
// 更新
await userRepository.update(1, { name: 'John Updated' });
// 删除
await userRepository.delete(1);
```
### 4. DataSource (数据源)
DataSource 是数据库连接的配置和管理中心:
```typescript
const dataSource = new DataSource({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'test',
entities: [User],
synchronize: true,
});
```
### 5. Relation (关系)
TypeORM 支持四种主要的关系类型:
* **One-to-One (一对一)**: 使用 `@OneToOne()` 装饰器
* **One-to-Many (一对多)**: 使用 `@OneToMany()` 装饰器
* **Many-to-One (多对一)**: 使用 `@ManyToOne()` 装饰器
* **Many-to-Many (多对多)**: 使用 `@ManyToMany()` 装饰器
```typescript
@Entity()
export class Profile {
@OneToOne(() => User, user => user.profile)
user: User;
}
@Entity()
export class User {
@OneToOne(() => Profile, profile => profile.user)
profile: Profile;
@OneToMany(() => Post, post => post.author)
posts: Post[];
}
```
## 设计模式
### Active Record 模式
在 Active Record 模式中,实体本身包含业务逻辑和数据访问方法:
```typescript
@Entity()
export class User extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
static async findByName(name: string) {
return this.find({ where: { name } });
}
}
// 使用
const users = await User.findByName('John');
```
### Data Mapper 模式
在 Data Mapper 模式中,数据访问逻辑与实体分离,通过 Repository 操作:
```typescript
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}
// 使用 Repository
const userRepository = dataSource.getRepository(User);
const users = await userRepository.find({ where: { name: 'John' } });
```
## 关键特性
1. **类型安全**: 完全支持 TypeScript,提供编译时类型检查
2. **装饰器驱动**: 使用装饰器简化配置
3. **迁移系统**: 支持数据库迁移和版本控制
4. **查询构建器**: 提供灵活的查询构建 API
5. **事务支持**: 完整的事务管理
6. **缓存支持**: 内置查询缓存机制
7. **多数据库支持**: MySQL, PostgreSQL, SQLite, SQL Server, Oracle 等
TypeORM 的这些核心概念使其成为 Node.js 生态中最流行的 ORM 框架之一,特别适合需要类型安全和面向对象编程风格的项目。
服务端 · 2月18日 22:19
TypeORM 如何在微服务架构中使用?包括数据一致性、分布式事务和服务间通信在微服务架构中使用 TypeORM 需要考虑数据一致性、服务间通信、分布式事务等复杂问题。
## 微服务中的数据管理
### 1. 数据库分离策略
```typescript
// 用户服务 - user-service
// config/database.ts
import { DataSource } from 'typeorm';
export const userDataSource = new DataSource({
type: 'postgres',
host: process.env.USER_DB_HOST || 'localhost',
port: parseInt(process.env.USER_DB_PORT || '5432'),
username: process.env.USER_DB_USER || 'user_service',
password: process.env.USER_DB_PASSWORD || 'password',
database: process.env.USER_DB_NAME || 'user_db',
entities: [User, UserProfile, UserSettings],
synchronize: false,
logging: true,
});
// 订单服务 - order-service
export const orderDataSource = new DataSource({
type: 'postgres',
host: process.env.ORDER_DB_HOST || 'localhost',
port: parseInt(process.env.ORDER_DB_PORT || '5432'),
username: process.env.ORDER_DB_USER || 'order_service',
password: process.env.ORDER_DB_PASSWORD || 'password',
database: process.env.ORDER_DB_NAME || 'order_db',
entities: [Order, OrderItem, Payment],
synchronize: false,
logging: true,
});
// 产品服务 - product-service
export const productDataSource = new DataSource({
type: 'postgres',
host: process.env.PRODUCT_DB_HOST || 'localhost',
port: parseInt(process.env.PRODUCT_DB_PORT || '5432'),
username: process.env.PRODUCT_DB_USER || 'product_service',
password: process.env.PRODUCT_DB_PASSWORD || 'password',
database: process.env.PRODUCT_DB_NAME || 'product_db',
entities: [Product, Category, Inventory],
synchronize: false,
logging: true,
});
```
### 2. 服务间数据同步
```typescript
// 用户服务 - 同步用户数据到其他服务
import { EventEmitter } from 'events';
class UserSyncService extends EventEmitter {
async syncUserCreated(user: User) {
// 发送用户创建事件
this.emit('user.created', {
userId: user.id,
email: user.email,
name: user.name,
timestamp: new Date(),
});
}
async syncUserUpdated(user: User) {
// 发送用户更新事件
this.emit('user.updated', {
userId: user.id,
email: user.email,
name: user.name,
timestamp: new Date(),
});
}
async syncUserDeleted(userId: number) {
// 发送用户删除事件
this.emit('user.deleted', {
userId,
timestamp: new Date(),
});
}
}
// 在用户服务中使用
@EventSubscriber()
export class UserSubscriber implements EntitySubscriberInterface<User> {
private syncService: UserSyncService;
constructor() {
this.syncService = new UserSyncService();
}
listenTo() {
return User;
}
afterInsert(event: InsertEvent<User>) {
this.syncService.syncUserCreated(event.entity);
}
afterUpdate(event: UpdateEvent<User>) {
this.syncService.syncUserUpdated(event.entity);
}
afterRemove(event: RemoveEvent<User>) {
this.syncService.syncUserDeleted(event.entity.id);
}
}
```
## 分布式事务处理
### 1. Saga 模式实现
```typescript
// 订单创建 Saga
class OrderCreationSaga {
private steps: SagaStep[] = [];
constructor(
private orderDataSource: DataSource,
private userDataSource: DataSource,
private productDataSource: DataSource
) {
this.setupSteps();
}
private setupSteps() {
// 步骤 1: 验证用户
this.steps.push({
name: 'validateUser',
execute: async (data: any) => {
const userRepo = this.userDataSource.getRepository(User);
const user = await userRepo.findOne({
where: { id: data.userId },
});
if (!user) {
throw new Error('User not found');
}
return { ...data, user };
},
compensate: async (data: any) => {
// 验证步骤无需补偿
},
});
// 步骤 2: 检查库存
this.steps.push({
name: 'checkInventory',
execute: async (data: any) => {
const inventoryRepo = this.productDataSource.getRepository(Inventory);
for (const item of data.items) {
const inventory = await inventoryRepo.findOne({
where: { productId: item.productId },
});
if (!inventory || inventory.quantity < item.quantity) {
throw new Error(`Insufficient inventory for product ${item.productId}`);
}
}
return data;
},
compensate: async (data: any) => {
// 检查库存步骤无需补偿
},
});
// 步骤 3: 扣减库存
this.steps.push({
name: 'reserveInventory',
execute: async (data: any) => {
const inventoryRepo = this.productDataSource.getRepository(Inventory);
for (const item of data.items) {
await inventoryRepo.decrement(
{ productId: item.productId },
'quantity',
item.quantity
);
}
return data;
},
compensate: async (data: any) => {
// 补偿:恢复库存
const inventoryRepo = this.productDataSource.getRepository(Inventory);
for (const item of data.items) {
await inventoryRepo.increment(
{ productId: item.productId },
'quantity',
item.quantity
);
}
},
});
// 步骤 4: 创建订单
this.steps.push({
name: 'createOrder',
execute: async (data: any) => {
const orderRepo = this.orderDataSource.getRepository(Order);
const order = orderRepo.create({
userId: data.userId,
items: data.items,
totalAmount: data.totalAmount,
status: 'pending',
});
const savedOrder = await orderRepo.save(order);
return { ...data, orderId: savedOrder.id };
},
compensate: async (data: any) => {
// 补偿:删除订单
const orderRepo = this.orderDataSource.getRepository(Order);
await orderRepo.delete(data.orderId);
},
});
// 步骤 5: 处理支付
this.steps.push({
name: 'processPayment',
execute: async (data: any) => {
const paymentRepo = this.orderDataSource.getRepository(Payment);
const payment = paymentRepo.create({
orderId: data.orderId,
amount: data.totalAmount,
status: 'processing',
});
await paymentRepo.save(payment);
// 模拟支付处理
await this.processPaymentAsync(payment);
return data;
},
compensate: async (data: any) => {
// 补偿:取消支付
const paymentRepo = this.orderDataSource.getRepository(Payment);
await paymentRepo.update(
{ orderId: data.orderId },
{ status: 'cancelled' }
);
},
});
}
async execute(data: any): Promise<any> {
const executedSteps: SagaStep[] = [];
try {
for (const step of this.steps) {
console.log(`Executing step: ${step.name}`);
data = await step.execute(data);
executedSteps.push(step);
}
// 所有步骤成功,更新订单状态
await this.orderDataSource.getRepository(Order).update(
{ id: data.orderId },
{ status: 'completed' }
);
return data;
} catch (error) {
console.error('Saga failed, compensating...', error);
// 执行补偿操作
for (let i = executedSteps.length - 1; i >= 0; i--) {
const step = executedSteps[i];
console.log(`Compensating step: ${step.name}`);
try {
await step.compensate(data);
} catch (compensationError) {
console.error(`Compensation failed for step ${step.name}:`, compensationError);
}
}
throw error;
}
}
private async processPaymentAsync(payment: Payment): Promise<void> {
// 模拟支付处理
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000);
});
}
}
interface SagaStep {
name: string;
execute: (data: any) => Promise<any>;
compensate: (data: any) => Promise<void>;
}
```
### 2. 两阶段提交 (2PC)
```typescript
// 两阶段提交协调器
class TwoPhaseCommitCoordinator {
private participants: TwoPhaseCommitParticipant[] = [];
registerParticipant(participant: TwoPhaseCommitParticipant) {
this.participants.push(participant);
}
async execute(): Promise<void> {
// 阶段 1: 准备
console.log('Phase 1: Prepare');
for (const participant of this.participants) {
await participant.prepare();
}
// 阶段 2: 提交
console.log('Phase 2: Commit');
for (const participant of this.participants) {
await participant.commit();
}
}
async rollback(): Promise<void> {
console.log('Rolling back');
for (const participant of this.participants) {
await participant.rollback();
}
}
}
interface TwoPhaseCommitParticipant {
prepare(): Promise<void>;
commit(): Promise<void>;
rollback(): Promise<void>;
}
// 用户服务参与者
class UserServiceParticipant implements TwoPhaseCommitParticipant {
constructor(private dataSource: DataSource) {}
async prepare(): Promise<void> {
console.log('UserService: Preparing...');
// 准备用户数据
}
async commit(): Promise<void> {
console.log('UserService: Committing...');
// 提交用户数据
}
async rollback(): Promise<void> {
console.log('UserService: Rolling back...');
// 回滚用户数据
}
}
// 订单服务参与者
class OrderServiceParticipant implements TwoPhaseCommitParticipant {
constructor(private dataSource: DataSource) {}
async prepare(): Promise<void> {
console.log('OrderService: Preparing...');
// 准备订单数据
}
async commit(): Promise<void> {
console.log('OrderService: Committing...');
// 提交订单数据
}
async rollback(): Promise<void> {
console.log('OrderService: Rolling back...');
// 回滚订单数据
}
}
```
## 事件驱动架构
### 1. 事件总线实现
```typescript
// 事件总线
class EventBus {
private eventQueue: any[] = [];
private subscribers: Map<string, Function[]> = new Map();
publish(event: any) {
console.log(`Publishing event: ${event.type}`);
this.eventQueue.push(event);
this.processEvent(event);
}
subscribe(eventType: string, handler: Function) {
if (!this.subscribers.has(eventType)) {
this.subscribers.set(eventType, []);
}
this.subscribers.get(eventType)!.push(handler);
}
private processEvent(event: any) {
const handlers = this.subscribers.get(event.type);
if (handlers) {
handlers.forEach(handler => {
try {
handler(event);
} catch (error) {
console.error(`Error processing event ${event.type}:`, error);
}
});
}
}
}
// 全局事件总线实例
export const eventBus = new EventBus();
// 在用户服务中发布事件
@EventSubscriber()
export class UserEventPublisher implements EntitySubscriberInterface<User> {
listenTo() {
return User;
}
afterInsert(event: InsertEvent<User>) {
eventBus.publish({
type: 'user.created',
payload: {
userId: event.entity.id,
email: event.entity.email,
name: event.entity.name,
timestamp: new Date(),
},
});
}
afterUpdate(event: UpdateEvent<User>) {
eventBus.publish({
type: 'user.updated',
payload: {
userId: event.entity.id,
email: event.entity.email,
name: event.entity.name,
timestamp: new Date(),
},
});
}
}
// 在订单服务中订阅用户事件
eventBus.subscribe('user.created', async (event: any) => {
console.log('OrderService received user.created event:', event);
// 处理用户创建事件
await handleUserCreated(event.payload);
});
eventBus.subscribe('user.updated', async (event: any) => {
console.log('OrderService received user.updated event:', event);
// 处理用户更新事件
await handleUserUpdated(event.payload);
});
```
### 2. 消息队列集成
```typescript
// 使用 RabbitMQ 作为消息队列
import amqp from 'amqplib';
class MessageQueueService {
private connection: any;
private channel: any;
async connect() {
this.connection = await amqp.connect('amqp://localhost');
this.channel = await this.connection.createChannel();
}
async publish(queue: string, message: any) {
await this.channel.assertQueue(queue, { durable: true });
this.channel.sendToQueue(
queue,
Buffer.from(JSON.stringify(message)),
{ persistent: true }
);
}
async subscribe(queue: string, handler: Function) {
await this.channel.assertQueue(queue, { durable: true });
this.channel.consume(queue, async (msg: any) => {
try {
const message = JSON.parse(msg.content.toString());
await handler(message);
this.channel.ack(msg);
} catch (error) {
console.error('Error processing message:', error);
this.channel.nack(msg, false, true); // 重新入队
}
});
}
async close() {
await this.channel.close();
await this.connection.close();
}
}
// 在用户服务中发布消息
const messageQueue = new MessageQueueService();
await messageQueue.connect();
@EventSubscriber()
export class UserMessagePublisher implements EntitySubscriberInterface<User> {
listenTo() {
return User;
}
async afterInsert(event: InsertEvent<User>) {
await messageQueue.publish('user.created', {
userId: event.entity.id,
email: event.entity.email,
name: event.entity.name,
timestamp: new Date(),
});
}
}
// 在订单服务中订阅消息
await messageQueue.subscribe('user.created', async (message: any) => {
console.log('OrderService received user.created message:', message);
await handleUserCreated(message);
});
```
## 数据一致性策略
### 1. 最终一致性
```typescript
// 用户数据同步服务
class UserDataSyncService {
async syncUserToOrderService(user: User) {
try {
// 调用订单服务 API
await fetch('http://order-service/api/users/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId: user.id,
email: user.email,
name: user.name,
}),
});
} catch (error) {
// 记录失败,稍后重试
console.error('Failed to sync user to order service:', error);
await this.scheduleRetry(user);
}
}
async scheduleRetry(user: User) {
// 使用延迟队列重试
setTimeout(() => {
this.syncUserToOrderService(user);
}, 5000); // 5 秒后重试
}
}
// 在用户服务中使用
@EventSubscriber()
export class UserSyncSubscriber implements EntitySubscriberInterface<User> {
private syncService: UserDataSyncService;
constructor() {
this.syncService = new UserDataSyncService();
}
listenTo() {
return User;
}
afterInsert(event: InsertEvent<User>) {
this.syncService.syncUserToOrderService(event.entity);
}
afterUpdate(event: UpdateEvent<User>) {
this.syncService.syncUserToOrderService(event.entity);
}
}
```
### 2. 幂等性处理
```typescript
// 幂等性处理器
class IdempotencyHandler {
private processedIds: Set<string> = new Set();
async processWithIdempotency(
id: string,
operation: () => Promise<void>
): Promise<void> {
if (this.processedIds.has(id)) {
console.log(`Operation ${id} already processed, skipping`);
return;
}
await operation();
this.processedIds.add(id);
}
// 使用数据库存储已处理的 ID
async processWithDatabaseIdempotency(
id: string,
operation: () => Promise<void>
): Promise<void> {
const idempotencyRepo = this.dataSource.getRepository(IdempotencyKey);
const existing = await idempotencyRepo.findOne({
where: { key: id },
});
if (existing) {
console.log(`Operation ${id} already processed, skipping`);
return;
}
await operation();
await idempotencyRepo.save({
key: id,
processedAt: new Date(),
});
}
}
// 在消息处理中使用
const idempotencyHandler = new IdempotencyHandler();
await messageQueue.subscribe('user.created', async (message: any) => {
await idempotencyHandler.processWithDatabaseIdempotency(
`user.created.${message.userId}`,
async () => {
await handleUserCreated(message);
}
);
});
```
## 服务发现与负载均衡
### 1. 服务注册
```typescript
// 服务注册中心
class ServiceRegistry {
private services: Map<string, ServiceInstance[]> = new Map();
register(serviceName: string, instance: ServiceInstance) {
if (!this.services.has(serviceName)) {
this.services.set(serviceName, []);
}
this.services.get(serviceName)!.push(instance);
console.log(`Registered ${serviceName} instance:`, instance);
}
unregister(serviceName: string, instance: ServiceInstance) {
const instances = this.services.get(serviceName);
if (instances) {
const index = instances.indexOf(instance);
if (index > -1) {
instances.splice(index, 1);
console.log(`Unregistered ${serviceName} instance:`, instance);
}
}
}
discover(serviceName: string): ServiceInstance | null {
const instances = this.services.get(serviceName);
if (!instances || instances.length === 0) {
return null;
}
// 轮询负载均衡
const index = Math.floor(Math.random() * instances.length);
return instances[index];
}
}
interface ServiceInstance {
host: string;
port: number;
healthCheckUrl: string;
}
// 在服务启动时注册
const serviceRegistry = new ServiceRegistry();
serviceRegistry.register('user-service', {
host: 'localhost',
port: 3001,
healthCheckUrl: 'http://localhost:3001/health',
});
serviceRegistry.register('order-service', {
host: 'localhost',
port: 3002,
healthCheckUrl: 'http://localhost:3002/health',
});
```
### 2. 健康检查
```typescript
// 健康检查服务
class HealthCheckService {
async checkService(instance: ServiceInstance): Promise<boolean> {
try {
const response = await fetch(instance.healthCheckUrl);
return response.ok;
} catch (error) {
console.error(`Health check failed for ${instance.host}:${instance.port}:`, error);
return false;
}
}
async startHealthChecks(registry: ServiceRegistry) {
setInterval(async () => {
const services = ['user-service', 'order-service', 'product-service'];
for (const serviceName of services) {
const instance = registry.discover(serviceName);
if (instance) {
const isHealthy = await this.checkService(instance);
if (!isHealthy) {
console.log(`Service ${serviceName} is unhealthy, removing from registry`);
registry.unregister(serviceName, instance);
}
}
}
}, 30000); // 每 30 秒检查一次
}
}
// 启动健康检查
const healthCheckService = new HealthCheckService();
healthCheckService.startHealthChecks(serviceRegistry);
```
TypeORM 在微服务架构中的应用需要仔细设计数据管理、事务处理和通信机制,以确保系统的可靠性和可扩展性。
服务端 · 2月18日 22:18
TypeORM 如何进行性能优化?包括查询优化、索引使用和批量操作性能优化是数据库操作中的关键环节,TypeORM 提供了多种优化策略和技巧。
## 查询优化
### 1. 选择性加载字段
```typescript
// ❌ 不好的做法:加载所有字段
const users = await userRepository.find();
// ✅ 好的做法:只加载需要的字段
const users = await userRepository.find({
select: ['id', 'name', 'email'],
});
// 使用 QueryBuilder 选择字段
const users = await userRepository
.createQueryBuilder('user')
.select(['user.id', 'user.name', 'user.email'])
.getMany();
// 排除特定字段
const users = await userRepository.find({
select: {
id: true,
name: true,
email: true,
password: false, // 排除密码字段
},
});
```
### 2. 使用索引
```typescript
import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm';
@Entity()
@Index(['email']) // 单列索引
@Index(['name', 'email']) // 复合索引
@Index(['createdAt']) // 普通索引
@Index(['status'], { unique: true }) // 唯一索引
@Index(['name'], { fulltext: true }) // 全文索引
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
@Index() // 列级索引
name: string;
@Column()
@Index({ unique: true })
email: string;
@Column()
status: string;
@Column({ type: 'timestamp' })
createdAt: Date;
@Column({ type: 'text' })
bio: string;
}
// 在查询中使用索引
const users = await userRepository.find({
where: {
email: 'john@example.com', // 使用索引
},
});
// 使用复合索引
const users = await userRepository.find({
where: {
name: 'John',
email: 'john@example.com',
},
});
```
### 3. 分页优化
```typescript
// 使用 skip 和 take 分页
const users = await userRepository.find({
skip: 0,
take: 10,
});
// 使用 QueryBuilder 分页
const users = await userRepository
.createQueryBuilder('user')
.skip(0)
.take(10)
.getMany();
// 使用游标分页(性能更好)
const users = await userRepository
.createQueryBuilder('user')
.where('user.id > :cursor', { cursor: lastId })
.orderBy('user.id', 'ASC')
.take(10)
.getMany();
// 使用 LIMIT 和 OFFSET
const users = await userRepository
.createQueryBuilder('user')
.limit(10)
.offset(0)
.getMany();
```
## 关联优化
### 1. 懒加载 vs 急切加载
```typescript
// ❌ 不好的做法:N+1 查询问题
const users = await userRepository.find();
for (const user of users) {
const posts = await user.posts; // 每次都查询数据库
}
// ✅ 好的做法:使用急切加载
const users = await userRepository.find({
relations: ['posts'],
});
// 使用 QueryBuilder 急切加载
const users = await userRepository
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.getMany();
// 选择性加载关联
const users = await userRepository.find({
relations: {
posts: true,
profile: false, // 不加载 profile
},
});
// 使用 join 加载关联
const users = await userRepository
.createQueryBuilder('user')
.leftJoin('user.posts', 'post')
.addSelect(['post.id', 'post.title'])
.getMany();
```
### 2. 关联查询优化
```typescript
// 使用 innerJoin 代替 leftJoin(如果确定有关联数据)
const users = await userRepository
.createQueryBuilder('user')
.innerJoin('user.posts', 'post')
.where('post.published = :published', { published: true })
.getMany();
// 使用 join 条件过滤
const users = await userRepository
.createQueryBuilder('user')
.leftJoin('user.posts', 'post', 'post.published = :published', { published: true })
.getMany();
// 使用子查询优化
const users = await userRepository
.createQueryBuilder('user')
.where((qb) => {
const subQuery = qb
.subQuery()
.select('post.userId')
.from(Post, 'post')
.where('post.published = :published', { published: true })
.getQuery();
return 'user.id IN ' + subQuery;
})
.setParameter('published', true)
.getMany();
```
## 批量操作优化
### 1. 批量插入
```typescript
// ❌ 不好的做法:循环插入
for (const userData of usersData) {
const user = userRepository.create(userData);
await userRepository.save(user);
}
// ✅ 好的做法:批量插入
const users = usersData.map(userData => userRepository.create(userData));
await userRepository.save(users);
// 使用 QueryBuilder 批量插入
await userRepository
.createQueryBuilder()
.insert()
.into(User)
.values(usersData)
.execute();
// 使用 bulk insert
await userRepository.insert(usersData);
```
### 2. 批量更新
```typescript
// ❌ 不好的做法:循环更新
for (const user of users) {
await userRepository.update(user.id, { status: 'active' });
}
// ✅ 好的做法:批量更新
await userRepository.update(
{ status: 'inactive' },
{ status: 'active' }
);
// 使用 QueryBuilder 批量更新
await userRepository
.createQueryBuilder()
.update(User)
.set({ status: 'active' })
.where('status = :status', { status: 'inactive' })
.execute();
// 使用 CASE WHEN 批量更新
await userRepository
.createQueryBuilder()
.update(User)
.set({
status: () => 'CASE WHEN age > 18 THEN "active" ELSE "inactive" END',
})
.execute();
```
### 3. 批量删除
```typescript
// ❌ 不好的做法:循环删除
for (const user of users) {
await userRepository.delete(user.id);
}
// ✅ 好的做法:批量删除
await userRepository.delete({ status: 'deleted' });
// 使用 QueryBuilder 批量删除
await userRepository
.createQueryBuilder()
.delete()
.from(User)
.where('status = :status', { status: 'deleted' })
.execute();
// 使用软删除批量删除
await userRepository.softDelete({ status: 'deleted' });
```
## 事务优化
### 1. 事务范围控制
```typescript
// ❌ 不好的做法:大事务
await dataSource.transaction(async (manager) => {
// 执行大量操作
for (let i = 0; i < 1000; i++) {
await manager.save(User, { name: `User ${i}` });
}
});
// ✅ 好的做法:分批处理
const batchSize = 100;
for (let i = 0; i < 1000; i += batchSize) {
await dataSource.transaction(async (manager) => {
const users = [];
for (let j = i; j < i + batchSize && j < 1000; j++) {
users.push({ name: `User ${j}` });
}
await manager.save(User, users);
});
}
```
### 2. 使用 QueryRunner 优化
```typescript
// 使用 QueryRunner 获得更好的性能控制
const queryRunner = dataSource.createQueryRunner();
try {
await queryRunner.connect();
await queryRunner.startTransaction();
// 执行多个操作
await queryRunner.manager.save(User, user1);
await queryRunner.manager.save(User, user2);
await queryRunner.commitTransaction();
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
```
## 缓存优化
### 1. 查询缓存
```typescript
// 启用查询缓存
const users = await userRepository.find({
cache: true,
});
// 自定义缓存时间
const users = await userRepository.find({
cache: {
id: 'users_list',
milliseconds: 60000, // 60 秒
},
});
// 使用 QueryBuilder 缓存
const users = await userRepository
.createQueryBuilder('user')
.cache(true)
.getMany();
// 清除缓存
await dataSource.queryResultCache?.remove(['users_list']);
```
### 2. 应用级缓存
```typescript
// 使用内存缓存
const cache = new Map<string, any>();
async function getUserWithCache(userId: number) {
const cacheKey = `user:${userId}`;
// 尝试从缓存获取
if (cache.has(cacheKey)) {
return cache.get(cacheKey);
}
// 从数据库获取
const user = await userRepository.findOne({
where: { id: userId },
});
// 存入缓存
cache.set(cacheKey, user);
return user;
}
```
## 数据库连接优化
### 1. 连接池配置
```typescript
const dataSource = new DataSource({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'myapp',
entities: [User, Post],
synchronize: false,
logging: true,
// 连接池配置
extra: {
connectionLimit: 20, // 连接池大小
acquireTimeout: 60000, // 获取连接超时时间
timeout: 60000, // 查询超时时间
waitForConnections: true, // 等待可用连接
queueLimit: 100, // 队列限制
},
});
```
### 2. 读写分离
```typescript
// 主数据库(写操作)
const writeDataSource = new DataSource({
type: 'mysql',
name: 'write',
host: 'write-db.example.com',
port: 3306,
username: 'root',
password: 'password',
database: 'myapp',
entities: [User, Post],
synchronize: false,
logging: true,
});
// 从数据库(读操作)
const readDataSource = new DataSource({
type: 'mysql',
name: 'read',
host: 'read-db.example.com',
port: 3306,
username: 'root',
password: 'password',
database: 'myapp',
entities: [User, Post],
synchronize: false,
logging: true,
});
// 使用读写分离
class UserRepository {
async findById(id: number) {
// 从从数据库读取
const repository = readDataSource.getRepository(User);
return await repository.findOne({ where: { id } });
}
async save(user: User) {
// 写入主数据库
const repository = writeDataSource.getRepository(User);
return await repository.save(user);
}
}
```
## 监控和调优
### 1. 查询日志分析
```typescript
// 启用查询日志
const dataSource = new DataSource({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'myapp',
entities: [User, Post],
synchronize: false,
logging: ['query', 'error', 'slow'], // 记录查询、错误和慢查询
maxQueryExecutionTime: 1000, // 慢查询阈值(毫秒)
});
// 自定义日志记录
const dataSource = new DataSource({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'myapp',
entities: [User, Post],
synchronize: false,
logger: new QueryLogger(),
});
class QueryLogger implements Logger {
logQuery(query: string, parameters?: any[]) {
console.log('Query:', query);
console.log('Parameters:', parameters);
}
logQueryError(error: string, query: string, parameters?: any[]) {
console.error('Query Error:', error);
console.error('Query:', query);
}
logQuerySlow(time: number, query: string, parameters?: any[]) {
console.warn('Slow Query:', query);
console.warn('Execution Time:', time);
}
}
```
### 2. 性能监控
```typescript
// 查询性能监控
class QueryPerformanceMonitor {
private queries: Map<string, number[]> = new Map();
trackQuery(query: string, executionTime: number) {
if (!this.queries.has(query)) {
this.queries.set(query, []);
}
this.queries.get(query)!.push(executionTime);
}
getAverageExecutionTime(query: string): number {
const times = this.queries.get(query);
if (!times || times.length === 0) {
return 0;
}
const sum = times.reduce((a, b) => a + b, 0);
return sum / times.length;
}
getSlowQueries(threshold: number = 1000): string[] {
const slowQueries: string[] = [];
for (const [query, times] of this.queries.entries()) {
const avgTime = this.getAverageExecutionTime(query);
if (avgTime > threshold) {
slowQueries.push(query);
}
}
return slowQueries;
}
}
// 使用性能监控
const monitor = new QueryPerformanceMonitor();
const startTime = Date.now();
const users = await userRepository.find();
const executionTime = Date.now() - startTime;
monitor.trackQuery('SELECT * FROM user', executionTime);
```
## 最佳实践
### 1. 避免 N+1 查询
```typescript
// ❌ 不好的做法:N+1 查询
const users = await userRepository.find();
for (const user of users) {
const posts = await user.posts; // N+1 查询
}
// ✅ 好的做法:使用急切加载
const users = await userRepository.find({
relations: ['posts'],
});
// ✅ 好的做法:使用 join
const users = await userRepository
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.getMany();
```
### 2. 使用适当的数据类型
```typescript
// ✅ 好的做法:使用适当的数据类型
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar', length: 255 }) // 限制长度
name: string;
@Column({ type: 'int' }) // 使用整数
age: number;
@Column({ type: 'decimal', precision: 10, scale: 2 }) // 使用精确小数
balance: number;
@Column({ type: 'boolean' }) // 使用布尔值
isActive: boolean;
@Column({ type: 'enum', enum: ['active', 'inactive'] }) // 使用枚举
status: string;
@Column({ type: 'json' }) // 使用 JSON
metadata: any;
}
```
### 3. 定期维护
```typescript
// 定期清理数据
async function cleanupOldData() {
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
await userRepository.delete({
createdAt: LessThan(thirtyDaysAgo),
status: 'deleted',
});
}
// 定期重建索引
async function rebuildIndexes() {
await dataSource.query('ANALYZE TABLE user');
await dataSource.query('OPTIMIZE TABLE user');
}
// 定期更新统计信息
async function updateStatistics() {
await dataSource.query('ANALYZE TABLE user');
}
```
TypeORM 的性能优化需要综合考虑查询、关联、批量操作、事务等多个方面,合理使用优化技巧可以显著提高应用性能。
服务端 · 2月18日 22:18
TypeORM 如何进行测试?包括单元测试、集成测试和 Mock 策略测试是保证代码质量的重要环节,TypeORM 提供了多种测试策略和工具,让开发者能够轻松编写和运行测试。
## 测试环境配置
### 使用内存数据库
```typescript
import { DataSource } from 'typeorm';
import { User } from '../entity/User';
// 使用 SQLite 内存数据库进行测试
export const testDataSource = new DataSource({
type: 'sqlite',
database: ':memory:', // 内存数据库
entities: [User],
synchronize: true, // 自动同步表结构
logging: false,
dropSchema: true, // 每次测试前清空数据库
});
// 测试套件设置
beforeAll(async () => {
await testDataSource.initialize();
});
afterAll(async () => {
await testDataSource.destroy();
});
// 每个测试前清空数据库
beforeEach(async () => {
await testDataSource.synchronize(true);
});
```
### 使用测试数据库
```typescript
// 使用独立的测试数据库
export const testDataSource = new DataSource({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'test_user',
password: 'test_password',
database: 'test_db', // 测试数据库
entities: [User, Post],
synchronize: true,
logging: false,
});
// 在 package.json 中配置测试脚本
{
"scripts": {
"test": "NODE_ENV=test jest",
"test:watch": "NODE_ENV=test jest --watch",
"test:coverage": "NODE_ENV=test jest --coverage"
}
}
```
## 单元测试
### 测试实体
```typescript
import { User } from '../entity/User';
describe('User Entity', () => {
it('should create a user with valid data', () => {
const user = new User();
user.name = 'John Doe';
user.email = 'john@example.com';
user.age = 25;
expect(user.name).toBe('John Doe');
expect(user.email).toBe('john@example.com');
expect(user.age).toBe(25);
});
it('should validate email format', () => {
const user = new User();
user.email = 'invalid-email';
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
expect(emailRegex.test(user.email)).toBe(false);
});
it('should validate age range', () => {
const user = new User();
user.age = 15;
expect(user.age).toBeLessThan(18);
});
});
```
### 测试 Repository
```typescript
import { DataSource } from 'typeorm';
import { User } from '../entity/User';
import { testDataSource } from './testDataSource';
describe('User Repository', () => {
let userRepository: any;
beforeAll(async () => {
await testDataSource.initialize();
userRepository = testDataSource.getRepository(User);
});
afterAll(async () => {
await testDataSource.destroy();
});
beforeEach(async () => {
// 清空数据库
await userRepository.clear();
});
it('should create a user', async () => {
const user = userRepository.create({
name: 'John Doe',
email: 'john@example.com',
age: 25,
});
const savedUser = await userRepository.save(user);
expect(savedUser.id).toBeDefined();
expect(savedUser.name).toBe('John Doe');
expect(savedUser.email).toBe('john@example.com');
});
it('should find user by id', async () => {
const user = userRepository.create({
name: 'John Doe',
email: 'john@example.com',
age: 25,
});
const savedUser = await userRepository.save(user);
const foundUser = await userRepository.findOne({
where: { id: savedUser.id },
});
expect(foundUser).toBeDefined();
expect(foundUser.name).toBe('John Doe');
});
it('should find users by email', async () => {
await userRepository.save({
name: 'John Doe',
email: 'john@example.com',
age: 25,
});
const users = await userRepository.find({
where: { email: 'john@example.com' },
});
expect(users.length).toBe(1);
expect(users[0].name).toBe('John Doe');
});
it('should update user', async () => {
const user = userRepository.create({
name: 'John Doe',
email: 'john@example.com',
age: 25,
});
const savedUser = await userRepository.save(user);
savedUser.name = 'Jane Doe';
await userRepository.save(savedUser);
const updatedUser = await userRepository.findOne({
where: { id: savedUser.id },
});
expect(updatedUser.name).toBe('Jane Doe');
});
it('should delete user', async () => {
const user = userRepository.create({
name: 'John Doe',
email: 'john@example.com',
age: 25,
});
const savedUser = await userRepository.save(user);
await userRepository.delete(savedUser.id);
const deletedUser = await userRepository.findOne({
where: { id: savedUser.id },
});
expect(deletedUser).toBeNull();
});
});
```
## 集成测试
### 测试服务层
```typescript
import { UserService } from '../service/UserService';
import { testDataSource } from './testDataSource';
describe('UserService Integration Tests', () => {
let userService: UserService;
beforeAll(async () => {
await testDataSource.initialize();
userService = new UserService(testDataSource);
});
afterAll(async () => {
await testDataSource.destroy();
});
beforeEach(async () => {
// 清空数据库
const userRepository = testDataSource.getRepository(User);
await userRepository.clear();
});
it('should create user with validation', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
age: 25,
};
const user = await userService.createUser(userData);
expect(user.id).toBeDefined();
expect(user.name).toBe('John Doe');
});
it('should throw error for invalid email', async () => {
const userData = {
name: 'John Doe',
email: 'invalid-email',
age: 25,
};
await expect(userService.createUser(userData)).rejects.toThrow(
'Invalid email format'
);
});
it('should find user by email', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
age: 25,
};
await userService.createUser(userData);
const user = await userService.findByEmail('john@example.com');
expect(user).toBeDefined();
expect(user.name).toBe('John Doe');
});
});
```
### 测试复杂查询
```typescript
describe('Complex Query Tests', () => {
let userRepository: any;
let postRepository: any;
beforeAll(async () => {
await testDataSource.initialize();
userRepository = testDataSource.getRepository(User);
postRepository = testDataSource.getRepository(Post);
});
afterAll(async () => {
await testDataSource.destroy();
});
beforeEach(async () => {
await userRepository.clear();
await postRepository.clear();
});
it('should find users with posts', async () => {
const user = userRepository.create({
name: 'John Doe',
email: 'john@example.com',
});
const savedUser = await userRepository.save(user);
await postRepository.save({
title: 'Post 1',
author: savedUser,
});
await postRepository.save({
title: 'Post 2',
author: savedUser,
});
const usersWithPosts = await userRepository.find({
relations: ['posts'],
where: { id: savedUser.id },
});
expect(usersWithPosts[0].posts.length).toBe(2);
});
it('should use query builder for complex queries', async () => {
const users = await userRepository
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.where('user.age > :age', { age: 18 })
.orderBy('user.createdAt', 'DESC')
.getMany();
expect(Array.isArray(users)).toBe(true);
});
});
```
## Mock 和 Stub
### Mock Repository
```typescript
import { Repository } from 'typeorm';
import { User } from '../entity/User';
describe('UserService with Mock Repository', () => {
let userService: UserService;
let userRepositoryMock: jest.Mocked<Repository<User>>;
beforeEach(() => {
// 创建 Mock Repository
userRepositoryMock = {
create: jest.fn(),
save: jest.fn(),
findOne: jest.fn(),
find: jest.fn(),
delete: jest.fn(),
} as any;
userService = new UserService(userRepositoryMock);
});
it('should create user', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
age: 25,
};
userRepositoryMock.create.mockReturnValue(userData);
userRepositoryMock.save.mockResolvedValue({
id: 1,
...userData,
});
const user = await userService.createUser(userData);
expect(userRepositoryMock.create).toHaveBeenCalledWith(userData);
expect(userRepositoryMock.save).toHaveBeenCalled();
expect(user.id).toBe(1);
});
it('should find user by email', async () => {
const user = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
age: 25,
};
userRepositoryMock.findOne.mockResolvedValue(user);
const foundUser = await userService.findByEmail('john@example.com');
expect(userRepositoryMock.findOne).toHaveBeenCalledWith({
where: { email: 'john@example.com' },
});
expect(foundUser).toEqual(user);
});
});
```
### Mock DataSource
```typescript
import { DataSource } from 'typeorm';
describe('Service with Mock DataSource', () => {
let dataSourceMock: jest.Mocked<DataSource>;
let service: MyService;
beforeEach(() => {
dataSourceMock = {
getRepository: jest.fn(),
createQueryBuilder: jest.fn(),
transaction: jest.fn(),
} as any;
service = new MyService(dataSourceMock);
});
it('should use repository from data source', async () => {
const repositoryMock = {
find: jest.fn().mockResolvedValue([]),
};
dataSourceMock.getRepository.mockReturnValue(repositoryMock);
await service.findAll();
expect(dataSourceMock.getRepository).toHaveBeenCalledWith(User);
expect(repositoryMock.find).toHaveBeenCalled();
});
});
```
## 事务测试
### 测试事务回滚
```typescript
describe('Transaction Tests', () => {
let dataSource: DataSource;
beforeAll(async () => {
dataSource = new DataSource({
type: 'sqlite',
database: ':memory:',
entities: [User, Account],
synchronize: true,
});
await dataSource.initialize();
});
afterAll(async () => {
await dataSource.destroy();
});
it('should rollback transaction on error', async () => {
const userRepository = dataSource.getRepository(User);
const accountRepository = dataSource.getRepository(Account);
// 创建初始用户
const user = userRepository.create({
name: 'John Doe',
email: 'john@example.com',
});
await userRepository.save(user);
const initialBalance = 1000;
// 创建账户
const account = accountRepository.create({
userId: user.id,
balance: initialBalance,
});
await accountRepository.save(account);
// 测试事务回滚
await expect(
dataSource.transaction(async (manager) => {
const userRepo = manager.getRepository(User);
const accountRepo = manager.getRepository(Account);
// 扣款
const account = await accountRepo.findOne({
where: { userId: user.id },
});
account.balance -= 500;
await accountRepo.save(account);
// 模拟错误
throw new Error('Transaction failed');
})
).rejects.toThrow('Transaction failed');
// 验证事务已回滚
const finalAccount = await accountRepository.findOne({
where: { userId: user.id },
});
expect(finalAccount.balance).toBe(initialBalance);
});
it('should commit transaction on success', async () => {
const userRepository = dataSource.getRepository(User);
const accountRepository = dataSource.getRepository(Account);
const user = userRepository.create({
name: 'Jane Doe',
email: 'jane@example.com',
});
await userRepository.save(user);
const initialBalance = 1000;
const account = accountRepository.create({
userId: user.id,
balance: initialBalance,
});
await accountRepository.save(account);
// 测试事务提交
await dataSource.transaction(async (manager) => {
const accountRepo = manager.getRepository(Account);
const account = await accountRepo.findOne({
where: { userId: user.id },
});
account.balance -= 500;
await accountRepo.save(account);
});
// 验证事务已提交
const finalAccount = await accountRepository.findOne({
where: { userId: user.id },
});
expect(finalAccount.balance).toBe(500);
});
});
```
## 测试工具和库
### 使用 Jest
```typescript
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
transform: {
'^.+\\.ts$': 'ts-jest',
},
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/**/__tests__/**',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};
// package.json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
```
### 使用 Supertest (API 测试)
```typescript
import request from 'supertest';
import { app } from '../app';
import { testDataSource } from './testDataSource';
describe('User API Tests', () => {
let server: any;
beforeAll(async () => {
await testDataSource.initialize();
server = app.listen(0); // 随机端口
});
afterAll(async () => {
await testDataSource.destroy();
server.close();
});
beforeEach(async () => {
const userRepository = testDataSource.getRepository(User);
await userRepository.clear();
});
it('should create user via POST', async () => {
const response = await request(server)
.post('/api/users')
.send({
name: 'John Doe',
email: 'john@example.com',
age: 25,
})
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.name).toBe('John Doe');
});
it('should get user by ID', async () => {
const userRepository = testDataSource.getRepository(User);
const user = userRepository.create({
name: 'John Doe',
email: 'john@example.com',
age: 25,
});
const savedUser = await userRepository.save(user);
const response = await request(server)
.get(`/api/users/${savedUser.id}`)
.expect(200);
expect(response.body.id).toBe(savedUser.id);
expect(response.body.name).toBe('John Doe');
});
it('should return 404 for non-existent user', async () => {
const response = await request(server)
.get('/api/users/999')
.expect(404);
expect(response.body).toHaveProperty('error');
});
});
```
## 测试最佳实践
### 1. 隔离测试
```typescript
// ✅ 好的做法:每个测试独立
describe('User Repository', () => {
beforeEach(async () => {
// 每个测试前清空数据库
await userRepository.clear();
});
it('test 1', async () => {
// 独立的测试
});
it('test 2', async () => {
// 不依赖 test 1
});
});
// ❌ 不好的做法:测试相互依赖
describe('User Repository', () => {
let userId: number;
it('test 1', async () => {
const user = await userRepository.save({ name: 'John' });
userId = user.id;
});
it('test 2', async () => {
// 依赖 test 1 的结果
const user = await userRepository.findOne({ where: { id: userId } });
});
});
```
### 2. 使用测试数据工厂
```typescript
// factory/UserFactory.ts
export class UserFactory {
static create(overrides: Partial<User> = {}): User {
const user = new User();
user.name = 'John Doe';
user.email = 'john@example.com';
user.age = 25;
Object.assign(user, overrides);
return user;
}
static createMany(count: number, overrides: Partial<User> = {}): User[] {
return Array.from({ length: count }, () => this.create(overrides));
}
}
// 在测试中使用
describe('User Repository', () => {
it('should create user', async () => {
const user = UserFactory.create({
name: 'Jane Doe',
email: 'jane@example.com',
});
const savedUser = await userRepository.save(user);
expect(savedUser.name).toBe('Jane Doe');
});
it('should create multiple users', async () => {
const users = UserFactory.createMany(5);
const savedUsers = await userRepository.save(users);
expect(savedUsers.length).toBe(5);
});
});
```
### 3. 测试边界情况
```typescript
describe('User Repository Edge Cases', () => {
it('should handle empty results', async () => {
const users = await userRepository.find({
where: { name: 'NonExistent' },
});
expect(users).toEqual([]);
});
it('should handle null values', async () => {
const user = userRepository.create({
name: 'John Doe',
email: 'john@example.com',
age: null, // 允许 null
});
const savedUser = await userRepository.save(user);
expect(savedUser.age).toBeNull();
});
it('should handle duplicate emails', async () => {
const userData = {
name: 'John Doe',
email: 'john@example.com',
age: 25,
};
await userRepository.save(userRepository.create(userData));
await expect(
userRepository.save(userRepository.create(userData))
).rejects.toThrow();
});
});
```
### 4. 使用测试覆盖率
```typescript
// 运行测试并生成覆盖率报告
npm run test:coverage
// 检查覆盖率报告
// coverage/lcov-report/index.html
// 设置覆盖率阈值
// jest.config.js
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
}
```
TypeORM 的测试策略提供了全面的测试支持,合理使用测试工具和最佳实践可以确保代码质量和可靠性。
服务端 · 2月18日 22:17
TypeORM 的缓存机制如何工作?包括查询缓存、Redis 缓存和缓存策略缓存是提高应用性能的重要手段,TypeORM 提供了多种缓存策略和机制,让开发者能够灵活地管理数据缓存。
## 缓存类型
### 1. 查询缓存
TypeORM 提供了内置的查询缓存功能,可以缓存查询结果。
```typescript
import { DataSource } from 'typeorm';
const dataSource = new DataSource({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'myapp',
entities: [User, Post],
synchronize: false,
logging: true,
// 启用查询缓存
cache: {
type: 'database', // 或 'redis', 'ioredis'
duration: 30000, // 缓存持续时间(毫秒)
options: {
// 数据库缓存选项
tableName: 'query_cache',
},
},
});
```
### 2. Redis 缓存
使用 Redis 作为缓存存储:
```typescript
import { DataSource } from 'typeorm';
const dataSource = new DataSource({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'myapp',
entities: [User, Post],
synchronize: false,
logging: true,
cache: {
type: 'redis', // 或 'ioredis'
duration: 30000,
options: {
host: 'localhost',
port: 6379,
password: 'password',
db: 0,
},
},
});
```
### 3. 数据库缓存
使用数据库表作为缓存存储:
```typescript
const dataSource = new DataSource({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'myapp',
entities: [User, Post],
synchronize: false,
logging: true,
cache: {
type: 'database',
duration: 30000,
options: {
tableName: 'query_cache',
},
},
});
```
## 查询缓存使用
### 基本查询缓存
```typescript
import { DataSource } from 'typeorm';
// 启用缓存的查询
const users = await dataSource
.getRepository(User)
.find({
cache: true, // 启用缓存
});
// 自定义缓存时间
const users = await dataSource
.getRepository(User)
.find({
cache: {
id: 'users_list', // 缓存 ID
milliseconds: 60000, // 缓存 60 秒
},
});
```
### QueryBuilder 缓存
```typescript
// 使用 QueryBuilder 启用缓存
const users = await dataSource
.getRepository(User)
.createQueryBuilder('user')
.cache(true) // 启用缓存
.getMany();
// 自定义缓存配置
const users = await dataSource
.getRepository(User)
.createQueryBuilder('user')
.cache('user_list', 60000) // 缓存 ID 和时间
.where('user.age > :age', { age: 18 })
.getMany();
```
### 关联查询缓存
```typescript
// 缓存关联查询
const usersWithPosts = await dataSource
.getRepository(User)
.find({
relations: ['posts'],
cache: true,
});
// 使用 QueryBuilder 缓存关联查询
const usersWithPosts = await dataSource
.getRepository(User)
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.cache('users_with_posts', 30000)
.getMany();
```
## 缓存管理
### 清除缓存
```typescript
// 清除特定查询缓存
await dataSource
.getRepository(User)
.query('CLEAR CACHE');
// 清除所有缓存
await dataSource.queryResultCache?.clear();
// 清除特定缓存 ID
await dataSource.queryResultCache?.remove(['users_list']);
```
### 缓存失效策略
```typescript
// 在更新数据后清除相关缓存
async function updateUser(userId: number, userData: Partial<User>) {
const userRepository = dataSource.getRepository(User);
// 更新用户
await userRepository.update(userId, userData);
// 清除相关缓存
await dataSource.queryResultCache?.remove([
'users_list',
'user_details',
]);
}
// 使用事务和缓存清除
async function updateUserWithCache(userId: number, userData: Partial<User>) {
await dataSource.transaction(async (manager) => {
const userRepository = manager.getRepository(User);
// 更新用户
await userRepository.update(userId, userData);
// 清除缓存
await manager.queryResultCache?.remove([
'users_list',
]);
});
}
```
## 实体缓存
### 实体级缓存
```typescript
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
email: string;
// 实体缓存配置
@Column({ type: 'timestamp' })
cachedAt: Date;
}
// 使用实体缓存
async function getUserWithCache(userId: number) {
const cacheKey = `user:${userId}`;
// 尝试从缓存获取
const cached = await dataSource.queryResultCache?.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// 从数据库获取
const user = await dataSource.getRepository(User).findOne({
where: { id: userId },
});
// 存入缓存
await dataSource.queryResultCache?.store(
cacheKey,
JSON.stringify(user),
60000
);
return user;
}
```
### 自定义缓存装饰器
```typescript
import { Cacheable } from './decorators/Cacheable';
@Entity()
export class Product {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column({ type: 'decimal', precision: 10, scale: 2 })
price: number;
@Cacheable({ ttl: 30000 }) // 缓存 30 秒
async getDetails(): Promise<Product> {
return this;
}
}
// 使用自定义缓存装饰器
const product = await productRepository.findOne({ where: { id: 1 } });
const details = await product.getDetails();
```
## 缓存策略
### 读写分离缓存
```typescript
// 读操作使用缓存
async function getUser(userId: number) {
const cacheKey = `user:${userId}`;
// 尝试从缓存读取
const cached = await dataSource.queryResultCache?.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// 从数据库读取
const user = await dataSource.getRepository(User).findOne({
where: { id: userId },
});
// 存入缓存
await dataSource.queryResultCache?.store(
cacheKey,
JSON.stringify(user),
60000
);
return user;
}
// 写操作清除缓存
async function updateUser(userId: number, userData: Partial<User>) {
const userRepository = dataSource.getRepository(User);
// 更新数据库
await userRepository.update(userId, userData);
// 清除缓存
const cacheKey = `user:${userId}`;
await dataSource.queryResultCache?.remove([cacheKey]);
}
```
### 分层缓存
```typescript
// 一级缓存:内存缓存
const memoryCache = new Map<string, any>();
// 二级缓存:Redis 缓存
const redisCache = dataSource.queryResultCache;
async function getProduct(productId: number) {
const cacheKey = `product:${productId}`;
// 尝试一级缓存
if (memoryCache.has(cacheKey)) {
return memoryCache.get(cacheKey);
}
// 尝试二级缓存
const cached = await redisCache?.get(cacheKey);
if (cached) {
const product = JSON.parse(cached);
memoryCache.set(cacheKey, product);
return product;
}
// 从数据库获取
const product = await dataSource.getRepository(Product).findOne({
where: { id: productId },
});
// 存入二级缓存
await redisCache?.store(cacheKey, JSON.stringify(product), 60000);
// 存入一级缓存
memoryCache.set(cacheKey, product);
return product;
}
```
### 缓存预热
```typescript
// 应用启动时预热缓存
async function warmupCache() {
// 预热热门用户
const popularUsers = await dataSource
.getRepository(User)
.createQueryBuilder('user')
.where('user.followersCount > :count', { count: 1000 })
.limit(100)
.getMany();
for (const user of popularUsers) {
const cacheKey = `user:${user.id}`;
await dataSource.queryResultCache?.store(
cacheKey,
JSON.stringify(user),
300000 // 5 分钟
);
}
// 预热热门文章
const popularPosts = await dataSource
.getRepository(Post)
.createQueryBuilder('post')
.where('post.views > :views', { views: 1000 })
.limit(100)
.getMany();
for (const post of popularPosts) {
const cacheKey = `post:${post.id}`;
await dataSource.queryResultCache?.store(
cacheKey,
JSON.stringify(post),
300000
);
}
}
// 在应用启动时调用
warmupCache();
```
## 缓存最佳实践
### 1. 选择合适的缓存策略
```typescript
// ✅ 好的做法:为不同类型的数据使用不同的缓存策略
// 静态数据:长时间缓存
const staticData = await dataSource
.getRepository(Category)
.find({
cache: {
milliseconds: 3600000, // 1 小时
},
});
// 动态数据:短时间缓存
const dynamicData = await dataSource
.getRepository(Post)
.find({
cache: {
milliseconds: 30000, // 30 秒
},
});
// 实时数据:不缓存
const realTimeData = await dataSource
.getRepository(OnlineUser)
.find({
cache: false,
});
```
### 2. 缓存键命名规范
```typescript
// ✅ 好的做法:使用清晰的缓存键命名
const cacheKeys = {
user: (id: number) => `user:${id}`,
userList: (page: number) => `user_list:page:${page}`,
userPosts: (userId: number) => `user:${userId}:posts`,
popularPosts: () => `posts:popular`,
};
// 使用示例
await dataSource.queryResultCache?.store(
cacheKeys.user(1),
JSON.stringify(user),
60000
);
```
### 3. 缓存失效时机
```typescript
// ✅ 好的做法:在数据变更时及时清除缓存
class UserService {
async updateUser(userId: number, userData: Partial<User>) {
const userRepository = dataSource.getRepository(User);
// 更新数据
await userRepository.update(userId, userData);
// 清除相关缓存
await this.clearUserCache(userId);
}
async clearUserCache(userId: number) {
const cacheKeys = [
`user:${userId}`,
`user:${userId}:posts`,
`user:${userId}:comments`,
];
await dataSource.queryResultCache?.remove(cacheKeys);
}
}
```
### 4. 监控缓存性能
```typescript
// 缓存性能监控
class CacheMonitor {
private hits = 0;
private misses = 0;
async get<T>(key: string): Promise<T | null> {
const cached = await dataSource.queryResultCache?.get(key);
if (cached) {
this.hits++;
return JSON.parse(cached);
}
this.misses++;
return null;
}
getHitRate(): number {
const total = this.hits + this.misses;
return total > 0 ? this.hits / total : 0;
}
getStats() {
return {
hits: this.hits,
misses: this.misses,
hitRate: this.getHitRate(),
};
}
}
// 使用监控
const monitor = new CacheMonitor();
async function getProduct(productId: number) {
const cacheKey = `product:${productId}`;
// 尝试从缓存获取
const cached = await monitor.get<Product>(cacheKey);
if (cached) {
return cached;
}
// 从数据库获取
const product = await dataSource.getRepository(Product).findOne({
where: { id: productId },
});
// 存入缓存
await dataSource.queryResultCache?.store(
cacheKey,
JSON.stringify(product),
60000
);
return product;
}
// 定期输出缓存统计
setInterval(() => {
console.log('Cache Stats:', monitor.getStats());
}, 60000);
```
## 高级缓存技巧
### 1. 缓存穿透防护
```typescript
// 使用空值缓存防止缓存穿透
async function getUser(userId: number) {
const cacheKey = `user:${userId}`;
// 尝试从缓存获取
const cached = await dataSource.queryResultCache?.get(cacheKey);
if (cached) {
const user = JSON.parse(cached);
// 检查是否为空值标记
if (user === null) {
return null;
}
return user;
}
// 从数据库获取
const user = await dataSource.getRepository(User).findOne({
where: { id: userId },
});
// 存入缓存(包括空值)
await dataSource.queryResultCache?.store(
cacheKey,
JSON.stringify(user),
60000
);
return user;
}
```
### 2. 缓存雪崩防护
```typescript
// 使用随机缓存时间防止缓存雪崩
async function getProduct(productId: number) {
const cacheKey = `product:${productId}`;
// 尝试从缓存获取
const cached = await dataSource.queryResultCache?.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// 从数据库获取
const product = await dataSource.getRepository(Product).findOne({
where: { id: productId },
});
// 使用随机缓存时间(30-60 秒)
const cacheTime = 30000 + Math.random() * 30000;
// 存入缓存
await dataSource.queryResultCache?.store(
cacheKey,
JSON.stringify(product),
cacheTime
);
return product;
}
```
### 3. 缓存击穿防护
```typescript
// 使用互斥锁防止缓存击穿
const locks = new Map<string, Promise<any>>();
async function getHotProduct(productId: number) {
const cacheKey = `product:${productId}`;
// 尝试从缓存获取
const cached = await dataSource.queryResultCache?.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// 检查是否有锁
if (locks.has(cacheKey)) {
return await locks.get(cacheKey);
}
// 创建锁
const promise = (async () => {
try {
// 从数据库获取
const product = await dataSource.getRepository(Product).findOne({
where: { id: productId },
});
// 存入缓存
await dataSource.queryResultCache?.store(
cacheKey,
JSON.stringify(product),
60000
);
return product;
} finally {
// 释放锁
locks.delete(cacheKey);
}
})();
locks.set(cacheKey, promise);
return await promise;
}
```
TypeORM 的缓存机制提供了强大的性能优化能力,合理使用缓存可以显著提高应用性能。
服务端 · 2月18日 22:17