使用DBunit和Oracle 10g自动生成的主键标识符进行的测试未同步(JPA / Hibernate)

时间:2011-08-30 07:58:15

标签: oracle hibernate jpa junit dbunit

我正在使用DBunit和Oracle 10g测试JPA / Hibernate应用程序。当我开始测试时,我用数据库加载到数据库25行。

这是我拥有数据的xml,我使用DBUnit

插入
  <entity entityId="1" .... 
  <entity entityId="2" ....
  <entity entityId="3" ....
  <entity entityId="4" .... 

这是我的JPA注释实体类(不是特定于hibernate的)

@Entity
@Table(name = "entity")
public class Entity{
@Id
@GeneratedValue(strategy=GenerationType.Auto)
private Integer entityId;
...}

这些是Oracle10g数据库连接的参数值

jdbc.driverClassName=oracle.jdbc.OracleDriver
jdbc.url=jdbc:oracle:thin:@192.168.208.131:1521:database
jdbc.username=hr
jdbc.password=root
hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
dbunit.dataTypeFactoryName=org.dbunit.ext.oracle.Oracle10DataTypeFactory

在Oracle中插入此数据后,我运行一个测试,我在其中创建Entity entity = new Entity()(我不必手动设置标识符,因为它是自动生成的)

@Test
public void testInsert(){

   Entity entity = new Entity();

   //other stuff

   entityTransaction.begin();
   database.insertEntity(entity);//DAO call
   entityTransaction.commit();

}

当测试提交事务时,我得到以下错误

javax.persistence.RollbackException: Error while commiting the transaction
    at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:71)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    ...
Caused by: org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:94)
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
    at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:266)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:167)
    at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027)
    at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:365)
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137)
    at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:54)
    ... 26 more
Caused by: java.sql.BatchUpdateException: ORA-00001: restricción única (HR.SYS_C0058306) violada

    at oracle.jdbc.driver.DatabaseError.throwBatchUpdateException(DatabaseError.java:345)
    at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:10844)
    at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
    at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
    ... 34 more 

我调试了它,问题是新对象的entityId是1,并且已经存在具有该Id的实体。所以,我不知道谁是负责任的DBunit?甲骨文?为什么不同步Oracle数据库的标识符和JPA / hibernate在我的测试代码中为我的实体提供的标识符?

感谢您的时间

3 个答案:

答案 0 :(得分:2)

我认为Oracle中的AUTO生成类型实际上是一个序列生成器。如果你没有指定它必须使用哪个序列,Hibernate可能会为你创建一个并使用它,它的默认起始值​​是1.

使用AUTO对快速原型制作很有用。对于实际应用程序,请使用具体的生成类型(SEQUENCE,for Oracle),并使用适当的起始值自行创建序列以避免重复键。

答案 1 :(得分:1)

AUTO排序策略通常默认为TABLE排序策略,但对于Oracle,排序策略使用名为hibernate_sequence的Oracle序列(这是默认值,除非您在策略中指定序列名称)。序列的起始值恰好为1,这与使用DbUnit加载的现有实体冲突,因此导致抛出ConstraintViolationException异常。

出于单元测试的目的,您可以执行以下任一操作:

  • 在将数据加载到数据库之后,发出ALTER SEQUENCE...命令来设置序列的下一个值。这将确保JPA提供程序将使用与DbUnit中当前XML文件中填充的实体的现有ID不冲突的序列值。
  • 指定加载的XML文件中的序列名称,作为DbUnit最终使用的IDataSet。必须使用IDataSetSELECT <sequence_name>.nextval FROM DUAL中替换实际的序列值。以下部分按原样复制,并记入this site
  

我花了几个小时阅读dbUnit docs / facs / wikis和   源代码试图弄清楚如何使用Oracle序列,但是   除非我忽略了某些东西,否则我认为这是不可能的   目前的实施。

     

所以我花了一些额外的时间来找到一个解决方法来插入Oracle   序列生成的ID到dbUnit的数据集中,非常类似于什么   ReplacementDataSet。我之前已经将DatabaseTestCase子类化了   在抽象类(AbstractDatabaseTestCase)中能够使用   在测试套件中插入我的测试用例时的常见连接。   但是我刚刚添加了以下代码。它查找了第一行   数据集中的每个表以确定哪些列需要序列   替换。替换是在“$ {...}”表达式值上完成的。

     

这段代码“快而脏”,肯定需要一些清理工作   调谐。

     

无论如何,这只是第一次尝试。我会像我一样发布进一步的改进   去吧,如果这对任何人都有帮助。

     

Stephane Vandenbussche

private void replaceSequence(IDataSet ds) throws Exception {
    ITableIterator iter = ds.iterator();
    // iterate all tables
    while (iter.next()) {
      ITable table = iter.getTable();
      Column[] cols = table.getTableMetaData().getColumns();
      ArrayList al = new ArrayList(cols.length);
      // filter columns containing expression "${...}"
      for (int i = 0; i < cols.length; i++) {
        Object o = table.getValue(0, cols[i].getColumnName());
        if (o != null) {
          String val = o.toString();
          if ((val.indexOf("${") == 0) && (val.indexOf("}") == val.length() - 1)) {
            // associate column name and sequence name
            al.add(new String[]{cols[i].getColumnName(), val.substring(2, val.length()-1)});
          }
        }
      }
      cols = null;
      int maxi = table.getRowCount();
      int maxj = al.size();
      if ((maxi > 0) && (maxj > 0)) {
        // replace each value "${xxxxx}" by the next sequence value
        // for each row
        for (int i = 0; i < maxi; i++) {
          // for each selected column
          for (int j = 0; j < maxj; j++) {
            String[] field = (String[])al.get(j);
            Integer nextVal = getSequenceNextVal(field[1]);
            ((DefaultTable) table).setValue(i, field[0], nextVal);
          }
        }
      }
    }
  }

  private Integer getSequenceNextVal(String sequenceName) throws SQLException, Exception {
      Statement st = this.getConnection().getConnection().createStatement();
      ResultSet rs = st.executeQuery("SELECT " + sequenceName + ".nextval FROM dual");
      rs.next();
      st = null;
      return new Integer(rs.getInt(1));
  }
     

我的AbstractDatabaseTestCase类有一个布尔标志   “useOracleSequence”告诉getDataSet回调方法调用   replaceSequence。

     

我现在可以按如下方式编写我的xml数据集:

  <dataset>
   <MYTABLE FOO="Hello" ID="${MYTABLE_SEQ}"/>
   <MYTABLE FOO="World" ID="${MYTABLE_SEQ}"/>
   <OTHERTABLE BAR="Hello" ID="${OTHERTABLE_SEQ}"/>
   <OTHERTABLE BAR="World" ID="${OTHERTABLE_SEQ}"/>
  </dataset>
     

其中MYTABLE_SEQ是要使用的Oracle序列的名称。

答案 2 :(得分:1)

你可以使用ids&lt;测试数据集中为0。您的序列不仅不会与测试记录冲突,而且您还可以轻松区分测试插入的记录。