Hibernate 4 -> 5 upgrade leads to LazyInitializationException - spring

I upgraded a project from Spring 4.3.9.RELEASE + Hibernate 4.3.11.Final to Spring Boot 2.1.4.RELEASE and Hibernate 5.3.9.Final. The queries are still working fine, but I'm getting LazyInitializationException with some #OneToMany class members.
First I retrieve the object, which has a reference to a #OneToMany List, from the #Transaction service. The collection is returned to the controller, and from there it goes back to Spring to be serialized into a json. The controller has #RestController, so it knows what to do.
In Spring 4.3.9.RELEASE + Hibernate 4.3.11.Final everything was fine, even though OpenEntityManagerInView wasn't enabled by configuration and the collection wasn't loaded with EAGER mode. But in Spring Boot 2.1.4.RELEASE and Hibernate 5.3.9.Final the same thing doesn't work anymore. I've tried enabling OEMIV, by setting spring.jpa.open-in-view=true, but even this doesn't seem to work or it's being overriden somewhere.
If I enable EAGER loading mode for that collection, everything works fine.
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
#Entity
#JsonSerialize(using = TemplateSerializer.class)
public class Template implements Serializable {
#Id
#GeneratedValue
private Long id;
private String name;
#ManyToOne
private ObjFormat objFormat;
#OneToOne
#JoinColumn(name = "event_id")
#OnDelete(action = OnDeleteAction.CASCADE)
private Event event;
#OneToMany
#JoinColumn(name = "category_id")
private List<Category> linkToCategories;
The problem is caused by field linkToCategories. If I configure #OneToMany(fetch = FetchType.EAGER) everything works fine.
Application configuration:
#Bean
public LocalSessionFactoryBean sessionFactory(DataSource dataSource) throws ClassNotFoundException {
LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean();
localSessionFactoryBean.setDataSource(dataSource);
localSessionFactoryBean.setPackagesToScan("com.project.backend.model",
"com.project.backend.hibernate.converters");
return localSessionFactoryBean;
}
#Bean
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
return new HibernateTransactionManager(sessionFactory);
}
Later edit:
After a lot of debugging, the difference between the old and the new Hibernate functionality is in the HibernateTransactionManager. In the method doGetTransaction(), in Hibernate 4 it finds the SessionHolder object when calling
TransactionSynchronizationManager.getResource(getSessionFactory())
while in Hibernate 5 it doesn't.
SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
if (sessionHolder != null) {
if (logger.isDebugEnabled()) {
logger.debug("Found thread-bound Session [" + sessionHolder.getSession() + "] for Hibernate transaction");
}
txObject.setSessionHolder(sessionHolder);
}
else if (this.hibernateManagedSession) {
try {
Session session = this.sessionFactory.getCurrentSession();
if (logger.isDebugEnabled()) {
logger.debug("Found Hibernate-managed Session [" + session + "] for Spring-managed transaction");
}
txObject.setExistingSession(session);
}
catch (HibernateException ex) {
throw new DataAccessResourceFailureException(
"Could not obtain Hibernate-managed Session for Spring-managed transaction", ex);
}
}
In the method doBegin, a new session is created and set on the txObject for every request.
if (txObject.getSessionHolder() == null || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
Interceptor entityInterceptor = getEntityInterceptor();
Session newSession = (entityInterceptor != null ?
getSessionFactory().withOptions().interceptor(entityInterceptor).openSession() :
getSessionFactory().openSession());
if (logger.isDebugEnabled()) {
logger.debug("Opened new Session [" + newSession + "] for Hibernate transaction");
}
txObject.setSession(newSession);
}
My experience with Hibernate is fairly small, so here I'm stuck. It's probably a configuration thing, but I can't find it.

As M. Deinum was saying, the Spring 4.3.9.RELEASE + Hibernate 4.3.11.Final configuration was loading OpenSessionInViewFilter, which explains why all the queries were going through successfully. After configuring the same filter in Spring Boot, everything is back to normal. Add the following bean to register the filter:
#Bean
public FilterRegistrationBean<OpenSessionInViewFilter> registerOpenSessionInViewFilterBean() {
FilterRegistrationBean<OpenSessionInViewFilter> registrationBean = new FilterRegistrationBean<>();
OpenSessionInViewFilter filter = new OpenSessionInViewFilter();
registrationBean.setFilter(filter);
return registrationBean;
}
The next step is to replace plain Hibernate with JPA, and OpenSessionInViewFilter with OpenEntityManagerInViewFilter.
Thanks M. Deinum.

#xxxToMany annotations inicates that fetch type is LAZY by default. It means that you need to initialize collection your entity refers to.
Eg.
#Entity
public class Book {
#OneToMany
public List<Author> authors;
}
There is few ways to resolve this. You can modify #OneToMany annotation with:
#OneToMany(FetcType=FetchType.EAGER)
Or to make a method where you will initialize authors eg.:
public void initializeAuthors(Book book) {
Book b = em.find(Book.class, book.getId());
List<Author> authors = new ArrayList<>(b.getAuthors());
book.setAuthors(authors);
}
If you have #NamedQueries on your entities, you can do that by adding LEFT JOIN FETCH on your collections.

Related

Spring-Boot Elasticseach EntityMapper can not be autowired

Based on this answer and the comments I implemented the code to receive the scores of an elastic search query.
public class CustomizedHotelRepositoryImpl implements CustomizedHotelRepository {
private final ElasticsearchTemplate elasticsearchTemplate;
#Autowired
public CustomizedHotelRepositoryImpl(ElasticsearchTemplate elasticsearchTemplate) {
super();
this.elasticsearchTemplate = elasticsearchTemplate;
}
#Override
public Page<Hotel> findHotelsAndScoreByName(String name) {
QueryBuilder queryBuilder = QueryBuilders.boolQuery()
.should(QueryBuilders.queryStringQuery(name).lenient(true).defaultOperator(Operator.OR).field("name"));
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(queryBuilder)
.withPageable(PageRequest.of(0, 100)).build();
DefaultEntityMapper mapper = new DefaultEntityMapper();
ResultsExtractor<Page<Hotel>> rs = new ResultsExtractor<Page<Hotel>>() {
#Override
public Page<Hotel> extract(SearchResponse response) {
ArrayList<Hotel> hotels = new ArrayList<>();
SearchHit[] hits = response.getHits().getHits();
for (SearchHit hit : hits) {
try {
Hotel hotel = mapper.mapToObject(hit.getSourceAsString(), Hotel.class);
hotel.setScore(hit.getScore());
hotels.add(hotel);
} catch (IOException e) {
e.printStackTrace();
}
}
return new PageImpl<>(hotels, PageRequest.of(0, 100), response.getHits().getTotalHits());
}
};
return elasticsearchTemplate.query(nativeSearchQuery, rs);
}
}
As you can see I needed to create a new instance of DefaultEntityMapper mapper = new DefaultEntityMapper(); which should not be the case because it should be possible to #Autowire EntityMapper. If I do so, I get the exception that there is no bean.
Description:
Field entityMapper in com.example.elasticsearch5.es.cluster.repository.impl.CustomizedCluserRepositoryImpl required a bean of type 'org.springframework.data.elasticsearch.core.EntityMapper' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.data.elasticsearch.core.EntityMapper' in your configuration.
So does anybody know if its possible to autowire EntityMapper directly or does it needs to create the bean manually using #Bean annotation.
I use spring-data-elasticsearch-3.0.2.RELEASE.jar where the core package is inside.
My pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
</dependency>
I checked out the source code of spring-data-elasticsearch. There is no bean/comoponent definition for EntityMapper. It seems this answer is wrong. I test it on my project and get the same error.
Consider defining a bean of type 'org.springframework.data.elasticsearch.core.EntityMapper' in your configuration.
I couldn't find any other option by except defining a #Bean

Spring Boot - PoolExhaustedException

I recently rebuild all my Spring 4 Projects with the latest Spring Boot Starter Framework.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.RELEASE</version>
</parent>
Everything is working fine so far, except that i am experiencing PoolExhaustedException randomly across all my rebuild projects.
The exact exception is:
org.apache.tomcat.jdbc.pool.PoolExhaustedException: [http-nio-8080-exec-4] Timeout: Pool empty. Unable to fetch a connection in 20 seconds, none available[size:50; busy:50; idle:0; lastwait:20000].
The last exception i received was from a controller which is downloading a document from my database:
#RequestMapping(value = "/getDocument/{value}/{text}", method = RequestMethod.GET)
public void get(HttpServletResponse response, #PathVariable String value, #PathVariable String text){
try {
Document ufile = documentService.getDocumentByID(Integer.parseInt(value));
response.setContentType(ufile.getType());
response.setContentLength(ufile.getContent().length);
FileCopyUtils.copy(ufile.getContent(), response.getOutputStream());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Service:
#Transactional
public Document getDocumentByID(int id) {
Document r = this.documentDAO.getDocumentByID(id);
return r;
}
DAO:
#Transactional
public Document getDocumentByID(int id)
{
Session session = this.sessionFactory.getCurrentSession();
Document p = (Document) session.load(Document.class, new Integer(id));
return p;
}
I already tried to annotate the Controller with #Transactional which temporarily solved the problem, but led to other transactional errors so i had to remove it.
I also tried to increase the pool via project.properties too 100 which only delayed the problem but didn't solve it.
project.properties
spring.datasource.tomcat.max-wait=20000
spring.datasource.tomcat.max-active=50
spring.datasource.tomcat.max-idle=20
spring.datasource.tomcat.min-idle=15
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect
spring.jpa.properties.hibernate.id.new_generator_mappings = false
spring.jpa.properties.hibernate.format_sql = true
spring.mvc.view.prefix: /WEB-INF/views/
spring.mvc.view.suffix: .jsp
spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate4.SpringSessionContext
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
Not sure if the properties with these names have any effect.
I'd also try to put a breakpoint and inspect the Tomcat DataSource directly to view the values to be convinced (can be easily autowired in a test).
Looking at the Spring Boot Tomcat Datasource Configuration Tests I see some interesting options like minEvictableIdleTimeMillis, timeBetweenEvictionRunsMillis and maxWait all prefixed with spring.datasource.tomcat., so give them a try.

hibernate 5 and spring - generate ddl using SchemaExport

In hibernate 4 - spring 4 setup it was possible to generate DDL using SchemaExport object:
LocalSessionFactoryBean sfb = (LocalSessionFactoryBean) context.getBean("&sessionFactory");
SchemaExport schema = new SchemaExport(sfb.getConfiguration());
But hibernate 5 replaces SchemaExport(Configuration configuration) constructor with SchemaExport(MetadataImplementator metadataImplementator).
MetadataImplementator is not readily available on
org.springframework.orm.hibernate5.LocalSessionFactoryBean or org.springframework.orm.hibernate5.LocalSessionFactoryBuilder
I hacked it like this:
MetadataSources metadataSources = (MetadataSources) FieldUtils.readField(configuration, "metadataSources", true);
Metadata metadata = metadataSources
.getMetadataBuilder(configuration.getStandardServiceRegistryBuilder().build())
.applyPhysicalNamingStrategy(new MyPhysicialNamingStrategy())
.applyImplicitNamingStrategy(ImplicitNamingStrategyJpaCompliantImpl.INSTANCE)
.build();
MetadataImplementor metadataImpl = (MetadataImplementor) metadata;
SchemaExport schema = new SchemaExport(metadataImplementor);
But it would be nice to have a better way and also, Validator annotations (#NotNull, #Size) are not used for DDL generation and I don't know if it is a bug in Hibernate 5 or this setup.
I am using hibernate 5.0.0.CR4 and spring 4.2.0.RELEASE
You need to implement org.hibernate.integrator.spi.Integrator where you can store required data to some holder.
Work example you can find here https://github.com/valery-barysok/spring4-hibernate5-stackoverflow-34612019
register it as the service at META-INF/services/org.hibernate.integrator.spi.Integrator file
public class Integrator implements org.hibernate.integrator.spi.Integrator {
#Override
public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
HibernateInfoHolder.setMetadata(metadata);
HibernateInfoHolder.setSessionFactory(sessionFactory);
HibernateInfoHolder.setServiceRegistry(serviceRegistry);
}
#Override
public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
}
}
Use it
new SchemaExport((MetadataImplementor) HibernateInfoHolder.getMetadata()).create(true, true);
new SchemaUpdate(HibernateInfoHolder.getServiceRegistry(), (MetadataImplementor) HibernateInfoHolder.getMetadata()).execute(true, true);
Additional info you can find here Programmatic SchemaExport / SchemaUpdate with Hibernate 5 and Spring 4
There is Configuration over Convention principle for Java Persistence API but Validation API is intended for validation purpose only. Validation is not absolute you can put different validation rules on the same field.
if you have for example
#Size(max = 50)
#NotNull(groups = DefaultGroup.class)
#Null(groups = SecondGroup.class)
private String shortTitle;
then it is interpreted as
#Size(max = 50)
#NotNull(groups = DefaultGroup.class)
#Null(groups = SecondGroup.class)
#Column(length = 255, nullable = true)
private String shortTitle;
see more details here
Why does Hibernate Tools hbm2ddl generation not take into account Bean Validation annotations?
For Hibernate 5.2.7 (in my case) I've wrote a method to export schema that is based on packages scan like:
static void exportSchema(
DataSource dataSource,
Class<? extends Dialect> dialect,
String... packagesToScan) {
StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder()
.applySetting(DATASOURCE, dataSource)
.applySetting(DIALECT, dialect); // dialect could be omitted
MetadataSources metadataSources = new MetadataSources(registryBuilder.build());
PathMatchingResourcePatternResolver resourceLoader = new PathMatchingResourcePatternResolver();
new LocalSessionFactoryBuilder(null, resourceLoader, metadataSources)
.scanPackages(packagesToScan);
Metadata metadata = metadataSources.buildMetadata();
new SchemaExport()
.setFormat(true)
.create(EnumSet.of(STDOUT, DATABASE), metadata);
}

Can't create an AbstractRoutingDataSource that needs some data from another database

We currently have an application which uses multiple databases with the same schema. At the moment we're using a custom solution for switching between them based on the user's session. This works via
public final class DataSourceProxy extends BasicDataSource {
...
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getDetails() instanceof Map) {
Map<String, String> details = (Map<String, String>) auth.getDetails();
String targetUrl = details.get("database");
Connection c = super.getConnection();
Statement s = c.createStatement();
s.execute("USE " + targetUrl + ";");
s.close();
return c;
} else {
return super.getConnection();
}
}
Now we want to build a solution using AbstractRoutingDataSource. The problem is:
#Component
public class CustomRoutingDataSource extends AbstractRoutingDataSource {
#Autowired
Environment env;
#Autowired
DbDetailsRepositoy repo;
public CustomRoutingDataSource() {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
for(DBDetails dbd : repo.findAll() {
// create DataSource and put it into the map
}
setTargetDataSources(new HashMap<Object, Object>());
}
#Override
protected Object determineCurrentLookupKey() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getDetails() instanceof Map) {
Map<String, String> details = (Map<String, String>) auth.getDetails();
return details.get("database");
}
return null;
}
}
Inside the constructor (or even via #PostConstruct) we have to fill the targetDataSources Map. But(!) for this we need the connection details which are stored in another database, which has its own DataSource and Entity Manager.
It seems like Spring can't determine the order of Bean construction, or maybe I'm just missing something. It always gives a NullPointerException when accessing the repository (which btw is a JpaRepository).
We're using Spring 3.2.3, Spring Data, Hibernate 4.2. Complete Annotation and Java-Code configuration of Spring and Spring Security.
Please help us!
Spring of course has to call the constructor before it can populate the properties. But that's not a Spring thing, that's basic Java 101 and one of the plenty downsides of using field injection.
To avoid this, simply add your dependencies to the constructor:
#Component
class CustomRoutingDataSource extends AbstractRoutingDataSource {
#Autowired
public CustomRoutingDataSource(DbDetailsRepository repo, Environment environment) {
…
}
…
}

Spring - jdbcTemplate

I'm just beginning with Spring framework. I'm also using DBCP pooling and i'm still not sure how to work right with jdbcTemplate.
It is best practice to reuse created/injected jdbcTemplate instance between multiple DAOs or it is right to create jdbcTemplate for each DAO ?
I'm currently using annotation approach:
public class FooDAO {
private JdbcTemplate jdbcTemplate;
#Autowired
public void setDatasource( DataSource dataSource ) {
this.jdbcTemplate = new JdbcTemplate( dataSource );
}
}
I'm aware about JdbcDaoSupport, but I don't know how to inject datasource, because method setDatasource is marked as final.
But still, I'm not sure if is best practice to reuse created jdbcTemplate or not.
Inject it in and share it. Don't call "new"; that takes control out of the hands of the Spring bean factory.
I'm aware about JdbcDaoSupport, but I don't know how to inject datasource, because method setDatasource is marked as final.
public class JdbcDaoSupportTest extends JdbcDaoSupport {
public void insert() {
this.getJdbcTemplate().execute("insert into tb_test1 values(1,'ycl','123')");
System.out.println("complete...");
}
}
Spring call set Method, don't care whether the method is final or not.
<bean id="jdbcDaoSupportTest" class="com.xxxxx.JdbcDaoSupportTest">
<property name="dataSource" ref="dataSource" />
</bean>
then in your JdbcDaoSupportTest, you can call this.getJdbcTemplate() to get JdbcTemplate do
any operator.
try {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
String sql = "select user.id as id,user.roll_no as createdId,user.name as name,user.type as company,role.role as year "
+ "from user_role join user on user.id=user_role.user_id "
+ "join role on role.id=user_role.role_id "
+ "where (user.status='ACTIVE' or user.status='ADMIN') AND user.username='" + userName + "'";
UserVo userDetails = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<UserVo>(UserVo.class));
or
Long company = jdbcTemplate.queryForObject(sql, Long.class);
or
List<UserVo> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<UserVo>(UserVo.class));
logger.info("Retrieve user details by username");
return userDetails;
} catch (Exception e) {
logger.error("error in getting UserDetails using UserName", e);
}

Resources