nestjs / TypeOrm 数据库事务
nestjs / TypeOrm database transaction
假设我们有 2 个服务,A 和 B。
服务 A 具有执行以下操作的功能:
- 验证数据
- 调用服务 B 函数,对数据库进行更改
- 多做一些事情
- 对数据库进行更改
现在,我们假设以下步骤 3 或 4 之一失败了。
由于服务 B 在数据库中进行了更改,因此这些更改仍然存在。
在这种情况下有什么方法可以回滚数据库吗?我虽然考虑过数据库事务,但我找不到在 nest js 中执行此操作的任何方法,尽管 TypeOrm 支持它,但嵌套看起来并不自然。
如果没有,我现在 "stuck" 服务 B 发生了变化,但没有发生应该由 A 发生的变化。
非常感谢。
在这种情况下,您必须对两个数据库操作使用相同的事务管理器。不幸的是,我没有示例存储库,但我找到了一个在 Node 中使用 Continuation Local Storage (CLS) 的潜在解决方案:
https://github.com/typeorm/typeorm/issues/1895
这适用于 Express.js,但您可以创建 TransactionManager 的实例(例如,在嵌套中间件中)并根据每个请求上下文存储它。然后,您将能够在您的服务方法调用中重复使用此事务管理器,前提是它们在上面的 link 中使用 @Transaction 装饰器实现进行注释。
如果您的函数链中没有错误,事务管理器将提交所做的所有更改。否则,管理器将回滚任何更改。
希望对您有所帮助!
解决方案很多,应该都是基于SQL事务管理。
我个人认为最简单的实现方法是在数据库上执行代码时使用相同的 EntityManager
实例。然后你可以使用类似的东西:
getConnection().transaction(entityManager -> {
service1.doStuff1(entityManager);
service2.doStuff2(entityManager);
});
您可以从 EntityManager
实例生成一个 QueryRunner
,如果您在 ORM 操作之外执行原始 SQL ,它将被包装在同一事务中。您还需要从 EntityManager
生成 Repository
个实例,否则它们将在主事务之外执行代码。
这是我解决它的方法,因为我需要使用悲观锁。
我觉得这是 "Nest" 做事的方式,因为您可以简单地要求 NestJS
注入 Typeorm Connection
的实例,您就可以开始了。
@Injectable()
class MyService {
// 1. Inject the Typeorm Connection
constructor(@InjectConnection() private connection: Connection) { }
async findById(id: number): Promise<Thing> {
return new Promise(resolve => {
// 2. Do your business logic
this.connection.transaction(async entityManager => {
resolve(
await entityManager.findOne(Thing, id, {
lock: { mode: 'pessimistic_write' },
}),
);
});
});
}
}
只需将您需要的任何其他逻辑放入 .transaction
块中即可。
注意: 您必须使用 .transaction
方法提供的 entityManager
否则它将不起作用。
typeorm-transactional-cls-hooked 使用 CLS(连续本地存储)处理和传播不同存储库和服务方法之间的事务。
@Injectable()
export class PostService {
constructor(
private readonly authorRepository: AuthorRepository,
private readonly postRepository: PostRepository,
) {}
@Transactional() // will open a transaction if one doesn't already exist
async createPost(authorUsername: string, message: string): Promise<Post> {
const author = await this.authorRepository.create({ username: authorUsername });
return this.postRepository.save({ message, author_id: author.id });
}
}
假设我们有 2 个服务,A 和 B。 服务 A 具有执行以下操作的功能:
- 验证数据
- 调用服务 B 函数,对数据库进行更改
- 多做一些事情
- 对数据库进行更改
现在,我们假设以下步骤 3 或 4 之一失败了。 由于服务 B 在数据库中进行了更改,因此这些更改仍然存在。
在这种情况下有什么方法可以回滚数据库吗?我虽然考虑过数据库事务,但我找不到在 nest js 中执行此操作的任何方法,尽管 TypeOrm 支持它,但嵌套看起来并不自然。 如果没有,我现在 "stuck" 服务 B 发生了变化,但没有发生应该由 A 发生的变化。
非常感谢。
在这种情况下,您必须对两个数据库操作使用相同的事务管理器。不幸的是,我没有示例存储库,但我找到了一个在 Node 中使用 Continuation Local Storage (CLS) 的潜在解决方案:
https://github.com/typeorm/typeorm/issues/1895
这适用于 Express.js,但您可以创建 TransactionManager 的实例(例如,在嵌套中间件中)并根据每个请求上下文存储它。然后,您将能够在您的服务方法调用中重复使用此事务管理器,前提是它们在上面的 link 中使用 @Transaction 装饰器实现进行注释。
如果您的函数链中没有错误,事务管理器将提交所做的所有更改。否则,管理器将回滚任何更改。
希望对您有所帮助!
解决方案很多,应该都是基于SQL事务管理。
我个人认为最简单的实现方法是在数据库上执行代码时使用相同的 EntityManager
实例。然后你可以使用类似的东西:
getConnection().transaction(entityManager -> {
service1.doStuff1(entityManager);
service2.doStuff2(entityManager);
});
您可以从 EntityManager
实例生成一个 QueryRunner
,如果您在 ORM 操作之外执行原始 SQL ,它将被包装在同一事务中。您还需要从 EntityManager
生成 Repository
个实例,否则它们将在主事务之外执行代码。
这是我解决它的方法,因为我需要使用悲观锁。
我觉得这是 "Nest" 做事的方式,因为您可以简单地要求 NestJS
注入 Typeorm Connection
的实例,您就可以开始了。
@Injectable()
class MyService {
// 1. Inject the Typeorm Connection
constructor(@InjectConnection() private connection: Connection) { }
async findById(id: number): Promise<Thing> {
return new Promise(resolve => {
// 2. Do your business logic
this.connection.transaction(async entityManager => {
resolve(
await entityManager.findOne(Thing, id, {
lock: { mode: 'pessimistic_write' },
}),
);
});
});
}
}
只需将您需要的任何其他逻辑放入 .transaction
块中即可。
注意: 您必须使用 .transaction
方法提供的 entityManager
否则它将不起作用。
typeorm-transactional-cls-hooked 使用 CLS(连续本地存储)处理和传播不同存储库和服务方法之间的事务。
@Injectable()
export class PostService {
constructor(
private readonly authorRepository: AuthorRepository,
private readonly postRepository: PostRepository,
) {}
@Transactional() // will open a transaction if one doesn't already exist
async createPost(authorUsername: string, message: string): Promise<Post> {
const author = await this.authorRepository.create({ username: authorUsername });
return this.postRepository.save({ message, author_id: author.id });
}
}