Last active
October 12, 2023 04:30
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.