본문 바로가기
연재/NHibernate

[챕터1] NHibernate 2

by 그저그런보통사람 2011. 7. 31.



* 스크롤의 압박 때문에 길어질 거 같은 경우는 가급적 새 글로 나누도록 하겠습니다.



여러 종류의 ORM 프레임워크 은 각각 장단이 있습니다. (이 부분은 구글신에게 기도를...)=>검색해보란 뜻 ㅋㅋ

여기서는 하이버네이트의 장점만 살펴보겠습니다. 


1. ORM 특징대로 저수준 (row-level)의 SQL 관련된 코딩이 필요치 않습니다.

모든 SQL Query는 하이버네이트가 위임받아 처리되며, DB에는 생성된 쿼리가 전달되어 처리됩니다. 

개발자는 반복되는 지루한 작업으로부터 벗어나 비즈니스에 집중할 수 있는 생산성을 얻을 수 있습니다.


2. 1.에서 SQL에 관련된 코드가 제거 됨으로서 코드가 간결해집니다. 

코드가 간결해짐으로써 가독성이 좋아지고 비즈니스 로직이 명확하게 드러나며 그로 인해 시스템 리펙토

링이 쉬워집니다. 이로인해 개발자는 업무의 흐름에 대한 직관성을 확보하여 유지보수가 용이해지는

이점을 얻을 수 있습니다.


1. 2. 에 대한 예를 들면 아래 코드는 전통적인 방법으로 작성된 DB 관련 코드입니다.

public class DAO
{
    private const string connectionString = @"connection provider string";
 
    public DataTable GetChildsOfParent(string parentId)
    {
        const string sql = 
            "select ChildNo, ChildName from Child where ParentNo = @parentId";
 
        try
        {
            using (var conn = new SqlConnection(connectionString))
            using (var cmd = new SqlCommand(sql, conn))
            {
                cmd.Parameters.Add("@parentId"SqlDbType.VarChar, 20).Value = parentId;
                IDataReader reader = cmd.ExecuteReader();
                DataTable dataChilds = new DataTable();
                dataChilds.Load(reader);
                return dataChilds;
            }
        }
        catch (SqlException sqlException)
        {
            // logging
        }
        catch (Exception e)
        {
            // logging
        }
        return null;
    }
}

GetChildsOfParent 메소드는 부모 식별자를 이용하여 관계를 맺고 있는 자식들의 목록을 가져오는 코드입니다.

다음의 코드는 하이버네이트를 이용하여 비슷한 작업을 하고 있습니다.

public IList<Child> GetChildsOfParent(string parentId)
{
    try
    {
        using (var session = NHibernateProvider.SessionFactory.OpenSession())
        {
            var query = session.Get<Parent>(parentId);
            return query.MyChilds;
        }
    }
    catch (HibernateException hibernateException)
    {
        // logging
    }
    catch (Exception exception)
    {
        // logging
    }
    return null;
}

ORM 프레임워크를 사용하면 개발자가 실제 데이터베이스에 요청할 쿼리(정확하게는 쿼리식)에 대해서

알 필요가 없습니다. (하지만 이건 어디까지나 표면적인 문제고 실제로는 DB에 대한 어느 정도의 수준이 요구됩니다)

위 코드가 수행되면 하이버네이트가 내부적으로 쿼리를 생성하여 데이터베이스에 아래와 같이 전달합니다.

NHibernate: 
    SELECT
        parent0_.PARENT_NO as PARENT1_3_0_,
        parent0_.PARENT_NAME as PARENT2_3_0_ 
    FROM
        Parents parent0_ 
    WHERE
        parent0_.PARENT_NO=@p0;
    @p0 = 1 [Type: Int32 (0)]

어라?! 분명히 코드상의 업무는 부모에 대한 자식 리스트 반환하는 내용인데 부모 테이블만 조회하네요?!

이유는 하이버네이트가 가지고 있는 지연 로딩 (Lazy Loading) 기술 때문입니다.

지연 로딩은 해당 데이터가 호출하는 처리문에서 가져오는 방식이 아닌 실제 사용되는 시점에 요청하여 가져오는

방식입니다.

위에서 처음 호출은 부모를 호출했습니다. (아래 코드에서 Get<>의 제네릭 타입을 보세요.)

var query = session.Get<Parent>(parentId);

엥?! 우리가 생각하기에 "참조 외래키를 이용해서 자식 데이터만 가져오면 되는데 왜 굳이 불필요하게

부모 테이블을 조회하는거야?!
" 라고 할 수 있습니다.

이는 데이터베이스 기준에서 보면 맞는 말입니다. 하지만 부모 테이블에 관계를 맺는 자식 테이블이 10개 

이상이라고 가정해 봅시다. 데이터베이스 기준에서 부모 식별키에 대한 각각의 테이블의 데이터를

가져오려면, 가져오려는 개수만큼 쿼리식이 생성되어야 하고, 맨 처음 작성한  방법(저수준의 DB 호출 코드)

대로 한다면 각각의 개별적인 처리를 위한 메소드가 또 다시 생성되어야 할 것입니다. 

하지만 객체 관점에서는 객체망 순환 방식을 이용해서 접근하기만 하면 됩니다. 즉, 부모 클래스 기준으로

객체망을 이용하여 접근하기만 하면 되는 것입니다.

([챕터1] NHibernate 시작 1 에서 예제로 사용한 Parent 와 Child 클래스간에 Parent.MyChilds[0], 또는

Child.MyParent 식으로 접근)

지연로딩 기술로 실제로 자식 데이터가 호출되는 시점은 위의 코드 (하이버네이트로 Child 리스트를 반환하는)

가 아닌 아래와 같이 반복기를 이용하여 실제 Child를 조회할 때 가져옵니다.

// DAO 클래스의 인스턴스를 얻어 dao 변수에 대입.
 
var childs = dao.GetChildsOfParent("1"); //Child 레코드를 호출하지 않음.
 
foreach (var child in childs)
{
    // 실제로 Child 목록을 요구하는 Query를 DB에 전달하여 가져옴.
    Console.WriteLine(child.ChildName); 
}

DAO 클래스 메소드에 위 코드가 연속적으로 작성되어 있다며 아래와 같이 수행됩니다.

NHibernate: 
    SELECT
        parent0_.PARENT_NO as PARENT1_3_0_,
        parent0_.PARENT_NAME as PARENT2_3_0_ 
    FROM
        Parents parent0_ 
    WHERE
        parent0_.PARENT_NO=@p0;
    @p0 = 1 [Type: Int32 (0)]
NHibernate: 
    SELECT
        mychilds0_.PARENT_NO as PARENT3_1_,
        mychilds0_.CHILD_NO as CHILD1_1_,
        mychilds0_.CHILD_NO as CHILD1_2_0_,
        mychilds0_.CHILD_NAME as CHILD2_2_0_,
        mychilds0_.PARENT_NO as PARENT3_2_0_ 
    FROM
        Childs mychilds0_ 
    WHERE
        mychilds0_.PARENT_NO=@p0;
    @p0 = 1 [Type: Int32 (0)]

이제 우리가 기대한 결과대로 되었네요. 비록 약간의 오버헤드(부모를 호출하는)가 발생했지만,

우리는 그로 인해 많은 편리함을 얻을 수 있습니다.


3. 성능의 이점을 얻을 수 있습니다.

직접 작성한 쿼리나 솔루션이 자동화 솔루션(하이버네이트와 같은)보다 빠르다고 생각하는 사람들이 있습니다.

이는 닷넷 코드보다 어셈블리 혹은 기계어가 빠르다고 하는 논리와 같습니다.

이는 핵심을 벗어나는 이야기 입니다. 직접 만든 솔루션이 최소한 실제 애플리케이션에서도 잘 동작한다는

전제를 기준으로 직접 개발하는 솔루션이 자동화 솔루션을 도입하는 비용과 비슷할 때만 받아들일 수 있

습니다. 생각해보세요. 직접 솔루션을 개발할 때 들어가는 시간과 비용이 어떨지... 

성능을 위한 최적화 방법은 몇몇은 직접 (예를 들어 오라클의 쿼리 힌트) 작성하는 편이 더 쉽고 낫습니다.

하지만 그 외의 대부분은 자동화된 ORM 프레임워크를 사용하는 것이 훨씬 쉽습니다.

ORM 프레임워크를 개발한 사람은 비즈니스를 구현하는 우리 개발자보다 더 많은 시간을 최적화에 투자

한다는 사실을 알아야 합니다. 실제 하이버네이트의 쿼리 최적화를 위해 DBA가 투입되었다고 합니다.

위 1.2. 항의 예와 같은 성능 이슈 해결과 하이버네이트가 구현해 내는 쿼리 최적화, 캐시 등과 같은 최적화

기법은 우리가 DB로부터 데이터를 가져오는데 "성능은 과연" 이라는 의구심으로 부터 벗어나게 해줄 것

입니다
. (서적에서 인용한 글임)

하지만 모든 부분에 대해서 성능이 최상이라고 보장 받을 수 없습니다. 어떨 때는 직접 (프로시저 작성 등) 

최적화 작업이 요구되고, 이러한 상황에 대해서도 하이버네이트는 수용할 수 있는 인터페이스를 제공합니다.

 
4. 특정 벤더에 종속적이지 않아(독립성) 이식성이 뛰어납니다.

하이버네이트는 MS-SQL이나 오라클등의 DB의 플랫폼 특성으로부터 추상화 됩니다.

즉, 특정 DB에 대한 종속성을 피하여 이기종 DB에 대한 이식성이 매우 뛰어납니다.

하지만 완벽한 이식성이 보장되려면 플랫폼이 같는 장점을 포기해야 합니다. 

이식성과 플랫폼의 장점 간의 우선순위는 업무를 진행할 때 의사결정을 통해 결정해야 겠지만, 우리가 사용

해온 기존 개발 방식, 플랫폼 등에 비하면 매우 뛰어난 플랫폼 이식성을 갖춘 애플리케이션을 개발할 수

있습니다. 어느날 갑자기 고객이 Oracle로 개발된 애플리케이션을 MS-SQL이나 DB2, 사이베이스 등의 다

른 플랫폼으로 이관하겠다는 요구가 들어왔다고 생각해보세요. PL-SQL로 개발된 모든 코드는 다시 타 DB

에 맞춰 수정되어야 합니다.
  야근/철야가 눈 앞에 훤하지 않나요?!



 
위 4가지 특징이자 장점은 우리 같은 개발자의 선배, 그 선배의 선배들로부터 겪은 문제와 고민등의 집합체로 나온 결과물

의 하나로 하이버네이트가 존재한다고 생각합니다.

그러나 절대적인 만능은 없드시 하이버네이트는 장점 외에도 단점이 분명히 존재합니다.

(학습 시간이 많이 요구되고, 어느정도 DB에 대한 지식이 요구 다는 점 외에도 환경적 제약 등으로 도입이 어려운 점 등이

있습니다. "하이버네이트의 단점" -> 검색 키워드 ㅋㅋㅋㅋ)

(검색 결과에 대한)단점에서처럼 도입이 껄끄럽고 오히려 비용이 많이 발생한다면 과감하게 기존 방식에서 발전성을 찾거

나 다른 방법을 강구하는게 맞습니다.) 

제가 말하고자 하는 것은 이런 좋은 프레임워크를 단지 학습하기 싫다 또는 그냥 막연히 안좋을 거 같다라는 가정으로

접근하지 말고 한 번 직접 시간내서 해보고 몸으로 느껴봤으면 하는 바램입니다. 


참고: 하이버네이트에 대한 오해와 미신, 무지 http://toby.epril.com/?p=468

중간에 링크 깨지는 부분은  (성능에 관련된) => 하이버네이트 성능




개인적인 시간과 회사 업무 등과 병행하려니 생각보다 쉽지가 않네요. 글을 쓴다는게 얼마나 부담되고 많은 시간이 

요구되는지 직접 겪어보니, 많은 개발에 대한 정보를 올려주는 분들이 고맙게 느껴집니다.


다음 챕터부터 실제로 프로젝트를 겸하여 진행하도록 하겠습니다.