如何在JDBC中开始一个事务?

82

Connection.setTransactionIsolation(int)方法提醒:

注意:如果在事务中调用此方法,则结果是由实现定义的。

这引出了一个问题:在JDBC中如何开始一个事务? 结束一个事务很清楚,但是开启一个事务却不太明确。

如果一个Connection在一个事务内启动,那么我们应该如何在事务外调用Connection.setTransactionIsolation(int)以避免特定于实现的行为呢?


4
根据 JDBC 4.0 规范:“何时启动新事务,是由 JDBC 驱动程序或底层数据源隐式决定的。尽管某些数据源实现了显式的“开始事务”语句,但没有 JDBC API 可以执行此操作。通常,在当前 SQL 语句需要事务且不存在已启动的事务时,将启动新事务。SQL:2003 同样指定了一个给定的 SQL 语句是否需要事务。” - mhrsalehi
8个回答

80

回答自己的问题:

  • JDBC 连接在开始时启用自动提交模式,每个SQL语句隐式地分隔了一个事务。
  • 希望执行多条语句的用户必须关闭自动提交
  • 更改自动提交模式会触发当前事务的提交(如果有活动事务)。
  • Connection.setTransactionIsolation()可以在启用自动提交时随时调用。
  • 如果禁用了自动提交,则只能在事务之前或之后调用Connection.setTransactionIsolation()。在事务中间调用它会导致未定义的行为。

请参考Oracle的JDBC教程


31

JDBC会将您在连接上执行的每个查询/更新隐式地使用事务标记。您可以通过调用setAutoCommit(false)来关闭自动提交模式并调用commit()/rollback()来指示事务的结束,以自定义此行为。伪代码:

try
{
  con.setAutoCommit(false);

   //1 or more queries or updates

   con.commit();
}
catch(Exception e)
{
   con.rollback();
}
finally
{
   con.close();
}

现在,你展示的方法中有一个错误。正确的方法应该是 setTransactionIsolation(int level) 而不是用于事务标记的API。它管理着一个操作所做的更改何时/如何能够被其他并发操作所看到,这个操作是ACID(http://en.wikipedia.org/wiki/Isolation_(database_systems))中的"I"


1
在哪里可以看到 setAutoCommit(false) 表示一个事务的开始? - Gili
你的回答没有说明在什么情况下调用setTransactionIsolation()是安全的。为什么Javadoc中会写道“注意:如果在事务期间调用此方法,则结果是由实现定义的。”? - Gili
1
另一个提示:友好一点,恢复AutoCommit的先前状态:boolean previousAutoCommit = conn.getAutoCommit(); try {...} finally {conn.setAutoCommit(previousAutoCommit)} - voho
@voho 为什么要回滚到 previousAutoCommit?即使它是池化的,在 con.close() 后,它会返回到池中,下次从池中获取连接时 autoCommit 将变为 true。 - Teddy
如果不行,那就把连接池改成HicariCP。 - Teddy
上面的伪代码表示//1个或多个查询或更新 - 如果只有一个单独的语句,是否应用事务/提交/回滚? - user2125853

19

我建议你阅读这篇文章,就会明白:

因此,setAutoCommit(false)的第一次调用以及每次commit()的调用都隐式地标识了事务的开始。通过调用rollback()可以在提交之前撤销事务。

编辑:

请查看JDBC事务的官方文档。

创建连接时,默认情况下处于自动提交模式。这意味着每个单独的SQL语句都被视为一个事务,并且在执行后自动提交。(更精确地说,当SQL语句完成时(而不是执行时)将其提交。语句完成时,表示已检索到所有结果集和更新计数。但几乎所有情况下,语句会在执行后立即完成并提交。)

允许将两个或多个语句分组成一个事务的方法是禁用自动提交模式。以下代码演示了如何禁用自动提交模式,其中con是一个活动连接:

con.setAutoCommit(false);

来源:JDBC Transactions


3
我在你提供的链接页面上找不到上述引用。此外,我更希望在JDBC规范中找到引用,而不是在devx上找到一些没有来源的引用(假设它没有来源)。 - Gili

11

令人惊讶的是,如果您希望将连接保持在“setAutoCommit(true)”模式但仍然想运行事务,则可以手动运行事务:

 try (Statement statement = conn.createStatement()) {
      statement.execute("BEGIN");
      try {
        // use statement ...
        statement.execute("COMMIT");
      }
      catch (SQLException failure) {
        statement.execute("ROLLBACK");
      }
  }

不需要禁用AutoCommit吗? - Alex78191
似乎对我有效...尽管可能会让JDBC类有点困惑! :) - rogerdpack
它可能是可移植的,也可能不跨越数据库... :) - rogerdpack
1
如果您的事务中没有多个语句,那么这个解决方案是可以的,否则请使用其他方法。 - JohnnyB
1
Derby/JavaDB 举例来说,没有 BEGIN/COMMIT/ROLLBACK SQL,而是完全依赖于连接方法,这些方法通过不同于 SQL 的协议元素传递到服务器。 - hobgoblin

10

您可以使用以下方法进行事务处理:

  1. 必须创建连接对象,例如con
  2. con.setAutoCommit(false);
  3. 执行您的查询操作
  4. 如果一切正常,则执行con.commit();
  5. 否则执行con.rollback();

2
这并没有提供其他答案没有涵盖的新内容... - Laurenz Albe
我假设调用 con.commit(); 会将要执行的 SQL 包装/添加到 BEGIN TRANSACTION;COMMIT; 语句中(或者以等效的方式运行),从而省去了代码显式包含这些语句的需要。这样理解正确吗? - Agi Hammerthief
实际上,我最喜欢这个答案,因为它很好地概述了“正常流程”,并且强调了一个观点(至少对我来说),即setAutoCommit(false)在发送下一个Statement之前为驱动程序开启了一个新的事务。 - David Bullock

9

实际上,JDBC教程中的这个页面可能更适合阅读。
你需要获取连接,设置隔离级别,执行更新等操作,然后提交或回滚。


4
即使教程没有明确提到这个问题,它也包含一些令人困惑的句子,比如:“只有在事务模式下建议禁用自动提交模式。”那么什么是“事务模式”? - Gili
1
@Gili 你被咬在哪里了? - Priidu Neemre

6
也许这能回答你的问题:
在一个连接中只能有一个事务。如果自动提交打开(默认情况),每个select、update、delete将自动开始并提交(或回滚)一个事务。
如果关闭自动提交,你就可以开始一个"新"事务(意味着提交或回滚不会自动发生)。在执行一些语句后,你可以调用commit或rollback,这将结束当前事务并自动开始一个新事务。
在纯JDBC中,不能在一个JDBC连接上同时打开两个活动的事务。

0

使用单个连接进行多个事务的操作(重用、汇集或链接),可能会出现一些奇怪的问题,这些问题可能会使人们不得不忍受,因为他们通常不能确定原因。

以下几种情况需要注意:

  1. 在进行中/未提交的事务中重复使用连接
  2. 有缺陷的连接池实现
  3. 某些数据库中的更高隔离级别实现(尤其是分布式SQL和NoSQL)

第1点非常简单易懂。 第2点基本上会导致第1点和/或第3点。

第3点与一个新事务在发出第一个语句之前已经开始的系统有关。从数据库的角度来看,这样的事务可能已经在“第一个”真正语句发出之前就已经开始了。如果并发模型基于快照思想,其中一个只读取在事务开始时有效的状态/值,但没有后来发生的任何变化,则在提交时非常重要验证当前事务的完整读取集合。

由于NoSQL和某些隔离级别(如MS SQL-Server快照)通常不会正确验证读取集,因此通常无法确定预期结果。虽然这是一个始终存在的问题,但当处理从上次提交或连接被池化而不是实际使用的事务时,情况会更糟。因此通常需要确保事务在预期开始时实际开始。(如果使用仅回滚只读事务,则非常重要)。

我在处理JAVA中的JDBC时使用以下规则:

  1. 如果公司在使用纯JDBC与任何连接池机制结合时,请务必在使用之前回滚JDBC连接(清除所有内容并启动新事务)。
  2. 即使只是使用一个由会话管理的JDBC连接进行普通SQL查询,也应该使用Hibernate进行事务处理。到目前为止从未遇到过事务问题。
  3. 使用BEGIN/COMMIT/ROLLBACK作为SQL语句(如前面所提到的)。大多数实现会失败,如果您在活动事务期间发出BEGIN语句(请对您的数据库进行测试并记住测试数据库不是生产数据库,并且JDBC驱动程序和JDBC服务器端实现可能与在实际服务器上运行SQL控制台的行为不同)。
  4. 将3个JDBC连接实例封装在自己的包装器中。这样事务处理总是正确的(如果没有使用反射并且连接池没有缺陷)。

只有在响应时间很关键或者没有Hibernate可用时,才会使用3+4。 4允许使用一些更高级的性能(响应时间)改进模式,适用于特定情况。


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