IT 이야기

하이버네이트 활용전략

달팽이1 2008. 8. 30. 01:31

하이버네이트 활용전략

박재성(NHN 블로그 서비스팀)   2008/08/16

 

하이버네이트는 객체 지향 프로그래밍과 관계형 데이터베이스의 철학적인 차이로 인해 발생하는 많은 제약사항을 해결해주는 좋은 ORM 프레임워크이다. 또, EJB 3.0의 JPA 스펙까지 지원하고 있지만 하청 위주의 구조적인 문제 탓에 아직 국내에서는 제대로된 대접을 받지 못하는 프레임워크이기도 하다. 특집 1부에서는 ORM 프레임워크의 개념과 함께 하이버네이트 기반의 ORM 활용 기법들에 대해 알아본다.

필자와 Object Relation Mapping(이하 ORM) 프레임워크의 첫 만남은 2002년 Contents Management System(이하 CMS) 솔루션을 개발할 때였다. 당시에 필자가 사용했던 ORM 프레임워크는 캐스터(Castor: http://www.castor.org)다.

그때만 해도 ORM 프레임워크가 지금처럼 많지 않은 상황이었으며, 기술적인 제약사항이 많았기 때문에 솔루션 개발에 적용하는 데 많은 어려움을 겪을 수밖에 없었다. JDBC 기반 개발 경험 밖에 없는 개발자들이 ORM 프레임워크의 새로운 개발 방식을 적용하는 것은 쉬운 일이 아니었다.

경험부족과 문서화까지 부족한 상태에서 솔루션 개발을 마무리하는 것은 개발자들의 야근과 일정 지연이라는 결과를 초래할 수밖에 없었다.

그러나 ORM의 위력은 CMS 솔루션 개발을 완료한 다음부터 나타났다. 최초 CMS 솔루션은 오라클 데이터베이스 기반으로 개발되었다. 그러나 CMS 솔루션을 사용하는 모든 고객이 오라클 데이터베이스를 보유하고 있는 것은 아니었다. 심지어 자바 기반에 MSSQL을 사용하고 있는 고객까지 있는 상태였다.

Castor ORM 프레임워크 또한 지금까지 존재하는 대부분의 관계형 데이터베이스를 지원하고 있었기 때문에 고객이 사용하고 있는 데이터베이스를 사용하면서 CMS 솔루션을 배포하는 것이 가능했다.

2002년 ORM 프레임워크를 처음 사용하면서 필자는 그 가능성과 막강함으로 인해 조만간 많은 프로젝트에서 사용될 거라고 생각했다. 하지만, ORM 프레임워크는 5년이 지난 지금 기술적으로 많은 발전을 해왔다. 그러나 국내 프로젝트에 적용은 극히 미미한 상태이다. 가장 큰 원인은 ORM 프레임워크에 대한 학습 곡선이 높다는 것이다.

SI 프로젝트가 일반화되어 있는 국내 환경에서 학습 곡선이 높은 ORM 프레임워크는 프로젝트를 성공으로 이끄는데 위험요소가 크기 때문이다. 그러나 ORM 프레임워크가 가지고 있는 무한한 가능성 때문에 ORM 프레임워크에 관심을 더 이상 늦출 수 없다.

이번호 특집을 통해 ORM 프레임워크가 가지는 가능성을 이해하고 ORM 프레임워크에 대한 관심을 가질 수 있는 계기가 되었으면 한다.

  ORM 프레임워크란?

최근 자바나 C#, 루비 같은 객체 지향 프로그래밍 언어가 애플리케이션 개발의 주요 언어로 사용되고 있다. Object Oriented Programming(이하 OOP)이 애플리케이션 개발의 주 패러다임이 된 것이 사실이다. OOP적인 개발이 가능하도록 하기 위해서는 객체들의 관계를 데이터베이스와 같은 영구 저장소에 저장한 다음 그대로 복원할 수 있어야 한다.

이에 대한 해결책으로 객체 지향 데이터베이스가 등장하게 되었으며, 일부분에서 적용되기도 했다. 그러나 역사가 그리 길지 않은 객체 지향 데이터베이스는 실행 속도의 문제 때문에 그리 오래 사용되지 못했다.

현재 대부분의 프로젝트에서 영구 데이터 저장소로 관계형 데이터베이스를 사용하고 있다. 그러나 관계형 데이터베이스를 사용하면서 발생하는 문제점은 객체와 객체들 사이의 관계를 데이터베이스에 그대로 저장하기 힘들다는 것이다.

관계형 데이터베이스는 데이터들의 집합 개념을 기반으로 하고 있기 때문에 객체 지향 개발 방식과의 근본적인 차이점으로 인해 많은 어려움이 발생한다.

관계형 데이터베이스에 저장된 데이터를 객체 지향적으로 변환하는 작업과 그 반대의 작업 또한 가능하다. 그러나 이 같은 차이점을 해결하는데 개발자들이 많은 시간을 투자해야 한다. 예를 들어 트리 구조의 관계형 데이터를 객체 지향적으로 변환하기 위하여 개발자들이 구현해야 하는 소스 코드를 보면 <리스트 1>과 같다.

 <리스트 1> ORM 프레임워크를 사용하지 않았을 때의 소스 코드


<리스트 1>의 예제 소스를 보면 관계형 데이터베이스에 저장되어 있는 모든 데이터를 오라클 쿼리의 ‘CONNECT BY’ 구문을 이용하여 데이터를 조회한 다음 객체 지향 형태로 데이터를 변경하는 소스 코드이다. <리스트 1>의 소스 코드를 Hibernate ORM 프레임워크(이하 하이버네이트)를 이용하여 개발할 경우 다음과 같이 간단하게 구현하는 것이 가능하다.

 <리스트 2> ORM 프레임워크를 사용할 때의 소스 코드


<리스트 1>과 같이 ORM 프레임워크를 이용하지 않더라도 충분히 객체 지향적으로 개발하는 것이 가능하다. 그러나 <리스트 1>에 구현된 예제 소스와 같이 개발하려면 많은 시간을 투자해야 하며, 버그가 발생할 확률이 높은 것이 사실이다.

ORM 프레임워크는 객체들의 관계와 집합 데이터 사이의 차이로 인해 발생하는 간극을 없애주는 역할을 한다. 이는 결과적으로 객체 지향적으로 개발하려는 개발자들이 관계형 데이터베이스를 사용함으로써 가지게 되는 제약사항을 줄여주는 역할을 한다.

ORM 프레임워크는 관계형 데이터베이스 계층을 추상화하고 있는 또 하나의 계층을 두어 관계형 데이터와 객체 사이의 직접적인 인터페이스를 근본적으로 차단하는 역할을 한다. 애플리케이션 계층을 담당하고 있는 객체들은 관계형 데이터와 직접적으로 접근하는 것이 아니라 ORM 프레임워크가 제공하는 API를 사용하게 된다.

<그림 1>은 애플리케이션 계층과 ORM 프레임워크 계층, 관계형 데이터베이스 계층 사이의 관계를 잘 보여주고 있다.

<그림 1> 애플리케이션, ORM 프레임워크, 관계형 데이터베이스 계층 사이의 관계



<그림 1>과 같이 애플리케이션 계층이 직접적으로 관계형 데이터베이스 계층과 인터페이스를 하지 않기 때문에 관계형 데이터베이스가 다른 데이터베이스로 변경되더라도 애플리케이션 계층을 변경할 필요가 없다. ORM 프레임워크 기반으로 개발할 때 얻을 수 있는 장점 중의 하나이다.

현재 자바 기반의 ORM 프레임워크로는 하이버네이트와 캐스터, 톱링크(TopLink) 등 상당히 많은 종류가 있다. 특집 1부에서는 그 중에서도 ORM 프레임워크의 선두주자라고 할 수 있는 하이버네이트에 대해 알아본다.

  하이버네이트 맛보기

필자는 새로운 프레임워크를 시작할 때 간단한 예제를 실행하여 직접 동작하는 모습을 확인한 다음에 세부 항목들을 분석한다. 물론 다양한 접근 방법이 있겠지만 필자가 즐겨 사용하고 있으며, 확실히 효과를 보고 있다고 생각하는 이 방법을 기반으로 하이버네이트를 분석해 나가보자.

하이버네이트를 시작하는 개발자들에게 가장 좋은 예제는 하이버네이트에 포함되어 있는 샘플 예제이다. 세 개의 테이블로 구성되어 있는 간단한 경매 관련 예제이다. 자바 객체와 데이터베이스 테이블간의 차이점을 이해하고, 이 둘 사이의 차이점을 하이버네이트를 이용하여 어떻게 해결하는 것이 가능한지 알 수 있다.

경매 예제
하이버네이트에 포함되어 있는 예제는 경매를 통하여 물건을 팔려는 사용자(Seller)와 경매에 나온 물건을 사려는 입찰자(Bidder)의 관계를 자바 객체와 관계형 테이블로 모델링한 예제이다. 자바 객체와 관계형 테이블을 모델링한 결과는 <그림 2>, <그림 3>과 같이 다르다.

<그림 2> 경매 예제에 대한 클래스 다이어그램



 

<그림 3> 경매 예제에 대한 ERD



하이버네이트 시작하기
하이버네이트 기반으로 애플리케이션을 개발하기 위한 첫 번째 작업은 하이버네이트 API를 사용하기 위하여 필요한 최소한의 라이브러리를 설치하는 것이다. 하이버네이트 API를 사용하기 위한 최소한의 라이브러리는 <그림 4>와 같다.

<그림 4> 하이버네이트 API를 이용하기 위한 최소한의 라이브러리



<그림 4>의 모든 라이브러리는 하이버네이트 코어 최신 버전을 다운 받은 후 [HIBERNATE_HOME]/lib 디렉토리에서 찾을 수 있다. <그림 4>의 라이브러리 외에 필요한 라이브러리는 데이터베이스의 JDBC 드라이버이다. 지금까지 추가한 라이브러리만으로 하이버네이트 대부분의 기능을 이용할 수 있다.

자바 소스 및 테이블 생성
하이버네이트 기반 애플리케이션을 개발하기 위한 두 번째 과정은 자바 소스와 테이블을 생성하는 것이다. <그림 2>의 클래스 다이어그램에서 User 클래스를 자바 소스로 구현한 예제는 <리스트 3>과 같다.

 <리스트 3> User 예제 소스


<리스트 3>의 User 클래스는 하이버네이트 API에 종속적인 부분이 없는 일반 POJO 클래스에 지나지 않는다. <리스트 3>의 예제 소스와 같이 Name, Bid, AuctionItem 클래스 또한 POJO 기반으로 구현한다. <그림 3>의 테이블 또한 하이버네이트의 사용과 관계없이 구현할 수 있다.

테이블 자동 생성  

하이버네이트는 객체와 테이블의 매핑 설정 정보를 바탕으로 데이터베이스에 테이블이 존재하지 않을 경우 자동으로 생성하는 것이 가능하다. 하이버네이트 설정 파일에 다음과 같이 설정하기만 하면 된다.

<property name=“hibernate.hbm2ddl.auto”>create</property>

애플리케이션을 개발하는 과정에서 테이블 스키마가 변경되는 경우가 많다. 따라서 테이블 스키마가 안정화될 때까지 이 기능을 이용하여 많은 도움을 받을 수 있다. 자바 소스와 매핑 파일만 생성하면 테이블을 자동 생성할 수 있기 때문이다.

그러나 이 기능을 이용할 경우 기본적인 테이블 스키마는 생성할 수 있으나 세세한 부분까지 설정하기는 힘든 탓에 실제 서버에 애플리케이션을 배포할 때 이 기능을 사용하기는 어렵다. 애플리케이션을 실 서버에 배포할 때는 하이버네이트가 제공하는 툴을 이용하여 DDL 스크립트를 생성한 다음 세부사항을 추가하여 테이블을 생성하는 것이 좋다.

하이버네이트는 이클립스 플러그인과 커맨드 라인 툴, Ant Task를 지원하고 있다. 이에 대한 자세한 정보는 http://www.hibernate.org/hib_docs /reference/en/html/toolsetguide.html 에서 찾을 수 있다.



하이버네이트 설정 파일
하이버네이트 기반 애플리케이션을 개발하기 위한 세 번째 과정은 자바 객체와 테이블을 매핑하기 위한 매핑 설정 파일이다. 매핑 설정 파일은 매핑할 자바 클래스와 테이블에 관한 정보를 포함하고 있으며, 자바 클래스의 속성과 테이블의 컬럼을 매핑하는 정보를 가지고 있다.

User 클래스와 AuctionUser 테이블의 매핑을 담당하고 있는 매핑 설정 파일은 <리스트 4>와 같다.

 <리스트 4> User 클래스와 AuctionUser 테이블의 매핑 설정 파일


하이버네이트 기반으로 개발을 하기 위하여 마지막으로 구현할 부분은 데이터베이스에 대한 설정 정보와 <리스트 4>에서 정의한 하이버네이트 매핑 파일을 설정하는 하이버네이트 설정 파일을 생성하는 것이다. 하이버네이트 설정 파일은 디폴트로 클래스 패스 하위에 hibernate.properties 또는 hibernate.cfg.xml 파일로 작성하면 된다.

이 문서의 예제에서는 hibernate.cfg.xml 파일을 기준으로 작성하였다. 앞에서 살펴본 경매 예제에서 작성한 하이버네이트 설정 파일은 <리스트 5>와 같다.

 <리스트 5> 하이버네이트 설정 파일


<리스트 5>의 하이버네이트 설정 파일에서는 데이터베이스에 대한 접속 정보, 사용할 데이터베이스의 종류, 하이버네이트 기본적인 설정, 자바 객체와 테이블 매핑 파일들에 대한 정보를 포함하고 있다.

<리스트 4>와 <리스트 5>의 하이버네이트 매핑 및 설정 작업은 JDBC 기반으로 개발할 때는 구현하지 않아도 되는 지루한 작업이다. 그러나 이 같은 매핑 작업으로 인해 많은 이점을 가지게 된다. <리스트 1>의 구현이 <리스트 2>와 같이 간단한 구현으로 쉽게 해결할 수 있는 것이 대표적인 예이다.

하이버네이트 매핑 파일과 설정파일에 대한 세부 항목은 하이버네이트 레퍼런스 문서를 참고하길 바란다.

기본 CRUD
하이버네이트 매핑 파일과 설정 파일에 대한 개발을 완료했다면 다음은 하이버네이트 API를 이용하여 <리스트 3>의 자바 객체를 이용하여 데이터베이스와 인터페이스 하는 것이 가능하다. <리스트 4>의 매핑 작업으로 인해 데이터베이스와의 인터페이스를 쉽게 구현할 수 있다.

하이버네이트 API를 이용하여 데이터베이스와 인터페이스 하는 템플릿 소스 코드는 <리스트 6>과 같다.

 <리스트 6> 하이버네이트 기반 개발을 위한 템플릿 소스 코드


<리스트 6>의 템플릿 소스 코드를 기반으로 기본적인 CRUD를 구현하는 과정을 살펴보면 다음과 같다.

<리스트 3>의 자바 객체에 저장되어 있는 데이터를 데이터베이스에 저장하기 위한 방법은 org.hibernate.Session API의 save(), persist() 메소드를 이용하여 다음과 같이 구현한다.

Serializable identifier = session.save(seller);
session.persist(seller);

save()와 persist() 메소드의 차이점은 반환 값의 존재유무이다. save() 메소드는 생성된 객체의 ID 값을 반환하는 반면 persist의 반환 값은 void이다. 애플리케이션을 개발할 때 오라클의 Sequence와 같이 Primary Key 값이 데이터베이스에 의하여 자동 증가하는 경우가 있다.

이와 같이 Primary Key 값이 프레임워크나 데이터베이스에 의하여 자동 생성되는 경우에는 save() 메소드를 이용하여 객체 ID 값을 반환하여 재사용하는 것이 가능하다.

자바 객체의 데이터를 수정, 삭제하는 방법 또한 객체의 생성만큼 간단하다. 자바 객체에 수정, 삭제는 org.hibernate. Session API의 update(), delete() 메소드를 이용하여 구현할 수 있다.

session.update(seller);
session.delete(seller);

데이터베이스에 저장되어 있는 데이터를 자바 객체로 변환하는 방법 또한 앞의 예만큼 쉽게 구현할 수 있다. 자바 객체로 데이터를 로딩하는데에는 org.hibernate.Session API의 get(), load() 메소드를 이용하여 구현할 수 있다.

User seller = (User)session.get(User.class, new Long(1));
User seller = (User)session.load(User.class, new Long(1));

get(), load() 메소드의 가장 큰 차이점은 데이터를 로딩할 때의 처리 방식이다. get() 메소드는 호출되는 시점에 쿼리를 실행하여 데이터를 로딩하는 반면 load() 메소드는 호출되는 시점에는 쿼리를 실행하지 않고 단순히 프록시 클래스를 반환한다.

load() 메소드가 쿼리를 실행하는 시점은 load() 메소드에 의하여 반환된 프록시 클래스의 API를 호출하는 시점이다. 또 하나의 차이점은 로딩하기 위한 데이터가 존재하지 않을 때의 처리 방식이다. get() 메소드는 Identifier에 해당하는 데이터가 존재하지 않을 경우 null 값을 반환한다.

그러나 load() 메소드의 경우 org.hibernate.ObjectNotFoundException을 throw한다.

애플리케이션을 개발할 때 데이터베이스와의 인터페이스에서 많은 시간을 투자해야 하는 부분이 기본 CRUD를 구현하는 과정이다. 복잡하고 어려운 구현은 아니지만 단순, 반복적으로 구현해야 하는 작업은 개발자들의 사기를 떨어뜨리기에 충분하다.

기본 CRUD에 대한 메소드를 생성하고, 각각에 대한 쿼리를 생성하는 작업은 <리스트 3>의 매핑 파일에 투자하는 시간과 크게 다르지 않다.

하이버네이트 기반으로 개발할 때 얻게 되는 하나의 이점은 기본 CRUD에 대한 공통 API를 쉽게 구현할 수 있다는 것이다. JDK 5.0부터 제공하는 Generic 기능을 이용하여 DAO 클래스에 대한 기본 클래스를 구현하고 재사용하는 것이 가능하다.

 <리스트 7> 기본 CRUD에 대한 인터페이스를 정의하고 있는 GenericDao.java


<리스트 7>의 인터페이스를 하이버네이트 기반으로 구현한다. 모든 DAO 클래스는 <리스트 7>의 구현 클래스를 상속함으로써 기본적인 CRUD에 대한 API를 제공하는 것이 가능해진다. <리스트 7>의 인터페이스에 대한 구현체는 지금까지 데이터베이스와 인터페이스를 위한 기본 CRUD 작업을 줄여주기 때문에 개발 생산성 향상에 기여한다.

Criteria, HQL
데이터베이스와의 인터페이스에서 기본 CRUD 외에 특정 조건에 해당하는 데이터를 로딩해야 하는 경우가 많다. 하이버네이트는 SQL에서 제공하는 다양한 조건들을 지원하기 위한 방법으로 Criteria API와 HQL(Hibernate Query Language)을 지원하고 있다.

Criteria API는 SQL의 모든 구문들을 하이버네이트 API를 통하여 생성하는 것이 가능하도록 지원하고 있으며, HQL은 SQL과 비슷한 구문을 사용하지만 객체 기반으로 쿼리를 구현하는 것이 가능하도록 지원하고 있다.

Criteria API와 HQL 기반으로 앞의 경매 예제를 구현하면 <리스트 8>, <리스트 9>와 같다.

 <리스트 8> Criteria API 기반으로 데이터를 조회하는 예제


 <리스트 9> HQL 기반으로 데이터를 조회하는 예제


Criteria API는 새로운 API를 학습해야 한다는 부담은 있지만 SQL에 익숙하지 않은 개발자들이 접근하기에는 유용한 방법이다. 반면 HQL은 SQL과 비슷한 구문을 가지고 있기 때문에 SQL의 사용에 익숙한 개발자들이 접근하기 좋은 방법이다.

HQL은 테이블을 직접 사용하는 것이 아니라 자바 객체와 객체의 속성을 사용한다는 것을 제외하면 SQL과 같은 구문을 사용하는 것이 가능하다.

그러나 하이버네이트를 사용하다 보면 SQL의 사용에 익숙해져 있는 개발자들도 직관적인 Criteria API를 조금씩 사용하면서 Criteria API의 매력에 빠지는 경우가 많다.

필자의 경우 하이버네이트를 사용하던 초기에는 SQL과 비슷한 구문을 가지는 HQL을 선호했지만, 시간이 지나면서 Criteria API를 사용하는 빈도수가 높아졌다. 최근에는 가능한 Criteria API를 이용하여 대부분의 기능을 구현하고 있다.

하이버네이트가 SQL을 직접 사용하지 하는 것이 아니라 Criteria API와 HQL을 별도로 지원하는 이유는 특정 데이터베이스에 종속적이지 않도록 하기 위해서이다. 하이버네이트는 특정 데이터베이스만을 지원하는 것이 아니라, 현재 존재하는 대부분의 관계형 데이터베이스를 지원하기 위하여 Criteria API와 HQL을 통하여 지원하고 있다.

Criteria API와 HQL에 의하여 작성된 소스 코드는 하이버네이트에 의하여 SQL로 변환된 다음 데이터베이스와 인터페이스 하기 때문이다. <리스트 8>의 Criteria API를 통하여 생성된 SQL 코드는 <리스트 10>과 같다.

 <리스트 10> <리스트 8>에 의하여 생성된 SQL


하이버네이트는 Criteria API와 HQL을 이용하여 대부분의 기능을 구현하는 것이 가능하다. 그러나 Criteria API와 HQL에 의하여 구현할 수 없는 기능이나 특정 데이터베이스에 종속적인 기능의 사용을 위하여 Native SQL을 사용하는 것이 가능하도록 지원하고 있다.

 <리스트 11> Native SQL을 직접 사용하는 예제


  하이버네이트 프레임워크의 튜닝 전략

ORM 프레임워크를 적용할 때 많은 시간을 투자해야 하는 부분이 실행속도 측면이다. 또한 가장 해결하기 힘든 부분이기도 하다. 지금까지 JDBC 기반으로 개발할 때는 SQL을 이용하여 원하는 데이터(특정 테이블의 특정 칼럼)만을 조회하는 것이 가능했다.

그러나 ORM 프레임워크를 이용할 경우 필요하지 않은 데이터라도 ORM 프레임워크의 구조적인 한계 때문에 데이터를 조회하는 부담을 가지게 된다.

예를 들어 앞의 경매 예제에서 ‘A라는 사용자가 경매로 판매하고자하는 모든 상품을 조회해야 한다’라는 기능을 구현한다고 가정하자. 지금까지의 JDBC 기반으로 API를 구현할 때는 AuctionUser 테이블과 AuctionItem 테이블의 join 함으로써 한 번의 쿼리로 해결할 수 있었다. 그러나 ORM 프레임워크의 경우 다음과 같이 N+1 만큼의 쿼리가 실행된다.

select * from AuctionUser where id=1; => 1 번의 쿼리 실행.
select * from AuctionItem where seller=1; => 조회된 경매 상품 수(N) 만큼 쿼리 실행

이는 ORM 프레임워크의 동작 원리 때문에 발생하는 문제로 일반적으로 N+1 문제라고 이야기한다. N+1 문제와 같이 기존의 JDBC API를 사용할 때는 발생하지 않았던 실행속도의 저하 문제로 인해 많은 개발자들이 ‘ORM 프레임워크는 느리다.”라는 인식을 하게 되었다.

그러나 하이버네이트는 다양한 방식을 이용하여 실행 속도의 향상을 꾀하고 있다. 하이버네이트를 적절히 사용할 경우 기존 JDBC API 기반보다 더 빠른 애플리케이션을 개발하는 것이 가능하다.

이 문서에서는 하이버네이트가 지원하는 실행속도 향상 방안 중에서 가장 큰 효과를 얻을 수 있는 일부 기능에 대하여 살펴보도록 하겠다.

첫째, 하이버네이트를 이용할 때 가장 많이 사용하는 실행속도 향상 방안은 연관되어 있는 테이블의 데이터를 필요한 시점에 로딩하도록 지원하는 Lazy Loading 기능이다.

Lazy Loading은 한 번에 모든 데이터를 로딩하는 것이 아니라 연관되어 있는 데이터가 필요한 시점에 데이터를 로딩하기 때문에 실행속도의 향상을 가져올 수 있다. 하이버네이트 2.x 버전까지는Lazy Loading의 디폴트 값이 false였다.

그러나 Lazy Loading에 대한 활용도가 높아지면서 하이버네이트 3.x 버전부터는 Lazy Loading의 디폴트 값이 true로 바뀌었다. 따라서 연관된 클래스를 매핑 시 다음과 같은 설정만으로 Lazy Loading 기능을 활성화할 수 있다.

<bag name=“bids” inverse=“true” cascade=“save-update,lock”>
<key column=“bidder”/>
<one-to-many class=“Bid”/>
</bag>

단, Lazy Loading 기능을 사용할 때 주의할 점은 사용하고 있는 Session을 현재 Thread가 종료되기 전까지 close 하면 안 된다는 것이다. 하이버네이트는 Open Session in View Pattern (http://www.hibernate.org/43.html) 기반으로 애플리케이션을 개발함으로써 이 같은 문제를 해결하도록 가이드하고 있다.

둘째, ORM 프레임워크에서 N+1 문제는 커다란 골칫거리다. 하이버네이트는 fetch 모드를 결정할 수 있도록 지원함으로써 문제를 해결할 수 있다. 매핑 설정 파일에서 다음과 같이 설정해서 JDBC API 기반으로 개발할 때와 같은 효과를 얻을 수 있다.

<bag name=“bids” inverse=“true” cascade=“save-update,lock” fetch=“join”>
<key column=“bidder”/>
<one-to-many class=“Bid”/>
</bag>

마지막으로 살펴볼 실행속도 향상 방안은 적절한 캐싱의 사용이다. JDBC API 기반으로 개발할 때는 캐싱 프레임워크를 사용하기 위해서는 많은 시간을 투자해야 했다. 그러나 하이버네이트를 이용하면 간단한 설정 파일의 추가로 캐싱 기능을 적용하는 것이 가능하다.

하이버네이트에서 캐싱 기능을 사용하기 위해서는 하이버네이트 설정 파일에 어떤 캐싱을 사용할지 hibernate.cache. provider_class 속성을 이용하여 설정해야 한다. 하이버네이트가 지원하는 캐싱은 EHCache, OSCache, SwarmCache, JBoss TreeCache가 있다.

이 캐싱 프레임워크에서 하나를 설정한 다음, 어떤 모델 클래스에 대하여 캐싱을 적용할지 결정하는 것이다. 예를 들어 경매 예제의 User 클래스에 읽기 전용 캐싱을 적용하고자 한다면 다음과 같이 설정할 수 있다.

<class name=“User” table=“AuctionUser” lazy=“true”>
<cache usage=“read-only”/>
...
</class>

위 예에서 볼 수 있듯이 하이버네이트는 간단한 설정만으로 캐싱의 사용유무를 결정하는 것이 가능하다. 적절한 캐싱의 사용은 하이버네이트 기반 애플리케이션의 실행 속도를 상당히 향상시킬 수 있다.

하이버네이트는 지금까지 살펴본 세 가지 방법 이외에도 애플리케이션의 요구사항에 따라 다양한 실행속도 향상 방안을 적용하는 것이 가능하도록 지원하고 있다. ‘ORM 프레임워크는 무조건 느리다’는 편견을 버리고 다양한 해결 방법을 찾는 것이 좋은 애플리케이션을 개발하는 지름길이 될 것이다.

  하이버네이트의 비상(飛上)을 꿈꾸며

하이버네이트는 객체 지향 프로그래밍과 관계형 데이터베이스의 철학적인 차이로 인해 발생하는 많은 제약사항을 해결해주는 좋은 ORM 프레임워크이다. 현재 EJB 3.0의 JPA 스펙까지 지원하고 있지만 아직까지 국내에서 사랑 받지 못하는 주된 이유는 하청 위주의 구조적인 문제 때문이다.

국내 소프트웨어 개발의 대다수가 SI(System Integration, 이하 SI) 프로젝트이다. SI 프로젝트는 대부분이 대형 SI 업체에 의하여 수주되며, 이렇게 수주된 프로젝트는 재하청을 통하여 진행되는 방식이다. 이런 방식으로 프로젝트가 진행되다 보니 프로젝트 구성원은 관리와 문서업무를 담당하는 을, 실질적인 개발을 담당하는 병, 정 … 구조가 된다.

프로젝트의 모든 구성원들은 프로젝트 시작할 때 구성되었다 흩어지는 구조로 되어 있다.

하이버네이트는 다른 프레임워크보다 많은 학습 곡선을 필요로 한다. 하이버네이트를 제대로 사용하기 위해서는 객체 지향에 대한 지식, 자바 언어에 대한 지식, 관계형 데이터베이스에 대한 지식이 바탕이 되어야 한다. 그러나 국내와 같은 하청 구조에서 이 같은 학습 곡선이 높은 하이버네이트를 적용하기란 상당한 위험요소를 내포하고 있다.

이런 위험요소를 안고 하이버네이트와 같은 ORM 프레임워크를 적용하기 위해서는 대형 SI 업체가 자체적인 개발자 양성이나 개발자 교육을 지원해야 한다. 그러나 개발자를 단순히 프로젝트를 진행하는 도구 정도로 생각하는 국내 개발 환경 속에서 하이버네이트와 같은 ORM 프레임워크가 생존하기는 힘들다.

그러나 세계적으로 ORM 프레임워크의 사용은 거스를 수 없는 하나의 큰 흐름이 되고 있다. EJB 3.0에서는 JPA 스펙을 통하여 ORM 프레임워크에 대한 지원을 강화하고 있으며, 루비 온 레일즈 프레임워크에서는 ORM 프레임워크를 기본으로 하고 있다. 지금부터라도 ORM 프레임워크를 활용하기 위한 전략을 수립하고 준비할 때이다.

국내에서도 하이버네이트가 멋진 날개짓을 하며, 하늘로 비상(飛上)하는 날을 기대해본다. @


참고자료
1. Hibernate Documentaion - http://www.hibernate.org/5.html
2. http://code.google.com/p/ormunit/ : ORMUnit
3. http://www.chrisrichardson.net/kb/testing/ormunit/ORMUnitCookbook.html - ORMUnit Cookbook
4. http://www.dhptech.com/node/18 : Generic DAO에 대한 인터페이스와 구현체
5. http://www.hibernate.org/328.html : Hibernate에서 제시하는 다양한 Generic DAO 패턴에 대한 설명.
6. http://static.springframework.org/spring/docs/2.0.x/reference/testing.html : Spring 프레임워크의 테스트 API에 대한 설명
7. 하이버네이트 3 프로그래밍, 최범균, 2007, 가메출판사
8. POJOs In Action, Chris Richardson, 2006, Manning, 213~224 Page
9. iBATIS 인 액션, 클린턴 비긴 | 브랜든 구딘 | 래리 메도스 저, 이동국 |손권남 역, 2007, 위키북스

* 이 기사는 ZDNet Korea의 제휴매체인 마이크로소프트웨어에 게재된 내용입니다. 

'IT 이야기' 카테고리의 다른 글

유능한 PM의 성공 원칙  (0) 2008.10.07
완벽한 프로젝트를 위한 7가지 기본 원칙  (0) 2008.10.07
네티즌 꿈 웹2.0이 이루어준다  (0) 2008.08.29
ITILV3.0  (0) 2008.08.28
`중기 정보화` 5단계중 3단계 수준  (0) 2008.08.23