4

I'm trying to upgrade our application with Hibernate 4.3.5.Final and Spring 4.0.6. Any where in my app with database write operation gets an error as below:

Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.
  at org.springframework.orm.hibernate4.HibernateTemplate.checkWriteOperationAllowed(HibernateTemplate.java:1135)
  at org.springframework.orm.hibernate4.HibernateTemplate$26.doInHibernate(HibernateTemplate.java:826)
  at org.springframework.orm.hibernate4.HibernateTemplate.doExecute(HibernateTemplate.java:340)
  at org.springframework.orm.hibernate4.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:308)
  at org.springframework.orm.hibernate4.HibernateTemplate.deleteAll(HibernateTemplate.java:823)
  ... 

The follwing is my spring configuration for sessionFactory and transactionManager:

<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
  <property name="dataSource" ref="dataSource"/>
  <property name="mappingResources">
    <list>
      <value>com/mycompany/Person.hbm.xml</value>   
    </list>
  </property>
  <property name="hibernateProperties">
    <props>
      <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
    </props>
  </property>
</bean>

<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
  <property name="sessionFactory" ref="sessionFactory"/>
</bean>

1:

In order to globally set the flushMode so that the app works the same way as before, I need to set flushMode to AUTO globally, so I don't want to use the @Transactional(readOnly = false) approach.

2:

In the post below, someone suggests setting singleSession to false, Java / Hibernate - Write operations are not allowed in read-only mode

The Spring documentations suggests that specifying "singleSession"="false" has side effect: http://docs.spring.io/spring/docs/4.0.6.RELEASE/javadoc-api/org/springframework/orm/hibernate3/support/OpenSessionInViewInterceptor.html

3:

I have seen quite a few suggestions like the below in web.xml, which allows you to intercept the hibernate3 session and provide a version of the session with e.g. flushMode.AUTO. However, this doesn't work in hibernate 4 when you use org.springframework.orm.hibernate4.support.OpenSessionInViewFilter.

<filter>
    <filter-name>openSessionInViewFilter</filter-name>
    <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
    <init-param>
        <param-name>flushMode</param-name>
        <param-value>AUTO</param-value>
    </init-param>
</filter>

4:

The approach suggested below is using JPA transaction manager, which follows a complicate re-implementation of HibernateJpaDialect. I'm not using JPA at the moment and this approach doesn't seem to be simple enough. How do I set flush mode to "COMMIT" in my configuration files?

5:

I have tried having the following in my spring configuration (following a suggestion on Spring ORM 4.0.5 and Hibernate 4.3.5 - Cant save to database), it doesn't seem to work and people suggest using the web.xml approach: Spring and Hibernate suddenly set the transaction to readonly

<tx:advice id="transactionAdvice" transaction-manager="transactionManager" >
  <tx:attributes>
    <tx:method name="*" read-only="false"/>
  </tx:attributes>
</tx:advice>

Question:

Can anyone suggest a simple approach to allow setting FlushMode for Hibernate 4.3.5.Final and Spring 4.0.6?

Community
  • 1
  • 1
Max
  • 1,064
  • 9
  • 23
  • If the flush mode isn't set I suspect wrong or absent transaction management. The fact that you have a `HibernateTransactionManager` doesn't mean you have tx setup properly. Also only adding `` without an `` to have it applied is pretty much useless. – M. Deinum Sep 02 '14 at 10:30
  • @M.Deinum Thanks for the comment. In my case, I need to mirror the previous behavior in the application, which is using FlashMode.AUTO (It's the default of the old Hibernate version 3.0.5). Your answer allows me to do further investigation of a cleaner solution (if setting read-only to false globally meets the need of our app). – Max Sep 03 '14 at 11:58

1 Answers1

3

I ended up overriding OpenSessionInViewFilter with custom implementation:

1:

web.xml:

<filter>
  <filter-name>openSessionInViewFilter</filter-name>
  <filter-class>com.mycompany.AutoFlushOpenSessionInViewFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>openSessionInViewFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
  • ContextLoaderListener is required for Spring to work.
  • AutoFlushOpenSessionInViewFilter is applied to intercept requests from the /* url pattern

2:

AutoFlushOpenSessionInViewFilter:

import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.orm.hibernate4.support.OpenSessionInViewFilter;

public class AutoFlushOpenSessionInViewFilter extends OpenSessionInViewFilter {

  protected Session openSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
    try {
      Session session = sessionFactory.openSession();
      session.setFlushMode(FlushMode.AUTO); // This line changes the default behavior
      return session;
    } catch (HibernateException ex) {
      throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex);
    }
  }
}
  • OpenSessionInViewFilter is the default way to intercept hibernate sessions (http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/orm/hibernate4/support/OpenSessionInViewFilter.html)
  • The openSession method opens a hibernate session. Hibernate would use this session instead of creating a new one
  • hibernate3.support.OpenSessionInViewFilter allows you to provide a FlushMode, hibernate4.support.OpenSessionInViewFilter hard codes the value, so I override it with my own implementation
  • Make sure your sessionFactory bean name is sessionFactory. Otherwise you need to set the sessionFactoryBeanName as a filter init-param in web.xml

3:

All the Spring beans need to be registered within the Web application context (web.xml):

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>
    classpath:appContext.xml
    ...
  </param-value>
</context-param>

4:

Make sure you get your spring beans only from the application context when you need to use them. The following is a how to example: http://sujitpal.blogspot.co.uk/2007/03/accessing-spring-beans-from-legacy-code.html

Make sure there is only one copy of the Spring beans being created! If you use org.springframework.context.support.ClassPathXmlApplicationContext to load Spring beans, these beans would not be picked up by the filter.

5:

In my case, a contextId is also required

<context-param>
  <param-name>contextId</param-name>
  <param-value>myApp</param-value>
  <description>Required contextId when filter is supplied</description>
</context-param>

Otherwise I get the issue below:

2014-09-02 10:59:50 StandardContext[/myApp]Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
java.lang.NoSuchMethodError: javax.servlet.ServletContext.getContextPath()Ljava/lang/String;
  at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:384)
  at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:306)
  at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:106)
  at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3827)
  at org.apache.catalina.core.StandardContext.start(StandardContext.java:4343)
  at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:823)
  at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:807)
  at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:595)
  at org.apache.catalina.core.StandardHostDeployer.addChild(StandardHostDeployer.java:903)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:606)
  at org.apache.commons.beanutils.MethodUtils.invokeMethod(MethodUtils.java:216)
  at org.apache.commons.digester.SetNextRule.end(SetNextRule.java:256)
  at org.apache.commons.digester.Rule.end(Rule.java:276)
  at org.apache.commons.digester.Digester.endElement(Digester.java:1058)
  at org.apache.catalina.util.CatalinaDigester.endElement(CatalinaDigester.java:76)
  at org.apache.xerces.parsers.AbstractSAXParser.endElement(Unknown Source)
  ...

If anyone is interested, the following is what's in my Ivy.xml

<!--Spring 4.0.6.RELEASE -->
<dependency org="org.springframework" name="spring-aop" rev="4.0.6.RELEASE" conf="compile->master,sources,javadoc"/>
<dependency org="org.springframework" name="spring-beans" rev="4.0.6.RELEASE" conf="compile->master,sources,javadoc"/>
<dependency org="org.springframework" name="spring-core" rev="4.0.6.RELEASE" conf="compile->master,sources,javadoc"/>
<dependency org="org.springframework" name="spring-expression" rev="4.0.6.RELEASE" conf="compile->master,sources,javadoc"/>
<dependency org="org.springframework" name="spring-context" rev="4.0.6.RELEASE" conf="compile->master,sources,javadoc"/>
<dependency org="org.springframework" name="spring-jdbc" rev="4.0.6.RELEASE" conf="compile->master,sources,javadoc"/>
<dependency org="org.springframework" name="spring-orm" rev="4.0.6.RELEASE" conf="compile->master,sources,javadoc"/>
<dependency org="org.springframework" name="spring-tx" rev="4.0.6.RELEASE" conf="compile->master,sources,javadoc"/>
<dependency org="org.springframework" name="spring-web" rev="4.0.6.RELEASE" conf="compile->master,sources,javadoc"/>
<dependency org="aopalliance" name="aopalliance" rev="1.0" conf="compile->master,sources,javadoc"/>

<!--Hibernate 4.3.5-->
<dependency org="org.hibernate" name="hibernate-core" rev="4.3.5.Final" conf="compile->master,compile,sources"/>
<dependency org="net.sf.ehcache" name="ehcache-core" rev="2.4.8" conf="compile->master,sources,javadoc"/>
<dependency org="org.slf4j" name="slf4j-api" rev="1.7.5" conf="compile->master,sources,javadoc"/>

Hope this helps anyone who comes across the same issue when upgrading Spring and Hibernate.

Max
  • 1,064
  • 9
  • 23
  • 2
    The flush mode in the `OpenSessionInViewFilter` is NEVER (or MANUAL) for a reason :). Normally when you have proper tx management the transaction manager will set it to AUTO temporary until the transaction ends. Hence my suggestion that this is more or less an indication that you have wrong or absent tx-management in yuor application. – M. Deinum Sep 03 '14 at 12:05
  • Hi @M.Deinum , thanks for your answer. Does it mean nowadays Spring and Hibernate by default executes read-only mode (because every write access gets the exception by default)? If this is the case, disabling it doesn't feel we should engage transaction management, right? I don't want to always enable the transaction feature and add @Transactional(readOnly=false) to every single update and delete. If you or anyone can point me to an official document about how we should properly implement write access for hibernate 4, it would be really appreciated! If transactional is the way to go, it's fine. – Max Sep 04 '14 at 15:50