Question

I'm trying to create a Scheduled task with Spring, but I'm probably not doing something correctly exposing the @Bean in configuration. My code is as follows:

@Configuration
@PropertySource("classpath:hibernate.properties")
@EnableJpaRepositories("org.app.repository")
@ComponentScan("org.app")
@EnableTransactionManagement
@EnableScheduling
public class JpaConfiguration {

    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
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();

        dataSource.setDriverClassName(
                env.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
        dataSource.setUrl(env.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
        dataSource.setUsername(env.getRequiredProperty(PROPERTY_NAME_DATABASE_USERNAME));
        dataSource.setPassword(env.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));

        return dataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {

        // will set the provider to 'org.hibernate.ejb.HibernatePersistence'
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        // will set hibernate.show_sql to 'true'
        vendorAdapter.setShowSql(true);
        // if set to true, will set hibernate.hbm2ddl.auto to 'update'
        vendorAdapter.setGenerateDdl(true);

        LocalContainerEntityManagerFactoryBean emfBean = new
                LocalContainerEntityManagerFactoryBean();
        emfBean.setDataSource(dataSource());
        emfBean.setJpaVendorAdapter(vendorAdapter);
        emfBean.setPersistenceProviderClass(
                org.hibernate.jpa.HibernatePersistenceProvider.class);

        emfBean.setPackagesToScan(
                env.getRequiredProperty(PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN));

        emfBean.setJpaProperties(hibProperties());

        return emfBean;
    }

    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 JpaTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return transactionManager;
    }

    @Bean
    public ThreadPoolTaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler ts = new ThreadPoolTaskScheduler();
        ts.initialize();
        ts.setPoolSize(8);
        ts.setWaitForTasksToCompleteOnShutdown(true);
        return ts;
    }
}

@Component
public class MainBean {

    @Autowired
    private MyRunnable myRunnable;

    @Autowired
    private CategoryRepo categoryRepo;

    @Autowired
    private ThreadPoolTaskScheduler taskScheduler;

    public void start() {

            //This works
        categoryRepo.findAll().forEach(System.out::println);


        //this throws an exception
        taskScheduler.execute(myRunnable);

        //if i use an infinite loop here in order to prevent 
        //the method from exiting everything works normal
        // while(true) {
        //    Thread.sleep(10000000);
        // }
        System.out.println("Application Started. . .");
    }
}

Category Repo

public interface CategoryRepo extends JpaRepository<Category, String> {

}

Runnable

@Component
public class MyRunnable implements Runnable {



    @Autowired
    private CategoryRepo categoryRepo;

    @Override
    public void run() {


        try {
            List<Category> list = categoryRepo.findAll();
            list.forEach(System.out::println);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Also if I dont use a scheduler and I just do

myRunnable.run(); 

it executes normally.

Anyone have an idea what I'm doing wrong, or maybe an alternative way in doing this?

EDIT: My pom.xml dependencies are as follows (Spring is 4.0.2 as you can see in the properties tag):

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>4.0.2.RELEASE</spring.version>
    </properties>

    <dependencies>

        <!--Hibernate Dependencies-->
        <dependency>
            <groupId>org.hibernate.javax.persistence</groupId>
            <artifactId>hibernate-jpa-2.1-api</artifactId>
            <version>1.0.0.Final</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>4.3.4.Final</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-c3p0</artifactId>
            <version>4.3.4.Final</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>4.3.4.Final</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.0.3.Final</version>
        </dependency>


        <!-- Spring  Dependencies -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>1.5.1.RELEASE</version>
        </dependency>


        <!--CGLIB is required to process @Configuration classes-->
        <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.1</version>
        </dependency>

        <!--Other Dependencies-->
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.7.2</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.6</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.28</version>
        </dependency>


    </dependencies>

And Java is 1.8 - also tried with 1.7 and had same results

EDIT:

Apparently I think the issue is that the program is exiting and it leaves the Spring Context. Therefore when the runnable is executed, the thread cant find the entitymanager.

If I use an infinite while loop right before the MainBean exits then everything executes normally

Was it helpful?

Solution 2

I havent found any satisfactory answers unfortunately. The only unorthodox solution (in my opinion) I have found is to do an infinite loop that doesnt allow the program to exit as follows:

public void start() {

    System.out.println("Application Started. . .");

    taskScheduler.scheduleAtFixedRate(myRunnable, Date.from(Instant.now()),
            TimeUnit.DAYS.toMillis(1));

    while (true) {
        try {
            Thread.sleep(Long.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

OTHER TIPS

The root cause is

Caused by: java.lang.NoSuchMethodError: org.app.config.JpaConfiguration.setBeanFactory(Lorg/springframework/beans/factory/BeanFactory;)V

A NoSuchMethodError almost always indicates that there is a versioning issue with your build. The application tries to execute a method that was available at compile time, but not at run time. In other words, the classpath at compilation is different than the classpath at run time.

spring-data-jpa version 1.5.1.RELEASE is compiled with Spring 3.2.8, but you're providing Spring libraries of 4.0.2.RELEASE. However, it is built in a way that it will delegate to your project's actual dependencies if those exist. In your current setup, it will use the following

<artifactId>spring-context-support</artifactId>
<artifactId>spring-context</artifactId>
<artifactId>spring-jdbc</artifactId>
<artifactId>spring-orm</artifactId>
<artifactId>spring-tx</artifactId>

with version 4.0.2.RELEASE, but it will use version 3.2.8.RELEASE of

<artifactId>spring-core</artifactId>
<artifactId>spring-beans</artifactId>

These are typically dependencies of spring-context with the same version, but here it seems they get overwritten by spring-data-jpa.

The simplest, but maybe incomplete (depending on the rest of your configuration), is to specifically declare those two dependencies

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>${spring.version}</version>
</dependency>

So spring-data-jpa will now delegate to these.

Alternatively, unless you are using some features of Spring 4.0.2.RELEASE, you could get rid of all your other Spring dependencies and only keep spring-data-jpa. It will be responsible for pulling the other Spring 3.2.8.RELEASE libraries.

It seems like a known bug on spring boot or Spring 4. https://github.com/spring-projects/spring-boot/issues/253

As it seems, the scheduler masks the real problem thrwing this strange error.

In your case, as it just happens with the scheduler, I would try to make a exception breakpoint to find out which kind of exception is being thrown in this case that is being hidden.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top