boobsbr
boobsbr

Reputation: 379

Spring read-only transaction committing data to the DB

I'm trying to implement a read-only transaction for a service method, following item 11.5.2 of the Spring Framework Reference, but the transaction still automatically commits data to the DB.

I'm using Spring 3.1.0.RELEASE, Hibernate 3.5.5-Final and Oracle 11g Express Edition Release 11.2.0.2.0. Here is my setup:

The XML for the advice, the pointcut, the advisor and the transaction manager:

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

<aop:config>
    <aop:pointcut id="myServiceMethods" expression="execution(* my.example.service.*.*(..))" />
    <aop:advisor pointcut-ref="myServiceMethods" advice-ref="transactionAdvice" />
</aop:config>

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
    <!-- the sessionFactory bean declaration does not set the -->
    <!-- hibernate.connection.autocommit property as either true or false -->
</bean>

The service interface:

package my.example.service;

public interface MyService {

    void getFoo();

    void bar();
}

The service implementation:

package my.example.service.impl;

import my.example.dao.MyDao;
import my.example.domain.MyEntity;
import my.example.service.MyService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MyServiceImpl implements MyService {

    @Autowired
    private MyDao dao;

    public void getFoo() {
        MyEntity example = this.dao.getMyEntity(1L);
        example.setSomeInteger(2);
        example.setSomeString("three");
    }

    public void bar() {
        MyEntity example = this.dao.getMyEntity(4L);
        example.setSomeInteger(5);
        example.setSomeString("six");
    }
}

After calling either getFoo() or bar(), the DB is updated, even though getFoo() is marked as read-only. But if I change these two lines:

<tx:method name="get*" read-only="true" />
<tx:method name="*"/>

to:

<tx:method name="*" read-only="true" />

both methods respect the read-only property and data does not get committed to the DB.

What's going on? What am I doing wrong? What did I miss?

Upvotes: 4

Views: 4661

Answers (2)

boobsbr
boobsbr

Reputation: 379

I found out why the read-only transactions were being overriden. There is an OpenSessionInViewFilter declared in web.xml, which previous developers used to prevent LazyInitializationExceptions when loading the user and its roles in the controller/interface.

Here is the filter declaration:

<filter>
    <filter-name>openSessionInViewFilter</filter-name>
    <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>

I worked around the issue by configuring the filter to let each transaction use its own session (called deferred mode).

Here is the filter declaration with deferred mode activated:

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

Please, read the NOTE in the filter's Javadoc. My DAO class extends org.springframework.orm.hibernate3.support.HibernateDaoSupport, and the the Hibernate Session returned by the inherited methods getSession() or getSessionFactory().getCurrentSession() had its flush mode is set to FlushMode.MANUAL, instead of FlushMode.NEVER, as I exptected after reading this SO answer.

Upvotes: 4

JamesENL
JamesENL

Reputation: 6540

Why don't you just use the readOnly flag on the @Transactional annotation.

Annotate your method with @Transactional(readOnly=true) and Hibernate will attempt to perform the transaction in a read-only manner and (I think, you may want to double check this) will throw an exception if a write is attempted.

There is no point in trying to re-invent the wheel, where this is concerned.

Upvotes: 0

Related Questions