How to increase transaction timeout in Quarkus? - quarkus

I have some configurations in my application.properties file:
...
quarkus.datasource.url=jdbc:postgresql://...:5432/....
quarkus.datasource.driver=org.postgresql.Driver
quarkus.datasource.username=user
quarkus.datasource.password=password
quarkus.hibernate-orm.database.generation=update
...
I have a scheduler with a #Transactional method that takes a long time to finish executing:
#ApplicationScoped
class MyScheduler {
...
#Transactional
#Scheduled(every = "7200s")
open fun process() {
... my slow proccess goes here...
entityManager.persist(myObject)
}
}
And then, the transactional method receives a timeout error like that:
2019-06-24 20:11:59,874 WARN [com.arj.ats.arjuna] (Transaction Reaper) ARJUNA012117: TransactionReaper::check timeout for TX 0:ffff0a000020:d58d:5cdad26e:81 in state RUN
2019-06-24 20:12:47,198 WARN [com.arj.ats.arjuna] (DefaultQuartzScheduler_Worker-3) ARJUNA012077: Abort called on already aborted atomic action 0:ffff0a000020:d58d:5cdad26e:81
Caused by: javax.transaction.RollbackException: ARJUNA016102: The transaction is not active! Uid is 0:ffff0a000020:d58d:5cdad26e:81
I believe that I must increase the timeout of my transacional method.
But I dont know how I can do this.
Someone could help me, please?
Thanks!

Seems that this has changed -> it is now possible to set the Transaction timeout:
https://quarkus.io/guides/transaction
You can configure the default transaction timeout, the timeout that applies to all transactions managed by the transaction manager, via the property:
quarkus.transaction-manager.default-transaction-timeout = 240s
-> specified as a duration (java.time.Duration format). Default is 60 sec

Quarkus don't allow you to globally configure the default transaction timeout yet (see https://github.com/quarkusio/quarkus/pull/2984).
But you should be able to do this at the user transaction level.
You can inject the UserTransaction object and set the transaction timeout in a postconstruct bloc.
Something like this should work :
#ApplicationScoped
class MyScheduler {
#Inject UserTransaction userTransaction;
#PostConstruct
fun init() {
//set a timeout as high as you need
userTransaction.setTransactionTimeout(3600);
}
#Transactional
#Scheduled(every = "7200s")
open fun process() {
entityManager.persist(myObject)
}
}
If you extract the code that make the transaction inside a Service, you can have a service with a #Transactional annotation, inject the UserTransaction in your scheduler and set the transaction timeout before calling the service.
All this works, I just tested both solution ;)

Thanks #loicmathieu for the answer!
I will just append some more details below.
You need to remove #Transactional and set transaction timeout before begin the transaction. In the end, you must commit the transaction:
import io.quarkus.scheduler.Scheduled
import javax.enterprise.context.ApplicationScoped
import javax.inject.Inject
import javax.transaction.UserTransaction
#ApplicationScoped
open class MyScheduler {
#Inject
lateinit var em: EntityManager
#Inject
lateinit var ut: UserTransaction
#Scheduled(every = "3600s")
open fun process() {
ut.setTransactionTimeout(3600)
ut.begin()
offerService.processOffers()
ut.commit()
}
}

Use the #TransactionConfiguration annotation and specify the seconds:
#Transactional
#TransactionConfiguration(timeout = 9876)
#Scheduled(every = "7200s")
open fun process() {
... my slow proccess goes here...
entityManager.persist(myObject)
}

Related

Spring Retry with Transactional Annotation

Is the below code the correct way to use Spring Retry with Transactional?
Or do I need to take care of anything extra ? I am using latest Spring Boot version
Is retry tried after the failed transaction is closed ?
#Repository
public class MyRepository {
#Retryable( value = CustomRetryAbleException.class, maxAttempts = 2, backoff = #Backoff(delay = 30000))
#Transactional
Employee updateAndGetEmployee(String date) throw CustomRetryAbleException;
{
try{
jdbcTemplate.exceute( ....) ; //Call Stored Proc
}
catch(CustomRetryAbleException c )
{
throw CustomRetryAbleException (" Retry this Exception " );
}
}
'This is the way.'
Do not forget to put the #EnableRetry annotation on either your config-class (annotated with #Configuration) or your application-class (annotated with #SpringBootApplication).
Read this for more information.
You can just log something and intentionally make it fail to see if it gets logged again after the delay.

How to initialize/enable Bean after another process finishes?

The idea is that I would like to first let a #Scheduled method retrieve some data and only when that process has finished enable/initialize my #KafkaListener. Currently the Kafka listener starts up immediately without waiting for the scheduler to be done.
I've tried to use #Conditional with a custom Condition, but this only is executed on context creation (aka startup). Also #ConditionalOnBean didn't work because actually my Scheduler bean is already created before it finishes the process.
This is how my setup looks like.
Kafka Listener:
#Service
class KafkaMessageHandler(private val someRepository) {
#KafkaListener(topics = ["myTopic"])
fun listen(messages: List<ConsumerRecord<*, *>>) {
// filter messages based on data in someRepository
// Do fancy stuff
}
}
Scheduler:
#Component
class Scheduler(private val someRepository) {
#Scheduled(fixedDelayString = "\${schedule.delay}")
fun updateData() {
// Fetch data from API
// update someRepository with this data
}
}
Is there any nice Spring way of waiting for the scheduler to finish before initializing the KafkaMessageHandler?

EventListener and retryable

I would like to invoke some code after my application start. Is there any way to handle event:
Started SomeApp in 14.905 seconds (JVM running for 16.268)
I'm going to try if another application is up. I've tried to use Retryable but not its executed before application started and exception is thrown so application exits.
#EventListener
fun handleContextRefresh(event: ContextRefreshedEvent) {
retryableInvokeConnection()
}
#Retryable(
value = [RetryableException::class, ConnectionException::class],
maxAttempts = 100000,
backoff = Backoff(delay = 5)
)
private fun retryableInvokeConnection() {
}
#Recover
private fun retryableInvokeConnectionExceptionHandler(ex: ConnectionException) {
}
Maybe I should use PostConstruct and while loop.
You can't call a #Retryable method within the same bean, it bypasses the proxy with the retry interceptor. Move the method to another bean and inject it.
The event is a better way than using #PostConstruct.

Spring Service #Transactional doesn't rollback transaction Mybatis SqlSession

The goal is to rollback all/any transactions in case of failure. But this doesn't work as expected.
We use Spring MVC + JMS + Service + Mybatis. In the logs, the JMS is set to rollback, but the row is inserted and not rollback. Would like to know what I'm missing or doing wrong?
The #Transactional tag was added recently. So not sure if it works as expected.
Code:
Service Class:
#Transactional(value = "transactionManager", propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class DataExchangeLogic implements DataExchangeService {
private DataExchDao dataExchDao;
...
#Override
public void save(DataExch dataExch) throws ValidationException {
if (dataExch.getId() != null && dataExch.getId() > 0) {
this.dataExchDao.update(dataExch);
} else {
//LOGGER.debug("in insert::");
this.dataExchDao.create(dataExch);
//Empty exception throw to test rollback
throw new RuntimeException();
}
}
}
DAO:
public interface DataExchDaoMybatis
extends NotificationDao {
void create(DataExch dataExch);
}
Spring Context
<bean id="dataExchLogic" class="com.abc.service.logic.DataExchLogic">
<property name="dataExchDao" ref="dataExchDao" />
</bean>
EAR/WAR project Spring Context
<!-- Transaction Manager -->
<bean id="transactionManager" class="org.springframework.transaction.jta.WebSphereUowTransactionManager" />
<tx:annotation-driven transaction-manager="transactionManager" />
Logs:
[31mWARN [0;39m [36mo.s.j.l.DefaultMessageListenerContainer[0;39m # Setup of JMS message listener invoker failed for destination 'queue://REQUEST?priority=1&timeToLive=500000' - trying to recover. Cause: Transaction rolled back because it has been marked as rollback-only
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:720)
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:240)
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1142)
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.executeOngoingLoop(DefaultMessageListenerContainer.java:1134)
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1031)
at java.lang.Thread.run(Thread.java:745)
[34mINFO [0;39m [36mo.s.j.l.DefaultMessageListenerContainer[0;39m # Successfully refreshed JMS Connection
[39mDEBUG[0;39m [36mo.s.j.l.DefaultMessageListenerContainer[0;39m # Received message of type [class com.ibm.ws.sib.api.jms.impl.JmsTextMessageImpl] from consumer [com.ibm.ws.sib.api.jms.impl.JmsQueueReceiverImpl#6ca01c74] of transactional session [com.ibm.ws.sib.api.jms.impl.JmsQueueSessionImpl#3ac3b63]
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#206ee277]
JDBC Connection [com.ibm.ws.rsadapter.jdbc.WSJdbcConnection#19b89f0c] will be managed by Spring
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create!selectKey[0;39m # ==> Preparing: SELECT ID.NEXTVAL FROM DUAL
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create!selectKey[0;39m # ==> Parameters:
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create!selectKey[0;39m # <== Total: 1
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create[0;39m # ==> Preparing: INSERT INTO TABLE ( COL1, COL2, COL N) VALUES ( ?, CURRENT_TIMESTAMP, ?, ?, ?, ?, ?, ?)
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create[0;39m # ==> Parameters: 468(Integer), SYSTEM(String), 2017-03-01 00:00:00.0(Timestamp), 2017-03-16 00:00:00.0(Timestamp), true(Boolean), test 112(String), ALL(String)
[39mDEBUG[0;39m [36mg.c.i.q.d.m.N.create[0;39m # <== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#206ee277]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#206ee277]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession#206ee277]
EDIT 1:
Controller code:
#ResourceMapping(value = "addNewDEURL")
public void addNewDE(#ModelAttribute(value = "dataObject") final DataExch dataExch,
final BindingResult bindingResult, final ResourceResponse response) {
if (!bindingResult.hasErrors()) {
try {
dataExchangeService.save(dataExch);
} catch (final ValidationException e) {
logger.error("A validation exception occurred.", e);
}
} else {
logger.error(bindingResult.getAllErrors().get(0)
.getDefaultMessage());
}
}
DAO changed:
public class DataExchDaoMybatis extends BaseDaoImpl implements DataExchDao {
public void create(DataExch dataExch) {
doSimpleInsert("insertDE", dataExch);
}
}
BaseDaoImpl:
public void doSimpleInsert(String queryId, Object o) {
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.insert(queryId, o);
}
Please put transactionManager configuration and tx:annotation-driven into root spring context
Rule: Root context can see all the beans which Spring created. Child context(any Web Context) can see only its own beans.
In this particular case tx:annotation-driven looks for beans with #Transactional annotation in Web context. It cannot find any because you defined dataExchLogic in root context. That's why you didn't have any transactional behavior.
#EnableTransactionManagement and only looks for #Transactional on beans in the same application context they are defined in. This means that, if you put annotation driven configuration in a WebApplicationContext for a DispatcherServlet, it only checks for #Transactional beans in your controllers, and not your services. See Section 21.2, “The DispatcherServlet” for more information.
Solution implies to move tx:annotation-driven to the root context because Root Context can find any bean defined either in root or in any web context.
Quoting from spring documentation:
You can place the #Transactional annotation before an interface
definition, a method on an interface, a class definition, or a public
method on a class. However, the mere presence of the #Transactional
annotation is not enough to activate the transactional behavior. The
#Transactional annotation is simply metadata that can be consumed by
some runtime infrastructure that is #Transactional-aware and that can
use the metadata to configure the appropriate beans with transactional
behavior. In the preceding example, the
element switches on the transactional behavior.
Which means,
void create(DataExch dataExch);
should be
public void create(DataExch dataExch);
#Transactional annotation behavior is not exhibited if it is not applied on a public method.
EDIT:
Since my answer was downvoted, to support my answer and to shed some light on the transactional behavior when a Transactional annotated method calls a method without annotation, take a look at this:
#Transactional method calling another method without #Transactional anotation? specifically the answer by Arun P Johny
I think of two possibilities.
1) Your DAO class is starting a new transaction.
2) Your DAO class is not participating in the transaction.
I dont see any other reason why the data should be updated to the database. Can you add below property to log4j to see how many transactions are being started.
log4j.logger.org.springframework.transaction.interceptor = trace
Also syosut the below transaction status in Service and DAO method to see if the transaction is active.
TransactionSynchronizationManager.isActualTransactionActive()
Let us know what happens.

Spring Boot JPA and HikariCP maintaining active connections

Brief:
Is there a way to ensure that a connection to the database is returned to the pool?
Not-brief:
Data flow:
I have some long running tasks that could be sent to the server in large volume bursts.
Each of the requests is recorded in the DB that the submission was started. Then send that request off for processing.
If failure or success the request is recorded after the task is completed.
The issue is that after the submission is recorded all the way through the long running task, the connection pool uses an "active" connection. This could potential use up any size pool I have if the burst was large enough.
I am using spring boot with the following structure:
Controller - responds at "/" and has the "service" autowired.
Service - Contains all the JPA repositories and #Transactional methods to interact with the database.
When every the first service method call is made from the controller it opens an active connection and doesn't release it until the controller method returns.
So, Is there a way to return the connection to the pool after each service method?
Here is the service class in total:
#Service
#Slf4j
class SubmissionService {
#Autowired
CompanyRepository companyRepository;
#Autowired
SubmissionRepository submissionRepository;
#Autowired
FailureRepository failureRepository;
#Autowired
DataSource dataSource
#Transactional(readOnly = true)
public Long getCompany(String apiToken){
if(!apiToken){
return null
}
return companyRepository.findByApiToken(apiToken)?.id
}
#Transactional
public void successSubmission(Long id) {
log.debug("updating submission ${id} to success")
def submissionInstance = submissionRepository.findOne(id)
submissionInstance.message = "successfully analyzed."
submissionInstance.success = true
submissionRepository.save(submissionInstance)
}
#Transactional
public long createSubmission(Map properties) {
log.debug("creating submission ${properties}")
dataSource.pool.logPoolState()
def submissionInstance = new Submission()
for (key in properties.keySet()) {
if(submissionInstance.hasProperty(key)){
submissionInstance."${key}" = properties.get(key)
}
}
submissionInstance.company = companyRepository.findOne(properties.companyId)
submissionRepository.save(submissionInstance)
return submissionInstance.id
}
#Transactional
public Long failureSubmission(Exception e, Object analysis, Long submissionId){
//Track the failures
log.debug("updating submission ${submissionId} to failure")
def submissionInstance
if (submissionId) {
submissionInstance = submissionRepository.findOne(submissionId)
submissionRepository.save(submissionInstance)
}
def failureInstance = new Failure(submission: submissionInstance, submittedJson: JsonOutput.toJson(analysis), errorMessage: e.message)
failureRepository.save(failureInstance)
return failureInstance.id
}
}
It turns out that #M.Deinum was onto the right track. Spring Boot JPA automatically turns on the "OpenEntityManagerInViewFilter" if the application property spring.jpa.open_in_view is set to true, which it is by default. I found this in the JPA Configuration Source.
After setting this to false, the database session wasn't held onto, and my problems went away.

Resources