views:

360

answers:

2

I wrote a test case that extends AbstractTransactionalJUnit4SpringContextTests. The single test case I've written creates an instance of class User and attempts to write it to the database using Hibernate. The test code then uses SimpleJdbcTemplate to execute a simple select count(*) from the user table to determine if the user was persisted to the database or not. The test always fails though. I was suspect because in the Spring controller I wrote, the ability to save an instance of User to the db is successful.

So I added the Rollback annotation to the unit test and sure enough, the data is written to the database since I can even see it in the appropriate table -- the transaction isn't rolled back when the test case is finished.

Here's my test case:

@ContextConfiguration(locations = {
    "classpath:context-daos.xml", 
    "classpath:context-dataSource.xml", 
    "classpath:context-hibernate.xml"})
public class UserDaoTest extends AbstractTransactionalJUnit4SpringContextTests {

  @Autowired
  private UserDao userDao;

  @Test
  @Rollback(false)
  public void teseCreateUser() {
    try {
        UserModel user = randomUser();
        String username = user.getUserName();

        long id = userDao.create(user);

        String query = "select count(*) from public.usr  where usr_name = '%s'";
        long count = simpleJdbcTemplate.queryForLong(String.format(query, username));

        Assert.assertEquals("User with username should be in the db", 1, count);
    }
    catch (Exception e) {
        e.printStackTrace();
        Assert.assertNull("testCreateUser: " + e.getMessage());
    }
  }
}

I think I was remiss by not adding the configuration files.

context-hibernate.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd&gt;

   <bean id="namingStrategy" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
  <property name="staticField">
     <value>org.hibernate.cfg.ImprovedNamingStrategy.INSTANCE</value>
  </property>
   </bean>

   <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean" destroy-method="destroy" scope="singleton">
  <property name="namingStrategy">
     <ref bean="namingStrategy"/>
  </property>

  <property name="dataSource" ref="dataSource"/>

  <property name="mappingResources">
     <list>
        <value>com/company/model/usr.hbm.xml</value>
     </list>
  </property>

  <property name="hibernateProperties">
     <props>
        <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
        <prop key="hibernate.show_sql">true</prop>
        <prop key="hibernate.use_sql_comments">true</prop> 
        <prop key="hibernate.query.substitutions">yes 'Y', no 'N'</prop>
        <prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>
        <prop key="hibernate.cache.use_query_cache">true</prop>
        <prop key="hibernate.cache.use_minimal_puts">false</prop>
        <prop key="hibernate.cache.use_second_level_cache">true</prop>
        <prop key="hibernate.current_session_context_class">thread</prop>
     </props>
  </property>
   </bean>

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

   <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
  <property name="transactionManager">
     <ref local="transactionManager"/>
  </property>

  <property name="transactionAttributes">
     <props>
        <prop key="create">PROPAGATION_REQUIRED</prop>
        <prop key="delete">PROPAGATION_REQUIRED</prop>
        <prop key="update">PROPAGATION_REQUIRED</prop>
        <prop key="*">PROPAGATION_SUPPORTS,readOnly</prop>
     </props>
  </property>
   </bean>      
 </beans>

context-dataSource.xml

<?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
   xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"&gt; 

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="org.postgresql.Driver" />
        <property name="jdbcUrl"     value="jdbc\:postgresql\://localhost:5432/company_dev" />
        <property name="user"        value="postgres" />
        <property name="password"    value="postgres" />
     </bean>   
</beans>

context-daos.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
   http://www.springframework.org/schema/beans 
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"&gt;

   <bean id="extendedFinderNamingStrategy" class="com.company.dao.finder.impl.ExtendedFinderNamingStrategy"/>
   <bean id="finderIntroductionAdvisor"    class="com.company.dao.finder.impl.FinderIntroductionAdvisor"/>

   <bean id="abstractDaoTarget" class="com.company.dao.impl.GenericDaoHibernateImpl" abstract="true" depends-on="sessionFactory">
      <property name="sessionFactory">
         <ref bean="sessionFactory"/>
      </property>

      <property name="namingStrategy">
         <ref bean="extendedFinderNamingStrategy"/>
      </property>
   </bean>

   <bean id="abstractDao" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true">
      <property name="interceptorNames">
         <list>
           <value>transactionInterceptor</value>
            <value>finderIntroductionAdvisor</value>
         </list>
       </property>
   </bean>

   <bean id="userDao" parent="abstractDao">
     <property name="proxyInterfaces">
       <value>com.company.dao.UserDao</value>
     </property>

  <property name="target">
     <bean parent="abstractDaoTarget">
        <constructor-arg>
           <value>com.company.model.UserModel</value>
        </constructor-arg>
     </bean>
  </property>
  </bean>


</beans>

Some of this I've inherited from someone else. I wouldn't have used the proxying that is going on here because I'm not sure it's needed but this is what I'm working with.

Any help much appreciated.

A: 

IIRC, Hibernate (and other O/R mappers) delay the database INSERT and UPDATE statements until the transaction commit. The SELECT then does not see the data, as it is not yet written. Try explicitly requesting a session flush.

See also the documentation for an explanation and example: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/objectstate.html#objectstate-flushing

Christian Semrau
Adding the call to Session.flush() did the trick. This worked for save, update and delete operations. Thank you.
richever
A: 

Doesn't transactionInterceptor automatically commits?

http://www.docjar.com/html/api/org/springframework/transaction/interceptor/TransactionInterceptor.java.html

117 commitTransactionAfterReturning(txInfo);

By default the flush mode should be auto, so there should be an implicit flush when commit is performed. I have the same problem and had to solve it by manually calling flush. That is very unsatisfying because my existing code depends on auto-flush before migrating to Spring transation manager. I wish there is a way to tell org.springframework.orm.hibernate3.HibernateTransactionManager to flush after commit.

Sam Lai