Hello!
While enabling the lazy-loading I have run into the following problem - hibernate automatically saves the persistent object if the one of it's setters is called. I have the impression that hibernate is automatically creating and commiting the transaction (with disabled lazy-loading everything is just fine). If this is true, then I really need to disable this feature.
I am using Hibernate 3.2 with annotations withing spring application.
Also there is an additional thing that I have to mention - I am using mysql replication and in order to make all the sql writes go to master and sql reads go to slave(s), I need to switch between read-only and non read only connection modes, so I have ugly hacks in my code as well.
Hibernate config code:
Code:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.provider_class">org.hibernate.connection.DatasourceConnectionProvider</property>
<property name="connection.datasource">java:/MY_DS</property>
<property name="transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
<property name="hibernate.max_fetch_depth">3</property>
<property name="hibernate.connection.release_mode">on_close</property>
<property name="dialect">cy.axiasoft.webspace.spring.h3.MySQLDialect</property>
<property name="current_session_context_class">managed</property>
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</property>
<property name="hibernate.cache.use_query_cache">true</property>
<property name="show_sql">false</property>
<!-- mapping list goes here -->
</session-factory>
</hibernate-configuration>
Hibernate config in springCode:
<bean id="hibernateSessionFactory" class="foo.h3.SessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
</bean>
foo.h3.SessionFactoryBean - this is used for initializing the annotation configuration
Code:
package foo.h3;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.cfg.Configuration;
import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
public class SessionFactoryBean extends LocalSessionFactoryBean {
public SessionFactoryBean() {
super();
}
@Override
public Configuration newConfiguration(){
return new AnnotationConfiguration();
}
}
I have extended spring HibernateTemplate class for switching between read only and non-read only connections
ConfigurableHibernateTemplate:Code:
package foo.spring.h3;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.context.ManagedSessionContext;
import org.springframework.orm.hibernate3.HibernateTemplate;
/**
* Hibernate template that allows to create read-only or non read-only
* connections
*
* @author armands
*
*/
public class ConfigurableHibernateTemplate extends HibernateTemplate {
private static final Log log = LogFactory
.getLog(ConfigurableHibernateTemplate.class);
private boolean readOnly;
public ConfigurableHibernateTemplate(SessionFactory sessionFactory,
boolean readOnly) {
super(sessionFactory);
this.readOnly = readOnly;
setAllowCreate(false);
setExposeNativeSession(true);
}
@Override
protected Session getSession() {
final org.hibernate.classic.Session s;
SessionFactory sessionFactory = getSessionFactory();
if (!ManagedSessionContext.hasBind(sessionFactory)) {
s = sessionFactory.openSession();
s.beginTransaction();
ManagedSessionContext.bind(s);
} else {
s = sessionFactory.getCurrentSession();
}
setConnectionModeIfNecessary(s.connection(), readOnly);
return createSessionProxy(s);
}
private void setConnectionModeIfNecessary(Connection connection, boolean ro)
throws HibernateException {
try {
if (connection.isReadOnly() != ro) {
connection.setReadOnly(ro);
}
} catch (SQLException e) {
throw new HibernateException(e);
}
}
/**
* The proxy created by this method will ignore close() calls to session
* object. This is a hack to prevent HibernateTemplate from automatically
* closing session after executing jdbc calls
*/
@Override
protected org.hibernate.classic.Session createSessionProxy(
final Session session) {
log.debug("Creating session proxy");
org.hibernate.classic.Session proxySession = (org.hibernate.classic.Session) Proxy
.newProxyInstance(Session.class.getClassLoader(),
new Class[] { org.hibernate.classic.Session.class },
new InvocationHandler() {
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
if (method.getName().equals("close")) {
return null;
}
return method.invoke(session, args);
}
});
return proxySession;
}
/**
* Override to always perform flush on non read-only connection
*/
@Override
protected void flushIfNecessary(Session session, boolean existingTransaction)
throws HibernateException {
try {
// set connection read-only = false and perform flush if necesary
session.connection().setReadOnly(false);
super.flushIfNecessary(session, existingTransaction);
// restore state
session.connection().setReadOnly(readOnly);
} catch (SQLException e) {
throw new HibernateException("SQL Exception encountered!", e);
}
}
}
Code for HttpFilter for transaction commit/rollback
Code:
public class HibernateSessionHandlerFilter implements Filter {
private Log log = LogFactory.getLog(HibernateSessionHandlerFilter.class);
private ServiceManager serviceManager;
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
SessionFactoryImplementor hibernateSessionFactory = serviceManager.getHibernateSessionFactory();
try {
chain.doFilter(request, response);
Session session = getCurrentSession(hibernateSessionFactory);
if (session != null && session.isOpen()) {
session.getTransaction().commit();
}
} finally {
try {
Session session = getCurrentSession(hibernateSessionFactory);
if (session != null && session.isOpen()) {
// if there still is active and not commited transaction
// then rollback!
if (session.getTransaction().isActive()) {
try {
log.warn("Transaction rollback");
session.getTransaction().rollback();
} catch (HibernateException ex) {
log.warn("Exception while trying to rollback transaction!", ex);
}
}
session.close();
}
} catch (HibernateException he) {
log.warn("Error while trying to close session!", he);
}
ManagedSessionContext.unbind(hibernateSessionFactory);
}
}
public void init(FilterConfig arg0) throws ServletException {
serviceManager = ServiceManager.getInstance();
}
public void destroy() {
}
protected Session getCurrentSession(SessionFactory sf) {
Session s;
try {
s = sf.getCurrentSession();
} catch (HibernateException he) {
// if exception was thrown then there is no active session
s = null;
}
return s;
}
Also I was not able to use other transaction factory than JDBCTransactionFactory, because then I got the exception that I cannot change connection mode in managed transaction. I could repeat this exception and post the stack trace if that could help.
Thanks in advance!