JDBC的getGeneratedKeys()方法是否总是按照插入元素的相同顺序返回结果?

4

我使用JDBC的executeBatch()来插入多行数据,我想要获取插入行的id以供另外一次插入使用。我使用以下代码实现这个目的:

insertInternalStatement = dbConncetion.prepareStatement(INSERT_RECORD, generatedColumns);

for (Foo foo: foosHashSet) {

    insertInternalStatement.setInt(1, foo.getMe());
    insertInternalStatement.setInt(1, foo.getMe2());
    // ..
    insertInternalStatement.addBatch();
}
insertInternalStatement.executeBatch();

// now get inserted ids
try (ResultSet generatedKeys = insertInternalStatement.getGeneratedKeys()) {

     Iterator<Foo> fooIterator= foosHashSet.iterator();
     while (generatedKeys.next() && fooIterator.hasNext()) {

         fooIterator.next().setId(generatedKeys.getLong(1));

     }
 }

它能正常工作并返回id,我的问题是:
  1. 如果我遍历getGeneratedKeys()foosHashSet,那么id会以相同的顺序返回,使得每个从数据库返回的id都属于相应的Foo实例吗?

  2. 当我使用多线程时,上述代码在多个线程中同时运行会怎样?

  3. 还有其他解决方案吗?我有两个表foo1和foo2,我想先插入foo1记录,然后将它们的主键作为foo2外键使用。

4个回答

3
鉴于JDBC规范中并未定义批量执行的getGeneratedKeys支持,行为将取决于所使用的驱动程序。我期望支持批量执行生成键的任何驱动程序都会按照它们添加到批处理中的顺序返回ID。
然而,使用Set可能存在问题。大多数Set的迭代顺序未定义,并且在迭代之间可能会更改(通常仅在修改后,但理论上您不能假设任何关于顺序的东西)。您需要使用具有保证顺序的内容,例如List或LinkedHashSet。
在这里应用多线程可能是个坏主意:您应该一次仅从单个线程使用JDBC连接。考虑到多线程会要求正确的锁定或要求您将工作负载拆分以便使用单独的连接。无法确定是否会提高或降低性能。

0
  1. 只要fooHashSet没有被更改,迭代就是相同的。

    可以考虑使用LinkedHashSet,它按插入顺序返回项。特别是当没有删除或覆盖任何内容时,这将非常好。

  2. 并发访问可能会有问题。

    仅添加新项目使用LinkedHashSet而不进行删除。并且另外将其包装在Collections.synchronizedMap中。对于集合的修改,需要一个Semaphore或类似的东西,因为同步如此大的代码块是不可行的。

  3. 甚至更好的解决方案可能是制作本地副本:

    List<Me> list = fooHashSet.stream().map(Foo::Me)
        .collect(Collectors.toList());
    

    然而,这仍然是一个有些不令人满意的解决方案:批量进行多个插入,然后每次插入进行几个其他更新/插入。

    转换到JPA而不是JDBC会在一定程度上缓解情况。

    然而,在一些经验之后,我会提出一个问题,即在那个点上是否仍然正确使用数据库(锤子)?如果它是一个图形、分层数据结构,那么将整个数据结构存储为XML并使用JAXB在单个数据库表中可能是最好的解决方案。更快。更容易开发。可验证的数据。 使用数据库作为主要数据,使用XML作为编辑/处理文档。


你如何推断出mohsenJsh正在插入一个更适合用XML表示的“图形”?他/她所做的只是批量插入多行。 - user330315
@a_horse_with_no_name,我这周刚好遇到了这样的情况。如果操作大量数据,有两种情况:要么确实是大规模处理,要么是将层次结构的数据模型映射到数据库中。如果生成的键在“数据模型”内部使用,则我们处于XML准备就绪状态。否则不是。 - Joop Eggen
我认为更令人担忧的问题是在数据上使用越来越多的层会导致性能受损,而不是额外几行代码:) - Buffalo

0
  1. 您应该能够轻松地遍历多个生成的键,它们将按照插入的正确顺序返回。
  2. 我认为在这方面添加线程不应该有任何问题。唯一确定的事情是,如果没有一些代码复杂性,您将无法控制两个表中插入id的顺序。
  3. 您可以将所有最初插入的id存储在集合中,在所有线程/迭代完成后,将它们插入第二个表中。

0

是的,根据批处理执行的定义来看

createFcCouponStatement.executeBatch()

将一批命令提交到数据库进行执行,如果所有命令都成功执行,则返回一个更新计数的数组。返回的数组中的int元素按照批处理中的命令顺序排序,这些命令按照它们添加到批处理中的顺序排序。方法executeBatch返回的数组中的元素可能是以下之一: 大于或等于零的数字--表示命令已成功处理并且是一个更新计数,给出了受命令执行影响的数据库行数

SUCCESS_NO_INFO的值--表示命令已成功处理,但受影响的行数未知 如果批量更新中的某个命令未能正确执行,则此方法会抛出BatchUpdateException异常,JDBC驱动程序可能会继续处理批处理中的其余命令,也可能不会继续处理。然而,驱动程序的行为必须与特定的DBMS一致,要么始终继续处理命令,要么从不继续处理命令。如果驱动程序在失败后继续处理,则方法BatchUpdateException.getUpdateCounts返回的数组将包含与批处理中的命令数量相同的元素,并且至少有一个元素将是以下之一:

EXECUTE_FAILED的值--表示命令未能成功执行,仅在驱动程序在命令失败后继续处理命令时发生

Java 2 SDK标准版1.3中已修改可能的实现和返回值,以适应在抛出BatchUpdateException对象后继续处理批量更新命令的选项。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接