Spring + TestNG没有事务性回滚 [英] Spring + TestNG not transactionally rollback

查看:141
本文介绍了Spring + TestNG没有事务性回滚的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

显然,它们都是最新的。

Obviously, they are all up-to-date.

我的测试类:

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注释放在测试方法的前面,数据也会自动回滚。

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.

AppUser的结构是:

The structure of AppUser is:

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

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

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

The transaction-manager is named "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

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

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

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脚本:

Here is the DB init script:

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集成测试。但是不能使用自动回滚功能,这让我很喜欢。

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.

推荐答案

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

Thanks to M. Deinum. To solve my problem, I just replace the class AbstractTestNGSpringContextTests with AbstractTransactionalTestNGSpringContextTests.

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

这篇关于Spring + TestNG没有事务性回滚的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆