Reputation: 3917
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
Upvotes: 2
Views: 3246
Reputation: 3917
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();
}
}
}
Upvotes: 2
Reputation: 280178
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.
Upvotes: 2
Reputation: 1294
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.
Upvotes: 1