Self Answer...
After I am looking into TypeORM index.d.ts, I take it that there are some parameter in createQueryBuilder named queryRunner so. If you input there your own query runner for transaction you can use queryBuilder with queryRunner
As like:
public createOrderTransaction = async (
data: CreateOrderInputDTO,
): Promise<Order> => {
const queryRunner = this.connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const order = await this.createOrder(data, queryRunner);
await this.createOrderRecord(order.id, data, queryRunner);
await queryRunner.commitTransaction();
return order;
} catch (error) {
this.logger.error(
"Transaction Error: 'createOrderTransaction' is failed: ",
error,
);
if (queryRunner.isTransactionActive) {
await queryRunner.rollbackTransaction();
}
}
};
createOrder as like:
private createOrderRecord = async (
orderId: string,
data: CreateOrderInputDTO,
queryRunner?: QueryRunner,
): Promise<void> => {
await this.orderRecordRepository
.createQueryBuilder('order_record', queryRunner)
.insert()
.into(OrderRecord)
.values([
{
...data,
orderId,
createdAt: new Date(),
},
])
.execute();
await this.orderRecordRepository
.createQueryBuilder('order_record', queryRunner)
.relation(OrderRecord, 'orderDetails')
.of(orderId)
.add(data.orderDetails);
};
Answer from Stark Jeon on Stack OverflowSelf Answer...
After I am looking into TypeORM index.d.ts, I take it that there are some parameter in createQueryBuilder named queryRunner so. If you input there your own query runner for transaction you can use queryBuilder with queryRunner
As like:
public createOrderTransaction = async (
data: CreateOrderInputDTO,
): Promise<Order> => {
const queryRunner = this.connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const order = await this.createOrder(data, queryRunner);
await this.createOrderRecord(order.id, data, queryRunner);
await queryRunner.commitTransaction();
return order;
} catch (error) {
this.logger.error(
"Transaction Error: 'createOrderTransaction' is failed: ",
error,
);
if (queryRunner.isTransactionActive) {
await queryRunner.rollbackTransaction();
}
}
};
createOrder as like:
private createOrderRecord = async (
orderId: string,
data: CreateOrderInputDTO,
queryRunner?: QueryRunner,
): Promise<void> => {
await this.orderRecordRepository
.createQueryBuilder('order_record', queryRunner)
.insert()
.into(OrderRecord)
.values([
{
...data,
orderId,
createdAt: new Date(),
},
])
.execute();
await this.orderRecordRepository
.createQueryBuilder('order_record', queryRunner)
.relation(OrderRecord, 'orderDetails')
.of(orderId)
.add(data.orderDetails);
};
I don't know if this helps help anyone facing this question, but instead of doing in above answer, we can use dataSource, as suggested in this official TypeOrm transaction document, and instead of adding Entity in queryBuilder, add it to .from()
await myDataSource.manager.transaction(async (transactionalEntityManager) => {
// We can use 'transactionalEntityManager' to create queryBuilder here:
await transactionalEntityManager.createQueryBuilder()
.from(Order, 'order')
.insert()
.into(OrderRecord)
.values([
{
...data,
orderId,
createdAt: new Date(),
},
])
.execute();
// And of course similar with relation table:
await transactionalEntityManager
.createQueryBuilder()
.from(Order, 'order')
.relation(OrderRecord, 'orderDetails')
.of(orderId)
.add(data.orderDetails);
})
TypeOrm transaction doesn't work [Help]
sql - How to execute multiple QueryBuilders in a transaction using TypeORM - Stack Overflow
TypeORM transaction saving entity to database despite the next transaction step failing - Stack Overflow
Queries being executed outside transaction
I'm trying to run a transaction that'll wrap 3 queries that must be executed or rollback if one of the fails.
If the 2nd query fails, the transaction doesn't roolback and the 1st query persists the data anyway, the error message i'm getting is : Query failed: SelectSQL: queryAll: cannot rollback - no transaction is active i want to use the querybuilder for this, is it possible ? i also tried to use the repositories but i'm getting the same error message
Here's the code i'm trying to execute :
await appDatasource.transaction(async (tem) => {
const qb = await tem.createQueryBuilder();
// Insert into food table const foodRes = await qb.insert().into(Food).values(food).updateEntity(false).execute();
const foodId: number = foodRes.raw;
// Insert into food_x_store table
foodXStore.forEach(fxS => fxS.foodId = foodId);
await qb.insert().into(FoodXStore).values(foodXStore).updateEntity(false).execute();
// Insert into food_x_tag table
const foodXTagJoins: FoodXTag[] = foodDto.tagIds!.map((tagId) => ({ tagId, foodId }));
await qb.insert().into(FoodXTag).values(foodXTagJoins).updateEntity(false).execute(); })I also tried this version of the code that starts a transaction beforehand :
const qr = appDatasource.createQueryRunner();
await qr.startTransaction();
const res = await appDatasource.transaction(async (tem) => {
const qb = await tem.createQueryBuilder();
// Insert into food table
const foodRes = await tem.createQueryBuilder().insert().into(Food).values(food).updateEntity(false).execute();
const foodId: number = foodRes.raw;
console.log("INSERT FOOD RES : ", foodRes);
// Insert into food_x_store table
foodXStore.forEach(fxS => fxS.foodId = foodId);
await tem.createQueryBuilder().insert().into(FoodXStore).values(foodXStore).updateEntity(false).execute();
// Insert into food_x_tag table
const foodXTagJoins: FoodXTag[] = foodDto.tagIds!.map((tagId) => ({ tagId, foodId }));
await tem.createQueryBuilder().insert().into(FoodXTag).values(foodXTagJoins).updateEntity(false).execute();
})But this doesn't work either, when it tries to execute the 2nd query (inserting joins) it fails because the 1st one isn't commited yet (i can see the created ID in the logs but it's not persisted in the database)
EDIT : Github issue with some more info
https://github.com/typeorm/typeorm/issues/10364
I had exactly the same expectations as you. Coming from Rails and Spring, I expected to have transactional tests and found no solution directly from Typeorm.
It is hard to reuse the same transactions during the tests because the connection class always create a new QueryRunner for every database command or transaction. Diving into TypeORM, the solution I found was to monkey patch the method which creates the query runner, to reuse it during the tests. I created this library to reuse this code in several projects: https://github.com/viniciusjssouza/typeorm-transactional-tests.
I know this is quite late, but I also actually worked on a solution which you can see here: https://www.npmjs.com/package/typeorm-test-transactions
A disclaimer from my side is that you have to use the @Transactional() decorator, but I like how that makes the code a lot cleaner and you don't have to pass the transaction manager down.
@viniciusjssouza I checked your solution and I really like it! It's funny, I think we both had the same problem at the same time :P