Hibernate 5 integration with Spring Boot 2 - java

Am planning to create a spring boot (version 2) app with hibernate 5.3 , but am facing issues while integrating hibernate 5 .
Since its a spring boot app, the container will auto configure the datasource and JPA variant EntityManagerFactory and we can create Hibernate SessionFactory from this EntityManagerFactory using the unwrap() method.
So this is my code for the Hibernate config class
#Configuration
public class HibernateUtil {
#Autowired
private EntityManagerFactory entityMangerFact;
#Bean
public SessionFactory sessionFactory() {
return entityMangerFact.unwrap(SessionFactory.class);
}
}
But it is thowing BeanCurrentlyInCreationException .
But if i put the unwrap() in the service class method , it wont throw exceptions .but i think that not the right thing, since we will have more service methods, and we may need to call unwrap() on each service methods.
Error log:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'sessionFactory': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339) ~[spring-beans-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215) ~[spring-beans-5.1.4.RELEASE.jar:5.1.4.RELEASE]
Why the unwrap() is failing in the configuration class ?

In spring-boot you have access EntityManagerFactory as you can check in this datasource configuration but you don't need to use EntityManager directly to interact with database, you can use spring-data-jpa

Can you try injecting it as SessionFactory bean dependency and not #Configuration bean?
#Configuration
public class HibernateUtil {
#Bean
public SessionFactory sessionFactory(EntityManagerFactory entityMangerFact) {
return entityMangerFact.unwrap(SessionFactory.class);
}
}

Related

How to use the JdbcTemplate that is configured and available through Spring-Boot in the JobRepository?

Hiho,
we use Spring-Batch 4.3.5 inside a Spring-Boot 2.6.7 service. All things work fine so far. While unit testing the use-cases, we realized that the BatchAutoConfiguration/BatchConfigurerConfiguration creates a JobRepository. This JobRepository needs and wants some JdbcOperations. Because no instance of JdbcOperations is taken from the Spring application context while initializing all the beans, the JobRepositoryFactoryBean decides to create a fresh instance of type JdbcTemplate and attach it to the JobRepository.
Therefore I would like to ask if there is an 'easy' possibility to attach the instance of the JdbcTemplate that is provided by Spring-Boot? Is there another possibility as overwriting the whole initialization mechanism? Do we need to provide our own BatchConfigurer?
Any help is really appreciated! :)
That is not possible. You need to provide a custom BatchConfigurer and use any bean auto-configured by Boot to configure your job repository. Here is a quick example:
#Bean
public BatchConfigurer batchConfigurer(DataSource dataSource, JdbcTemplate jdbcTemplate) {
return new DefaultBatchConfigurer(dataSource) {
#Override
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factoryBean = new JobRepositoryFactoryBean();
factoryBean.setJdbcOperations(jdbcTemplate);
// set other properties on the factory bean
factoryBean.afterPropertiesSet();
return factoryBean.getObject();
}
};
}
In this snippet, the dataSource and jdbcTemplate passed as parameters to the batchConfigurer method will be those auto-configured by Boot (and autowired by Spring).

Consider defining a bean of type 'javax.persistence.EntityManagerFactory' in your configuration

I am using Spring Boot 2.0.0.RC1 (It include Spring Framework 5.0.3.RELEASE), Hibernate 5.2.12.Final, JPA 2.1 API 1.0.0.Final .
I have a class
package com.example;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.EntityManagerFactory;
#Configuration
public class BeanConfig {
#Autowired
EntityManagerFactory emf;
#Bean
public SessionFactory sessionFactory(#Qualifier("entityManagerFactory") EntityManagerFactory emf) {
return emf.unwrap(SessionFactory.class);
}
}
Then error
Error
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method sessionFactory in com.example.BeanConfig required a bean of type 'javax.persistence.EntityManagerFactory' that could not be found.
Action:
Consider defining a bean of type 'javax.persistence.EntityManagerFactory' in your configuration.
Process finished with exit code 1
How to fix this?
If you include this:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
You won't have to autowire the Entity Manager or provide a Session Factory bean.
You would only need to provide JpaRepository interfaces like:
public interface ActorDao extends JpaRepository<Actor, Integer> {
}
where Actor is a JPA entity class and Integer is the ID / primary key and inject ActorDao in a service impl class.
In BeanConfig, you should inject the JPA EntityManager via #PersistenceUnit, not #Autowired.
And remove the getSessionFactory since the Hibernate SessionFactory is already created internally and you can always unwrap the EntityManagerFactory.
Like this:
#Configuration
public class BeanConfig {
#PersistenceUnit
EntityManagerFactory emf;
}
The specific error you are having is caused by the #Qualifier annotation; Spring is looking for a Bean with the specific name you mentioned, instead of looking for any Bean of type EntityManagerFactory. Just remove the annotation.
However, once you fix that, and because you are also injecting the Bean in the method that constructs SessionFactory, Spring Boot will generate another error related to cyclic dependencies. To avoid that, just remove the parameter altogether from sessionFactory method, since you already injected EntityManagerFactory in your Config class.
This code will work :
#Bean
public SessionFactory sessionFactory() {
return emf.unwrap(SessionFactory.class);
}

Perform native SQL Query during Spring startup

I'm using Spring and Hibernate with an automatically generated database (for that I have set "hibernate.hbm2ddl.auto" to "update" in the JPA configuration properties).
I also have a class annotated #Configuration with a #PostConstruct method that is called on application startup after the database has been created or updated. This is where I setup the database with some default data if it's empty (first launch).
I would like to execute some custom native SQL queries at this moment. These queries won't return anything, they're just configuration stuff (like creating additional indexes or extensions).
Currently I'm stuck on creating a SessionFactory in order to create a new Hibernate Session. I've tried auto wiring it, but it doesn't work :
#Autowired
SessionFactory sessionFactory;
Gives me: Field sessionFactory in ... required a bean of type 'org.hibernate.SessionFactory' that could not be found.
I understand that I probably need to configure it elsewhere, but I don't know where. Several answers on SO use an xml configuration file, but I'm not using any configuration file so I can't do it that way.
Is there a way Spring can create the SessionFactory with the appropriate configuration ?
You don't even need to access SessionFactory. Please just put your scripts into a file src/main/resources/scripts/myscript.sql. You can then do the following with Spring:
#Component
public class Startup {
#Autowired
private DataSource dataSource;
#PostConstruct
public void runNativeSql() {
ClassPathResource resource = new ClassPathResource("scripts/myscript.sql");
try(Connection connection = dataSource.getConnection()) {
ScriptUtils.executeSqlScript(connection, resource);
} catch (SQLException | ScriptException e) {
//LOG
}
}
}
You can autowire the JPA EntityManager as:
#PersistenceContext
EntityManager entityManager;
If you really need a Hibernate Session and are using using JPA 2.1, the Session can be obtained from the EntityManager as:
entityManager.unwrap(Session.class);

MyBatis mapper class not registered in Spring Boot application with two datasources

We have a Spring Boot application that should access stored procedures from two different databases, DB2 and Oracle, through MyBatis mappers
We have created two DB2 context classes, e.g. for DB2
#Configuration
#MapperScan({ "...mapper.mybatis.db2" })
public class Db2Context {
#Primary
#Bean(name = "db2DataSource")
public DataSource getDataSource() { ...
#Primary
#Bean(name = "db2SqlSessionFactory")
public SqlSessionFactory getSqlSessionFactory() {...
The MyBatis beans look like
public interface Db2Mapper extends MyBatisMapper<SomeType> {
#Override
#Select(value = ...)
#Options(statementType = StatementType.CALLABLE)
#Results({...})
List<SomeType> select(Map<String, Object> parameters);
And the SqlSessionFactory beans are injected into the respective DAO classes with the appropriate qualification, e.g.
#Repository
public class Db2Dao {
#Autowired
#Qualifier("db2SqlSessionFactory")
SqlSessionFactory sqlSessionFactory;
...
try(SqlSession session= sqlSessionFactory.openSession(true);) {
Db2Mapper mapper = session.getMapper(Db2Mapper.class);
resultSet = mapper.select(parameters);
We have the identical config, mapper and DAO for Oracle as well, except that in that config the DataSource and SqlSessionFactory beans are not annotated with #Primary. This was necessary as per described in the Spring Boot reference: http://docs.spring.io/spring-boot/docs/1.2.3.RELEASE/reference/htmlsingle/#howto-two-datasources; without that the Spring Boot application startup would result in NoUniqueBeanDefinitionException
With this configuration the Spring Boot application starts up succesfully, and during the startup there are even INFO log printouts indicating that both mapper classes have been succesfully identified
INFO BeanPostProcessorChecker : Bean 'db2Mapper' of type [class org.mybatis.spring.mapper.MapperFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
INFO BeanPostProcessorChecker : Bean 'oracleMapper' of type [class org.mybatis.spring.mapper.MapperFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
However, in runtime we have a problem. First Db2Dao is executed, and with that everything goes perfectly, the DB2 stored procedure is getting executed, and the retrieved results are stored through Db2Mapper. Then comes OracleDao; however after the Oracle SP execution the following exception is received
ERROR Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
[Request processing failed; ... Type interface com....mapper.mybatis.oracle.OracleMapper is not
known to the MapperRegistry.]
We have been fighting with this issue for some while now, but could not find a resolution. Possibly the usage of #Primary might have something to do with it, but without that we are not even able to start up the application. Our researches actually seem to indicate that different library versions might even provide different behaviour: our stack is Java 1.8, Spring Boot 1.2.6, Spring 4.1.7, MyBatis 3.2.5, MyBatis-Spring 1.2.2
First of all, I would suggest not to autowire SqlSessionFactory into your DAOs at all. In fact you can get rid of DAOs completely and use your mappers in service layer as spring beans.
So you do something like this:
public interface Db2Mapper extends MyBatisMapper<SomeType> {
#Override
#Select(value = ...)
#Options(statementType = StatementType.CALLABLE)
#Results({...})
List<SomeType> select(Map<String, Object> parameters);
}
#Service
public class Db2Service{
#Autowired
private Db2Mapper db2Mapper;
//...
}
Secondly, the key to have various datasources integrated with mybatis-spring is in sqlSessionFactoryRef attribute of #MapperScan annotation. With that you can narrow down which SqlSessionFactory instance is used for witch #MapperScan. Something like this:
#Configuration
#MapperScan(value = { "...mapper.mybatis.db2" }, sqlSessionFactoryRef = "db2SqlSessionFactory")
public class Db2Context {
#Primary
#Bean(name = "db2DataSource")
public DataSource getDataSource() { ...
#Primary
#Bean(name = "db2SqlSessionFactory")
public SqlSessionFactory getSqlSessionFactory() {...
#Configuration
#MapperScan(value = { "...mapper.mybatis.other" }, sqlSessionFactoryRef = "otherSqlSessionFactory")
public class OtherContext {
#Bean(name = "otherDataSource")
public DataSource getDataSource() { ...
#Bean(name = "otherSqlSessionFactory")
public SqlSessionFactory getSqlSessionFactory() {...
Obviously you shouldn't scan same packages with these two #MapperScan annotations.

Spring-Data-JPA in CDI environment?

Does anyone tried integrating spring-data-jpa with java-ee application?
I'm using glassfish3 as an application container.
I followed an official spring-data-jpa tutorial and created a class:
public class EntityManagerFactoryProducer {
#Produces
#ApplicationScoped
public EntityManagerFactory createEntityManagerFactory() {
return Persistence.createEntityManagerFactory("myPU");
}
public void close(#Disposes EntityManagerFactory entityManagerFactory) {
entityManagerFactory.close();
}
#Produces
#RequestScoped
public EntityManager createEntityManager(EntityManagerFactory entityManagerFactory) {
return entityManagerFactory.createEntityManager();
}
public void close(#Disposes EntityManager entityManager) {
entityManager.close();
}
}
But when I try to deploy my application, I'm getting an exception:
Error occurred during deployment: Exception while preparing the app : Could not resolve a persistence unit corresponding to the persistence-context-ref-name [org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean/entityManager] in the scope of the module called [App]. Please verify your application.. Please see server.log for more details.
Command deploy failed.
What am I missing? Should I also have another configuration file or maybe some xml file?
Since you are in a Java EE Application Container you do not want to create your own Persistence instance. The example from the Spring Data documentation you used is for CDI environmets that do not have built in JPA support. Glasfish creates EntityManagerFactory and EntityManager for you. You only need to republish it as CDI bean. So in your case it is important to use the second example shown in the documentation:
public class EntityManagerProducer {
#Produces
#RequestScoped
#PersistenceContext
private EntityManager entityManager;
}
It's a bit more tricky that what is told in official documentation. To handle properly a Spring Repository in a CDI env, you need to declare:
a dependent entity manager producer
#Produces #Dependent #PersistenceContext
EntityManager entityManager;
a eager repository
#Eager public interface TestRepository extends CrudRepository<TestEntity, Long>
Then you'll be able to #Inject the repository in a CDI managed Bean.
If you don't use the #Dependent and the #Eager annotation, Spring will cause exceptions at the initialization of the repositories, leading to uncatch expcetions on the first request made against it.
References:
Spring Data JPA repositories use in EJB timer causes TransactionRequiredException
Getting a reference to EntityManager in Java EE applications using CDI

Categories

Resources