Java:语义不变时提交vs回滚vs什么都没有?

时间:2012-01-18 20:58:02

标签: java sql database jdbc transactions

好的,我知道提交和回滚之间的区别以及这些操作应该做什么。 但是,在使用commit(),rollback()和/或什么都不做的情况下我可以实现相同的行为时,我不确定该怎么做。

例如,假设我有以下代码执行查询而无需写入db: 我正在开发一个与SQLite数据库通信的应用程序。

try {
  doSomeQuery()
  // b) success
} catch (SQLException e) {
  // a) failed (because of exception)
}

或者更有趣的是,请考虑以下代码,删除单行:

try {
  if (deleteById(2))
    // a) delete successful (1 row deleted)
  else
    // b) delete unsuccessful (0 row deleted, no errors)
} catch (SQLException e) {
  // c) delete failed (because of an error (possibly due to constraint violation in DB))
}

从语义角度观察,在情况b)和c)中进行提交或回滚会导致相同的行为。

通常,每种情况下都有几种选择(a,b,c):

  • 提交
  • 回滚
  • 什么都不做

选择特定操作是否有任何指导或性能优势?什么是正确的方法?

注意:假设禁用了自动提交。

5 个答案:

答案 0 :(得分:2)

如果它只是一个选择,我不会打开一个交易,因此,无论如何都无事可做。可能你已经知道是否有更新/插入,因为你已经传递了参数。 您打算进行操作的情况更有趣。删除的情况很明显,你要提交;如果存在异常,您应该回滚以保持数据库一致,因为某些操作失败并且您可以做很多事情。如果删除失败,因为没有删除任何内容我会提交3个原因:

  • 从语义上讲,它似乎更正确,因为操作是 技术上成功并按规定执行。

  • 这是更具未来性的证明,所以如果有人添加更多代码 交易他们不会感到惊讶,因为它回滚 一个删除只是没有做任何事情(他们会期望例外 事务被回滚)

  • 当有操作要做时,提交更快,但在这种情况下我 不要认为这很重要。

答案 1 :(得分:1)

任何非平凡的应用程序都需要完成需要多个SQL语句的操作。在第一个SQL语句之后和最后一个SQL语句之前发生的任何故障都将导致数据不一致。

事务旨在使多语句操作与您当前使用的单语句操作一样原子化。

答案 2 :(得分:1)

你所说的取决于所调用的代码,它是否会返回一个标记供你测试,或者它是否会在出现问题时专门抛出异常?

API会抛出异常,但也返回一个布尔值(true | false):

这种情况发生了很多,并且使得调用代码难以处理这两个条件,正如您在OP中指出的那样。在这种情况下你可以做的一件事是:

// Initialize a var we can test against later
//  Lol, didn't realize this was for java, please excuse the var
//  initialization, as it's demonstrative only
$queryStatus = false;

try {
    if (deleteById(2)) {
        $queryStatus = true;
    } else {
        // I can do a few things here
        // Skip it, because the $queryStatus was initialized as false, so 
        //  nothing changes
        // Or throw an exception to be caught
        throw new SQLException('Delete failed');
    }        
} catch (SQLException $e) {
    // This can also be skipped, because the $queryStatus was initialized as 
    //  false, however you may want to do some logging
    error_log($e->getMessage());    
}

// Because we have a converged variable which covers both cases where the API
//  may return a bool, or throw an exception we can test the value and determine
//  whether rollback or commit
if (true === $queryStatus) {
    commit();
} else {
    rollback();
}

API专门抛出异常(无返回值)

没问题。我们可以假设如果没有捕获到异常,则操作完成且没有错误,我们可以在try / catch块中添加rollback()/ commit()。

try {
    deleteById(2);

    // Because the API dev had a good grasp of error handling by using
    //  exceptions, we can also do some other calls
    updateById(7);

    insertByName('Chargers rule');

    // No exception thrown from above API calls? sweet, lets commit
    commit();

} catch (SQLException $e) {

    // Oops exception was thrown from API, lets roll back
    rollback();
}

API不会抛出任何异常,只返回一个bool(true | false):

这可以追溯到旧学校的错误处理/检查

if (deleteById(2)) {
    commit();
} else {
    rollback();
}

如果您有多个查询组成事务,您可以从方案#1中借用单个var构思:

$queryStatus = true;

if (!deleteById(2)) {
    $queryStatus = false;
} 

if (!updateById(7)) {
    $queryStatus = false;
} 

...

if (true === $queryStatus) {
    commit();
} else {
    rollback();
}

“注意:假设禁用了自动提交。”

  • 一旦你禁用自动提交,你就告诉RDBMS你从现在起控制提交直到重新启用自动提交,所以IMO,这是一个很好的做法,回滚或提交事务而不是留下任何查询在limbo。

答案 3 :(得分:1)

我问自己完全相同的问题。最后,我找到了一个解决方案,我总是提交成功的交易,并且总是回滚它的非成功交易 ofles 。这简化了许多代码,使其更清晰,更易于阅读。

我在.net上使用NHibernate + SQLite的应用程序中没有任何重大性能问题。你的milage可能会有所不同。

答案 4 :(得分:1)

正如其他人在答案中所述,这不是性能问题(对于您描述的等效案例),我认为这可以忽略不计,而是可维护性问题,这一点非常重要!

为了使您的代码能够很好地维护,我建议(无论如何)始终在try块的最底部提交,并始终关闭Connection中的finally阻止。在finally块中,如果存在未提交的事务(也就是说您未在try块结束时到达提交),则还应该回滚。

这个例子展示了我认为最好的做法(miantainability-wise):

public boolean example()
{
    Connection conn;
    ...
    try
    {
        ...
        //Do your SQL magic (that can throw exceptions)
        ...
        conn.commit();
        return true;
    }
    catch(...)
    {
        ...
        return false;
    }
    finally
    {//Close statements, connections, etc
        ...
        closeConn(conn);
    }
}

public static void closeConn(Connection conn)
{
    if (conn != null)
        if (!conn.isClosed())
        {
            if (!conn.getAutoCommit())
                conn.rollback();//If we need to close() but there are uncommitted transacitons (meaning there have been problems)
            conn.close();
            conn = null;
        }
}