Spring with quartz and jpa transactions - java

Reading http://www.quartz-scheduler.org/documentation/quartz-2.x/configuration/ConfigJobStoreCMT.html it says that JTA transactions are supported with JobStoreCMT
Is it possible to configure quartz to run with JPA transaction manager? If not I assume Atomikos or Bitronix should be used with spring to enable JTA?
Basically I want quartz scheduler to roll back if the exception is thrown e.g.
#Transactional
public void scheduleJob(QuartzJobData quartzJobData) throws Exception {
SimpleTrigger trigger = (SimpleTrigger) newTrigger()
.withIdentity(name, group)
...
.build();
scheduler.scheduleJob(trigger);
throw new Exception("my exception");
// after exception I'd expect quartz to roll back
}
Note that I don't have any problems with running transactions in quartz jobs themselves. I only have problems with quartz scheduler not rolling back as shown in code example above.

Related

"javax.persistence.TransactionRequiredException: no transaction is in progress", even with #Transactional; possible to implicitly manage Transactions?

I'm upgrading an old system that is a batch job that uses Camel Spring Main to continue running, so that it can basically loop and query a database every few seconds, and then process those records. It uses Spring for configuration, but doesn't use Spring Boot. It was on Camel 2.x and I'm having to upgrade it to Camel 3.14. Also upgrading from Spring 4 to 5 and Hibernate 4 to 5.
After those upgrades, when I run it, I'm getting this TransactionRequiredException. Have found that a fair amount here; I have added the #Transactional to the method that does the update where the error occurs, but that made no difference.
Found another answer that says you also need context:component-scan in appContext.xml, which I do have.
This answer suggested adding a hibernate property, hibernate.allow_update_outside_transaction = true, which I tried in my app-confix.xml, but it didn't work.
This answer said to add the following code, which I did, and which did fix it:
protected static void storeObject(Object object) throws DAOException {
Transaction tx = null;
try {
Session session = Helper.getHibernateSession();
session.saveOrUpdate(object);
tx = session.beginTransaction();
session.flush();
tx.commit();
} catch (HibernateException he) {
if(tx != null){
tx.rollback();
}
throw new DAOException(he);
}
}
But to add this to every method that does an update seems like a lot of boilerplate, repeated code. Is there any way to implicitly do basic Transactions all the time? I tried configuring #Transactional and #EnableTransactionManagement and configured the supporting #Beans in a #Configuration class, by following the outline here, but still got the TransactionRequiredException: no transaction in progress error.
One article said #Transactional is only available in Spring Boot: is this true?

Spring boot quartz schema other than public doesn't work

I am not able to use other schema (than public) for quartz tables. This is my quartz setup:
spring.quartz.job-store-type=jdbc
spring.quartz.jdbc.initialize-schema=always
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
spring.quartz.properties.org.quartz.jobStore.isClustered=true
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=2000
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
spring.quartz.properties.org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
spring.quartz.properties.org.quartz.jobStore.useProperties=false
spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_
And config class:
#Bean
public SchedulerFactoryBean schedulerFactory(ApplicationContext applicationContext, DataSource dataSource, QuartzProperties quartzProperties) {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
AutowireCapableBeanJobFactory jobFactory = new AutowireCapableBeanJobFactory(applicationContext.getAutowireCapableBeanFactory());
Properties properties = new Properties();
properties.putAll(quartzProperties.getProperties());
schedulerFactoryBean.setOverwriteExistingJobs(true);
schedulerFactoryBean.setDataSource(dataSource);
schedulerFactoryBean.setQuartzProperties(properties);
schedulerFactoryBean.setJobFactory(jobFactory);
return schedulerFactoryBean;
}
#Bean
public Scheduler scheduler(ApplicationContext applicationContext, DataSource dataSource, QuartzProperties quartzProperties)
throws SchedulerException {
Scheduler scheduler = schedulerFactory(applicationContext, dataSource, quartzProperties).getScheduler();
scheduler.start();
return scheduler;
}
This works fine, and the tables are getting created. However I would like to have the tables in a different schema. So I set quartz to use 'quartz' schema.
spring.quartz.properties.org.quartz.jobStore.tablePrefix=quartz.QRTZ_
This is the error I'm getting:
[ClusterManager: Error managing cluster: Failure obtaining db row lock: ERROR: current transaction is aborted, commands ignored until end of transaction block] [org.quartz.impl.jdbcjobstore.LockException: Failure obtaining db row lock: ERROR: current transaction is aborted, commands ignored until end of transaction block
Any ideas on how to solve it?
It was a bold hope that "tablePrefix" can also adjsut the "db schema", (and there is no documented property concerning "db schema"), but you could get more lucky, if you configure it on the datasource.
i.e. you would introduce/configure different (spring) datasource( bean)s for every user/schema used by your application ...
(like here:) Spring Boot Configure and Use Two DataSources
or here
, then you'd wire the scheduler factory with the appropriate datasource (quartz).
schedulerFactoryBean.setDataSource(quartzDataSource);
Or via (#Autowired) parameter injection, or method invocation : #Bean initialization - difference between parameter injection vs. direct method access?
UPDATE (regarding "wiring"):
..from current spring-boot doc:
To have Quartz use a DataSource other than the application’s main DataSource, declare a DataSourcebean, annotating its #Bean method with #QuartzDataSource. Doing so ensures that the Quartz-specific DataSource is used by both the SchedulerFactoryBean and for schema initialization.
Similarly, to have Quartz use a TransactionManager other than the application’s main ... declare a TransactionManager bean, ...#QuartzTransactionManager.
You can take even more control by customizing:
spring.quartz.jdbc.initialize-schema
Database schema initialization mode.
default: embedded (embedded|always|never)
spring.quartz.jdbc.schema
Path to the SQL file to use to initialize the database schema.
default: classpath:org/quartz/impl/jdbcjobstore/tables_##platform##.sql
... properties, where ##platform## refers to your db vendor.
But it is useless for your requirement... since looking at
and complying with the original schemes - they seem schema independent/free. (So the data source approach looks more promising, herefor.)
REFS:
https://www.quartz-scheduler.org/documentation/quartz-2.3.0/configuration/ConfigJobStoreTX.html
Spring Boot Configure and Use Two DataSources
https://stackoverflow.com/a/42360877/592355
https://www.baeldung.com/spring-annotations-resource-inject-autowire
#Bean initialization - difference between parameter injection vs. direct method access?
spring.quartz.jdbc.initialize-schema
What are the possible values of spring.datasource.initialization-mode?
spring.quartz.jdbc.schema
https://github.com/quartz-scheduler/quartz/tree/master/quartz-core/src/main/resources/org/quartz/impl/jdbcjobstore
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.quartz
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/quartz/QuartzDataSource.html
So the idea is that Quartz doesn't create the tables using spring.quartz.properties.org.quartz.jobStore.tablePrefix
Table names are static. Eg qrtz_triggers. as #xerx593 pointed out.
What we can do is to create the tables (manual, flyway, liquibase) in a different schema, update tablePrefix=schema.qrtz_ and it will work.
Tested with Postgres

Spring boot and Quartz - Job not executing immediately

I am configuring Quartz job with Spring boot. The requirement is to execute the job immediately without attaching any schedule.
Here is what my code looks like
JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
String jobName = jobName(taskContext);
factoryBean.setJobClass(MyJobClass.class);
factoryBean.setDurability(true);
factoryBean.setApplicationContext(applicationContext);
factoryBean.setName("Hello job");
factoryBean.setGroup("Hello job group");
JobDataMap jobData = new JobDataMap(new HashMap<>());
factoryBean.setJobDataMap(jobData);
factoryBean.afterPropertiesSet();
JobDetail job = factoryBean.getObject();
Scheduler scheduler = schedulerFactoryBean.getScheduler();
scheduler.addJob(job, replace);
scheduler.triggerJob(job.getKey());
And here is how quartz.properties looks like
org.quartz.scheduler.instanceName=springBootQuartzApp
org.quartz.scheduler.instanceId=AUTO
org.quartz.threadPool.threadCount=10
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
org.quartz.jobStore.useProperties=true
org.quartz.jobStore.misfireThreshold=2000
org.quartz.jobStore.tablePrefix=qrtz_
org.quartz.jobStore.isClustered=false
org.quartz.plugin.shutdownHook.class=org.quartz.plugins.management.ShutdownHookPlugin
org.quartz.plugin.shutdownHook.cleanShutdown=TRUE
The problem is that the job is not firing immediately and is getting picked up as misfire instruction. It is executed right exactly after the misfireThreshold.
Please let me know, if I have missed something in the configuration or didn't call any appropriate API.
I got the same issue.
If your quartz is using a data source with transaction: #EnableTransactionManagement.
Please add #Transactional to the method of your code, then the transaction is committed immediately.
Later the scheduler thread looks up the db again and fire it finally.

Issue with Quartz and Spring Boot

So I have a technical challenge I need help with.
A large scale project is using a Quartz scheduler to schedule a job to run every night at 9.
The Job that is scheduled, however needs to read values from property files, get some beans using auto-wiring etc.
When I used #Autowired and #Value annotations, I found the values to be null.
The issue is that Quartz creates JobDetail objects using newJob() outside the spring container. As can be seen in the below code.
JobKey jobKey = new JobKey("NightJob", "9-PM Job");
JobDetail jobDetail = newJob(NightJob.class).withIdentity(jobKey)
.usingJobData("Job-Id", "1")
.build();
The jobDetail object which wraps NightJob thus cannot access property files or beans using spring.
Here is my NightJob class
public class NightJob implements Job{
//#Value to read from property file; here
//#Autowired to use some beans; here
#Override
public void execute(JobExecutionContext context) throws JobExecutionException{
}
}
I scanned Stack Overflow and shortlisted several solutions. I also read through the comments and listed the top counter-comments.
Suggestion 1: Get rid of Quartz and use Spring Batch due to its good integration with Spring Boot
Counter argument 1: Spring Batch is overkill for simple tasks. Use #Scheduled
Suggestion 2: Use #Scheduled annotations and cron expressions provided by spring
Counter argument 2: Your application will not be future ready if you remove Quartz. Complex scheduling may be required in the future
Suggestion 3 : Use the spring interface ApplicationContextAware.
Counter argument 3: Lots of additional code. Defeats the simple and easy concept of Spring boot
Is there a simpler way in Spring Boot to access property file values and autowire objects in a class that implements a Quartz job (In this situation , the NightJob class)
As written in the comments, Spring supports bean injection into Quartz jobs by providing setter methods: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-quartz.html

Spring Integration Transaction management using Atomikos

I am thinking to create a Spring Integration Spring Boot application to
1-Poll messages from a DB
2-Do some processing on it
3-Publish messages to EMS Queue
using Atomikos for Transaction management. My question is: If the above configuration will be transactional with all the required JTA configurations done? Also I have read somewhere, if multiple threads are created in Spring Integration,e.g,using a Splitter, then the context won't to transactional. How to overcome this?
If you configure the poller as transactional, the flow will run in a transaction, as long as you don't hand off to another thread (via an ExecutorChannel or QueueChannel channel, for example).
Adding a splitter will not break the transaction boundary as each split will be processed on the same thread.
Spring Integration has different requirements for transactions, to Do so you need to pass a transaction manager in the poller metaData, for example:
#Bean
public PollerMetadata pollerMetadata() throws NamingException {
return Pollers.fixedDelay(Long.valueOf(env.getProperty("poller.interval")))
.transactional(**transactionManager**).get();
}
With
#Autowired
private PlatformTransactionManager **transactionManager**;
And putting :
#InboundChannelAdapter(channel = "jpaInputChannel", poller = #Poller(value = "**pollerMetadata**"))

Categories

Resources