Spring + TestNG没有事务性回滚

时间:2015-12-22 15:59:30

标签: java spring maven transactions testng

我正在使用TestNG 6.9.9构建回归测试环境。但遇到一个我在使用JUnit时从未遇到过的问题。 在我看来,当完成每个测试用例时,如果测试方法在与他们调用的相同的事务上下文中运行,则每个数据的更改将默认自动回滚。但似乎这不是事实,我不知道我的代码中是否有任何错误。请帮帮我。 pom.xml中的属性,表示框架的版本

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <springframework.version>4.2.4.RELEASE</springframework.version>
    <hibernate.version>4.3.11.Final</hibernate.version>
<testng.version>6.9.9</testng.version>
</properties>

显然,它们都是最新的。

我的测试班:

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);
  }
}

创建一个实体实例,而不是调用createUser()将其保存到DB。根据我在JUnit中所做的,即使我没有将@Rollback注释放在测试方法的前面,数据也会自动回滚。

AppUser的结构是:

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;
  }

}

BaseDataModel.java

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;
  }
}

的ApplicationContext-的test.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" 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">

    <bean
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location">
            <value>classpath:jdbc.test.properties</value>
        </property>
    </bean>

    <context:annotation-config />
    <context:component-scan base-package="com.noahwm.hkapp.api" />

    <aop:aspectj-autoproxy />

    <bean id="lobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler"
        lazy-init="true" />

    <bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource"
        destroy-method="close">
        <property name="driverClass" value="${jdbc.driver}" />
        <property name="jdbcUrl" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="maxConnectionsPerPartition" value="${jdbc.maxConnectionsPerPartition}" />
        <property name="minConnectionsPerPartition" value="${jdbc.minConnectionsPerPartition}" />
        <property name="partitionCount" value="${jdbc.partitionCount}" />
        <property name="acquireIncrement" value="${jdbc.acquireIncrement}" />
    </bean>

    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan">
            <list>
                <value>com.noahwm.hkapp.api.db.model</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                <prop key="hibernate.jdbc.batch_size">10</prop>
                <prop key="hibernate.jdbc.fetch_size">30</prop>
                <prop key="hibernate.default_batch_fetch_size">10</prop>
            </props>
        </property>
    </bean>

    <tx:annotation-driven transaction-manager="txManager"
        proxy-target-class="true" />
    <bean id="txManager"
        class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
</beans>

事务管理器名为“txManager”。 AppUserService.java

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);

}

AppUserServiceImpl.java

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);
  }
}

AppUserDao.java

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<AppUser> {
  public void testsRollBack(AppUser appUser) throws Exception{
    save(appUser);
  }
}

BaseDao.java

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<T extends BaseDataModel> {

  private Class<T> domainClass;

  @Resource(name = "sessionFactory")
  protected SessionFactory sessionFactory;

  protected SessionFactory getSessionFactory() {
    return sessionFactory;
  }

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

  @SuppressWarnings("unchecked")
  public Class<T> getDomainClass() {
    if (domainClass == null) {
      Type type = this.getClass().getGenericSuperclass();
      ParameterizedType parameterizedType = (ParameterizedType) type;
      domainClass = (Class<T>) 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<T> findAll() {
    return createCriteria().list();
  }

  @SuppressWarnings("unchecked")
  public T findById(Serializable o) {
    List<T> 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<T> findBy(Map<String, Object> propertyNameValues) {
    return createCriteria().add(Restrictions.allEq(propertyNameValues)).list();
  }

  @SuppressWarnings("unchecked")
  public List<T> findBy(String propertyName, Object value) {
    return createCriteria().add(Restrictions.eq(propertyName, value)).list();
  }

  @SuppressWarnings("unchecked")
  public List<T> find(SimpleSearchCriteria simpleSearchCriteria) {
    Criteria criteria = createCriteria();
    Iterator<Criterion> 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<T> results = find(simpleSearchCriteria);
    if(results.isEmpty()) {
      return null;
    } else {
      return results.get(0);
    }
  }

  public Object callNamedQuery(String sql, Map<String, Object> parameter) {
    Query query = getCurrentSession().createSQLQuery(sql);
    for(Entry<String, Object> entry:parameter.entrySet()){
      query.setParameter(entry.getKey(), entry.getValue());
    }
    return query.executeUpdate();
  }
}

这是DB init脚本:

CREATE TABLE "APP_USERS" (
"ID" VARCHAR(36),
"NAME" VARCHAR(50),
"GENDER" VARCHAR(1),
"AGE" NUMERIC(3,0),
"MOBILE_PHONE" VARCHAR(20),
    VERSION INTEGER)

如您所见,这是一个非常常见的Spring TestNG集成测试。但是不能使用自动回滚功能,这与我有很多接触。

1 个答案:

答案 0 :(得分:5)

感谢M. Deinum。 为了解决我的问题,我只需用AbstractTransactionalTestNGSpringContextTests替换AbstractTestNGSpringContextTests类。

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.AbstractTransactionalTestNGSpringContextTests;
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 AbstractTransactionalTestNGSpringContextTests {

  @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);
  }
}