본문 바로가기

IT 살이/04. 기술 - 프로그래밍

개발 프레임워크 만들기 대장정 30 - Spring.NET의 데이터 액세스 II

앞에서 AdoTemplate을 이용하는 코딩 구조를 알아봤다. 이 포스트에서는 AdoTemplate를 이용해서 DB 데이터에 액세스하는 코드를  살펴본다. Dao 객체, AdoTemplate의 Execute를 호출하기, 이 호출시 콜백 객체( ICommandCallback 객체 또는 CommandDelegate 객체)를 넘겨주기, 콜백객체에서 AdoTemplate에서 넘겨준 command 객체를 이용해서 DB에 접근하기 등의 과정을 염두에 두면서 코드를 따라가 보자. 레이어관점에서 봤을때 DAO객체나 콜백객체 그리고 Spring.NET의 AdoTemplate는 모두 데이터 액세스 레이어에 속하는 객체들이다.


▶  AdoTemplate를 이용하는 샘플 코드


AdoTemplate를 이용할때 개발자가 개발해야 하는 부분은 무엇인가.  비즈니스 설계에 맞게 DAO 객체를 만들어야 하고 그리고 콜백 객체를 만들어야 한다.  Spring.DataQuickStart.2005 샘플 프로젝트에는 샘플 DAO 객체와 콜백 객체가 구현되어 있다.

많은 페이지가 있지만, 그 중에서 CommandCallbackDao.cs 페이지를 보자.  이 페이지에는 CommandCallbackDao 타입의 DAO를 객체를 정의하고 있고 그리고 ICommandCallback, CommandDelegate 타입의 콜백 객체의 사용을 모두 보여주고 있다. 


▶ 콜백객체로 ICommandCallback 객체 사용하기


먼저 콜백객체로 인터페이스 ICommandCallback를 사용하는 경우를 보자.

private class PostalCodeCommandCallback<T> : ICommandCallback<T> where T : ResultObject, new()

{

    private string postalCode;

    public PostalCodeCommandCallback(string postalCode)

    {

        this.postalCode = postalCode;

    }


    public T DoInCommand(DbCommand command)

    {

        T resultObject = new T();


        // 예제에서는 명령문을 지정하는 부분이 빠져 있다. 에러다.

        command.CommandText = cmdText;
        command.CommandType = CommandType.Text; //CommandType.StoredProcedure;

        DbParameter p = command.CreateParameter();

        p.ParameterName = "@PostalCode";

        p.Value = postalCode;

        command.Parameters.Add(p);


        resultObject.count = (int)command.ExecuteScalar();

        return resultObject;

    }

}

제너릭 타입의 ICommandCallback 인터페이스를 상속해서 PostalCodeCommandCallback 타입이 구현되고 있다.  이 구현체의 DoInCommand() 메소드를 AdoTemplate객체의 내부에서 콜백하게 된다.  DoInCommand 메소드 내부네서는 command 객체를 이용해서 실제로 DB 작업을 하게 된다. DbCommand 객체 command에는 이미 DB작업에 필요한 커넥션 정보와 명령( sql, 저장 프로시져 등)이 있다. 마지막으로 DAO 객체에서 넘겨준 값 postalCode를 이용해서 파라미터를 구성해서 command 객체에 넘겨주면 된다. 그 다음 command 객체의 메소드 ExecuteScalar()를 호출해서 실제 DB 작업을 하고 반환값을 받아온다.  참고로 현재 샘플 소스에서는 붉은 색 부분의 코드가 빠져 있다. command 객체에 전달할 명령문cmdText와 명령문의 타입을 지정해줘야 한다. 이 부분을 콜백 객체 외부로부터 전달받던 내부에 하드 코딩하던 이 정보가 있어야 한다.

제너릭 타입 T는 DoInCommand 메소드의 반환값 타입으로 사용되는데,  where 절 부분을 보면 제너릭 타입 T의 구체적인 타입으로 ResultObject 타입을 사용하고 있다.  그리고 그 정의는 다음과 같이 되어 있다. int는 제너릭 타입으로 사용될 수 없기 때문에 이런 래핑 타입이 필요하다.

public class ResultObject

{

    public int count;

}

단순히 int 값을 가지고 있는 타입이긴 하지만 현실의 실제 프로젝트에서의 반환값은 이 보다 더 복잡한 정의가 될 것이다.

이제 정의된 콜백 객체를 이용해서 DAO 객체에서 AdoTemplate 객체를 호출하는 코드를 보자.

public class CommandCallbackDao : AdoDaoSupport

{


    private string cmdText = "select count(*) from Customers where PostalCode = @PostalCode";


    ...


    public virtual int FindCountWithPostalCode(string postalCode)

    {

        // Type inference allows you not to explicitly write .Execute<ResultObject>


        return AdoTemplate.Execute(new PostalCodeCommandCallback<ResultObject>(postalCode)).count;

    }

    ...

DAO 객체 CommandCallbackDao의 FindCountWithPostalCode() 메소드를 보자. AdoTemplate 객체의 Execute() 메소드를 호출하고 있는데 이 AdoTemplate은 CommandCallbakDao 타입이 상속받고 있는 부모 타입 AdoDaoSupport 타입의 속성으로 정의되어 있다.

AdoDaoSupport 타입의 AdoTemplate 속성을 호출하면 AdoTemplate 타입의 객체가 반환된다. AdoDaoSupport 타입의 AdoTemplate 속성에 대한 정의를 보면 다음과 같다.

public class AdoDaoSupport : DaoSupport

{

    private AdoTemplate adoTemplate;

    ...

    public AdoTemplate AdoTemplate

    {

        set

        {

            adoTemplate = value;

        }

        get

        {

            return adoTemplate;

        }


    }

    ...

샘플 프로젝트에서는 AdoTemplate 속성에 인스턴스를 할당하는 작업에 Spring.NET 컨테이너의 Inversion of Control 기능을 사용하고 있다. DAO 객체가 정의되어 있는 Spring.DataQuickStart.2005 프로젝트를 사용하는 클라이언트 프로젝트 Spring.DataQuickStart.Test.2005 프로젝트를 보면 환경 설정 파일이 있다. DataQuickStart/GenericTemplate 폴더에 있는 ExampleTests.xml 파일을 보자. 다음은 그 일부이다.

<object id="commandCallbackDao" type="Spring.DataQuickStart.Dao.GenericTemplate.CommandCallbackDao, Spring.DataQuickStart">

  <property name="AdoTemplate" ref="adoTemplate"/>

</object>

String.DataQuickStart.Dao.GenericTemplate 네임스페이스하의 CommandCallbackDao 객체를 사용할때는 그 속성 AdoTemplate에 "adoTemplate"라는 id로 참조하고 있는 객체를 자동으로 할당하라는 표시이다. 참조하고 있는 객체를 따라가 보면 다음과 같은 정의가 있다.

<object id="adoTemplate" type="Spring.Data.Generic.AdoTemplate, Spring.Data">

  <property name="DbProvider" ref="dbProvider"/>

  <property name="DataReaderWrapperType" value="Spring.Data.Support.NullMappingDataReader, Spring.Data"/>

</object>

id가 "adoTemplate"로 설정되어 있는 객체에 대한 설정을 보면 Spring.Data.Generic 네임스페이스하의 AdoTemplate에 대한 정의를 나타내고 있다. 이 정의의 DbProvider라는 속성은 다시 "dbProvider"로 정의되어 있는 객체를 참조하고 있다. 역시 AdoTemplate객체가 인스턴스화될때에는 이 id로 정의되어 있는 객체가 자동으로 인스턴스화되어 속성에 할당될 것이다. 참조하고 있는 dbProvider라는 id의 객체 정의를 보면 다음과 같다.

<db:provider id="dbProvider"

              provider="SqlServer-2.0"

              connectionString="Data Source=.\SQL2005;Initial Catalog=Northwind;Persist Security Info=True;User ID=springqa;Password=springqa"/>

provider값으로 "SqlServer-2.0"을 사용하고 있는데 이 값은 .NET V2.0에 있는 MS SQL 서버 , provider v2.0.0.0을 프로바이더로 사용하고 있다는 의미이다. 그리고 이 요소에는 DB 연결정보도 있다. 

Spring.NET 컨테이너가 DAO 객체 CommandCallbackDao 객체의 인스턴스를 생성하면 결국 DB에 연결하기 위해서 필요한 정보를 갖는 AdoTemplate객체의 인스턴스도 자동 생성되어 CommandCallbackDao 객체의 AdoTemplate 속성에 할당되게 된다.

이제 다시 CommandCallbackDao 객체의 FindCountWithPostalCode() 메소드를 호출하는 부분으로 가 보자. 이 메소드에서는 AdoTemplate 객체의 Execute() 메소드를 호출하면서 인자로 이전에 정의한 콜백객체 PostalCodeCommandCallback<ResultObject>객체를 넘겨주고 있다. 이때 DB 작업에 필요한 변수값 postalCode도 함께 넘겨준다. 그럼 AdoTemplate에서는 DbCommand 객체를 정의해서 ICommandCallback에서 정의한 DoInCommand()를 호출하는데 사용한다. 그 이후는 앞의 코드에서 보는대로이다.


▶ 콜백객체로 CommandDelegate 타입의 객체 사용하기


public class CommandCallbackDao : AdoDaoSupport

{


    private string cmdText = "select count(*) from Customers where PostalCode = @PostalCode";


    /// <summary>

    /// Finds the number of customers with the given postal code.

    /// </summary>

    /// <param name="postalCode">The postal code.</param>

    /// <returns>Number of customers with the given postal code.</returns>

    public virtual int FindCountWithPostalCodeWithDelegate(string postalCode)

    {

        // Using anonymous delegates allows you to easily reference the

        // surrounding parameters for use with the DbCommand processing.


        return AdoTemplate.Execute<int>(delegate(DbCommand command)

               {

                   // Do whatever you like with the DbCommand... downcast to get

                   // provider specific funtionality if necesary.

 

                   command.CommandText = cmdText; 

                   DbParameter p = command.CreateParameter();

                   p.ParameterName = "@PostalCode";

                   p.Value = postalCode;

                   command.Parameters.Add(p);

 

                   return (int)command.ExecuteScalar();

 

               });

    }

    ...

AdoTemplate객체에서 사용하고 있는 Execute() 메소드는 다음과 같은 정의의 버전을 사용하고 있다.

public T Execute<T>(CommandDelegate<T> del)

CommandDelegate<T> 타입의 델리게이트 객체를 제공하기 위해서 익명 델리게이트 객체를 사용하고 있다. 익명 델리게이트 객체? 지금 Execute 메소드의 파라미터를 넘겨받는 괄호( )안에서 인라인 형식으로 델리게이트가 가리킬 코드를 정의하고 있다. 이 코드는 DbCommand 객체를 넘겨받는 메소드이다. 이 코드 자체를 Execute 메소드의 인자로 넘기는 것이 아니라 이 코드가 정의하고 있는 메소드를 가리키고 있는 델리게이트 객체를 넘기고 있는 것이다.

앞의 익명 델리게이트를 사용하는 코드를 완전한 모습으로 재정의해서 구성하면 다음과 같은 유사한 모양이 될 것이다. 

public virtual int FindCountWithPostalCodeWithDelegate(string postalCode)

{


    CallbackObject<ResultObject> callbackObject = new CallbackObject<ResultObject>(cmdText, postalCode);

    return AdoTemplate.Execute<ResultObject>(

        new CommandDelegate<ResultObject>(callbackObject.CallbakcMethod ) ).count;


}

private class CallbackObject<T> where T : ResultObject, new()

{

    string cmdText = string.Empty;

    string postalCode = string.Empty;

    public CallbackObject(string cmdText, string postalCode)

    {

        this.cmdText = cmdText;

        this.postalCode = postalCode;

    }


    public T CallbakcMethod(DbCommand command)

    {

        T resultObject = new T();

        command.CommandText = cmdText;

        DbParameter p = command.CreateParameter();

        p.ParameterName = "@PostalCode";

        p.Value = postalCode;

        command.Parameters.Add(p);

        resultObject.count = (int)command.ExecuteScalar();

        return resultObject;


    }

}

콜백 메소드에서 정의한 코드가 간단하다면 익명 델리게이트 표현을 사용한 방법을 사용해서 콜백 객체의 타입과 메소드를 만들지 않아도 될 것이다.  그러나 실제 현실 프로젝트에서는 코드의 통일성이 중요하기 때문에 다소 복잡하더라도 이런 정식 표현이 더 바람직할 것으로 보인다. 이렇게 원래의 모습으로 변형시켜 놓으면 이제 ICommandCallback 타입의 콜백객체를 사용할때와 구조는 유사하게 된다.

지금까지는 AdoTemplate 객체와 콜백 객체를 사용해서 DB 데이터에 접근하고 조작하는 구조에 대한 이야기였다.

AdoTemplate의 Execute형의 메소드를 사용하면  DB의 데이터를 조작하고 쿼리하는 모든 작업을 할 수 있다. 그러나 AdoTemplate에는 데이터 쿼리를 위한 좀 더 특별한 메소드 타입이 더 있다.  QueryWith를 접두어로 하고 있는 메소드류가 그것인데, 이 타입의 메소드를 사용하면 OR 매핑( Object Relational Mapping )이라는 작업을 할 수 있다. 이 OR 매핑 작업을 해주면 조회되는 레코드를 애플리케이션단에서 정의하고 있는 객체로 변환시킬 수 있게 된다. 이 방법을 사용하면 데이터 액세스 레이어의 DAO 객체로 DataSet 대신에 사용자 정의 객체 집합을 반환해 줄 수 있게 된다.  이 방법에 대해서는 다음 포스트에. 휴~ !