-
-
Save jelies/5181262 to your computer and use it in GitHub Desktop.
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(); | |
} | |
} | |
} |
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.
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.
Thanks for good code.