views:

75

answers:

4

I am writing a simple application (Spring + Hibernate + PostgreSql db). I am just trying to construct a sample object and persist in db.

I run a simple java class main method where i have loaded the applicationContext and have got reference to the service class as below

TestService srv = (TestService)factory.getBean("testService");  

Application Context - context :

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

<bean id="testService" class="com.test.service.TestServiceImpl">
    <property name="testDao" ref="testDao"/>
</bean>

<bean id="testDao" class="com.test.dao.TestDaoImpl>
    <property name="sessionFactory" ref="sessionFactoryVsm"/>
</bean>

In TestService I have injected TestDao. In test service method I have constructed to employee objects emp1 and emp2 and calling dao twice to update.

TestDaoImpl code:

public void saveOrUpdate(BaseDomainModel baseObject) {

    Session session = null;
    try {
        session = getHibernateTemplate().getSessionFactory().openSession();
        session.saveOrUpdate(baseObject);
        session.flush();
    } catch (Exception e) {
        logger.error("Generic DAO:saveOrUpdate::" + e);
        e.printStackTrace();
    } finally {
        if (session != null) {
            session.close();
        }
    }

}

When emp2 update fails emp1 should also fail. How do I do that. Please advice

Thanks in advance

Updated :

Thanks Nanda. I tried Declarative transaction. But it is not working. emp1 gets persisted and not rolled back eveb second dao call fails. I have added transaction advice to the method.

to test if the transaction advice is applied or not i changed the propagation to "NOT_SUPPORTED". but still emp1 gets persisted. the expectation is we should have got Transaction Not Supported type of exception. please advice .

UPDATED

@seanizer - Thanks for the update. I have even tried adding
@Transactional(propagation=Propagation.NOT_SUPPORTED) public void saveEmp(Employee emp) to that service method. But it didn't work. Moreover iterating the collection hold good only if i need to call one dao. If in case i have to call two different dao to persist obj1 and obj2- this may not help. Just to check if the transaction is getting applied I get @Transactional(propagation=Propagation.NOT_SUPPORTED). But still obj1 got persisted. I just doubt if the xml configuration/ annotation given is correct. please check

<bean id="txManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactoryVsm" />
    </bean>

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
         <tx:method name="saveEmp" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="*"/>
    </tx:attributes>
 </tx:advice>   
 <aop:config>
    <aop:pointcut id="testServiceOperation" expression="execution(*com.test.service.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="testServiceOperation"/>
  </aop:config> 

I am using org.springframework.orm.hibernate3.HibernateTransactionManager for the transactionManager. Is this correct ?


Updated

I have created my exception class myRuntimeExp extending from RuntimeException and throwing the same from Dao method to the service method. but still the rollback is not happening. I just doubt if I have correctly given the configurations in the applnContext.xml. Can someone help me how to check if the transaction advice / annotation is being applied to the mehtod or not? is there any way of running it in a debbuging mode and check

Issue :

I was using

session = getHibernateTemplate().getSessionFactory().openSession();

But it should be current session and it is working fine.

session = getHibernateTemplate().getSessionFactory().getCurrentSession();
A: 

You can put the sessionFactory in TestServiceImpl and open the session there.

nanda
Does Spring Declarative Transaction help in this case ?
Vanathi
yes of course, declare that the transaction is needed on the dao AND on service level. If the dao detects that the service has opened the transaction, it will use this instead of creating new transaction
nanda
I disagree, transactions should be needed on service methods, not dao methods. That way a service call can be atomic even if it uses multiple dao methods (yes, I know you can also achieve that by joining existing transactions, but I prefer a clean separation of layers)
seanizer
@seanizer: my only problem is that in case of simple modification we have to provide the same methods in daos also in service level. This duplication hurts my eyes
nanda
@nanda I know. The question is: is a dao layer still needed, given that hibernate session and JPA entitymanager both expose dao functionality. Many projects skip the dao layer and just have a service layer.
seanizer
Updated the description. Please check
Vanathi
+1  A: 

If you use declarative transaction management, you can lose most of this boilerplate:

TestDaoImpl:

private SessionFactory sessionFactory;

public void setSessionFactory(SessionFactory f){
    this.sessionFactory = f;
}

public void saveOrUpdate(BaseDomainModel baseObject) {
    Session session = sessionFactory.getCurrentSession();
    session.saveOrUpdate(baseObject);
}

And you can control the transaction handling from the service layer using @Transactional (or xml configuration)

TestServiceImpl:

private TestDao testDao;

public void setTestDao(TestDao  d){
    this.testDao = d;
}

@Transactional // one transaction for multiple operations
public void someServiceMethod(Collection<BaseDomainModel> data){
     for(BaseDomainModel baseObject : data)
         testDao.saveOrUpdate(baseObject);
}

Reference:

seanizer
+1  A: 

By default, Spring only rolls back for unchecked exception. You have to provide the rollback-for attribute and specify what exception you are trying to catch.


From Spring documentation: However, please note that the Spring Framework's transaction infrastructure code will, by default, only mark a transaction for rollback in the case of runtime, unchecked exceptions; that is, when the thrown exception is an instance or subclass of RuntimeException. (Errors will also - by default - result in a rollback.) Checked exceptions that are thrown from a transactional method will not result in the transaction being rolled back.

virgium03
note that you also need to add this: <!-- enable the configuration of transactional behavior based on annotations --> <tx:annotation-driven transaction-manager="txManager"/>
virgium03
@virgium03 - Thanks. u can see the above xml snipper where i have given <tx:method name="saveEmp" propagation="REQUIRED" rollback-for="Exception"/>. This should take care - am I correct ?
Vanathi
also, you can turn the logging level to debug and see when the transactions are commited, rolled back etc
virgium03
The Spring team's recommendation is that you only annotate concrete classes with the @Transactional annotation, as opposed to annotating interfaces. You certainly can place the @Transactional annotation on an interface (or an interface method), but this will only work as you would expect it to if you are using interface-based proxies.
virgium03
I think that if you use the <tx:annotation-driven transaction-manager="txManager"/> in conjuction with the @Transactional annotation, you can remove the xml declarations
virgium03
@ virgium03 - I have <tx:annotation-driven transaction-manager="txManager"/> in my appContext.xml.
Vanathi
I have created my exception class myRuntimeExp extending from RuntimeException and throwing the same from Dao method. but still the rollback is not happening. Can someone help me how to check if the transaction advice / annotation is being applied to the mehtod or not? is there any way of running it in a debbuging mode and check.
Vanathi
yes, if you are using log4j set the debug level for the spring appender.
virgium03
+1  A: 

Here, get these snippets and hope they will help you:

<bean id="abstractService"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
abstract="true">
<property name="transactionManager" ref="transactionManager" />
<property name="transactionAttributes">
    <props>
        <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
        <prop key="add*">PROPAGATION_REQUIRED, -Exception</prop>
        <prop key="update*">PROPAGATION_REQUIRED, -Exception</prop>
        <prop key="modify*">PROPAGATION_REQUIRED, -Exception</prop>
        <prop key="delete*">PROPAGATION_REQUIRED, -Exception</prop>
        <prop key="save*">PROPAGATION_REQUIRED, -Exception</prop>
    </props>
</property>
</bean>

<bean id="persistenceServiceTarget" class="com.blahblah.server.service.impl.PersistenceServiceImpl">
<property name="persistenceDAO" ref="persistenceDAO" />
</bean>
<bean id="persistenceService" parent="abstractService">
     <property name="target" ref="persistenceServiceTarget" />
</bean>

<tx:annotation-driven transaction-manager="transactionManager" />

<bean id="abstractDAO"
class="org.springframework.orm.hibernate3.support.HibernateDaoSupport"
abstract="true">
<property name="sessionFactory">
    <ref bean="webSessionFactory" />
</property>
</bean>

<bean id="persistenceDAO" class="com.blahblah.server.dao.impl.PersistenceDAOImpl"
parent="abstractDAO">
</bean>

These should be the things that you need. They don't have to be in the same file, maybe split them between services and daos.

virgium03