Java: коммит против отката против ничего, когда семантика не изменилась? - PullRequest
8 голосов
/ 19 января 2012

Хорошо, я знаю разницу между фиксацией и откатом и тем, что эти операции должны делать.Тем не менее, я не уверен, что делать в тех случаях, когда я могу добиться того же поведения при использовании 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):

  • commit
  • откат
  • ничего не делать

Существуют ли какие-либо рекомендации или преимущества в производительности при выборе конкретной операции?Какой правильный путь?

Примечание. Предположим, что автоматическая фиксация отключена.

Ответы [ 5 ]

2 голосов
/ 19 января 2012

Если это просто выборка, я бы не открывал транзакцию и, следовательно, ничего не делать в любом случае.Возможно, вы уже знаете, есть ли обновление / вставка, поскольку вы уже передали параметры.Случай, когда вы собираетесь делать манипуляции, более интересен.Случай, когда он удаляется, ясно, что вы хотите зафиксировать;если есть исключение, вы должны выполнить откат, чтобы сохранить согласованность БД, поскольку что-то не получилось, и вы ничего не можете сделать.Если удаление не удалось из-за того, что удалить было нечего, я бы зафиксировал 3 причины:

  • Семантически, это кажется более правильным, поскольку операция была технически успешной и выполнялась в соответствии с указаниями.

  • Это более надежно на будущее, поэтому, если кто-то добавит больше кода в транзакцию, он не будет удивлен, что откат произойдет, потому что одно удаление просто ничего не сделало (они ожидали быза исключением того, что транзакция откатывается)

  • Когда есть какая-либо операция, коммит выполняется быстрее, но в этом случае я не думаю, что это имеет значение.

1 голос
/ 19 января 2012

Как и другие отмечали в своих ответах, дело не в производительности (для описанных вами эквивалентных случаев), которую я считаю незначительной, а в необходимости обслуживания, и это очень важно!

Для того чтобы ваш код был удобен в обслуживании, я предлагаю (несмотря ни на что) всегда фиксировать в самом низу вашего try блока и всегда закрывать ваш Connection в вашем finally блоке.В блоке finally вы также должны выполнить откат, если есть незафиксированные транзакции (это означает, что вы не достигли коммита в конце блока try).

В этом примере показано, что я считаюнаилучшая практика (в отношении управляемости):

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;
        }
}
1 голос
/ 19 января 2012

Я задал себе тот же вопрос.В конце концов я выбрал решение, в котором я всегда совершал успешные транзакции и всегда откатывал неудачные транзакции , рассматривая их как 1002 *, оказывая какое-либо влияние.Это упростило большую часть кода и сделало его более понятным и легким для чтения.

У него не было серьезных проблем с производительностью в приложении, в котором я работал, в котором использовался NHibernate + SQLite в .net.Ваш пробег может варьироваться.

1 голос
/ 19 января 2012

То, что вы говорите, зависит от вызываемого кода, возвращает ли он вам флаг для проверки или он генерирует исключительные ситуации, если что-то идет не так?

API генерирует исключения, но также возвращает логическое значение (true | false):

Такая ситуация часто возникает, и вызывающему коду трудно обрабатывать обаусловия, как вы указали в своем ОП.Единственное, что вы можете сделать в этой ситуации:

// 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 генерирует исключительно исключения (без возвращаемого значения) :

Нет проблем.Можно предположить, что если исключение не было обнаружено, операция завершилась без ошибок, и мы можем добавить rollback () / commit () в блок try / catch.

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:

$queryStatus = true;

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

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

...

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

"Примечание. Предположим, что автоматическая фиксация отключена."

  • После отключения автоматической фиксации высообщаем СУБД, что вы берете контроль над коммитами с этого момента до тех пор, пока автоматическая фиксация не будет повторно включена, поэтому, IMO, рекомендуется откатывать или фиксировать транзакцию, а не оставлять любые запросы в подвешенном состоянии.
1 голос
/ 19 января 2012

Любое нетривиальное приложение будет иметь операции, требующие выполнения нескольких операторов SQL.Любой сбой, произошедший после первого оператора SQL и до последнего оператора SQL, приведет к тому, что данные будут несовместимыми.

Транзакции предназначены для того, чтобы сделать операции с несколькими операторами такими же атомарными, как операции с одним оператором, с которыми вы сейчас работаете.

...