我正在使用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在我的测试代码中为我的实体提供的标识符?
感谢您的时间
答案 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不冲突的序列值。IDataSet
。必须使用IDataSet
在SELECT <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。您的序列不仅不会与测试记录冲突,而且您还可以轻松区分测试插入的记录。