Тесты с автоматически генерируемыми идентификаторами первичного ключа DBunit и Oracle 10g не синхронизированы (JPA / Hibernate) - PullRequest
2 голосов
/ 30 августа 2011

Я тестирую приложение JPA / Hibernate с DBunit и Oracle 10g.Когда я начинаю свой тест, я загружаю в базу данных 25 строк с идентификатором.

Это xml, где у меня есть данные, которые я вставляю с помощью DBUnit

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

Это мой класс сущности с аннотациями JPA (не специфично для спящего режима)

@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 ofновый объект равен 1, и уже существует объект с таким идентификатором.Итак, я не знаю, кто является ответственным DBunit?Оракул?Почему не синхронизируются идентификаторы базы данных Oracle и идентификатор, который JPA / hibernate дает моей сущности в моем тестовом коде?

Спасибо за ваше время

Ответы [ 3 ]

2 голосов
/ 30 августа 2011

Я думаю, что тип генерации AUTO в Oracle на самом деле является генератором последовательности.Если вы не укажете, какую последовательность он должен использовать, Hibernate, вероятно, создаст для вас и использует ее, и его начальное значение по умолчанию равно 1.

Использование AUTO полезно для быстрого создания прототипов.Для реального приложения используйте конкретный тип генерации (SEQUENCE для Oracle) и создайте свои последовательности самостоятельно с соответствующим начальным значением, чтобы избежать дублирования ключей.

1 голос
/ 30 августа 2011

Вы можете использовать идентификаторы <0 в ваших тестовых наборах данных. Ваши последовательности не только не будут конфликтовать с записями тестов, но также вы легко сможете различить записи, которые были вставлены тестами. </p>

1 голос
/ 30 августа 2011

В стратегии секвенирования AUTO обычно по умолчанию используется стратегия секвенирования TABLE, но в случае Oracle в стратегии секвенирования используется последовательность Oracle с именем hibernate_sequence (по умолчанию, если в стратегии не указано имя последовательности). Начальное значение последовательности оказывается равным 1, что конфликтует с существующим объектом, который загружается с использованием DbUnit, что приводит к возникновению исключения ConstraintViolationException.

Для модульных тестов вы можете выполнить одно из следующих действий:

  • Введите команду ALTER SEQUENCE... для установки следующего значения последовательности после загрузки данных в базу данных. Это гарантирует, что поставщик JPA будет использовать значение последовательности, которое не конфликтует с существующими идентификаторами сущностей, заполненных из вашего текущего XML-файла, DbUnit.
  • Укажите имя последовательности в XML-файле, загруженном как IDataSet, в конечном итоге используемое DbUnit. Фактические значения последовательности должны быть заменены на IDataSet с помощью SELECT <sequence_name>.nextval FROM DUAL. Следующий раздел воспроизводится как есть и зачисляется на этот сайт :

Я провожу пару часов, читая dbUnit docs / facs / wikis и Исходный код пытается выяснить, как использовать последовательности Oracle, но если я что-то упустил, я думаю, что это невозможно с текущая реализация.

Так что я потратил дополнительное время, чтобы найти обходной путь для вставки Oracle последовательность генерирует идентификаторы в наборы данных dbUnit, очень похоже на то, что ReplacementDataSet делает. Я подкласса DatabaseTestCase уже ранее в абстрактном классе (AbstractDatabaseTestCase), чтобы иметь возможность использовать обычное соединение в случае вставки моих тестовых наборов в тестовый набор. Но я добавил следующий код только сейчас. Это выглядит в первом ряду каждая таблица в наборе данных, чтобы определить, какие столбцы нуждаются в последовательности замена. Замена выполняется для значения выражения "$ {…}".

Этот код "быстрый и грязный" и, безусловно, нуждается в некоторой очистке и настройка.

В любом случае, это только первая попытка. Я буду публиковать дальнейшие улучшения, как я иди, если это может кому-нибудь помочь.

Стефан Ванденбюссе

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.

...