How can I insert and get last inserted id in mysql with transactions using quarkus reactive mysql client with mutiny api - reactive

How would i go about creating a transaction, inserting a row, committing the transaction and getting the last inserted id. So the method should return a Uni<Integer>. I'm new to the mutiny api, I previously used the vertx.io chaining future handlers mechanism, and so it's a bit tough readjusting myself to work with the mutiny api. I have checked the documentation and think something similar to the following snippet should work, but i'm stumped on how to make it work and return Uni<Integer> from the last query instead of Uni<Void> from the tx.commit()
return this.client.begin()
.flatMap(tx -> tx
.preparedQuery("INSERT INTO person (firstname,lastname) VALUES ($1,$2)")
.execute(Tuple.of(person.getFirstName(),person.getLastName()))
.onItem().produceUni(id-> tx.query("SELECT LAST_INSERT_ID()"))
.onItem().produceUni(res -> tx.commit())
.onFailure().recoverWithUni(ex-> tx.rollback())
);

Try this:
return client.begin().onItem().produceUni(tx -> tx
.preparedQuery("INSERT INTO person (firstname,lastname) VALUES ($1,$2)").execute(Tuple.of(person.getFirstName(),person.getLastName()))
.onItem().produceUni(id -> tx.query("SELECT LAST_INSERT_ID()").execute())
.onItem().apply(rows -> rows.iterator().next().getInteger(0))
.onItem().produceUni(item -> tx.commit().on().item().produceUni(v -> Uni.createFrom().item(item)))
.on().failure().recoverWithUni(throwable -> {
return tx.rollback().on().failure().recoverWithItem((Void) null)
.on().item().produceUni(v -> Uni.createFrom().failure(throwable));
})
);
A SqlClientHelper is coming to Quarkus in a future version (hopefully 1.6). You will be able to simplify to:
return SqlClientHelper.inTransactionUni(client, tx -> tx
.preparedQuery("INSERT INTO person (firstname,lastname) VALUES ($1,$2)").execute(Tuple.of(person.getFirstName(),person.getLastName()))
.onItem().produceUni(id -> tx.query("SELECT LAST_INSERT_ID()").execute())
.onItem().apply(rows -> rows.iterator().next().getInteger(0))
);

Related

Reactive programming - switch second query if first query returns nothing

I'm trying to implement this scenario using reactive programming(switchIfEmpty(Mono.defer() ->...)):
client needs data from first query ==>
basketReadRepository.findByBasketId()
if data is already present in first query => return that data
if data not present from first execute second query for data ==>
basketWriteRepository.findByBasketId()
if second fails then
switchIfEmpty(Mono.error.....)
I tried like this.....
public Mono updateBasket(UUID basketId) { return
basketReadRepository.findByBasketId(basketId) .doFirst(() ->
log.debug("Processing update basket request) .flatMap(basket ->
Mono.error(new
NotFoundException(ErrorConstants.BASKET_NOT_FOUND_WITH_STATUS_CODE)))
.switchIfEmpty(Mono.defer(() ->
basketWriteRepository.findByBasketId(basketId).cast(Basket.class)
.switchIfEmpty(Mono.error(new
NotFoundException(ErrorConstants.BASKET_NOT_FOUND_WITH_STATUS_CODE)))
.flatMap(existingBasket -> validateAndConvertBasket(patch,
existingBasket)) .flatMap(this::validateBasket)
.flatMap(this::updateBasket) .doOnError(throwable ->
log.error("Error in updating basket : {}", throwable.getMessage()))
.doOnSuccess(basket -> log.debug("Basket updated successfully); }
......but it always come inside second query and check, not inside first one... I can not do switchIfEmpty(Mono.error()...) after first query because then it never come to second query

Reactive programming - Return response when the result is empty

I have a reactive code below which does the following.
query items table by ID - (itemRepository.findByd(itemId))
Create a map from the result from Step1
Invoke retrieveItemsQty from DB
Here, it works fine when the Step1 returns one or more result and retrieveItemsQty method fails when the result is empty. My requirement is to return back when the first step result(itemRepository.findByd) is empty. Not sure how to do this?
private Mono<Long> updateItemsQty(final Long itemId) {
return itemRepository.findByd(itemId).collectList()
.zipWhen((items) -> Mono.just(items.stream()
.collect(Collectors.toMap(ItemQty::getId, ItemQty::getQty))))
.map((tuple) -> tuple.getT2())
.zipWhen((items) -> qtyRepository
.retrieveItemsQty(items.keySet()).collectList())
.zipWhen((tuple) -> reduceItemQty(tuple.getT2(), tuple.getT1(), itemId))
.flatMap((response) -> {
return Mono.just(itemId);
});
}
I tried switchIfEmpty and defaultIfEmpty like the below.
return itemRepository.findByd(itemId).collectList()
.switchIfEmpty(). /// Looks like the return statement is not allowed here.
.zipWhen((items) -> Mono.just(items.stream()
.collect(Collectors.toMap(ItemQty::getId, ItemQty::getQty))))
In case you want to keep the current flow, the easiest way would be to use filter
return itemRepository.findByd(itemId)
.collectList()
.filter(items -> !items.isEmpty())
...
but I would suggest to simplify the flow to make it more readable and don't overuse reactive operators where you don't really need them. For example, something like
return itemRepository.findByd(itemId)
.collectList()
.flatMap(items -> {
if (items.isEmpty()) {
return Mono.empty();
}
Map<Long, Integer> itemMap = items.stream()
.collect(Collectors.toMap(ItemQty::getId, ItemQty::getQty));
return retrieveItemsQty(itemMap.keySet())
.collectList()
.flatMap(availableQty -> reduceItemQty(availableQty, itemMap, itemId));
})
.then(Mono.just(itemId));
you could simplify even futher and move inner body into a separate method

How to create PostgreSQL savePoint with Reactor and Spring

io.r2dbc.spi.Connection and PostgresqlConnection have createSavepoint, releaseSavepoint and rollbackTransactionToSavepoint methods.
How can I use these method by having R2dbcTransactionManager and TransactionalOperator?
I want to create an idempotent service which tries to insert to a table, if a unique constraint is violated then selects the existing record and continues with that
Mono.just(newOrder)
.flatMap(order -> orderRepository.save(order)
.onErrorResume(throwable -> orderRepository.findByUniqueField(newOrder.uniqueField))
.otherProcesses...
.as(transactionalOperator::transactional)
And I receive current transaction is aborted, commands ignored until end of transaction block from PostgreSQL
I saw this answer https://stackoverflow.com/a/48771320/5275087 but it seams autosave=always doesn't work with r2dbc
I wanted to try something like:
transactionalOperator.execute(reactiveTransaction -> {
GenericReactiveTransaction genericReactiveTransaction = (GenericReactiveTransaction) reactiveTransaction;
ConnectionFactoryTransactionObject txObject = (ConnectionFactoryTransactionObject) genericReactiveTransaction.getTransaction();
Connection connection = txObject.getConnectionHolder().getConnection();
return Mono.from(connection.createSavepoint(TRANSACTION_LEVEL_1))
.then(Mono.just(newOrder)
.flatMap(order -> orderRepository.save(order)
.onErrorResume(throwable ->
Mono.from(connection.rollbackTransactionToSavepoint(TRANSACTION_LEVEL_1))
.then(orderRepository.findByUniqueField(newOrder.uniqueField)))
));
But R2dbcTransactionManager.ConnectionFactoryTransactionObject is a private class.
How do I achieve this without using reflection

How to handle failure in reactive my sql client in quarkus

In https://quarkus.io/guides/reactive-sql-clients page we have code snippet to execute query changes using transaction :
SqlClientHelper.inTransactionUni(client, tx -> tx
.preparedQuery("INSERT INTO person (firstname,lastname) VALUES ($1,$2) RETURNING id").execute(Tuple.of(person.getFirstName(), person.getLastName()))
.onItem().transformToUni(id -> tx.preparedQuery("INSERT INTO addr (person_id,addrline1) VALUES ($1,$2)")
.execute(Tuple.of(id.iterator().next().getLong("id"), person.getLastName()))).onItem().ignore().andContinueWithNull());
so here SqlClientHelper will begin the transaction,commit and rollback if any failure but is there any way to find out the root cause of the failure and print it in logs ?
In the documentation its not mentioned how we can do that.
You can use Mutiny's onFailure to get the exception class and act on it. See this for more details.
based on the link as given in the accepted Answer this is working for me :
return SqlClientHelper.inTransactionUni(mysqlPool, tx -> {
return tx.query(query).execute().onItem().transformToUni(
id -> tx.query("SELECT TRAN_ID FROM " + tableName + "
ORDER BY TO_DB_TS DESC LIMIT 1").execute())
.onItem().transform(rows ->
rows.iterator().next().getString(0)).onFailure().invoke(f -> {
LOG.error("Error while inserting data to " +
tableName + " table::"+f.getMessage());
});
});

How to create a Bulk consumer in Spring webflux and Kafka

I need to poll kafka and process events in bulk. In Reactor kafka, since its a steaming API, I am getting events as stream. Is there a way to combine and get a fixed max size of events.
This is what I doing currently.
final Flux<Flux<ConsumerRecord<String, String>>> receive = KafkaReceiver.create(eventReceiverOptions)
.receiveAutoAck();
receive
.concatMap(r -> r)
.doOnEach(listSignal -> log.info("got one message"))
.map(consumerRecords -> consumerRecords.value())
.collectList()
.flatMap(strings -> {
log.info("Read messages of size {}", strings.size());
return processBulkMessage(strings)
.doOnSuccess(aBoolean -> log.info("Processed records"))
.thenReturn(strings);
}).subscribe();
But code just hangs after collectList and never goes to the last flatMap.
Thanks In advance.
You just do a "flattening" with your plain .concatMap(r -> r) therefore you fully eliminate what is there is a batching originally built by that receiveAutoAck(). To have a stream of lists for your processBulkMessage() to process consider to move all the batch logic into that concatMap():
.concatMap(batch -> batch
.doOnEach(listSignal -> log.info("got one message"))
.map(ConsumerRecord::value)
.collectList())
.flatMap(strings -> {