【问题标题】:How to properly start, working and finish a transaction?如何正确开始、工作和完成交易?
【发布时间】:2014-10-03 15:12:16
【问题描述】:

我正在使用 MySQL,并且我知道嵌套连接是不允许的 - 为此使用“保存点” - 但我想创建一个更通用的代码,也可以与其他 DBMS 一起使用。

那么,我想知道如何在下面的代码中正确启动、工作和完成事务?

一旦ExampleDAO.Save() 函数可以在其他函数中使用,例如OtherExampleDAO.Save(),我需要在尝试启动新事务之前验证事务是否已启动。

验证if Assigned(dbTransaction) then的行总是返回true,那么如何正确验证dbTransaction是否被实例化了?

function TExampleDAO.Save(const Example: TExample): Boolean;
var
  dbxTransaction: TDBXTransaction;
begin
  if Assigned(Example) then // prevents invalid object, like ExampleDAO.Save(nil);
  begin
    try
      if (_connection.TransactionsSupported) AND
        ((not _connection.InTransaction) OR (_connection.MultipleTransactionsSupported)) then
      begin
        dbxTransaction := _connection.BeginTransaction(TDBXIsolations.ReadCommitted);
      end;

      try
        // example
        _sqlQuery.Close;
        _sqlQuery.SQL.Clear;
        _sqlQuery.SQL.Add('INSERT INTO example(a, b) '
                        + 'VALUES(:a, :b)');
        _sqlQuery.ParamByName('a').AsAnsiString := Example.A;
        _sqlQuery.ParamByName('b').AsDateTime := Example.B;
        _sqlQuery.ExecSQL(False);

        // example info
        _sqlQuery.Close;
        _sqlQuery.SQL.Clear;
        _sqlQuery.SQL.Add('INSERT INTO example_info(c, d) '
                        + 'VALUES(:c, :d)');
        _sqlQuery.ParamByName('c').AsInteger := Example.Info.C;
        _sqlQuery.ParamByName('d').AsFloat := Example.Info.D;
        _sqlQuery.ExecSQL(False);

        if Assigned(dbxTransaction) then
          _connection.CommitFreeAndNil(dbxTransaction);

        Result := True;
      except
        on Exc:Exception do
        begin
          if Assigned(dbxTransaction) then
            _connection.RollBackFreeAndNil(dbxTransaction);

          raise Exc;
          Result := False;
        end;
      end;
    finally
      if Assigned(dbxTransaction) then
        FreeAndNil(dbxTransaction);
    end;    
  end
  else
  begin
    Result := False;
  end;
end;

【问题讨论】:

  • Result := False;raise 之后是没用的。如果没有分配Example,我会提出EArgumentNilException,并将其从函数转换为过程
  • 在加薪之前分配给结果也是没用的!

标签: delphi dbexpress


【解决方案1】:

您需要在函数开始时将dbxTransaction 正确初始化为nil。 Delphi 中的局部变量(至少在 Win32 平台上)在为它们赋值之前不会初始化,这意味着内容是未知的。将nil 以外的任何值传递给Assigned 将导致True。我建议永远不要在 any 平台上测试局部变量的内容,直到它在您的代码中分配了一个值。

这里有一个如何使它工作的例子。 (我还删除了异常块中对Result 的不必要分配。)

function TExampleDAO.Salve(const Example: TExample): Boolean;
var
  dbxTransaction: TDBXTransaction;
begin
  dbxTransaction := nil;            // Initialize the transaction variable here

  if Assigned(Example) then // prevents invalid object, like ExampleDAO.Save(nil);
  begin
    try
      if (_connection.TransactionsSupported) AND
        ((not _connection.InTransaction) OR (_connection.MultipleTransactionsSupported)) then
      begin
        dbxTransaction := _connection.BeginTransaction(TDBXIsolations.ReadCommitted);
      end;

      try
        // example
        _sqlQuery.Close;
        _sqlQuery.SQL.Clear;
        _sqlQuery.SQL.Add('INSERT INTO example(a, b) '
                        + 'VALUES(:a, :b)');
        _sqlQuery.ParamByName('a').AsAnsiString := Example.A;
        _sqlQuery.ParamByName('b').AsDateTime := Example.B;
        _sqlQuery.ExecSQL(False);

        // example info
        _sqlQuery.Close;
        _sqlQuery.SQL.Clear;
        _sqlQuery.SQL.Add('INSERT INTO example_info(c, d) '
                        + 'VALUES(:c, :d)');
        _sqlQuery.ParamByName('c').AsInteger := Example.Info.C;
        _sqlQuery.ParamByName('d').AsFloat := Example.Info.D;
        _sqlQuery.ExecSQL(False);

        if Assigned(dbxTransaction) then
          _connection.CommitFreeAndNil(dbxTransaction);

        Result := True;
      except
        on Exc:Exception do
        begin
          if Assigned(dbxTransaction) then
            _connection.RollBackFreeAndNil(dbxTransaction);

          raise Exc;
        end;
      end;
    finally
      if Assigned(dbxTransaction) then
        FreeAndNil(dbxTransaction);
    end;    
  end
  else
  begin
    Result := False;
  end;
end;

正如@SirRufo 在 cmets 中对您的问题所指出的那样,未能将 Example 作为参数传递也可能会引发异常,这意味着它可能成为一个过程而不是一个函数和 Result将不再适用。

【讨论】:

  • 谢谢@KenWhite。目前,我无法将函数更改为过程,因为其他对象期望函数的 True 结果继续执行。所以,我也在函数的开头开始了 Result := False。你可以在这里查看最终代码:gist.github.com/anonymous/c1ce27723cf793828c66
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多