为什么无法在 Quartz 中的 class 方法上使用 spring 数据 jpa 的存储库 class?

Unable to use Repository class of spring data jpa on a class method in Quartz why?

我正在开发 multiple-jobs-in-quartz-spring-example,它是 Spring + Quartz + Spring Data JPA 的组合。我希望开发一个代码,它将在 5 秒内 运行,它会访问数据库并从数据库中获取记录。

我快要让它工作了,但我发现了一个小问题。在我的 JobA.class 中,为什么我没有得到 CustomerRepository.java 的实例?它始终为 null,这就是代码无法访问 DB 的原因。

源代码位于:http://www.github.com/test512/multiple-jobs-in-quartz-spring-example.gi‌​t.

JobA.java

@Service
public class JobA extends QuartzJobBean {

    private CustomerRepository customerRepository = null;
    
    @Autowired
    public JobA(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }
    public JobA() { }
    
    @Override
    protected void executeInternal(JobExecutionContext executionContext) throws JobExecutionException {
        System.out.println("~~~~~~~ Job A is runing ~~~~~~~~");
        Trigger trigger = executionContext.getTrigger();
        System.out.println(trigger.getPreviousFireTime());
        System.out.println(trigger.getNextFireTime());
        getCustomerList();
    }
    
    private List<Customer> getCustomerList(){
        List<Customer> customers = customerRepository.findAll();
        for (Customer customer : customers) {
            System.out.println("------------------------------");
            System.out.println("ID : "+customer.getId());
            System.out.println("NAME : "+customer.getName());
            System.out.println("STATUS : "+customer.getStatus());
        }
        return customers;
    }
}

Customer.java

@Entity
@Table(name="customer")
public class Customer {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="ID")
    private Long id;
    
    @Column(name="NAME")
    private String name;
    
    @Column(name="STATUS")
    private String status;
    
    
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getStatus() {
        return status;
    }
    public void setStatus(String status) {
        this.status = status;
    }
}

CustomerRepository.java

public interface CustomerRepository extends JpaRepository<Customer, Long>{

}

dataSourceContext.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
        
    <context:property-placeholder location="classpath:database.properties"/>
    
    <!-- <jdbc:initialize-database data-source="dataSource" enabled="true">
        <jdbc:script location="classpath:db-schema.sql" />
        <jdbc:script location="classpath:db-test-data.sql" />
    </jdbc:initialize-database> -->
    
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${mysql.driver.class.name}" />
        <property name="url" value="${mysql.url}" />
        <property name="username" value="${mysql.username}" />
        <property name="password" value="${mysql.username}" />
    </bean>
    
    <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <property name="showSql" value="true"/>
        <property name="generateDdl" value="true"/>
        <property name="database" value="MYSQL"/>
    </bean>
    
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
        <!-- spring based scanning for entity classes-->
        <property name="packagesToScan" value="com.mkyong.*"/>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"/>
</beans>

Spring-Quartz.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xmlns:repository="http://www.springframework.org/schema/data/repository"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
        http://www.springframework.org/schema/data/repository http://www.springframework.org/schema/data/repository/spring-repository.xsd">

    <import resource="classpath:dataSourceContext.xml"/>
    
    <jpa:repositories base-package="com.mkyong.repository" />
    <context:component-scan base-package="com.mkyong.*" />
    <context:annotation-config />

    <bean id="jobA" class="com.mkyong.job.JobA" />
    <bean id="jobB" class="com.mkyong.job.JobB" />
    <bean id="jobC" class="com.mkyong.job.JobC" />
    <bean id="autowiredA" class="com.mkyong.job.JobASpringBeanJobFactory" />

    <!-- ~~~~~~~~~ Quartz Job ~~~~~~~~~~ -->
    <bean name="JobA" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="jobClass" value="com.mkyong.job.JobA" />
    </bean>

    <bean name="JobB" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="jobClass" value="com.mkyong.job.JobB" />
    </bean>
    
    <bean name="JobC" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="jobClass" value="com.mkyong.job.JobC" />
    </bean>
    
    <!-- ~~~~~~~~~~~ Cron Trigger, run every 5 seconds ~~~~~~~~~~~~~ -->
    <bean id="cronTriggerJobA" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="JobA" />
        <property name="cronExpression" value="0/5 * * * * ?" />
    </bean>
    
    <bean id="cronTriggerJobB" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="JobB" />
        <property name="cronExpression" value="0/5 * * * * ?" />
    </bean>
    
    <bean id="cronTriggerJobC" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="JobC" />
        <property name="cronExpression" value="0/5 * * * * ?" />
    </bean>

    <!-- ~~~~~~~~~~~~~~~~  Scheduler bean Factory   ~~~~~~~~~~~~~~~~ -->
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="jobFactory" ref="autowiredA"/> 
        <property name="triggers">
            <list>
                <ref bean="cronTriggerJobA" />
                <!-- <ref bean="cronTriggerJobB" />
                <ref bean="cronTriggerJobC" /> -->
            </list>
        </property>
    </bean>
</beans>

JobASpringBeanJobFactory.java

public class JobASpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {

    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(final ApplicationContext context) {
        beanFactory = context.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }
}

App.java

public class App {
    public static void main(String[] args) throws Exception {
        ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Quartz.xml");
    }
}

完成以下 link http://codrspace.com/Khovansa/spring-quartz-with-a-database/ 应该会有帮助。

引用上面link,

Quartz creates a new job instance on each invocation. It means that Quartz jobs are not regular Spring beans and we can't expect the Spring magic to take an effect automatically (and Spring's 'JobDetailFactoryBean' is not smart enough to do it for us). So we'll have to implement our own job factory that would overwrite the default SpringBeanJobFactory.

所以你需要 custom SpringBeanJobFactory by extending SpringBeanJobFactory & implementing ApplicationContextAware 最后调用 beanFactory.autowireBean(job)