2

I'm working in a web app using JSF 2.1 + Hibernate 4.1.7 + Spring 3.2.1 + Spring Security + SQLServer2012. Everything works fine i.e. CRUD operations. But some methods need to work with 2 or more entities (update, add, etc), for example

getEntity1Service().merge();  // line 1
getEntity2Service().create(); // line 2
getEntity3Service().delete(); // line 3

If an error occurs executing line #2 (create entity) I need the merged entity (or update, create) or prior DB function to get rollback so the data on my DB maintain correct

I'm using the OpenSessionInViewFilter in combination with @Transactional Spring annotation.

<filter>
    <filter-name>hibernateFilter</filter-name>
    <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class>
    <init-param>
        <param-name>sessionFactoryBeanName</param-name>
        <param-value>SessionFactory</param-value>
    </init-param>
</filter>
    <filter-mapping>
        <filter-name>hibernateFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

And in my GenericDAO I have

getSessionFactory().getCurrentSession().merge(objeto);
getSessionFactory().getCurrentSession().delete(objeto);
getSessionFactory().getCurrentSession().createQuery(queryString);

What I'm missing? because when the line #1 is executed the data is sent to DB.

Extract from my App LOG

when executing line #1

...
23 05 2013 00:04:46,650 DEBUG [http-apr-8080-exec-345] (e.engine.transaction.internal.jdbc.JdbcTransaction:doCommit:113) - committed JDBC Connection
23 05 2013 00:04:46,650 DEBUG [http-apr-8080-exec-345] (e.engine.transaction.internal.jdbc.JdbcTransaction:releaseManagedConnection:126) - re-enabling autocommit
23 05 2013 00:04:46,651 DEBUG [http-apr-8080-exec-345]
...

when executing line #2

(ork.orm.hibernate4.support.OpenSessionInViewFilter:lookupSessionFactory:188) - Using SessionFactory 'SessionFactory' for OpenSessionInViewFilter 23 05 2013 00:05:27,777 DEBUG [http-apr-8080-exec-349]
(ramework.beans.factory.support.AbstractBeanFactory:doGetBean:246) - Returning cached instance of singleton bean 'SessionFactory' 23 05 2013 00:05:27,777 DEBUG [http-apr-8080-exec-349]
(ork.orm.hibernate4.support.OpenSessionInViewFilter:doFilterInternal:141) - Opening Hibernate Session in OpenSessionInViewFilter 23 05 2013 00:05:27,778 DEBUG [http-apr-8080-exec-349]
(org.hibernate.internal.SessionImpl ::312) - Opened session at timestamp: 13692891277

Thank you in advance.

** thanks for responding, updated question: ****

This is my applicationContext.xml:

<bean id="entity1DAO" class="com.x.dao.generic.GenericDAOHibernateImpl">
    <constructor-arg><value>com.x.entities.modules.general.Entity1</value></constructor-arg>
    <property name="sessionFactory"><ref bean="SessionFactory"/></property>
</bean>
<bean id="entity2DAO" class="com.x.dao.generic.GenericDAOHibernateImpl">
        <constructor-arg><value>com.x.entities.modules.general.Entity2</value></constructor-arg>
        <property name="sessionFactory"><ref bean="SessionFactory"/></property>
    </bean>
<bean id="entity3DAO" class="com.x.dao.generic.GenericDAOHibernateImpl">
        <constructor-arg><value>com.x.entities.modules.general.Entity3</value></constructor-arg>
        <property name="sessionFactory"><ref bean="SessionFactory"/></property>
    </bean>

<bean id="DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="driverClass" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
    <property name="jdbcUrl" value="jdbc:sqlserver://127.0.0.1:1433;databaseName=db1;user=sa;password=abcde1234" />
    <property name="maxPoolSize" value="10" />
    <property name="maxStatements" value="0" />
    <property name="minPoolSize" value="5" />
</bean>

<bean id="SessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource" ref="DataSource" />
    <property name="annotatedClasses">
        <list>
            <value>com.x.entities.modules.configuration.cg.CgEntity1</value>
            <value>com.x.entities.modules.configuration.cg.CgEntity2</value>
            <value>com.x.entities.modules.configuration.cg.CgEntity3</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">com.x.dao.SqlServer2008Dialect</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.hbm2ddl.auto">update</prop>
            <prop key="hibernate.id.new_generator_mappings">true</prop>
            <prop key="hibernate.format_sql">false</prop>
            <prop key="use_sql_comments">true</prop>
        </props>
    </property>
</bean>

<!--Tells Spring framework to read @Transactional annotation-->
<context:annotation-config/> 
 <!-- Enable the configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="txManager"/>
<!-- Transaction Manager is defined -->
<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="SessionFactory"/>
</bean>

My Data Access Layer: GenericDAOHibernateImpl.java :

@Transactional(rollbackFor=Exception.class)
public class GenericDAOHibernateImpl <T, PK extends Serializable> implements GenericDAOHibernate<T, PK>, Serializable{

    @Override
    public void mergeEntity(T object) {
        getSessionFactory().getCurrentSession().merge(object);
    }

    @Override
    public void deleteEntity(T object) {
        getSessionFactory().getCurrentSession().delete(object);
    }
}

My Business Logic Layer> BeanJSF1.java (@ManagedBean):

//Injection to my generic dao:
    @ManagedProperty(value = "#{entity1DAO}")
    GenericDAOHibernate<Entity1,Integer> entity1Service;
    @ManagedProperty(value = "#{entity2DAO}")
    GenericDAOHibernate<Entity2,Integer> entity2Service;
    @ManagedProperty(value = "#{entity3DAO}")
    GenericDAOHibernate<Entity3,Integer> entity3Service;
    //other variables and methods 
    @Transactional(rollbackFor = Exception.class)
    public void onUpdatingRowData(RowEditEvent ree) {
        try{
            getEntity1Service().mergeEntity(ree.getObject());//this get committed on DB
            getEntity2Service().deleteEntity(object2);//this fires an Exception
        } catch (Exception ex) {
            Logger.getLogger(BeanJSF1.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

I have tried using @Transactional in my GenericDAOHibernateImpl only, GenericDAOHibernateImpl and Bussines Layer class both, but I get the same result all the time

*THIRD QUESTION UPDATE***

Ok, I have added the service layer

DAO Layer:

public interface IGenericDAOHibernate <T, PK extends Serializable>{ ...

public class GenericDAOHibernate <T, PK extends Serializable> implements IGenericDAOHibernate<T, PK>, Serializable{ ...

Service layer:

public interface IGenericDAOHibernateService <T, PK extends Serializable>{ ...

@Transactional(rollbackFor = Exception.class)
public class GenericDAOHibernateImpl <T, PK extends Serializable> implements IGenericDAOHibernateService<T,PK>{
    public IGenericDAOHibernate genericDAOHibernate; ...

ApplicationContext.xml:

<bean id="GenericDAOService" class="com.x.services.generic.GenericDAOHibernateImpl"><property name="genericDAOHibernate" ref="GenericDAOHibernate" /></bean> 
 <bean id="GenericDAOHibernate" class="com.x.dao.generic.GenericDAOHibernate">
     <property name="sessionFactory" ref="SessionFactory" />
 </bean>

JSF Managed Bean:

@ManagedBean
@ViewScoped
public class BJsfBeanX extends BCommon implements Serializable {
    @ManagedProperty(value = "#{GenericDAOService}")
        IGenericDAOHibernateService genericService; ...


public void onUpdatingDataRow(RowEditEvent ree) throws Exception {
    try{
                getGenericService().updateEntity(entity1); //line1: where updateEntity go throw layers and execute on DAO Layer: getSessionFactory().getCurrentSession().merge(object);
            //this line at DAO class does not execute commit on data base, the commit is executed when the control is back to the managedBean method line1
            getGenericService().updateEntity(entity2);//line2: this throw Exception, but there is nothing to do rollback `cause the entity1 (line 1) has been already committed
     }catch
}

I have also tried using the @Transactional on service layer Interface / service layer Class but the commit is still happening when control comes back to JSF managedBean.

Scenario 2. *When removed the @Transactional from service layer and using it on the JSF managed bean method:*

@Transactional(rollbackFor = Exception.class)
    public void onUpdatingDataRow(RowEditEvent ree) throws Exception {

the change on DB is not committed by the service layer any more, BUT the problem is that all the flow get finished (control comes back to client side) but the commit to the DB never occurs! Please see my THIRD QUESTION UPDATE

daniel
  • 31
  • 1
  • 6
  • What transaction manager do you use, and how is configured? Also the question is how are your transactions set up. From what I understand the problem doesn't seem to be with the OpenSessionInViewFilter but rather with the transaction setup since ther is a dedicated commit after line #1. – Carsten May 24 '13 at 07:47
  • The default behaviour of `OpenSessionInViewFilter` is to wrap the entire request with a read-only transaction in order to support lazy loading in JSPs. It doesn't commit transactions - and assumes that service-layer `@Transactional` methods will handle committing the data to the database. Are your `merge()` and `create()` operations happening within the same `@Transactional` method? If not, I think they'll be treated as independent transactions. – Will Keeling May 24 '13 at 07:54
  • Thanks for responding. I have updated my question for @Carsten too. – daniel May 24 '13 at 17:28

4 Answers4

3

You've made your DAO transactional, so it shouldn't be surprising that each DAO method causes a separate transaction. DAO methods should never be transactional. Transactions belong at the service layer.

Ryan Stewart
  • 126,015
  • 21
  • 180
  • 199
  • I have added the service layer and used the @Transactional on it but i got the same result, then I used the Transactional spring annotation on JSF managed bean method but now nothing get committed on DB. Please help! – daniel May 27 '13 at 22:58
  • please see my THIRD QUESTION UPDATE. – daniel May 27 '13 at 23:22
  • Your original complaint was that the tx committed every time a DAO method returned. Now you seem to be saying that you've successfully moved the tx boundary up to the service layer. This is the correct place to do it, but you still don't seem satisfied. I'm not sure what you're looking for. – Ryan Stewart May 28 '13 at 01:39
  • Yes thats correct I have now implemented the service layer. The problem I have is the same, I use the @Transactional on service layer Interface / service layer Class the commit is still happening when control comes back to JSF managedBean method.. And if I use the Transactional annotation directly on the JSF managedBean method any of the entities get committed ever. – daniel May 28 '13 at 04:31
  • 1) If you're using a service layer approach, then everything transactional should happen in the service layer. 2) Spring will only manage transactions within the ApplicationContext where `` is declared. This is a common mistake when you don't fully understand how Spring works. See, e.g., http://stackoverflow.com/questions/7561360/spring-transaction-not-starting-transactions/7563697#7563697 and http://stackoverflow.com/questions/10538345/spring-transactional-annotations-ignored/10564908#10564908. – Ryan Stewart May 28 '13 at 13:49
  • Ok thank you very much, I have move the code working with entities to service layer. Now it is transactional I have made many scenarios test. – daniel May 29 '13 at 06:16
1

Ok thank you all for your time. The solution is as @Ryan Stewart said. And in conclusion:

  1. Use a service layer
  2. Work all the entities at the service layer side instead of bussines logic layer if you need to work with many entities at the same time
  3. After the method on service layer return control to bussiness logic layer method and if something was wrong then nothing get persisted to DB; but if nothing trigger an exception then all entities get commited.

One more thing, I have added a new DaoService that extends from my GenericDAOService so in this new service implementation I have the particular logic as mentioned on point number 2.

Thanks one more time you all.

daniel
  • 31
  • 1
  • 6
0

To get rollback you must ensure following things:

  1. All 3 lines from first code snipped must be wrapped into one transaction
  2. Transaction annotation must be picked up by Spring and actual transaction must be applied in run time. This point may not work due to multiple problems. To be sure that transaction started, make a brakepoint on the first line and check stacktrace. You must see transaction interceptor in the stack.
  3. To be sure that you do not miss anything it will be better to indicate that you want a rollback in a case of ANY exception type (by default rollback will be triggered only for RuntimeException and it subclasses)
  4. EDIT. To force rollback an exception must cross transaction boundaries, which means: a) you do not use try/catch at all; b) or if you use try/catch then you throws exception (see my second code snippet)
@Transactional(rollbackFor=Exception.class)
public void doInTransaction() {
    getDAOImplEntity1().merge();  // line 1
    getDAOImplEntity2().create(); // line 2
    getDAOImplEntity3().delete(); // line 3
}

Second code snippet (exceptions):

@Transactional(rollbackFor=Exception.class)
public void doInTransaction() throws Exception {
    try{
        getDAOImplEntity1().merge();  // line 1
        getDAOImplEntity2().create(); // line 2
        getDAOImplEntity3().delete(); // line 3
    } catch (Exception ex) {
        Logger.getLogger(BeanJSF1.class.getName()).log(Level.SEVERE, null, ex);
        throw ex; // Very important!!! Without this, exception do not cross transaction boundaries (`doInTransaction()` method) and Spring wan't do rollback for you
    }
}
Maksym Demidas
  • 7,707
  • 1
  • 29
  • 36
  • When I use the Transactional annotation on the JSF managedBean method any of the entities updated in the method get committed on the data base. Why? please help. – daniel May 28 '13 at 04:35
0

Ok, after seeing this my guess is that the problem is one of Spring-JSF integration. The problem could be caused by the fact that you are using a Spring transaction Manager. But your Beans are managed and injected by JSF (@ManagedBean and @ManagedProperty). This might cause some unexpected transaction behaviour. To solve this my suggestion would be to put all your beans (or at least the ones relevant for the transactions) under spring controll using @Component instead of @ManagedBean and @Autowired or @Inject instead of @ManagedProperty. To still be able to access your Beans from JSF via EL you need to add the SpringBeanFacesELResolver to your faces-config like this.

<application>
    <el-resolver>
            org.springframework.web.jsf.el.SpringBeanFacesELResolver
    </el-resolver>
</application>

Here is a basic tutorial for a more step by step explanation: http://www.mkyong.com/jsf2/jsf-2-0-spring-integration-example/

Even if this does not solve the immediate problem at hand it is still beneficial and makes the project more consistent and less prone to errors.

Carsten
  • 1,511
  • 2
  • 13
  • 24