I have two databases that are used on an endpoint, this endpoint is developed with nodejs and serverless-framework, that is, it is a lambda function.
I need to insert the information using transactions in both, in case an error occurs, the information does not persist, however, in some cases, I receive an error when performing a select on one of the bases.
Error: Transaction query already complete, run with DEBUG=knex:tx for more info
The log for DEBUG=knex:tx is:
knex:tx trx1: Transaction completed: select 'cod_farmacia' from 'farmacia' where 'cod_farmacia' = ?
This error occurs infrequently, usually the process happens normally, without any errors, but it is something that interferes a lot.
Below is the code where I manage the database: connection, commits and rollbacks of transactions.
import Knex, { Knex as KnexTypes } from 'knex';
import env from './env';
interface IConfigKnex {
client: string;
connection: object;
}
interface ITransactions {
drugstore: KnexTypes.Transaction;
payment: KnexTypes.Transaction;
}
class DB {
transactions = <ITransactions>{
drugstore: null,
payment: null,
};
connectToDrugstore = async () => {
if (this.transactions.drugstore) {
return true;
}
const knex = Knex(<IConfigKnex>{
client: 'mysql',
connection: {
host: env('DB_DRUGSTORE_HOST'),
user: env('DB_DRUGSTORE_USER'),
password: env('DB_DRUGSTORE_PASSWORD'),
database: env('DB_DRUGSTORE_DATABASE'),
port: env('DB_DRUGSTORE_PORT'),
timezone: env('TIMEZONE'),
},
});
const trx = await knex.transaction();
this.transactions = {
...this.transactions,
drugstore: trx,
};
return true;
};
commitDrugstore = async () => {
if (this.transactions.drugstore === null) {
return true;
}
await this.transactions.drugstore.commit();
return true;
};
connectToPayment = async () => {
if (this.transactions.payment) {
return true;
}
const knex = Knex(<IConfigKnex>{
client: 'mysql',
connection: {
host: env('DB_PAYMENT_HOST'),
user: env('DB_PAYMENT_USER'),
password: env('DB_PAYMENT_PASSWORD'),
database: env('DB_PAYMENT_DATABASE'),
port: env('DB_PAYMENT_PORT'),
timezone: env('TIMEZONE'),
},
});
const trx = await knex.transaction();
this.transactions = {
...this.transactions,
payment: trx,
};
return true;
};
commitPayment = async () => {
if (this.transactions.payment === null) {
return true;
}
await this.transactions.payment.commit();
return true;
};
connectToAll = async () => {
await this.connectToDrugstore();
await this.connectToPayment();
return true;
};
commitAll = async () => {
await this.commitDrugstore();
await this.commitPayment();
return true;
};
closeConnections = async () => {
if (this.transactions.payment !== null) {
await this.transactions.payment.destroy();
}
if (this.transactions.drugstore !== null) {
await this.transactions.drugstore.destroy();
}
};
}
export default DB;
Endpoint code, I left it summarized so as not to contain a lot of private information, but it contains the calls to make the connection, commit and close the connection with the database.
export class Payment {
paymentTrx: Knex.Transaction;
drugstoreTrx: Knex.Transaction;
constructor(private connectDB: DB) {}
async request(event: APIGatewayEvent): Promise<any> {
try {
await this.connectDB.connectToAll();
this.drugstoreTrx = await this.connectDB.transactions['drugstore'];
this.paymentTrx = await this.connectDB.transactions['payment'];
const response = await getDrugstoreById(10, 1, this.drugstoreTrx);
// Rest of code..
await this.connectDB.commitAll();
return {
statusCode: 200,
message: "ok"
};
} catch (error: AxiosError | any) {
console.log('Error payment.ts', error);
if (error.isAxiosError) {
throwApiError(this.payload.payment_api_id, error);
} else {
throw new HttpResponseError({
statusCode: 500,
message: 'Erro desconhecido',
});
}
} finally {
console.log('fechando conexão...');
await connectDB.closeConnections();
console.log('conexão fechada');
}
}
}
const connectDB = new DB();
export const handler = handleAuthBasic(
async (event: APIGatewayEvent) => await new Payment(connectDB).request(event)
);
And here is the code for the getDrugstoreById function.
import { Knex } from 'knex';
import { Model } from 'objection';
import { IPaymentGatewaySettings } from './PaymentGatewaySettings';
class Drugstore extends Model {
static modelPaths = [__dirname];
static get tableName() {
return 'farmacia';
}
static relationMappings = {
payment_gateway_settings: {
relation: Model.HasManyRelation,
modelClass: 'PaymentGatewaySettings',
join: {
to: 'payment_gateway_settings.drugstore_id',
from: 'farmacia.cod_farmacia',
},
},
};
}
export default <any>Drugstore;
export const getDrugstoreById = async (
drugstore_id: number,
payment_gateway_settings_id: number,
trx: Knex.Transaction
): Promise<DrugstoreDatabaseResponse> => {
const drugstore: DrugstoreDatabaseResponse = <any>await Drugstore.query(trx)
.select('cod_farmacia')
.where('cod_farmacia', drugstore_id)
.withGraphFetched('payment_gateway_settings(select)')
.modifiers({
select(builder) {
builder.select().where('id', payment_gateway_settings_id);
},
})
.first();
return drugstore;
};