Skip to content

트랜잭션 관리

데이터베이스 액세스에 있어서 트랜잭션 관리는 매우 중요한 요소 입니다. Fox DB Access 는 직관적이고 편리한 트랜잭션 API 를 제공하며 개발자는 이들 API 를 통해 전통적인 데이터베이스 액세스 방법보다 적은 노력과 시간으로 트랜잭션을 제어할 수 있습니다.

Note

트랜잭션 처리는 하나의 데이터베이스 연결만이 참여(enlist)하는 로컬 트랜잭션(local transaction)과 여러 데이터베이스 혹은 하나의 데이터베이스에 대한 여러 연결이 참여하는 분산 트랜잭션(distributed transaction)이 있습니다. 여기에서 다루는 트랜잭션은 로컬 트랜잭션이며 분산 트랜잭션에 대한 처리는 Fox Transactions을 참조 하십시요.

이 문서와 관련된 예제 코드는 다음 예제를 참조 하십시요.

전통적인 트랜잭션 처리

전통적인 ADO.NET 기반의 코드에서 트랜잭션을 처리하는 코드를 먼저 살펴보고 Fox DB Access 의 트랜잭션 처리가 어떻게 다른지 살펴보도록 하겠습니다.

다음 코드는 PostgreSQL 에서 트랜잭션을 사용하여 INSERT 를 수행하는 코드입니다. 트랜잭션을 시작하기 위해서는 BeginTransaction 메서드를 호출해야 하고 이 메서드가 반환하는 IDbTransaction 객체, Npgsql 의 경우 NpgsqlTransaction 객체를 Command 객체의 Transation 속성에 할당해야 합니다. 이렇게 함으로써 Command 객체는 트랜잭션 내에서 쿼리를 수행하게 됩니다. 마지막으로 트랜잭션을 커밋하기 위해 Commit 메서드를 호출하거나 롤백하기 위해 Rollback 메서드를 호출합니다.

using NpgsqlConnection conn = new(connectionString);
conn.Open();
try
{
    string query = "INSERT INTO my_demo_table VALUES(:id, :str, :int)";
    using NpgsqlCommand cmd1 = new(query, conn);
    cmd1.Parameters.AddWithValue("id", 98);
    cmd1.Parameters.AddWithValue("str", "str98");
    cmd1.Parameters.AddWithValue("int", 98);
    using NpgsqlTransaction tx = conn.BeginTransaction();
    cmd1.Transaction = tx;
    try
    {
        cmd1.ExecuteNonQuery();

        tx.Commit();
        Console.WriteLine("Transaction Committed.");
    }
    catch (Exception ex)
    {
        tx.Rollback();
        Console.WriteLine($"Transaction Rollbacked: {ex.Message}");
    }
}
finally
{
    conn.Close();
}

Note

대부분의 데이터베이스에서 하나의 SQL 문장 수행은 명시적으로 트랜잭션을 시작하지 않더라도 자동으로 트랜잭션이 시작되고 커밋/롤백 됩니다. 여기에서는 트랜잭션 설명을 위해서 명시적으로 트랜잭션을 시작하였습니다.

위 트랜잭션 코드의 문제점은 BeginTransaction 메서드가 반환하는 트랜잭션 객체(NpgsqlTransaction)를 Command.Transaction 속성에 할당하여 쿼리가 트랜잭션에서 수행된다고 지정해야 한다는 것입니다. 그렇다면 BeginTransaction 메서드 호출 이후 트랜잭션 객체를 Transaction 속성에 할당하지 않은 쿼리를 수행하면 어떻게 될까요? 트랜잭션 바깥에서 쿼리가 수행될 것 처럼 보이지만 전혀 그렇지 않습니다. Npgsql 의 경우, BeginTransaction 메서드가 호출된 이후에 해당 연결을 사용하여 수행되는 모든 쿼리는 트랜잭션 내에서 수행되며 모두 커밋되거나 롤백될 것입니다. 즉, Npgsql 은 Command 객체의 Transaction 속성을 무시합니다.

더욱 문제가 되는 것은 Transaction 속성의 작동 방식이 데이터베이스 마다 다르다는 것입니다. Managed ODP.NET Core(Oracle)은 Npgsql 과 동일하게 Transaction 속성과 무관하게 BeginTransaction 메서드 호출과 Commit/Rollback 메서드 호출 사이의 모든 쿼리를 하나의 트랜잭션으로 묶습니다. 한편 Microsoft.Data.SqlClient 는 트랜잭션을 사용하기 위해 Transaction 속성에 트랜잭션 객체를 반드시 할당해야 하며 BeginTransaction 메서드 호출 이후 Transaction 속성이 할당되지 않은 쿼리가 수행되면 예외를 유발합니다.

전통적인 트랜잭션 데이터액세스 코드는 불필요하게 트랜잭션 객체를 사용하여 코드를 복잡하게 만들며 데이터베이스마다 코딩 방식을 다르게 가져가야 한다는 문제가 있습니다.

Fox DB Access 트랜잭션 처리

FoxDBAccess 및 그 파생 클래스들은 트랜잭션 처리를 위해 다음 세 메서드를 제공합니다.

  • BeginTrans : 트랜잭션을 시작합니다. 데이터베이스 연결은 명시적으로 Open 된 상태이어야 합니다. CommitTrans, RollbackTrans 메서드가 호출될 때까지 Execute- 메서드들에 의해 수행되는 모든 쿼리는 트랜잭션에 참여합니다.
  • CommitTrans : 트랜잭션을 종료하고 변경 사항들을 모두 커밋합니다.
  • RollbackTrans : 트랜잭션을 종료하고 변경 사항들을 모두 롤백합니다.

FoxDbAccess 의 트랜잭션 사용법은 전통적인 트랜잭션 코드와 유사합니다. 하지만 BeginTrans 메서드가 트랜잭션 객체 반환하지도 않으며 Command.Transaction 속성에 트랜잭션 객체를 설정하는 작업도 필요하지 않습니다. 트랜잭션 객체는 FoxDbAccess 내부에서 관리되며 필요에 따라 자동으로 Transaction 속성에 할당합니다. 앞서 살펴본 전통적인 트랜잭션 처리 예제와 동등한 FoxDbAccess 기반의 트랜잭션 처리 코드 예제는 다음과 같습니다.

using FoxDbAccess dbAccess = FoxDbAccess.CreateDbAccess();
dbAccess.Open();
try
{
    string query = "INSERT INTO my_demo_table VALUES(:id, :str, :int)";
    FoxDbParameterCollection parameters1 = dbAccess.CreateParamCollection();
    parameters1.AddWithValue("id", 99);
    parameters1.AddWithValue("str", "str99");
    parameters1.AddWithValue("int", 99);
    dbAccess.BeginTrans();
    try
    {
        dbAccess.ExecuteSqlNonQuery(query, parameters1);
        dbAccess.CommitTrans();
    }
    catch (Exception ex)
    {
        dbAccess.RollbackTrans();
        Console.WriteLine($"Transaction Rollbacked: {ex.Message}");
    }
}
finally
{
    dbAccess.Close();
}

BeginTrans 메서드가 호출되면 내부적으로 BeginTransaction 메서드를 호출하고 이 메서드가 반환하는 트랜잭션 객체를 FoxDbAccess.DbTransaction 속성에 기록해 둡니다. 이 이후 Execute- 메서드들이 호출될 때 DbTransation 속성 값이 null 이 아닌 경우 이 값을 Command.Transaction 속성에 할당합니다. 이러한 방식으로 BeginTrans 메서드 호출과 CommitTrans/RollbackTrans 메서드 호출 사이에 수행되는 쿼리들을 모두 트랜잭션에 참여 시키는 것입니다.

전통적인 트랜잭션 코드에 비해 FoxDbccess 기반 트랜잭션 코드의 다른점은 개발자가 트랜잭션 객체를 신경 쓸 필요가 없다는 점입니다. 또한 BeginTrans 메서드 호출과 CommitTrans/RollbackTrans 메서드 호출 사이에 수행되는 모든 쿼리가 트랜잭션에 참여하기 때문에 직관적이며 데이터베이스 종류에 상관없이 일관적이라는 것입니다.

트랜잭션 관련 속성들

FoxDbAccess 클래스는 트랜잭션과 관련하여 두 속성을 제공합니다.

  • DbTransaction

    BeginTrans 메서드 호출에 의해 생성된 트랜잭션 객체(IDbTransaction) 입니다. 아직 BeginTrans 메서드가 호출되지 않았거나 CommitTrans/RollbackTrans 메서드 호출이 완료된 후에는 null 값을 반환합니다. 이 속성은 데이터베이스 종류에 독립적인 속성이므로 IDbTransaction 타입의 객체를 반환합니다만 실제로는 어떤 데이터 프로바이더를 사용하는가에 따라 NpgsqlTransaction, OracleTransaction, SqlTransaction 과 같은 구체적인 트랜잭션 객체 입니다.

    이 속성의 활용 대상은 CommandBuilder 와 같이 FoxDbAccess 를 사용하지 않고 외부에서 생성한 Command 객체FoxDbAccess 가 시작한 트랜잭션 내에서 수행하고자 할 때 사용할 수 있습니다.

    1
    2
    3
    4
    5
    6
    // DataAdapter 구성... (생략)
    NpgsqlCommandBuilder builder = new((NpgsqlDataAdapter)adapter);
    NpgsqlCommand command = builder.GetInsertCommand();
    // Command 매개변수 설정... (생략)
    command.Transaction = (NpgsqlTransaction)dbAccess.DbTransaction;
    dbAccess.ExecuteCommandNonQuery(command);
    
  • IsInLocalTransaction

    이 속성은 FoxDbAccess 객체가 BeginTrans 메서드를 호출하여 트랜잭션을 진행 중인가를 나타냅니다. 매개변수로 FoxDbAccess 를 받는 메서드에서 이미 트랜잭션이 시작된 상태로 그 메서드를 호출했는지 판단할 때 사용가능합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public void SomeUtilMethod(FoxDbAccess dbAccess)
    {
        if (dbAccess.IsInLocalTransaction)
        {
            dbAccess.BeginTransaction()
        }
    
        // ... doing awesome job ...    
    }
    

    Warning

    .net 환경에서 데이터베이스 트랜잭션은 앞서 살펴본 것과 같이 BeginTransaction 혹은 BeginTrans 메서서드 호출을 통해 명시적으로 트랜잭션에 참여(enlist)하는 명시적 트랜잭션과 TransactionScope 를 사용하여 암시적으로 트랜잭션에 참여하는 경우도 있습니다. IsInLocalTransaction 속성은 DbTransation 속성의 null 여부를 통해 BeginTrans 호출 여부를 판단합니다. 따라서 암시적 트랜잭션이 사용되는 경우 현재 트랜잭션에 참여 중일 지라도 IsInLocalTransaction 속성 값이 false 일 수도 있습니다.

Summary

Fox DB Access 는 트랜잭션 처리를 위해 BeginTrans/CommitTrans/RollbackTrans 메서드를 제공합니다. BeginTrans 메서드를 호출하여 트랜잭션을 시작하고 이 이후 수행되는 모든 쿼리들이 트랜잭션 내에서 수행되도록 할 수 있습니다. 이 이후 CommitTrans/RollbackTrans 메서드를 호출하여 트랜잭션을 커밋 하거나 롤백하면 됩니다. 이러한 Fox DB Access 의 트랜잭션 처리는 전통적인 트랜잭션 처리와 비교하여 직관적이며 데이터베이스 종류와 상관없이 일관적인 코딩 패턴을 제공합니다.