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
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.
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();
}
}
}
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.
Related
I have a simple Dog entity:
Dog.java:
#Entity
#Table(name = "dogs")
public class Dog {
public Dog() {}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
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;
}
}
DogController.java:
#RestController
public class DogController {
#Autowired
private DogRepository dogRepository;
#RequestMapping("/api/dogs/insert/default")
#Transactional
public String insertDefault() {
Dog dog = new Dog();
dog.setName("Alpha dog");
dogRepository.save(dog);
return "dog inserted OK";
}
}
DogRepository:
#Repository
public interface DogRepository extends JpaRepository<Dog, Long> {
}
However when I go to /api/dogs/insert/default it simply displays dog inserted OK without changing a database or even attempting to change it (no queries are printed to the console).
What could be the problem here? In debugger I can see that dogRepository is not null.
My WebMvcConfigurer class looks like this:
#EnableWebMvc
#Configuration
#EnableJpaRepositories("testproject")
#EnableTransactionManagement
#ComponentScan("testproject")
public class WebConfig implements WebMvcConfigurer {...}
I tried adding the #EnableTransactionManagement to it, but it didn't work
EDIT:
I don't have an application.properties file. All properties are configured using Java classes.
DbConfig.java:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "testproject", entityManagerFactoryRef = "entityManagerFactory")
public class DbConfig {
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(getDatasource());
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
entityManagerFactoryBean.setJpaProperties(getHibernateProperties());
entityManagerFactoryBean.setPackagesToScan("testproject");
return entityManagerFactoryBean;
}
#Bean
public DataSource getDatasource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://127.0.0.1:5432/testproject");
dataSource.setUsername("postgres");
dataSource.setPassword("bigfalcon");
return dataSource;
}
#Bean
public SessionFactory getSessionFactory() throws IOException {
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setPackagesToScan("testproject");
//getHibernateProperties method is a private method
sessionFactoryBean.setHibernateProperties(getHibernateProperties());
sessionFactoryBean.setDataSource(getDatasource());
sessionFactoryBean.afterPropertiesSet();
return sessionFactoryBean.getObject();
}
#Bean(name = "transactionManager")
public HibernateTransactionManager getTransactionManager() throws IOException {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(getSessionFactory());
return transactionManager;
}
#Bean
private static Properties getHibernateProperties() {
Properties hibernateProperties = new Properties(); //PostgreSQLDialect
//hibernateProperties.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
hibernateProperties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
hibernateProperties.put("hibernate.show_sql", true);
// hibernateProperties.put("spring.jpa.hibernate.ddl-auto", "create");
hibernateProperties.put( "hibernate.hbm2ddl.auto", "create-drop");
/*hibernateProperties.setProperty(
"hibernate.dialect", "org.hibernate.dialect.H2Dialect");
*/
System.out.println();
// other properties
return hibernateProperties;
}
}
EDIT:
Using the same dogRepository to select existing dogs from the database (which I manually inserted) works fine:
#RequestMapping("/api/dogs/view/all")
public Iterable<Dog> viewDogs() {
Iterable<Dog> dogs = dogRepository.findAll(); //non-null size, returns a JSON with dogs
return dogs;
}
EDIT: Maven dependencies:
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.3.1.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.3.1.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.3.1.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.3.1.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<version>3.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.opennlp</groupId>
<artifactId>opennlp-tools</artifactId>
<version>1.9.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.dv8tion/JDA -->
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>4.0.0_46</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.4.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.197</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4-1203-jdbc4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.4.1.RELEASE</version>
</dependency>
<!--is needed for jwt-->
<!-- https://mvnrepository.com/artifact/javax.xml/jaxb-api -->
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.sun.xml.bind/jaxb-impl -->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>3.0.0-M4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.sun.xml.bind/jaxb-core -->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>3.0.0-M4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-jwt -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.jakewharton.fliptables/fliptables -->
<dependency>
<groupId>com.jakewharton.fliptables</groupId>
<artifactId>fliptables</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.4.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.json/json -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20190722</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
</dependencies>
EDIT:
I also tried to get it to update the entity:
#RequestMapping("/api/dogs/update/{id}")
public Iterable<Dog> updateDog(#PathVariable("id") long id) {
Optional<Dog> dog = dogRepository.findById(id);
Dog dogValue = dog.get();
dogValue.setName("New name");
dogRepository.save(dogValue);
return dogRepository.findAll();
}
which however doesn't work: the JSON returned to the browser contains updated name (New name), but doesn't update the database itself.
EDIT:
I managed to get it to work with manual Hibernate commands, see the added methods insertDog(Dog dog) and updateDog(Dog dog): I take the Session object from autowired DbConfig object and use session.save(dog), session.flush() to update the database. With the following controller code both /api/dogs/insert/default and /api/dogs/update/3/Buddy work fine.
So the problem seems to be related to JpaRepository specifically, seeing as if I forego using it and write the Hibernate code myself, it works fine:
DogController.java:
#CrossOrigin
#RestController
#Transactional( readOnly = false)
public class DogController {
#Autowired
private DogRepository dogRepository;
#Autowired
private DbConfig dbConfig;
#RequestMapping("/api/dogs/insert/default")
#Transactional(readOnly=false)
public String insertDefault() throws Exception {
Dog dog = new Dog();
dog.setName("Alpha dog");
//dogRepository.save(dog);
insertDog(dog);
return "dog inserted OK";
}
#RequestMapping("/api/dogs/view/all")
public Iterable<Dog> viewDogs() {
Iterable<Dog> dogs = dogRepository.findAll();
return dogs;
}
#RequestMapping("/api/dogs/update/{id}/{name}")
public Iterable<Dog> updateDog(#PathVariable("id") long id,
#PathVariable("name") String name) throws Exception {
Optional<Dog> dog = dogRepository.findById(id);
Dog dogValue = dog.get();
dogValue.setName(name);
//dogRepository.save(dogValue);
Session session = dbConfig.getSessionFactory().openSession();
updateDog(dogValue);
return dogRepository.findAll();
}
public void updateDog(Dog dog) throws Exception {
Session session = dbConfig.getSessionFactory().openSession();
session.beginTransaction();
session.merge(dog);
session.flush();
session.getTransaction().commit();
session.clear();
session.close();
}
public boolean insertDog(Dog dog) throws Exception {
boolean result = false;
Session session = dbConfig.getSessionFactory().openSession();
session.beginTransaction();
try {
session.save(dog);
session.flush();
session.getTransaction().commit();
result = true;
}
catch (Exception e) {
result = false;
}
finally {
session.clear();
session.close();
}
return result;
}
}
EDIT:
I wonder if something to do with generating ids of the Dog entity could be the problem. I use PostgreSQL and in the logs I see
WARN [RMI TCP Connection(2)-127.0.0.1] org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler.logWarning sequence "hibernate_sequence" does not exist, skipping
And then when I access /api/dogs/insert/default in console
Hibernate: select nextval ('hibernate_sequence')
gets printed.
I tried changing GenerationStrategy but it didn't help. I also tried adding
hibernateProperties.put("hibernate.connection.driver_class", "org.postgresql.Driver");
to getHibernateProperties() method in DbConfig.java but it didn't seem to help either.
EDIT:
I also tried changing HibernateTransactionManager to JpaTransactionManager in transactionManager() method in DbConfig.java:
#Bean(name = "transactionManager")
public JpaTransactionManager transactionManager() throws IOException {
JpaTransactionManager transactionManager = new JpaTransactionManager();
//transactionManager.setSessionFactory(getSessionFactory());
return transactionManager;
}
but that resulted the
NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.persistence.EntityManagerFactory' available: more than one 'primary' bean found among candidates: [entityManagerFactory, getSessionFactory]
error on server startup
I also figured that maybe JpaRepository can't generate the id, so I tried making it manual:
#Id
#Column(name = "dog_id", unique = true, nullable = false)
private Long id;
and in DogController.java:
#RequestMapping("/api/dogs/insert/default")
#Transactional(readOnly=false, propagation = Propagation.REQUIRED)
public String insertDefault() throws Exception {
Dog dog = new Dog();
Random random = new Random();
dog.setId((long) random.nextInt(1000_000_000));
dog.setName("Alpha dog");
dogRepository.save(dog);
dogRepository.flush();
//insertDog(dog);
return "dog inserted OK";
}
however it resulted in error:
org.springframework.web.util.NestedServletException:
Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException:
no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress
EDIT:
Also I figured out why there was no no transaction is in progress error earlier (that error helped understand what was wrong): the error only appeared after I added
dogRepository.flush();
to DogController.java.
If I remove dogRepository.flush() the dogRepository.save(dog) method still wouldn't work, but no visible error would appear (other than Hibernate: select nextval ('hibernate_sequence') in console).
I managed to make JpaRepository insert on /api/dogs/insert/default work!
What it took: adding
hibernateProperties.put("hibernate.allow_update_outside_transaction", true);
to getHibernateProperties() method of DbConfig.java class.
However I'm not sure that's a correct way to handle it? And if there are better methods of dealing with that error? And why that error only appeared when I use JpaRepository for inserting/updating entities, while if I use raw Hibernate commands (session.save()) the error didn't appear and updates worked fine?
Transactional is read-only by default. Write queries fails silently.
Set your annotation as #Transactional(readOnly=false)
Your configuration is almost OK, i changed a couple of things and it works on my Spring Boot v2.3.1.RELEASE.
Do you need this class public class WebConfig implements WebMvcConfigurer {...}? If yes consider annotating either public LocalContainerEntityManagerFactoryBean entityManagerFactory() or this public SessionFactory getSessionFactory() throws IOException { with #Primary. Else you will get this:
Parameter 0 of method openEntityManagerInViewInterceptorConfigurer in
org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration$JpaWebConfiguration
required a single bean, but 2 were found:
entityManagerFactory: defined by method 'entityManagerFactory' in class path resource [.../DbConfig.class]
getSessionFactory: defined by method 'getSessionFactory' in class path resource [.../DbConfig.class]
You have repeated annotations on public class WebConfig and public class DbConfig. #Configuration is enough on WebConfig.
Otherwise, it looks fine, just make sure that everywhere you define testproject as values is correct and that your postgresql is accessible:
Spring Boot v2.3.1.RELEASE:
Completed initialization in 5 ms
Hibernate: insert into dogs (name) values (?)
PostgreSQL:
postgres=# SELECT version();
version
------------------------------------------------------------
PostgreSQL 12.3, compiled by Visual C++ build 1914, 64-bit
(1 row)
postgres=#
postgres=# select * from dogs;
id | name
----+-----------
1 | Alpha dog
(1 row)
postgres=#
P.S: I tested with an empty WebConfig class, like so:
#Configuration
public class WebConfig implements WebMvcConfigurer {
}
I'm writing a jpa repostory example and I'm getting a runtime exception of type UnsatisfiedDependencyException.
Here is my program:
#Configuration
#EnableJpaRepositories(basePackageClasses = { PersonRepository.class, ProfessionRepository.class})
#ComponentScan( basePackageClasses =MyService.class)
public class SpringDataJpa {
public static void main( String[] args ) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringDataJpa.class);
service myService = applicationContext.getBean(MyService.class);
}
}
The service interface:
public interface service {
void add( Person person );
List<Person> getListOfPersons();
}
The implementation that throw the exception:
#Service
public class MyService implements service {
#Autowired
PersonRepository personRepository;
#Override
public void add( Person person ){
System.out.println("saved");
}
#Override
public List<Person> getListOfPersons() {
return null;
}
}
The repositories:
#Repository
public interface PersonRepository extends JpaRepository<Person, Integer> {
}
#Repository
public interface ProfessionRepository extends JpaRepository<Profession, Integer> {
}
The exception i'm getting:
Exception in thread "main"
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'myService': Unsatisfied dependency
expressed through field 'personRepository'; nested exception is
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'personRepository': Post-processing of merged
bean definition failed; nested exception is
java.lang.NoClassDefFoundError: javax/persistence/SynchronizationType
As I checked this discussion, I added the proposed dependencies. My dependencies in the pom.xml file:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>LATEST</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>LATEST</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>LATEST</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>LATEST</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>LATEST</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>LATEST</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.197</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.1.Final</version>
</dependency>
How to solve this error?
My second question is: should we use #EnableJpaRepositories if we use Spring Boot?
a datasource and an entityManagerFactory beans were missing.
To solve my problem i added the followed code in my configuration class:
#Bean
public DataSource firstDataSource (){
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/ride_tracker?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC");
driverManagerDataSource.setUsername("root");
driverManagerDataSource.setPassword("password");
return driverManagerDataSource;
}
#Bean
LocalContainerEntityManagerFactoryBean entityManagerFactory( DataSource dataSource) {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManagerFactoryBean.setPackagesToScan("data");
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
entityManagerFactoryBean.setJpaProperties(jpaProperties);
return entityManagerFactoryBean;
}
You can try the following version, SynchronizationType is available since 2.1.
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.0.Final</version>
</dependency>
I have inherited a Spring Boot microservice which does not have a Service or API layer, it is behaving in a HATEOAS style.
This is not an optimal architecture and needs to be changed into MVC.
Currently all repository methods are accessed directly using the #RepositoryRestResource annotation.
The plan is to refactor this and add Controllers and a API layer (DTOs), however after adding a controller, swagger is not showing the Rest controllers
Also to note that when debugging the controller endpoint, it is not actually reached. It is being bypassed, which is another clue.
#CrossOrigin
#RestController
#RequestMapping("/fixing")
public class FixingController {
private final FixingRepository fixingRepository;
#Autowired
FixingController(final FixingRepository fixingRepository) {
this.fixingRepository = checkNotNull(fixingRepository, "Fixing Repository cannot be null");
}
/**
* Builds a list of Fixing strings from the database
* #return list
*/
#RequestMapping(value = "/", method = RequestMethod.GET)
public List<String> getAllFixings() {
final List<String> fixingList = new ArrayList<>();
for (Fixing fixing : fixingRepository.findAll()) {
String name = fixing.getName();
fixingList.add(name);
}
return fixingList;
}
}
This is the spring swagger config
#Configuration
public class SwaggerConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.regex("/api.*"))
.build();
}
}
The repository (note no #RepositoryRestResource annotation)
public interface FixingRepository extends JpaRepository<Fixing, Long> {
#Override
Fixing findOne(Long id);
#Override
List<Fixing> findAll();
}
When I rebuild and start the service, the controller is not shown. It only shows all the entities and their repository methods.
POM dependencies
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- included explicitly to avoid javadoc generation error
due to a conflict with a class used by #Transactional annotation -->
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>8.0.12</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-data-rest</artifactId>
<version>2.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>1.5.13.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.json/json -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180130</version>
</dependency>
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.5</version>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>3.6.2</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-storage</artifactId>
<version>1.0.0.RELEASE</version>
</dependency>
</dependencies>
Any ideas what is causing this? There is nothing else I can see in the config which is preventing this from working
The issue is with your SwaggerConfig. You are only selecting a subset of your APIs (either the JPA repository sourced or your RestController sourced) via this :
.paths(PathSelectors.regex("/api.*"))
I replicated your scenario and I just commented the path selection out and I can see both type of APIs. Note that you can also use a custom predicate for selecting the paths:
#Configuration
#Import({SpringDataRestConfiguration.class})
public class SwaggerConfig {
#Autowired
#SuppressWarnings({"UnusedDeclaration"})
private ServletContext servletContext;
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.pathProvider(relativePath())
.select()
.apis(RequestHandlerSelectors.any())
// .paths(paths2())
.build();
}
// Select only a few
private Predicate<String> paths2() {
return and(
(regex("/fixing.*")),
(regex("/api.*")));
}
// Exclude these
private Predicate<String> paths() {
return and(
not(regex("/error.*")),
not(regex("/metrics.*")),
not(regex("/jolokia.*")),
not(regex("/health.*")),
not(regex("/env.*")),
not(regex("/metrics.*")),
not(regex("/info.*")),
not(regex("/mappings.*")),
not(regex("/trace.*")),
not(regex("/dump.*")),
not(regex("/heapdump.*")),
not(regex("/configprops.*")),
not(regex("/beans.*")),
not(regex("/autoconfig.*")),
not(regex("/logfile.*")),
not(regex("/shutdown.*")),
not(regex("/actuator.*")));
}
}
Sample Rest Controller:
#CrossOrigin
#RestController
#RequestMapping("/fixing")
public class FixingController {
/**
* Builds a list of Fixing strings from the database
* #return list
*/
#RequestMapping(value = "/", method = RequestMethod.GET)
public List<String> getAllFixingsViaRestController() {
final List<String> fixingList = new ArrayList<>();
fixingList.add("foo");
fixingList.add("bar");
return fixingList;
}
}
Now my Swagger UI looks like this; you can see both the JPA Repository contributed REST APIs and the RestController contributed API (/fixing path):
I am trying to use HibernateValidator in SpringMVC tests, but I can't get it to work.
I added it to classpath along with el and validation api deps. In WebMvcConfigurerAdapter overrided getValidator:
#Override
public Validator getValidator() {
LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
validatorFactoryBean.setProviderClass(HibernateValidator.class);
return validatorFactoryBean;
}
Binding result just doesn't have errors that should be produced by hibernate validator, and DTO has null values.
However javax validation annotations works.
What I should to do to make hibernate validator work in
spring mvc tests (with junit runner)?
UPDATE:
Here is how my test looks like:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = {AppConfig.class, WebConfig.class})
public class UserControllerTest {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#Before
public void before() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void RegisterUser_ValidData_UserRegistered() throws Exception {
ObjectMapper mapper = new ObjectMapper();
UserRegistrationDTO dto = new UserRegistrationDTO();
String payload = mapper.writeValueAsString(dto);
RequestBuilder request = MockMvcRequestBuilders
.post(URI.create("/users/register"))
.contentType(MediaType.APPLICATION_JSON)
.content(payload);
this.mockMvc.perform(request).andDo...
}
}
Dependencies:
<properties>
<spring.version>4.3.9.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</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-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.1.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
Controller:
#RestController
#RequestMapping(path = "users", produces = "application/json", consumes = "application/json")
public class UserController {
#PostMapping(path = "/register")
public ResponseEntity register(#Valid #RequestBody UserRegistrationDTO registration) {
return ResponseEntity.ok("registered");
}
}
LocalContainerEntityManagerFactoryBean not created context faild
Below is the error:
java.lang.IllegalStateException: Failed to load ApplicationContext at
org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'entityManagerFactory' defined in
com.test.spring.AppConfig: Invocation of init method failed; nested
exception is java.lang.NoSuchMethodError:
javax.persistence.spi.PersistenceUnitInfo.getSharedCacheMode()Ljavax/persistence/SharedCacheMode;
at
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1578)
and code:
#ComponentScan
#Configuration
#EnableJpaRepositories("com.test.spring.repository")
public class AppConfig {
#Value("${jdbc.driverClassName}")
private String jdbcDriverClassName;
#Value("${jdbc.url}")
private String jdbcURL;
#Value("${jdbc.username}")
private String jdbcUserName;
#Value("${jdbc.password}")
private String jdbcPassword;
public static PropertySourcesPlaceholderConfigurer getPropertiesFile(String profileName) {
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
String path = new StringJoiner("").add("application_dev")/*.add(profileName)*/.add(".properties").toString();
Resource location = new ClassPathResource(path);
pspc.setLocations(location);
return pspc;
}
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(jdbcDriverClassName);
dataSource.setUrl(jdbcURL);
dataSource.setUsername(jdbcUserName);
dataSource.setPassword(jdbcPassword);
return dataSource;
}
#Bean(name="entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setPackagesToScan("com.test.spring.domain");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
factoryBean.setJpaVendorAdapter(vendorAdapter);
Properties props = new Properties();
props.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
props.put("hibernate.hbm2ddl.auto", "create");
factoryBean.setJpaProperties(props);
return factoryBean;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
}
//Here is the pom file
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.test.spring</groupId>
<artifactId>EntityExample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.7.0.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.30</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
Since you are missing hibernate classes and libraries, try to extend your pom dependency with something like this:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.5.0</version>
</dependency>