3

I want to create dynamically jobs using Quartz, Spring and Hibernate. Users interact with a web service to create jobs of this class:

public class StartJobSpring extends QuartzJobBean {

    private String jobId;
    private String jobType;

    @Autowired
    private NoaJobInstancesDAO njiDAO;

    @Transactional
    @Override
    protected void executeInternal(JobExecutionContext context)
            throws JobExecutionException {

        JobKey key = context.getJobDetail().getKey();
        JobDataMap dataMap = context.getMergedJobDataMap();

        // some logic
        njiDAO.create(instanceUUID, noaJob.getNoaJob(jobId), jobType);
    }
}

The NoaJobInstancesDAO is a simple DAO class which makes use of Hibernate's EntityManager:

@Repository
public class NoaJobInstancesDAOHibImpl implements NoaJobInstancesDAO {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    @Transactional
    public NoaJobInstanceJPA create(NoaJobInstanceJPA entity) {
        entityManager.persist(entity);
        return entity;
    }

    @Override
    public void create(String instance_uuid, NoaJobJPA job, String job_type) {
        NoaJobInstanceJPA entity = new NoaJobInstanceJPA(instance_uuid, job,
                job_type, "CREATED", null, null, "", "N", "N");
        this.create(entity);
    } 
}

The problem is that when this job fires, an exception is thrown:

javax.persistence.TransactionRequiredException: No transactional EntityManager available

and i can't understand why! I schedule the job in this way in a Manager class

JobDetail job = newJob(StartJobSpring.class).withIdentity(//anId)
                .setJobData(//aJobMap).build();
getScheduler().getObject().scheduleJob(job, trigger);

where the scheduler is wired to the manager as

@Autowired
private ApplicationContext applicationContext;

@Bean
SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource, JpaTransactionManager transactionManager) {

    SchedulerFactoryBean bean = new SchedulerFactoryBean();

    AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
    jobFactory.setApplicationContext(applicationContext);
    bean.setJobFactory(jobFactory);

    bean.setTransactionManager(transactionManager);

    return bean;
}

The class AutowiringSpringBeanJobFactory is the same as Autowiring.

In my opion there's something wrong in the scheduler wiring. In fact, I don't understand how I can retrieve the application context.

EDIT1: The application context seems to be correctly instanced. The problem could not be there.

EDIT2: I'm using a single configuration bean (not xml files). Here the main methods:

@Bean
LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource   dataSource) {
     LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
     entityManagerFactoryBean.setDataSource(dataSource);
     entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
     entityManagerFactoryBean.setPackagesToScan("package");

    Properties jpaProperties = new Properties();
    jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.OracleDialect");
    jpaProperties.put("hibernate.show_sql", "false");
    jpaProperties.put("hibernate.hbm2ddl.auto", "update");

    entityManagerFactoryBean.setJpaProperties(jpaProperties);

    return entityManagerFactoryBean;
}

@Bean
JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(entityManagerFactory);
    return transactionManager;
}

@Bean
public NoaJobInstancesDAO noaJobInstancesDAO() {
    NoaJobInstancesDAOHibImpl noaJobInstancesDAO = new NoaJobInstancesDAOHibImpl();
    return noaJobInstancesDAO;
}
dylaniato
  • 516
  • 9
  • 23

3 Answers3

0

you are in a spring managed context and you try to access the EntityManager with @PersistentContext which is a javax.persistence annotation. Try autowiring the EntityManagerFactory bean with @Autowire which i assume you configure it in the spring-context.xml and use the entityManagerFactory.createEntityManager() to give you spring managed entity manager which will be wrapped by spring and be in the transaction manager you define

AntJavaDev
  • 1,204
  • 1
  • 18
  • 24
  • Not sure i have understood. I updated my question with the main methods of my configuration bean. I suppose you mean to add the @Autowired annotation in my DAO and add another bean in my configuration which injects the entityManagerFactory.createEntityManager(). Am I right? – dylaniato Jun 09 '15 at 13:08
  • yes at your DAO layer , instead of injecting the EntityManager , inject the EntityManagerFactory with Autowired annotation and inside your methods , get the entity manager from entityManagerFactory.createEntityManager() – AntJavaDev Jun 09 '15 at 13:22
  • I'll try asap. Just a couple of questions. The first: I use DAOs in my main thread too. This kind of modification can affect my normal flow? The second: still I can ignore transactions management using the createEntityManager() method? – dylaniato Jun 09 '15 at 13:27
  • the persistence can be configured to be per thread context (default configuration so it will not cause any concurrency problems,) or you can bind it on an Application Server pre configured context, for the second question as you can see in your code , at the transaction manager you pass the EntityManagerFactory so every entity manager that you get will be a spring managed bean and will automatically start / end a transaction – AntJavaDev Jun 09 '15 at 13:39
  • I made your suggested modifications but I still have a big problem: changes to DB tables are not persisted. But now the entity manager is correctly seen. – dylaniato Jun 09 '15 at 14:58
  • well at your configuration class , in the LocalContainerEntityManagerFactoryBean try setting a value for the property autocommit to true , otherwise you have a problem with the transactional annotation in the StartJobSpring class , because you have placed in the DAO layer also the same annotation , so the spring handles it in one big transaction. the correct logic would be that the services that accessing the DAO layer would be transactional and not the DAO's – AntJavaDev Jun 09 '15 at 15:08
  • As far as I can see, there's no autocommit property, but I can be wrong. What I have done up to now, is to take the transaction, begin it programmatically (entityManager.getTransaction().begin()) and then commit it. But this is not a good approach, I'd like to make Spring manage all of this. – dylaniato Jun 12 '15 at 07:32
  • this is the property for the settingsfactory hibernate.connection.autocommit , also please enable sql logging to monitor the steps that hibernate does , when you call your methods – AntJavaDev Jun 12 '15 at 07:37
0

SHORT SOLUTION: let Spring make your jobs through factories.

LONG SOLUTION: here the long description. I have modified my configuration file by importing an xml configuration file:

<bean name="complexJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    <property name="jobClass" value="jobs.StartJob" />
    <property name="durability" value="true" />
</bean>

<bean id="cronTrigger"
        class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="complexJobDetail" />
    <property name="cronExpression" value="0/5 * * ? * SAT-SUN" />
</bean>

This way, you have a spring factory which produces jobs instances. Now, here's my updated java config class

@ImportResource({"spring-quartz-context.xml"})
public class BeanConfig {
    //autowired from xml
    @Autowired JobDetailFactoryBean jobDetailFactory;
    @Autowired CronTriggerFactoryBean cronTriggerFactory;

    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(LocalContainerEntityManagerFactoryBean entityManagerFactory) {

        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        bean.setApplicationContextSchedulerContextKey("applicationContext");

        bean.setSchedulerName("MyScheduler");

        //used for the wiring
        Map<String, Object> schedulerContextAsMap = new HashMap<String, Object>();
        schedulerContextAsMap.put("noaJobDAO", noaJobDAO());
        schedulerContextAsMap.put("noaJobInstancesDAO", noaJobInstancesDAO());
        schedulerContextAsMap.put("esbClient", this.esbClient());
        bean.setSchedulerContextAsMap(schedulerContextAsMap);

        bean.setQuartzProperties(quartzProperties());

        return bean;
    }

    @Bean
    public Properties quartzProperties() {
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("quartz.properties"));
        Properties properties = null;
        try {
            propertiesFactoryBean.afterPropertiesSet();
            properties = propertiesFactoryBean.getObject();

        } catch (IOException e) {
            log.warn("Cannot load quartz.properties.");
        }

        return properties;
    }

    // other beans (as included in the question)
}

I use a bean to schedule jobs. So first I inject factories in this bean. Then when I want to schedule a Job, I use this snippet

JobDetail job = jobDetailFactory.getObject();
Trigger trigger = cronTriggerFactory.getObject();
scheduler.schedule(job, trigger);

I have also modified the job class

@Service
public class StartJob extends QuartzJobBean {

    // the DAO
    private NoaJobInstancesDAO njiDAO;

    public void executeInternal(JobExecutionContext context)
            throws JobExecutionException {
        init(context.getJobDetail().getJobDataMap(), context.getScheduler()
                    .getContext());
        // some logic here
        njiDAO.create(params);
    }

    private void init(JobDataMap jobContextMap,
            SchedulerContext schedulerContext) {
        // some initialization using the job data map, not interesting for DAOs

        // row that inject the correct DAO
        this.njiDAO = (NoaJobInstancesDAO) schedulerContext
                .get("noaJobInstancesDAO");
    }
}

Problem solved!

dylaniato
  • 516
  • 9
  • 23
0

I solved this problem doing this:

In the Job (It is mandatory to get an Interface):

public class SchedulerJob extends QuartzJobBean {
public void executeInternal(JobExecutionContext context)
        throws JobExecutionException {
    try{
        <YOUR_BEAN_DAO_INTERFACE_OBJECT> = ((ApplicationContext) context.getJobDetail().getJobDataMap().get("applicationContext")).get("<YOUR_BEAN_DAO_INTERFACE_ID>");
    } catch (Exception e ){
        e.printStackTrace();
        return;
    }
}

}

In the .xml context of the application: It is also necessary to declare in this xml as a bean:

<!-- Spring Quartz Scheduler job -->
<bean name="schedulerJob" class="org.springframework.scheduling.quartz.JobDetailBean">
    <property name="jobClass" value="<PATH_OF_YOUR_CLASS_JOB>.SchedulerJob" />
    <property name="applicationContextJobDataKey" value="applicationContext" />
</bean>

<!-- Cron Trigger, run every 10 seconds -->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
    <property name="jobDetail" ref="schedulerJob" />
    <property name="cronExpression" value="0/10 * * * * ?" />
</bean>

<!-- DI -->
<bean id="scheduler"
    class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="jobDetails">
        <list>
            <ref bean="schedulerJob" />
        </list>
    </property>

    <property name="triggers">
        <list>
            <ref bean="cronTrigger" />
        </list>
    </property>
</bean>
juldeh
  • 129
  • 13