typeorm-transactional
typeorm-transactional copied to clipboard
[Question] @Transactional on controller level and nested transaction errors
Currently I have this on my NestJS application on the controller level
Controller level:
@Controller('supplier')
export class SupplierController {
constructor(private readonly supplierService: SupplierService) {}
@Transactional()
@Post()
async create(@Body() createSupplierDto: CreateSupplierDto) {
return await this.supplierService.create(createSupplierDto);
}
@Transactional({ propagation: Propagation.SUPPORTS })
@Get()
async findAll() {
return await this.supplierService.findAll();
}
@Transactional({ propagation: Propagation.SUPPORTS })
@Get(':id')
async findOne(@Param('id') id: string) {
return await this.supplierService.findOne(+id);
}
@Transactional()
@Patch(':id')
async update(@Param('id') id: string, @Body() updateSupplierDto: UpdateSupplierDto) {
return await this.supplierService.update(+id, updateSupplierDto);
}
@Transactional()
@Delete(':id')
async remove(@Param('id') id: string) {
return await this.supplierService.remove(+id);
}
}
Service Level:
@Injectable()
export class SupplierService {
constructor(
@InjectRepository(Supplier)
private readonly supplierRepository: Repository<Supplier>,
@Inject(forwardRef(() => MaterialService))
private readonly materialService: MaterialService,
) { }
async create(createSupplierDto: CreateSupplierDto) {
await this.checkNoSameName(createSupplierDto);
const newSupplier = this.supplierRepository.create(createSupplierDto);
return await this.supplierRepository.save(newSupplier);
}
async findAll() {
return await this.supplierRepository.find();
}
async findOne(id: number) {
return await this.supplierRepository.findOneBy({ id });
}
async findOneByName(name: string) {
return await this.supplierRepository.findOneBy({ name });
}
async update(id: number, updateSupplierDto: UpdateSupplierDto) {
await this.checkNoSameName(updateSupplierDto);
const supplier = await this.findOne(id);
if (!supplier) {
throw new NotFoundException('Supplier not found!');
}
return this.supplierRepository.save({ ...supplier, ...updateSupplierDto });
}
async remove(id: number) {
const supplier = await this.findOne(id);
if (!supplier) {
throw new NotFoundException('Supplier not found!');
}
// since many materials can't survive without supplier
// check before deleting this supplier that there's no material belonging to it
const taggedMaterials = await this.materialService.findTaggedSupplier(id);
if (taggedMaterials.length > 0) {
throw new BadRequestException(
ERROR_MESSAGE_FORMATS.SUPPLIER.TAGGED_MATERIALS(taggedMaterials.length),
);
}
return this.supplierRepository.remove(supplier);
}
async checkNoSameName(dto: UpdateSupplierDto) {
if (!dto || !dto.name) return;
const sameNameSupplier = await this.findOneByName(dto.name);
if (sameNameSupplier) {
throw new BadRequestException(
ERROR_MESSAGE_FORMATS.SUPPLIER.SAME_NAME(sameNameSupplier.id),
);
}
}
}
Datasource
const SQLIITE = 'sqlite';
{
type: SQLIITE,
database: process.env.DATABASE_PATH.endsWith(`.${SQLIITE}`)
? process.env.DATABASE_PATH
: `${process.env.DATABASE_PATH}.${SQLIITE}`,
entities: ['dist/**/*.entity.js'],
migrations: ['dist/db/migrations/*.js'],
}
I've noticed that the examples all dictate service-level transactions. But may I ask if controller level transactions are okay?
Note: I have GET endpoints use the propagation level of SUPPORTS because for some reason when I have my FE do concurrent GET requests it'll throw a SQLITE_ERROR: cannot start a transaction within a transaction
tldr
- Are controller level transactions are okay?
- GET endpoints with transactions throw
SQLITE_ERROR: cannot start a transaction within a transaction
when hit concurrently, what's the proper way to fix this case? I was under the impression that REQUIRES_NEW (default) supports current transaction instead of starting new when there's one