How to run update query in Spring JPA for quartz j

2019-07-19 17:07发布

问题:

I have a quartz job in spring 4 and I am using JPA hibernate to update database value through quartz job but I am getting javax.persistence.TransactionRequiredException: Executing an update/delete query

I don't understand what kind of configuration is missing in quartz job. I referred to SpringBeanAutowiringSupport example still update is failing but select is working fine.

Below is my code

@Configuration 
@ComponentScan("com.stock") 
public class QuartzConfiguration {

  @Autowired
  private ApplicationContext applicationContext;

  @Bean
  public JobDetailFactoryBean jobDetailBalanceCarryForward(){
      JobDetailFactoryBean factory = new JobDetailFactoryBean();
      factory.setJobClass(BillingCroneSvcImpl.class);
      Map<String,Object> map = new HashMap<String,Object>();
      map.put("task", "balanceCarryForward");
      factory.setJobDataAsMap(map);
      factory.setGroup("BalanceCarryForwardJob");
      factory.setName("balance carry forward");
      return factory;
  }

  @Bean
  public CronTriggerFactoryBean cronTriggerBalanceCarryForward(){
      CronTriggerFactoryBean stFactory = new CronTriggerFactoryBean();
      stFactory.setJobDetail(jobDetailBalanceCarryForward().getObject());
      stFactory.setStartDelay(3000);
      stFactory.setName("balancCarryForwardTrigger");
      stFactory.setGroup("balanceCarryForwardgroup");
      stFactory.setCronExpression("0 0/1 * 1/1 * ? *");
      return stFactory;
  }

    @Bean
  public SpringBeanJobFactory springBeanJobFactory() {
      AutoWiringSpringBeanJobFactory jobFactory = new    AutoWiringSpringBeanJobFactory();
      jobFactory.setApplicationContext(applicationContext);
      return jobFactory;
}

  @Bean
  public SchedulerFactoryBean schedulerFactoryBean() {
      SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
      schedulerFactory.setJobFactory(springBeanJobFactory());
      schedulerFactory.setTriggers(cronTriggerBalanceCarryForward().getObject());
      return schedulerFactory;
  }
}

Below class where quartz executeInternal method is written

@Service
@PersistJobDataAfterExecution
@DisallowConcurrentExecution

@Autowired
private BillingCroneRepo billingCroneRepo;

public class BillingCroneSvcImpl extends QuartzJobBean implements BillingCroneSvc  {

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

        SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(context);
        billingCroneRepo.updateBalance(); 
        // this method throws exception javax.persistence.TransactionRequiredException: Executing an update/delete query
    }
}

App config class

@EnableWebMvc
@EnableTransactionManagement
@Configuration
@ComponentScan({ "com.stock.*" })
@Import({ SecurityConfig.class })
@PropertySource("classpath:jdbc.properties")
public class AppConfig extends WebMvcConfigurerAdapter {

private static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver";
private static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";
private static final String PROPERTY_NAME_DATABASE_URL = "db.url";
private static final String PROPERTY_NAME_DATABASE_USERNAME = "db.username";

private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
private static final String PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN = "entitymanager.packages.to.scan";

@Resource
private Environment env;

@Bean(name = "dataSource")
public DriverManagerDataSource dataSource() {
    DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
    driverManagerDataSource.setDriverClassName(env.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
    driverManagerDataSource.setUrl(env.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
    driverManagerDataSource.setUsername(env.getRequiredProperty(PROPERTY_NAME_DATABASE_USERNAME));
    driverManagerDataSource.setPassword(env.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));
    return driverManagerDataSource;
}

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
    LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
    entityManagerFactoryBean.setDataSource(dataSource());
    entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistence.class);
    entityManagerFactoryBean.setPackagesToScan(env.getRequiredProperty(PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN));
    entityManagerFactoryBean.setJpaProperties(hibProperties());
    return entityManagerFactoryBean;
}

private Properties hibProperties() {
    Properties properties = new Properties();
    properties.put(PROPERTY_NAME_HIBERNATE_DIALECT,env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
    properties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL,env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
    return properties;
}

@Bean
public PlatformTransactionManager transactionManager() {
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
    return transactionManager;
}

@Bean
public ReloadableResourceBundleMessageSource messageSource(){
    ReloadableResourceBundleMessageSource messageSource=new ReloadableResourceBundleMessageSource();
    String[] resources= {"classpath:messages"};
    messageSource.setBasenames(resources);
    return messageSource;
}

@Bean
public LocaleResolver localeResolver() {
    final CookieLocaleResolver ret = new CookieLocaleResolver();
    ret.setDefaultLocale(new Locale("en_IN"));
    return ret;
}

@Bean 
public LocaleChangeInterceptor localeChangeInterceptor(){
    LocaleChangeInterceptor localeChangeInterceptor=new LocaleChangeInterceptor();
    localeChangeInterceptor.setParamName("language");
    return localeChangeInterceptor;
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/Angular/**").addResourceLocations("/Angular/");
    registry.addResourceHandler("/css/**").addResourceLocations("/css/");
    registry.addResourceHandler("/email_templates/**").addResourceLocations("/email_templates/");
    registry.addResourceHandler("/fonts/**").addResourceLocations("/fonts/");
    registry.addResourceHandler("/img/**").addResourceLocations("/img/");
    registry.addResourceHandler("/js/**").addResourceLocations("/js/");
    registry.addResourceHandler("/Landing_page/**").addResourceLocations("/Landing_page/");
}

@Bean
public static PropertySourcesPlaceholderConfigurer properties() {
    PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
    org.springframework.core.io.Resource[] resources = new ClassPathResource[] { new ClassPathResource("application.properties") };
    pspc.setLocations(resources);
    pspc.setIgnoreUnresolvablePlaceholders(true);
    return pspc;
}

@Bean
public InternalResourceViewResolver viewResolver() {
    InternalResourceViewResolver viewResolver   = new InternalResourceViewResolver();
    viewResolver.setViewClass(JstlView.class);
    viewResolver.setPrefix("/WEB-INF/pages/");
    viewResolver.setSuffix(".jsp");
    return viewResolver;
}

// through below code we directly read properties file in jsp file
@Bean(name = "propertyConfigurer")
public PropertiesFactoryBean mapper() {
    PropertiesFactoryBean bean = new PropertiesFactoryBean();
    bean.setLocation(new ClassPathResource("application.properties"));
    return bean;
}

}

Can anybody please assist me how to resolve transational issue in spring JPA with quartz

回答1:

Thanks you all for your help. Finally I autowired EntityManagerFactory instead of persitance EntityManager and it is working fine. I tried all scenario but nothing worked to inject spring transactional in quartz so finally autoriwed entitymanagerfactory

Below is my repo class code.

@Repository
public class BillingCroneRepoImpl implements BillingCroneRepo {
 /*@PersistenceContext
 private EntityManager entityManager;*/

 @Autowired
 EntityManagerFactory entityManagerFactory;

 public boolean updateTable(){
    EntityManager entityManager =  entityManagerFactory.createEntityManager();
    EntityTransaction entityTransaction = entityManager.getTransaction();
    entityTransaction.begin(); // this will go in try catch
    Query query = entityManager.createQuery(updateSql);
    // update table code goes here

    entityTransaction.commit(); // this will go in try catch
 }
}


回答2:

I'm not the Spring specialist, but I think new ... doesn't work with @Transactional

@Service
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class BillingCroneSvcImpl extends QuartzJobBean implements BillingCroneSvc  {

@Autowired
BillingCroneRepo billingCroneRepo;

@Override
@Transactional
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
       SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(context);
       billingCroneRepo.updateBalance();
}
}


回答3:

It's because quartz is using the bean instead of the proxy generated for @Transactional.

Use either MethodInvokingJobDetailFactoryBean (instead of inheriting QuartzJob) or use a dedicated wrapper quarz bean (inheriting from QuartzJob) that call the spring bean (not inheriting from QuartzJob) having the @Transactionnal annotation.

EDIT : this is in fact not the problem

The problem is here :

 JobDetailFactoryBean factory = new JobDetailFactoryBean();
  factory.setJobClass(BillingCroneSvcImpl.class);

By passing the class, I presume that Quartz will instantiate it itself, so Spring won't create it and won't wrap the bean in a Proxy that handle the @Transactionnal behaviour.

Instead you must use something along the line :

 @Bean(name = "billingCroneSvc")
 public BillingCroneSvc getSvc(){
        return new BillingCroneSvcImpl();
 }


@Bean
public JobDetailFactoryBean jobDetailBalanceCarryForward(){
  JobDetailFactoryBean factory = new JobDetailFactoryBean();
  getSvc();// just make sure the bean is instantiated
  factory.setBeanName("billingCroneSvc");
  ...
}