背景
拿最简单的一个例子举例,用户注册成功后,还会为用户生成一个钱包
typeorm 官方文档里提供了两种使用事务的方式:typeorm.io/transaction…
我们可以很简单的在一个service里使用事务,来达到我们的需求。
// user.service.ts
const dataSource = this.dataSourceManager.getDataSource("default");
await dataSource.transaction(async (transactionalEntityManager) => {
await transactionalEntityManager.save(UserEntity, new UserEntity());
await transactionalEntityManager.save(WalletEntity, new WalletEntity());
});
但是实际开发过程中,往往没有这么简单,有以下两种情况
- 事务里调用了其他的方法,而这个方法里有操作数据库
// user.service.ts
const dataSource = this.dataSourceManager.getDataSource("default");
await dataSource.transaction(async (transactionalEntityManager) => {
await transactionalEntityManager.save(UserEntity, new UserEntity());
await this.walletService.createWallet();
});
- 一个方法里调用了多个方法,这多个方法里都有操作数据库
// user.controller.ts
await this.userService.createUser()
await this.walletService.createWallet();
解决办法1: 避免出现上述两种情况
也就是说 需要保证事务性的,全都放在一个方法里。类似下面这样
// user.service.ts
const dataSource = this.dataSourceManager.getDataSource("default");
await dataSource.transaction(async (transactionalEntityManager) => {
await transactionalEntityManager.save(UserEntity, new UserEntity());
// await this.walletService.createWallet();
// 改为直接操作数据库
await transactionalEntityManager.save(WalletEntity, new WalletEntity());
});
但是只能适用于简单场景
解决办法2: 通过调整业务顺序
比如上面这个场景,我们可以先创建钱包,再把钱包id保存到user表里,假设钱包创建成功,用户创建失败,那也只会多一个空钱包而已
解决办法3: 把 transactionalManager 存储到ctx中
此思路参考:github.com/go-kratos/e…
我们可以这样做
// transaction_manager.ts
import { Inject, Provide } from "@midwayjs/core";
import { EntityManager } from "typeorm";
import { TypeORMDataSourceManager } from "@midwayjs/typeorm";
import { Context } from "@midwayjs/koa";
@Provide()
export class TransactionManager {
@Inject()
dataSourceManager: TypeORMDataSourceManager;
@Inject()
ctx: Context;
async inTx(func) {
const dataSource = this.dataSourceManager.getDataSource('default');
return await dataSource.transaction(async transactionalEntityManager => {
this.ctx.entityManager = transactionalEntityManager;
await func();
});
}
db(): EntityManager {
if (this.ctx.entityManager) {
return this.ctx.entityManager;
}
const dataSource = this.dataSourceManager.getDataSource('default');
return dataSource.manager;
}
}
使用
@Inject()
tm: TransactionManager;
....
await this.tm.inTx(async () => {
await this.userService.createUser()
await this.walletService.createWallet();
});
....
// user.service.ts 中创建用户
await this.tm.db().save(UserEntity, user);