Spring + Hibernate + Quartz:动态任务

3

我希望使用Quartz、Spring和Hibernate创建动态作业。用户通过Web服务与此类作业交互:

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);
    }
}

NoaJobInstancesDAO 是一个简单的 DAO 类,它使用 Hibernate 的 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);
    } 
}

问题在于当此任务触发时,会抛出异常:
javax.persistence.TransactionRequiredException: No transactional EntityManager available

我不明白为什么!我在一个管理器类中按照这种方式安排了工作。

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

调度程序与管理器相连的位置

@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;
}

类AutowiringSpringBeanJobFactory与Autowiring相同。

在我看来,调度程序的布线有些问题。事实上,我不明白如何检索应用程序上下文。

编辑1:应用程序上下文似乎被正确实例化了。问题可能不在那里。

编辑2:我正在使用单个配置bean(不是xml文件)。以下是主要方法:

@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;
}
3个回答

0

简短解决方案:通过工厂让Spring来完成你的工作。

详细解决方案:这里是详细描述。我通过导入一个XML配置文件修改了我的配置文件:

<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>

这样,您就拥有了一个生产作业实例的Spring工厂。现在,这是我的更新后的Java配置类:

@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)
}

我使用一个Bean来调度任务。因此,首先我在这个Bean中注入工厂。然后当我想要调度一个任务时,我使用以下代码片段

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

我也修改了工作类

@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");
    }
}

问题已解决!


0

你正在使用Spring管理的上下文,并尝试使用javax.persistence注释@PersistentContext访问EntityManager。尝试使用@Autowire自动装配EntityManagerFactory bean,我假设你在spring-context.xml中进行了配置,并使用entityManagerFactory.createEntityManager()来获取由Spring管理的实体管理器,该实体管理器将被Spring包装并位于您定义的事务管理器中。


在DAO层中,使用@Autowired注解注入EntityManagerFactory而不是注入EntityManager。在方法内部,通过entityManagerFactory.createEntityManager()获取EntityManager。 - AntJavaDev
我会尽快尝试。只有几个问题。第一个:我也在我的主线程中使用DAOs。这种修改会影响我的正常流程吗?第二个:我仍然可以使用createEntityManager()方法来忽略事务管理吗? - dylaniato
持久性可以配置为每个线程上下文(默认配置,因此不会引起任何并发问题),或者您可以将其绑定在预配置的应用程序服务器上下文中。对于第二个问题,正如您在代码中所看到的,在事务管理器中传递EntityManagerFactory,因此您获得的每个实体管理器都将是Spring托管的bean,并且将自动启动/结束事务。 - AntJavaDev
在你的配置类中,尝试将LocalContainerEntityManagerFactoryBean中属性autocommit的值设置为true,否则在StartJobSpring类中使用transactional注释时会出现问题,因为你还在DAO层中放置了相同的注释,所以spring会将其处理为一个大事务。正确的逻辑应该是访问DAO层的服务应该具有事务性而不是DAO。 - AntJavaDev
这是用于SettingsFactory的属性:hibernate.connection.autocommit。同时,请启用SQL日志记录以监视调用方法时Hibernate执行的步骤。 - AntJavaDev
显示剩余3条评论

0
我通过以下方法解决了这个问题:
在工作中(必须获得一个接口):
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;
    }
}

}

在应用程序的 .xml 上下文中: 还需要在此 xml 中声明为 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>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接