Skip to content

Instantly share code, notes, and snippets.

@jelies
Last active December 4, 2024 12:26
Show Gist options
  • Save jelies/5181262 to your computer and use it in GitHub Desktop.
Save jelies/5181262 to your computer and use it in GitHub Desktop.
A Spring FactoryBean to create a Hibernate's StatelessSession to be injected in your custom repository implementation when using Spring Data JPA.
package com.jelies.spring3tomcat7.repository;
import org.hibernate.Criteria;
import org.hibernate.ScrollableResults;
import org.hibernate.StatelessSession;
import org.hibernate.Transaction;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import com.jelies.spring3tomcat7.model.entity.User;
public class MyRepositoryImpl implements MyRepositoryCustom {
@Autowired
private StatelessSession statelessSession;
@Override
@Transactional
public void myBatchStatements() {
Criteria c = statelessSession.createCriteria(User.class);
ScrollableResults itemCursor = c.scroll();
while (itemCursor.next()) {
myUpdate((User) itemCursor.get(0));
}
itemCursor.close();
return true;
}
}
package com.jelies.spring3tomcat7.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import com.jelies.spring3tomcat7.config.util.hibernate.StatelessSessionFactoryBean;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories("com.jelies.spring3tomcat7.repository")
public class PersistenceConfig implements TransactionManagementConfigurer {
...
@Bean
public StatelessSessionFactoryBean statelessSessionFactory() {
return new StatelessSessionFactoryBean();
}
...
}
package com.jelies.spring3tomcat7.config.spring;
import static org.springframework.orm.jpa.EntityManagerFactoryUtils.ENTITY_MANAGER_SYNCHRONIZATION_ORDER;
import static org.springframework.util.ReflectionUtils.invokeMethod;
import java.sql.Connection;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.hibernate.SessionFactory;
import org.hibernate.StatelessSession;
import org.hibernate.ejb.HibernateEntityManagerFactory;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.transaction.spi.TransactionContext;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.orm.jpa.LocalEntityManagerFactoryBean;
import org.springframework.transaction.support.ResourceHolderSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* Hibernate's {@link StatelessSession} factory which will be bound to the
* current transaction. This factory returns a Proxy which delegates method
* calls to the underlying {@link StatelessSession} bound to transaction. At the
* end of the transaction the session is automatically closed. This class
* borrows idea's from {@link DataSourceUtils},
* {@link EntityManagerFactoryUtils}, {@link ResourceHolderSynchronization} and
* {@link LocalEntityManagerFactoryBean}.
*/
public class StatelessSessionFactoryBean implements FactoryBean<StatelessSession> {
private final HibernateEntityManagerFactory entityManagerFactory;
private SessionFactory sessionFactory;
@Autowired
public StatelessSessionFactoryBean(HibernateEntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
this.sessionFactory = entityManagerFactory.getSessionFactory();
}
/**
* Use this to override the {@link SessionFactory} obtained from the
* {@link EntityManagerFactory}. Please note that the connection will still
* be used from the {@link EntityManager}.
*/
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Override
public StatelessSession getObject() throws Exception {
StatelessSessionInterceptor statelessSessionInterceptor = new StatelessSessionInterceptor(
entityManagerFactory, sessionFactory);
return ProxyFactory.getProxy(StatelessSession.class, statelessSessionInterceptor);
}
@Override
public Class<?> getObjectType() {
return StatelessSession.class;
}
@Override
public boolean isSingleton() {
return true;
}
private static class StatelessSessionInterceptor implements MethodInterceptor {
private final EntityManagerFactory entityManagerFactory;
private final SessionFactory sessionFactory;
public StatelessSessionInterceptor(EntityManagerFactory entityManagerFactory,
SessionFactory sessionFactory) {
this.entityManagerFactory = entityManagerFactory;
this.sessionFactory = sessionFactory;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
StatelessSession statelessSession = getCurrentSession();
return invokeMethod(invocation.getMethod(), statelessSession, invocation.getArguments());
}
private StatelessSession getCurrentSession() {
if (!TransactionSynchronizationManager.isActualTransactionActive()) {
throw new IllegalStateException(
"There should be an active transaction for the current thread.");
}
StatelessSession statelessSession = (StatelessSession) TransactionSynchronizationManager
.getResource(sessionFactory);
if (statelessSession == null) {
statelessSession = openNewStatelessSession();
bindWithTransaction(statelessSession);
}
return statelessSession;
}
private StatelessSession openNewStatelessSession() {
Connection connection = obtainPhysicalConnection();
return sessionFactory.openStatelessSession(connection);
}
/**
* It is important we obtain the physical (real) connection otherwise it
* will be double proxied and there will be problems releasing the
* connection.
*/
private Connection obtainPhysicalConnection() {
EntityManager entityManager = EntityManagerFactoryUtils
.getTransactionalEntityManager(entityManagerFactory);
SessionImplementor sessionImplementor = (SessionImplementor) entityManager
.getDelegate();
return sessionImplementor.getTransactionCoordinator().getJdbcCoordinator()
.getLogicalConnection().getConnection();
}
private void bindWithTransaction(StatelessSession statelessSession) {
TransactionSynchronizationManager
.registerSynchronization(new StatelessSessionSynchronization(sessionFactory,
statelessSession));
TransactionSynchronizationManager.bindResource(sessionFactory, statelessSession);
}
}
private static class StatelessSessionSynchronization extends TransactionSynchronizationAdapter {
private final SessionFactory sessionFactory;
private final StatelessSession statelessSession;
public StatelessSessionSynchronization(SessionFactory sessionFactory,
StatelessSession statelessSession) {
this.sessionFactory = sessionFactory;
this.statelessSession = statelessSession;
}
@Override
public int getOrder() {
return ENTITY_MANAGER_SYNCHRONIZATION_ORDER - 100;
}
@Override
public void beforeCommit(boolean readOnly) {
if (!readOnly) {
((TransactionContext) statelessSession).managedFlush();
}
}
@Override
public void beforeCompletion() {
TransactionSynchronizationManager.unbindResource(sessionFactory);
statelessSession.close();
}
}
}
@debop
Copy link

debop commented Sep 7, 2014

Thanks for good code.

@tnleeuw
Copy link

tnleeuw commented Sep 1, 2017

This is a very useful example, I wish it was easier however to somehow tell Spring Boot that I want to use a Stateless Session for a certain Service method and then have all repository calls from within that service method automatically use a stateless session... Or even if I could annotate a single repository method to use a stateless session.

In the way you show it, I still have to create manual implementation of my repository methods which sucks.

@sumanjara
Copy link

sumanjara commented Feb 11, 2018

Hi, For 2 million records .. i got a too many cursors open exception . while it is working for the 50 thousand records. we set 1000 as the batch size. it will help us, let us know how/when the connections will be closed and opened ? where we are closing the session ? We are inserting 2 million records. what would be best setting for batch size and want to print the connections that are used while this code is executed.

one more issue we may get in terms of Connection reset. if the connection is open for a too much long time. what if we make isSingleton to false(what would be the impact)? If we print the database information like how many sessions, cursors the program is using. it would help in understand and fix the issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment