title: Spring + TestNG not transactionally rollback
body: <p>I'm using TestNG 6.9.9 to build-up a regression test environment. But encounter a problem which I have never met when using JUnit. 
  In my mind, when finish each test cases, the change of each data would be automatically rollback by default if the test methods run in the same transaction context as what they call. But seems that it's not the truth, and I cannot find out if any mistake in my code. Please help me out. 
  properties in pom.xml which indicates the frameworks' version</p>
  
  <pre><code>&lt;properties&gt;
      &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
      &lt;springframework.version&gt;4.2.4.RELEASE&lt;/springframework.version&gt;
      &lt;hibernate.version&gt;4.3.11.Final&lt;/hibernate.version&gt;
  &lt;testng.version&gt;6.9.9&lt;/testng.version&gt;
  &lt;/properties&gt;
  </code></pre>
  
  <p>Obviously, they are all up-to-date.</p>
  
  <p>My test class:</p>
  
  <pre><code>package com.noahwm.hkapp.api;
  
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.test.annotation.Rollback;
  import org.springframework.test.context.ContextConfiguration;
  import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
  import org.springframework.test.context.transaction.TransactionConfiguration;
  import org.springframework.transaction.annotation.Propagation;
  import org.springframework.transaction.annotation.Transactional;
  import org.testng.Assert;
  import org.testng.annotations.Test;
  
  import com.noahwm.hkapp.api.db.dao.AppUserDao;
  import com.noahwm.hkapp.api.db.model.AppUser;
  import com.noahwm.hkapp.api.service.AppUserService;
  
  @ContextConfiguration(locations = { "classpath:applicationContext-test.xml" })
  public class AppUserServiceTestNGTest extends AbstractTestNGSpringContextTests {
  
    @Autowired
    private AppUserService appUserService;
  
    @Test
    @Rollback
    @Transactional
    public void testApp() {
      AppUser appUser = new AppUser();
      appUser.setAge(10);
      appUser.setGender("F");
      appUser.setMobilePhone("13219201034");
      appUser.setName("HKAPP Test");
      appUserService.createUser(appUser);
      String appUserId = appUser.getId();
      Assert.assertNotNull(appUserId);
    }
  }
  </code></pre>
  
  <p>Created a entity instance, than call createUser() to save it to DB. According what I have done in JUnit, the data will automatically rollback even if I didn't put the @Rollback annotation in the front of the test method. </p>
  
  <p>The structure of AppUser is:</p>
  
  <pre><code>package com.noahwm.hkapp.api.db.model;
  
  import javax.persistence.Column;
  import javax.persistence.Entity;
  
  @Entity(name = "APP_USERS")
  public class AppUser extends BaseDataModel {
  
    @Column(name = "NAME")
    private String name;
  
    @Column(name = "GENDER")
    private String gender;
  
    @Column(name = "AGE")
    private Integer age;
  
    @Column(name = "MOBILE_PHONE")
    private String mobilePhone;
  
    public String getId() {
      return id;
    }
  
    public void setId(String id) {
      this.id = id;
    }
  
    public String getName() {
      return name;
    }
  
    public void setName(String name) {
      this.name = name;
    }
  
    public String getGender() {
      return gender;
    }
  
    public void setGender(String gender) {
      this.gender = gender;
    }
  
    public Integer getAge() {
      return age;
    }
  
    public void setAge(Integer age) {
      this.age = age;
    }
  
    public String getMobilePhone() {
      return mobilePhone;
    }
  
    public void setMobilePhone(String mobilePhone) {
      this.mobilePhone = mobilePhone;
    }
  
  }
  </code></pre>
  
  <p>BaseDataModel.java</p>
  
  <pre><code>package com.noahwm.hkapp.api.db.model;
  
  import javax.persistence.Column;
  import javax.persistence.GeneratedValue;
  import javax.persistence.Id;
  import javax.persistence.MappedSuperclass;
  import javax.persistence.Version;
  
  import org.hibernate.annotations.GenericGenerator;
  
  @MappedSuperclass
  public class BaseDataModel {
  
    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
    @Column(name = "ID", unique = true, length = 36, nullable = false)
    protected String id;
  
    @Version
    @Column(name = "version")
    protected Integer version;
  
    public String getId() {
      return id;
    }
  
    public void setId(String id) {
      this.id = id;
    }
  
    public Integer getVersion() {
      return version;
    }
  }
  </code></pre>
  
  <p>ApplicationContext-test.xml</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  &lt;beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
      xmlns:aop="http://www.springframework.org/schema/aop" xmlns:jee="http://www.springframework.org/schema/jee"
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:util="http://www.springframework.org/schema/util"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
              http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
              http://www.springframework.org/schema/context
              http://www.springframework.org/schema/context/spring-context-4.0.xsd
              http://www.springframework.org/schema/jee
              http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
              http://www.springframework.org/schema/tx
              http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
              http://www.springframework.org/schema/aop
              http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
              http://www.springframework.org/schema/util
              http://www.springframework.org/schema/util/spring-util-4.0.xsd"&gt;
  
      &lt;bean
          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"&gt;
          &lt;property name="location"&gt;
              &lt;value&gt;classpath:jdbc.test.properties&lt;/value&gt;
          &lt;/property&gt;
      &lt;/bean&gt;
  
      &lt;context:annotation-config /&gt;
      &lt;context:component-scan base-package="com.noahwm.hkapp.api" /&gt;
  
      &lt;aop:aspectj-autoproxy /&gt;
  
      &lt;bean id="lobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler"
          lazy-init="true" /&gt;
  
      &lt;bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource"
          destroy-method="close"&gt;
          &lt;property name="driverClass" value="${jdbc.driver}" /&gt;
          &lt;property name="jdbcUrl" value="${jdbc.url}" /&gt;
          &lt;property name="username" value="${jdbc.username}" /&gt;
          &lt;property name="password" value="${jdbc.password}" /&gt;
          &lt;property name="maxConnectionsPerPartition" value="${jdbc.maxConnectionsPerPartition}" /&gt;
          &lt;property name="minConnectionsPerPartition" value="${jdbc.minConnectionsPerPartition}" /&gt;
          &lt;property name="partitionCount" value="${jdbc.partitionCount}" /&gt;
          &lt;property name="acquireIncrement" value="${jdbc.acquireIncrement}" /&gt;
      &lt;/bean&gt;
  
      &lt;bean id="sessionFactory"
          class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"&gt;
          &lt;property name="dataSource" ref="dataSource" /&gt;
          &lt;property name="packagesToScan"&gt;
              &lt;list&gt;
                  &lt;value&gt;com.noahwm.hkapp.api.db.model&lt;/value&gt;
              &lt;/list&gt;
          &lt;/property&gt;
          &lt;property name="hibernateProperties"&gt;
              &lt;props&gt;
                  &lt;prop key="hibernate.dialect"&gt;${hibernate.dialect}&lt;/prop&gt;
                  &lt;prop key="hibernate.show_sql"&gt;${hibernate.show_sql}&lt;/prop&gt;
                  &lt;prop key="hibernate.jdbc.batch_size"&gt;10&lt;/prop&gt;
                  &lt;prop key="hibernate.jdbc.fetch_size"&gt;30&lt;/prop&gt;
                  &lt;prop key="hibernate.default_batch_fetch_size"&gt;10&lt;/prop&gt;
              &lt;/props&gt;
          &lt;/property&gt;
      &lt;/bean&gt;
  
      &lt;tx:annotation-driven transaction-manager="txManager"
          proxy-target-class="true" /&gt;
      &lt;bean id="txManager"
          class="org.springframework.orm.hibernate4.HibernateTransactionManager"&gt;
          &lt;property name="sessionFactory" ref="sessionFactory" /&gt;
      &lt;/bean&gt;
  &lt;/beans&gt;
  </code></pre>
  
  <p>The transaction-manager is named “txManager”.
  AppUserService.java</p>
  
  <pre><code>package com.noahwm.hkapp.api.service;
  
  import java.util.List;
  import java.util.Map;
  
  import com.noahwm.hkapp.api.db.dao.AppUserDao;
  import com.noahwm.hkapp.api.db.model.AppUser;
  
  public interface AppUserService {
    void createUser(AppUser user);
  
  }
  </code></pre>
  
  <p>AppUserServiceImpl.java</p>
  
  <pre><code>package com.noahwm.hkapp.api.service.impl;
  
  import java.util.List;
  import java.util.Map;
  
  import org.hibernate.criterion.Order;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.stereotype.Service;
  import org.springframework.transaction.annotation.Propagation;
  import org.springframework.transaction.annotation.Transactional;
  import org.springframework.util.StringUtils;
  
  import com.noahwm.hkapp.api.db.dao.AppUserDao;
  import com.noahwm.hkapp.api.db.model.AppUser;
  import com.noahwm.hkapp.api.service.AppUserService;
  import com.noahwm.hkapp.api.service.EntityService;
  import com.noahwm.hkapp.utils.SimpleSearchCriteria;
  
  @Service("AppUserService")
  @Transactional(propagation=Propagation.REQUIRED)
  public class AppUserServiceImpl extends EntityService implements AppUserService {
  
    private static final Logger logger = LoggerFactory.getLogger(AppUserServiceImpl.class);
  
    @Autowired
    private AppUserDao dao;
    @Override
    public void createUser(AppUser user) {
      logger.debug("Creating user with name {}", user.getName());
      dao.save(user);
    }
  }
  </code></pre>
  
  <p>AppUserDao.java</p>
  
  <pre><code>package com.noahwm.hkapp.api.db.dao;
  
  import org.springframework.stereotype.Repository;
  import org.springframework.transaction.annotation.Propagation;
  import org.springframework.transaction.annotation.Transactional;
  
  import com.noahwm.hkapp.api.db.model.AppUser;
  
  @Repository
  @Transactional(propagation=Propagation.REQUIRED)
  public class AppUserDao extends BaseDao&lt;AppUser&gt; {
    public void testsRollBack(AppUser appUser) throws Exception{
      save(appUser);
    }
  }
  </code></pre>
  
  <p>BaseDao.java</p>
  
  <pre><code>package com.noahwm.hkapp.api.db.dao;
  
  import java.io.Serializable;
  import java.lang.reflect.ParameterizedType;
  import java.lang.reflect.Type;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  
  import javax.annotation.Resource;
  
  import org.hibernate.Criteria;
  import org.hibernate.Query;
  import org.hibernate.Session;
  import org.hibernate.SessionFactory;
  import org.hibernate.criterion.Criterion;
  import org.hibernate.criterion.Order;
  import org.hibernate.criterion.Restrictions;
  import org.springframework.transaction.annotation.Transactional;
  
  import com.noahwm.hkapp.api.db.model.BaseDataModel;
  import com.noahwm.hkapp.utils.SimpleSearchCriteria;
  
  class BaseDao&lt;T extends BaseDataModel&gt; {
  
    private Class&lt;T&gt; domainClass;
  
    @Resource(name = "sessionFactory")
    protected SessionFactory sessionFactory;
  
    protected SessionFactory getSessionFactory() {
      return sessionFactory;
    }
  
    public void setSessionFactory(SessionFactory sessionFactory) {
      this.sessionFactory = sessionFactory;
    }
  
    @SuppressWarnings("unchecked")
    public Class&lt;T&gt; getDomainClass() {
      if (domainClass == null) {
        Type type = this.getClass().getGenericSuperclass();
        ParameterizedType parameterizedType = (ParameterizedType) type;
        domainClass = (Class&lt;T&gt;) parameterizedType.getActualTypeArguments()[0];
      }
      return domainClass;
    }
  
    protected Session getCurrentSession() {
      return getSessionFactory().getCurrentSession();
    }
  
    public Criteria createCriteria() {
      return getCurrentSession().createCriteria(getDomainClass());
    }
  
    public void save(T o) {
      getCurrentSession().save(o);
    }
  
    public void update(T o) {
      getCurrentSession().update(o);
    }
  
    public void saveOrUpdate(T o) {
      getCurrentSession().saveOrUpdate(o);
    }
  
    public Object merge(Object o) {
      return getCurrentSession().merge(o);
    }
  
    public void delete(T o) {
      getCurrentSession().delete(o);
    }
  
    public T deleteById(Serializable id) {
      T o = findById(id);
      getCurrentSession().delete(o);
      return o;
    }
  
    public void evict(Object o) {
      getCurrentSession().evict(o);
    }
  
    @SuppressWarnings("unchecked")
    public List&lt;T&gt; findAll() {
      return createCriteria().list();
    }
  
    @SuppressWarnings("unchecked")
    public T findById(Serializable o) {
      List&lt;T&gt; results = createCriteria().add(Restrictions.idEq(o)).list();
      if (results.isEmpty()) {
        return null;
      } else {
        return results.get(0);
      }
    }
  
    @SuppressWarnings("unchecked")
    public T load(Serializable o) {
      return (T) getCurrentSession().load(getDomainClass(), o);
    }
  
    @SuppressWarnings("unchecked")
    public List&lt;T&gt; findBy(Map&lt;String, Object&gt; propertyNameValues) {
      return createCriteria().add(Restrictions.allEq(propertyNameValues)).list();
    }
  
    @SuppressWarnings("unchecked")
    public List&lt;T&gt; findBy(String propertyName, Object value) {
      return createCriteria().add(Restrictions.eq(propertyName, value)).list();
    }
  
    @SuppressWarnings("unchecked")
    public List&lt;T&gt; find(SimpleSearchCriteria simpleSearchCriteria) {
      Criteria criteria = createCriteria();
      Iterator&lt;Criterion&gt; criterions = simpleSearchCriteria.iterator();
      while(criterions.hasNext()) {
        criteria.add(criterions.next());
      }
      for(Order o : simpleSearchCriteria.getOrders()) {
        criteria.addOrder(o);
      }
      if(simpleSearchCriteria.getFetchSize() != null) {
        criteria.setFetchSize(simpleSearchCriteria.getFetchSize());
      }
      if(simpleSearchCriteria.getFirstResult() != null) {
        criteria.setFirstResult(simpleSearchCriteria.getFirstResult());
      }
      if(simpleSearchCriteria.getMaxResults() != null) {
        criteria.setMaxResults(simpleSearchCriteria.getMaxResults());
      }
      if(simpleSearchCriteria.getTimeout() != null) {
        criteria.setTimeout(simpleSearchCriteria.getTimeout());
      }
      return criteria.list();
    }
  
    public T findFirst(SimpleSearchCriteria simpleSearchCriteria) {
      simpleSearchCriteria.setMaxResults(1);
      List&lt;T&gt; results = find(simpleSearchCriteria);
      if(results.isEmpty()) {
        return null;
      } else {
        return results.get(0);
      }
    }
  
    public Object callNamedQuery(String sql, Map&lt;String, Object&gt; parameter) {
      Query query = getCurrentSession().createSQLQuery(sql);
      for(Entry&lt;String, Object&gt; entry:parameter.entrySet()){
        query.setParameter(entry.getKey(), entry.getValue());
      }
      return query.executeUpdate();
    }
  }
  </code></pre>
  
  <p>Here is the DB init script:</p>
  
  <pre><code>CREATE TABLE "APP_USERS" (
  "ID" VARCHAR(36),
  "NAME" VARCHAR(50),
  "GENDER" VARCHAR(1),
  "AGE" NUMERIC(3,0),
  "MOBILE_PHONE" VARCHAR(20),
      VERSION INTEGER)
  </code></pre>
  
  <p>As you see, it's a very common Spring TestNG integration test. But the auto rollback function cannot be used which bordered me a lot.</p>