【问题标题】:Java SQLite: Is it necessary to call rollback when auto commit is false and a transaction fails?Java SQLite:当自动提交为假并且事务失败时是否需要调用回滚?
【发布时间】:2022-01-16 18:49:23
【问题描述】:

在 Java 中使用 SQLite 数据库时,假设我将自动提交设置为 false。发生 SQLException 时是否有必要调用rollback() 方法?或者我可以直接忽略调用它,事务将自动回滚(我在事务期间所做的所有更改都会自动撤消)?

【问题讨论】:

    标签: java database sqlite rollback


    【解决方案1】:

    快速回答:你问的事实可能意味着你做错了。但是,如果您必须知道:是的,您需要显式回滚。

    幕后发生了什么

    在 JDBC 级别(如果您使用 JOOQ、JDBI、Hibernate 或类似的东西,那通常是构建在 JDBC 之上的库),您有一个 Connection 实例。您可以通过 DriverManager.getConnection(...) 获得此信息 - 或者连接池为您获得了它,但确实发生了一些事情。

    该连接可以在事务的中间(自动提交模式仅意味着该连接假定您打算在您关心在该连接的上下文中运行的每个 SQL 语句之后编写额外的commit(),这都是自动的commit 确实如此,但显然,如果开启,您可能处于“干净”状态,也就是说,该连接处理的最后一个命令是 COMMITROLLBACK)。

    如果它在事务的中间并且您关闭了连接,ROLLBACK 是隐含的

    连接必须做出选择,它不能保持存在,所以,它提交或回滚。该规范保证它不会仅仅为你提供乐趣,因此,它会回滚。

    然后问题归结为您的具体设置。具体来说,这很危险:

    try (Connection con = ...) {
      con.setAutoCommit(false);
      try {
        try (var s = con.createStatement()) {
          s.execute("DROP TABLE foobar");
        }
      } catch (SQLException ignore) {
        // ignoring an exception usually bad idea. But for sake of example..
      }
    
      // A second statement on the same connection...
      try (var s = con.createStatement()) {
        s.execute("DROP TABLE quux");
      }
    }
    

    就规范而言,JDBC 驱动程序可以随意抛出 SQLException,类似于“连接已中止;您必须先显式回滚,然后才能在第二条语句中再次使用它。

    但是,上面的代码很糟糕。你不能在这种代码中使用事务隔离级别 SERIALIZABLE(一旦你的用户数量超过少数,应用程序就会崩溃并在一系列重试异常中烧毁),它要么是做一些无用的事情(当你有一个连接池正在使用时,为多个事务重新使用一个连接),或者正在糟糕地解决一个问题(问题:为每个事务使用一个新连接是昂贵的)。

    1 个事务,1 个连接

    上述危险的唯一原因是因为我们在与连接对象关联的单个 try-block 中做两件不相关的事情(即:2 个事务)。我们正在重新使用连接。这是一个坏主意:连接有与之关联的包袱:已设置的属性,是的,处于“中止”状态(在连接愿意执行任何其他 SQL)。只需关闭连接并获得一个新连接,您就可以抛弃所有这些包袱。这种包袱会导致单元测试不容易捕捉到的错误,也就是一旦触发就会花费大量金钱/眼球/善意/时间来修复的错误。客观地说,如果它避免了一个 100 倍难以捕捉的 bug,那么你肯定更喜欢 99 个易于捕捉的 bug,而这是属于后一类的那些 bug 之一。

    连接很贵?什么?

    这样做有一个问题:只需为单个事务使用一个连接,然后将其返回,这样就无需回滚,因为如果您 close() 它,连接将自动执行此操作:获取连接是相当资源-重。

    因此,人们倾向于/可能应该使用连接池来避免这种成本。也不要在这里写你自己的;使用HikariCP 或类似的东西。这些工具为您汇集连接:您无需调用DriverManager.getConnection,而是向 HikariCP 索要一个,并在完成后将连接交还给 HikariCP。 Hikari 将负责为您重置它,包括在连接处于事务中途时回滚,并处理任何其他每个连接的设置,使其恢复到已知状态。

    常见的 DB 交互模型本质上就是这种“流程”:

    someDbAccessorObject.act(db -> {
      // do a single transaction here
    });
    

    就是这样。这段代码在底层做了各种各样的事情:

    • 使用连接池。
    • 以正确的方式设置连接,主要包括将 auto-commit 设置为 false,以及设置正确的事务隔离级别。
    • 将在 lambda 块末尾提交,如果没有发生异常。无论哪种情况,都将连接交还给池。
    • 将捕获 SQLExceptions 并分析它们是否是重试异常。如果是,nagle 的算法或其他一些随机指数退避并重新运行 lambda 块(这就是重试异常的含义)。
    • 负责将“获取”连接的代码(例如,确定要使用的正确 JDBC url)放在一个地方,这样 db 配置的更改就不需要在您的系统中进行全局搜索/替换代码库。

    在该模型中,您遇到问题的情况很少见,因为您最终会陷入 '1 事务? 1 个连接!模型。通常这是昂贵的(创建连接比像往常一样回滚/提交然后继续在同一个连接对象上进行新事务要昂贵得多),但一旦使用池化器,它就归结为同样的事情。

    换句话说:正确编写的 DB 代码不应该有您的问题,除非您自己编写连接池,在这种情况下,答案肯定是:显式回滚。

    【讨论】:

    • 我没有编写连接池程序,也没有使用 HikariCP 或类似的东西。我正在使用“1 事务 1 连接”方法(对每个事务使用连接和关闭方法)。所以每个事务都有一个单独的连接。在这种情况下,我想我不需要显式回滚,对吗?我知道这会使我的应用程序变慢,但我的应用程序的用户数量非常少(少于 5 个),所以我认为它会没事的。你怎么看?
    • 不,那就不用担心了。连接消失,您无需担心。你会没事的。
    猜你喜欢
    • 2018-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-19
    • 2020-01-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多