Skip to content

자동 트랜잭션

Fox Transactions 는 트랜잭션을 자동으로 시작하고 종료(커밋/롤백)하는 기능을 제공합니다. 이 기능을 자동 트랜잭션(automatic transaction)이라고 부르며 FoxAutoCompleteAttribute 특성을 사용합니다.

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

트랜잭션 시작과 종료

자동 트랜잭션을 이해하기 위해서는 트랜잭션은 어떻게 시작하고 종료하는지 그리고 트랜잭션 종료 시 트랜잭션을 커밋할 것인지 롤백할 것인지 결정하는 메카니즘을 먼저 이해해야 합니다.

트랜잭션 시작과 트랜잭션 루트

Fox Transactions 에서 다음과 같은 상황에서 새로운 트랜잭션이 시작하며 트랜잭션을 시작하는 메서드를 트랜잭션 루트라고 부릅니다.

  • 트랜잭션이 시작하지 않은 상태에서 트랜잭션 옵션Required 인 메서드가 호출된 경우

  • 기존 트랜잭션 여부와 무관하게 트랜잭션 옵션이 RequiresNew 인 메서드가 호출된 경우

트랜잭션 루트는 트랜잭션의 시작점이자 트랜잭션이 종료하는 시점입니다. 즉, 트랜잭션 루트 메서드가 종료하는 시점에서 트랜잭션 역시 종료되며 이때 트랜잭션이 커밋되거나 롤백 됩니다. 다음 그림은 다양한 상황에서 트랜잭션 루트 메서드를 보여 줍니다.

다양한 상황에서 트랜잭션 루트 메서드

트랜잭션 종료와 결과

Fox Transactions 기능에서 트랜잭션 루트 메서드가 종료될 때 트랜잭션 역시 종료됩니다. 트랜잭션의 결과(커밋/롤백)는 트랜잭션에 참여한 트랜잭션 메서드들이 성공적일 때 트랜잭션은 커밋되며, 그렇지 않은 경우, 즉 어느 한 메서드라도 성공하지 못했을 때 트랜잭션은 롤백됩니다. 이는 트랜잭션의 원자성(atomicity), 일관성(consistency)을 고려했을 때 손쉽게 이해됩니다.

Fox Transactions 이 트랜잭션을 커밋/롤백하는 방식에 대해 이해하기 쉽도록 간단한 다음 코드를 고려해 봅시다. SimpleClass 메서드는 트랜잭션 옵션으로 Required 를 사용하므로 InsertData 메서드가 호출될 때 트랜잭션이 시작됩니다. Fox Transactions 는 InsertData 메서드가 종료될 때 이 메서드가 성공했는지 여부를 판단하여 성공한 경우(complete) 트랜잭션을 커밋하며 성공하지 못한 경우(abort) 트랜잭션을 롤백합니다.

using SimpleClass comp = new();
ISimpleClass itf = comp.CreateExecution<ISimpleClass>();
itf.InsertData(999);

[FoxTransaction(FoxTransactionOption.Required)]
public class SimpleClass : FoxComponentBase, ISimpleClass
{
    [FoxAutoComplete]
    public void InsertData(int id)
    {
        using FoxDbAccess dbAccess = FoxDbAccess.CreateDbAccess();
        string query = "INSERT INTO my_demo_table(col_id, col_str) VALUES(:id, :str)";
        FoxDbParameterCollection parameters = dbAccess.CreateParamCollection();
        parameters.AddWithValue("id", id);
        parameters.AddWithValue("str", "str #" + id);
        dbAccess.ExecuteSqlNonQuery(query, parameters);
    }
}

FoxAutoCompleteAttribute 특성은 트랜잭션 메서드가 예외 없이 종료하면 성공한 것으로 간주(complete)하고 예외가 발생하면 성공하지 못한 것으로 간주(abort) 합니다. 예를 들어, InsertData 메서드에 다음과 같이 예외를 발생시키면 트랜잭션은 자동으로 롤백되어 데이터가 추가되지 않습니다.

...... 기존 코드와 동일 ......
// 예외 catch 와 상관없이 트랜잭션이 롤백됨.

[FoxTransaction(FoxTransactionOption.Required)]
public class SimpleClass : FoxComponentBase, ISimpleClass
{
    [FoxAutoComplete]
    public void InsertData(int id)
    {
        ...... 기존 코드와 동일 ......

        throw new InvalidOperationException("TRANSACTION ABORT TEST (ROLLBACK)");
    }
}

이렇게 예외 발생 여부에 따라 트랜잭션 메서드의 성공 여부를 결정하는 것을 자동 트랜잭션(Automatic Transaction)이라 합니다. 예외 발생과 상관없이 SetComplete/SetAbort 메서드를 명시적으로 호출하여 트랜잭션 메서드의 성공 여부를 결정하는 것을 수동 트랜잭션(Manual Transaction)이라 합니다.

트랜잭션 투표

Fox Transaction 에서 트랜잭션에 참여하는 여러 트랜잭션 메서드들이 존재할 수 있습니다. 예를 들어, 위 그림에서 트랜잭션 A 에는 2개의 트랜잭션 메서드가 참여하는 트랜잭션이며, 트랜잭션 B 는 3개의 트랜잭션 메서드가 참여합니다. 트랜잭션에 여러 트랜잭션 메서드가 참여하는 경우 각 트랜잭션 메서드의 성공 여부(complete/abort)가 트랜잭션 결과에 영향을 줍니다.

트랜잭션에 참여한 개별 트랜잭션 메서드가 종료할 때 성공 여부를 검사하며 모든 트랜잭션 메서드가 성공(complete)한 경우, 트랜잭션 루트 메서드가 종료될 때 트랜잭션은 커밋됩니다. 어느 한 트랜잭션 메서드라도 실패(abort)한 경우 트랜잭션 루트 메서드가 종료될 때 트랜잭션은 롤백됩니다.

Note

트랜잭션의 원자성(atomicity) 원칙상 트랜잭션에 참여한 어떤 메서드가 실패한 경우 그 트랜잭션은 나머지 트랜잭션 메서드 결과에 상관없이 실패해야 합니다. Fox Transactions 에서도 트랜잭션 메서드가 성공적으로 완료되지 않은 경우 트랜잭션은 즉시 실패한 것으로 간주합니다(doomed). .net 런타임은 실패가 확정된 트랜잭션에 대해 추가적인 트랜잭션 작업이 수행되면 TransactionAbortedException 예외가 발생합니다.

FoxAutoComplete 특성

FoxAutoCompleteAttribute 특성은 예외 발생 여부에 따라 트랜잭션 메서드의 투표를 자동으로 수행합니다. 즉, 트랜잭션 메서드에서 예외가 발생하지 않으면 자동으로 트랜잭션이 성공(complete)한 것으로 간주 합니다. 반대로 트랜잭션 메서드에서 예외가 발생했다면 트랜잭션은 실패(abort)한 것으로 간주합니다.

트랜잭션 성공/실패(complete/abort) 여부를 결정하는 시점은 메서드가 종료한 이후이므로 메서드에서 예외가 발생하더라도 try~catch 문을 사용하여 예외를 처리하는 경우에는 트랜잭션은 성공한 것으로 간주합니다.

[FoxAutoComplete]
public void AlwaysCompleteMethod()
{
    try
    {
        ...... 생략 ......
    }
    catch (Exception)
    {
        // 이 메서드는 항상 성공(Complete)으로 간주된다.
    }
}

FoxAutoCompleteAttribute 특성은 트랜잭션 메서드에 명시되지 않았더라도 명시된 것으로 간주됩니다. 다음 예제에서 두 메서드는 동일한 FoxAutoCompleteAttribute 특성을 사용합니다.

1
2
3
4
5
6
7
8
[FoxAutoComplete]
public void ExplicitAutoCompleteMethod()
{
}

public void ImplicitAutoCompleteMethod()
{
}

FoxAutoCompleteAttribute 특성은 매개변수로 자동 트랜잭션 여부를 나타내는 bool 매개변수를 사용하며 기본값으로 true를 사용합니다. 다음 예제에서 메서드들은 모두 동일한 FoxAutoCompleteAttribute 특성을 사용합니다.

[FoxAutoComplete]
public void Method1()
{
}

[FoxAutoComplete(true)]
public void Method2()
{
}

public void Method3()
{
}

FoxAutoCompleteAttribute 특성을 명시하지 않더라도 자동 트랜잭션이 적용되지만 코드 내에서 자동 트랜잭션과 수동 트랜잭션을 섞어서 사용하는 경우에는 메서드가 자동 트랜잭션을 사용한다는 것을 표시하기 위해서 명시적으로 FoxAutoCompleteAttribute 특성을 사용하는 것이 코드 가독성을 높일 수 있습니다.

수동 트랜잭션

Fox Transactions 에서 수동 트랜잭션이란 메서드 종료 시점에서 예외 발생 여부를 트랜잭션 메서드의 성공/실패 여부로 간주하는 것과 달리 코드를 통해 명시적으로 설정하는 것을 말합니다. 수동 트랜잭션을 사용하기 위해서는 FoxAutoCompleteAttribute 특성의 매개변수 값을 false로 설정해야 하며 FoxContextUtil 클래스의 SetComplete/SetAbort 메서드를 호출하여 메서드의 성공/실패 여부를 설정합니다. 다음 코드는 동등한 작업을 자동 트랜잭션과 수동 트랜잭션으로 구현하는 예를 보여줍니다.

public class DacClass : FoxDacBase, IDacClass
{
    [FoxAutoComplete]
    public void AutoCompleteInsertData(int id)
    {
        InsertData(id);
    }

    [FoxAutoComplete(false)]
    public void ManualCompleteInsertData(int id)
    {
        try
        {
            InsertData(id);
            FoxContextUtil.SetComplete();
        }
        catch
        {
            FoxContextUtil.SetAbort();
            throw;
        }
    }

    private void InsertData(int id)
    {
        ...... 생략 ......
    }
}

Note

FoxContextUtil 클래스는 트랜잭션 투표와 관련하여 SetComplete/SetAbort 메서드 외에도 EnableCommit/DisableCommit 메서드를 제공합니다. 이들 메서드는 SetComplete/SetAbort 와 동일한 기능을 수행하며 COM+ 와 호환을 위해 제공되는 메서드 입니다. COM+ 에서 SetComplete/SetAbort 메서드와 EnableCommit/DisableCommit 메서드가 수행하는 역할은 조금 다르지만 Fox Transactions 에서는 완전히 동등한 역할을 수행합니다.

수동 트랜잭션은 예외를 유발하지 않고 트랜잭션의 실패에 투표하고자 할 때 사용합니다. 예를 들어, 보안 상 예외 정보(오류 메시지, 호출 스택 등)를 호출자에게 제공하지 않고 사용자 친화적인 오류 메시지를 호출자에게 전달하고자 하는 상황입니다.

[FoxAutoComplete(false)]
public string BizLogicMethod()
{
    string result;
    try
    {
        ...... 생략 ......
        FoxContextUtil.SetComplete();
        result = "작업 성공";
    }
    catch (Exception ex)
    {
        WriteErrorLog(ex);
        FoxContextUtil.SetAbort();
        result = "사용자 친화적인 오류 메시지";
    }
}

수동 트랜잭션 메서드의 성공/실패 여부는 FoxContextUtil 클래스의 MyTransactionVote 속성을 통해 확인할 수 있으며 기본값은 false, 즉 실패 상태입니다. 즉, 수동 트랜잭션 메서드에서 SetComplete 호출이 없는 경우 트랜잭션은 실패(abort)한 것으로 간주됩니다. 예를 들어 다음 DoomedMethod 메서드는 RequiesNew 옵션을 사용하므로 항상 새로운 트랜잭션이 시작되지만 수동 트랜잭션을 사용하고 SetComplete 호출이 없으므로 항상 롤백 됩니다.

1
2
3
4
5
6
7
8
[FoxTransaction(FoxTransactionOption.RequiresNew)]
[FoxAutoComplete(false)]
public void DoomedMethod()
{
    using FoxDbAccess dbAccess = FoxDbAccess.CreateDbAccess();
    dbAcccess.ExecuteSqlNonQuery("INSERT INTO ....");
    // 수동 트랜잭션이고 SetComplete 호출이 없으므로 트랜잭션은 롤백 된다.
}

Warning

COM+ 에서는 수동 트랜잭션의 트랜잭션 결과 기본값은 true 입니다. 즉, 명시적으로 SetAbort 호출을 하지 않는다면 트랜잭션은 성공(complete)한 것으로 간주됩니다. Fox Transactions 가 COM+ 와 동일하게 작동하도록 하려면 구성 설정의 appSettingsFoxTransactionComplusCompatible 값을 true 로 지정하면 됩니다.

1
2
3
4
5
{
    "appSettings": {
        "FoxTransactionComplusCompatible": true
    }
}

SetComplete, SetAbort 메서드와 MyTransactionVote 속성은 기본적으로 수동 트랜잭션 메서드(FoxAutoCompleteAttribute 특성이 false 인 경우)에만 호출이 가능하며 자동 트랜잭션 메서드나 비 트랜잭션 메서드에서 호출하면 InvalidOperationException 예외가 발생합니다.

FoxContextUtil 클래슨는 현재 메서드가 자동 트랜잭션인가를 나타내는 IsAutoComplete 속성을 제공합니다. IsAutoComplete 속성은 자동 트랜잭션 메서드와 수동 트랜잭션 메서드가 모두 호출하는 공통 메서드에서 유용하게 사용할 수 있습니다. 다음 코드와 같이 IsAutoComplete 속성값이 false 인 경우에만 SetComplete, SetAbort 메서드를 호출하도록 코드를 작성할 수 있습니다.

private static void SharedUtilMethod()
{
    ...... 생략 ......
    // 트랜잭션을 롤백해야 하는 경우
    if (isError)
    {
        if (!FoxContextUtil.IsAutoComplete)
        {
            FoxContextUtil.SetAbort();
        }
        throw new SomeException(".....");
    }
}

주의 사항

Fox Transactions 에서는 한 트랜잭션에 여러 트랜잭션 메서드가 참여 하기 때문에 기대하지 않은 문제 상황에 직면할 수 있습니다. 가장 대표적인 상황으로 TransactionAbortedException 예외가 발생하는 상황입니다. 이 예외는 트랜잭션이 이미 실패하였음에도 추가적인 트랜잭션 작업을 시도하는 경우 발생합니다.

마스터/디테일을 데이터베이스에 저장하기 위해 두 트랜잭션 메서드를 호출하는 다음 코드를 생각해 봅시다. SaveMasterData 메서드와 SaveDetailData 메서드는 자동 트랜잭션을 사용한다고 가정합니다.

[FoxTransaction(FoxTransactionOption.Required)]
[FoxAutoComplete(false)]
public bool SaveMethod()
{
    using MasterDac masterDac = new();
    IMasterDac itf1 = masterDac.CreateExecution<IMasterDac>();
    try
    {
        itf1.SaveMasterData();
    }
    catch (Exception)
    {
        HandleError();
    }

    using DetailDac detailDac = new();
    IDetailDac itf2 = detailDac.CreateExecution<IDetailDac>();
    try
    {
        itf2.SaveDetailData();
        FoxContextUtil.SetComplete();
        return true;
    }
    catch (Exception)
    {
        HandleError();
        FoxContextUtil.SetAbort();
        return false;
    }
}

위 메서드는 문제가 없어 보이지만 SaveMasterData 메서드에서 오류가 발생한 경우 SaveDetailData 메서드 호출 시 TransactionAbortedException 예외가 발생합니다. SaveMasterData 메서드가 오류를 발생한 경우 트랜잭션은 이미 실패한 것으로 간주되고(doomed) SaveDetailData 메서드 호출은 이미 실패한 트랜잭션에 추가적으로 참여(enlist)하려고 시도하기 때문에 TransactionAbortedException 예외가 발생합니다.

TransactionAbortedException 예외를 피하기 위해서는 트랜잭션이 실패했다면 추가적인 트랜잭션 메서드 호출을 수행해서는 안됩니다. 따라서 위 코드는 다음과 같이 SaveMasterData 실패시 즉시 트랜잭션을 롤백하도록 SetAbort 메서드 호출과 더불에 메서드를 종료해야 합니다.

[FoxTransaction(FoxTransactionOption.Required)]
[FoxAutoComplete(false)]
public bool SaveMethod()
{
    using MasterDac master = new();
    IMasterDac itf1 = master.CreateExecution<IMasterDac>();
    try
    {
        itf1.SaveMasterData();
    }
    catch (Exception)
    {
        HandleError();
        FoxContextUtil.SetAbort();
        return false;
    }
    ...... 생략 ......
}

권장 코딩 패턴

Fox Transactions 를 사용할 때 권장되는 트랜잭션 코드는 다음과 같습니다.

  • 명시적으로 FoxAutoCompleteAttribute 특성을 사용하여 자동 트랜잭션을 사용합니다.

    자동 트랜잭션은 코드의 예외 발생 여부에 따라 자동으로 트랜잭션을 커밋 혹은 롤백해 주기 때문에 코드의 가독성을 높여주며 로직 작성을 용이하게 만들어 줍니다. 특별한 이유가 없는 이상 자동 트랜잭션을 사용하는 것이 좋으며 자동 트랜잭션이 사용된다는 것을 명시하기 위해 FoxAutoCompleteAttribute 특성을 코드에 포함하는 것을 권장 합니다.

    1
    2
    3
    4
    5
    [FoxAutoComplete]
    public void TxMethod()
    {
        ...... 생략 ......
    }
    
  • try~catch 문장의 사용을 최대한 피하여 예외가 호출자에게 전파되도록(propagated) 합니다.

    자동 트랜잭션은 예외에 의해 트랜잭션 성공/실패 여부를 결정하기 때문에 try~catch 문장을 사용하면 트랜잭션 결과에 영향을 줄 수 있습니다. 따라서 가급적 try~catch 문장을 사용하지 않고 예외가 호출자에게 전파되도록 하는 것이 좋습니다. try~catch 문장을 사용하는 경우에는 잡힌(catched) 예외를 re-throw 하여 트랜잭션 실패(abort)에 투표해야 합니다.

    [FoxAutoComplete]
    public void TxMethod()
    {
        // try ~ catch 를 가급적 사용하지 마세요.
        try
        {
            ...... 트랜잭션 로직 (생략) ......
        }
        catch (Exception)
        {
            ...... 예외 처리 (생략) ......
            throw;
        }
    }
    

    로깅과 같은 예외 처리가 필요한 경우, Fox Transactions 기능을 호출하는 호출자(대개 ASP.NET Web API 혹은 Razor 페이지) 측에서 중앙 집중적으로 처리하는 것을 권장합니다.

  • 수동 트랜잭션 사용하는 경우 모든 코드 패스에서 SetComplete/SetAbort 메서드가 호출되도록 해야 합니다.

    자동 트랜잭션을 권장하지만 수동 트랜잭션을 사용해야만 하는 경우, 하나의 트랜잭션에서 자동 트랜잭션과 섞어 사용하지 않는 것이 좋습니다. 자동 트랜잭션과 수동 트랜잭션이 섞여 있는 경우 혼동을 유발하기 쉬우며 트랜잭션 논리를 추적하기 어렵습니다.

Summary

Fox Transactions 기능은 FoxAutoCompleteAttribute 특성을 통해 트랜잭션의 커밋과 롤백을 자동으로 처리하는 자동 트랜잭션 기능을 지원합니다. 메서드가 성공적으로 종료되면 트랜잭션을 커밋하고, 예외가 발생하면 자동으로 롤백하여 데이터의 일관성을 보장합니다. 필요에 따라 FoxContextUtil 클래스를 사용하여 명시적으로 트랜잭션 결과를 결정하는 수동 트랜잭션도 지원하지만, 코드의 간결성과 안정성을 위해 자동 트랜잭션 사용과 예외 전파(propagation) 패턴을 권장합니다.