EJB 3 가 나오면서 Java Persistence API (JPA)가 등장하게 되었다. Persistent 는 Cobuild 사전에 다음과 같이 정의되어 있다.
1. Something that is persistent continues to exist or happen for a long time; used especially about bad or undesirable states or situations.
2. Someone who is persistent continues trying to do something, even though it is difficult or other people are against it.

첫번째는 별로 안좋은 의미로 '완고한', '고집이 센' 정도로 받아들일 수 있을 것 같다. 두번째는 어려움이나 저항이 심해도 무엇인가를 계속해서 시도하려는 상태를 의미하는 것 같다. 보통은 '영속성', '지속성' 이라는 것으로 표현한다.
그럼 Java Persistence 를 '자바 지속성', '자바 영속성' 이라고 표현하는게 올바른 것인가? 또한, Hibernate나 JDO와 같은 Persistence Framework를 '지속성/영속성' 프레임워크라고 표현해야 하는가? 한글로 어떻게 표현할지에 대한 것은 조금은 미루어두고 우선 Persistence라는 것이 소프트웨어에서 무엇을 의미하는지 살펴보자.

최근 들어, 3 amigo들(Ivar Jacobson, James Rumbaugh, Grady Booch)의 개정판들이 나오기 시작했다.
Booch 책의 3판이 2007년도에 개정되어서 출판되었으며, Jacobson의 UseCase 책도 2008년도에 개정판이 나올 예정인 것 같다.
Booch 의 책 69쪽을 보면 Persistence에 대한 의미를 설명하고 있다.

Persistence의 의미
소프트웨어에서 객체는 어느 정도의 공간을 점유하고 특정 시간 동안 존재하게 된다. Atkinson 등 여러 사람들은 수식의 평가 내에서 발생되는 임시적인(transitory) 객체에서 부터 단일 프로그램의 실행보다 오래 유지되는 데이터베이스에 있는 객체까지 객체 존재에 대한 지속성(continuum)이 있다고 주장한다. 이러한 객체 persistence에 대한 의견은 다음과 같은 사항들을 포함하고 있다.
■ 수식 평가의 임시적인(transient) 결과
■ 절차적인 프로그램의 지역 변수
■ (ALGOL 60과 같은) 프로그램 언어 자체적인 변수, 전역 변수, 범위마다 정도의 차이가 있는 heap 항목
■ 프로그램 실행 간 존재하는 데이터
■ 프로그램의 다양한 버전 간 존재하는 데이터
■ 프로그램 보다 오래 남는 데이터

전형적인 프로그래밍 언어는 통상 처음 세가지 정도의 객체 persistence 만을 강조하는 반면에, 나머지 세가지 종류의 persistence는 일반적으로 데이터베이스 기술의 영역이다. 이로 인해서 매우 엉뚱한 아키텍처 결과를 초래하기도 하는 문화적인 충돌이 발생된다. 즉, 프로그래머들은 프로그램 실행 간에 상태가 보존되어야만 하는 객체를 저장하기 위한 특별한 목적의 스키마를 잘 만들고자 하고, 데이터베이스 설계자들은 자신들의 기술을 임시적인(transient) 객체에도 처리하게끔 잘못 적용한다는 것이다.

Atkinson 등이 말한 "프로그램 보다 오래 남는 데이터"에 대한 재미있는 확대 해석은 어플리케이션이 전체 트랜잭션 실행 동안 사용중인 데이터와 연결이 끊어질 수 있는 웹 어플리케이션의 경우이다. 데이터 소스와 연결이 끊어진 동안에 클라이언트 어플리케이션이나 웹 서비스에 제공된 데이터에게 어떤 변화가 발생될 것이며, 둘 간의 정확성은 어떻게 처리되어야 하는가? MS의 ADO.NET과 같은 프레임워크는 그러한 분산되고 연결이 끊어지는 시나리오를 해결하는데 도움을 주고 있다.

동시성(concurrency)과 객체에 대한 개념의 연결됨으로 인해 동시성 객체지향 프로그래밍 언어(concurrent object-oriented programming language)를 발생하게 했다. 비슷한 경향으로 객체에 대한 persistence 개념의 대두는 객체지향 데이터베이스를 발생하게 했다. 실제적으로 이러한 데이터베이스는 sequential, indexed, hierarchial, network, 관계형 DB 모델과 같은 검증된 기술을 토대로 만들어졌으나, 개별 프로그램의 생명기간(lifetime)을 넘어서는 객체의 생명시간이라는 관점에서 DB 쿼리와 다른 오퍼레이션들이 완성되는 것을 통해 객체지향 인터페이스의 추상화를 프로그래머에게 제공한다. 이러한 통합(unification)은 전반적으로 특정 종류의 어플리케이션에 대한 개발을 단순화시킨다. 특히, 어플리케이션의 DB적인 측면과 DB가 아닌 측면에 동일한 설계 방법을 적용을 가능하게 한다.

어떤 객체지향 프로그래밍 언어는 persistence에 대한 직접적인 지원을 제공한다. Java는 EJB와 JDO(Java Data Object)를 제공한다. Smalltalk는 저장소로 혹은 저장소로부터 객체를 스트리밍하는 프로토콜을 가진다. (하위 클래스에 의해서 재정의가 되어야 함) 하지만, 객체를 2차원적인 평평한 파일로 스트리밍하는 것은 제대로 맞지않는 persistence에 대한 원초적인 해결책이다. Persistence는 몇몇 상업적으로 유용한 객체지향 DB를 통해서 달성될 수 있다. persistence에 대한 좀 더 전형적인 방법은 관계형 DB에 객체지향 껍질(skin)을 입히는 것이다. 수정된 객체-관계형 매핑이 개별 개발자들에 의해서 만들어질 수 있다. 하지만, 이것은 잘 하기에는 매우 도전적인(문제가 많은) 작업이다. 오픈소스 프레임워크인 Hibernate와 같은 프레임워크가 이러한 작업을 수월하게 해준다. 이러한 방법은 대체하기에는 위험하거나 너무 비용이 많이 발생될 수 있는 관계형 DB 기술에 대규모 투자가 이루어졌을 때 가장 매혹적이다.

persistence는 데이터에 대한 생명기간 이외에 더 많은 것을 다룬다. 객체지향 DB에서 객체의 상태를 영구 보존할 뿐만 아니라 객체의 클래스도 개별 프로그램보다 더 오래 유지시킴으로써 모든 프로그램이 동일한 방식으로 저장된 상태를 해석한다. 이는 특히 객체의 클래스를 변경해야하는 경우 DB의 무결성(integrity)을 유지하는데에 노력을 하게끔 한다.

대부분의 시스템에서 일단 생성된 객체는 존재가 소멸되기 까지 동일한 물리적인 메모리를 소비한다. 하지만, 분산된 여러 프로세서에서 실행되는 시스템에서 공간을 건너띄는 persistence에 관심을 쏟아야만 하는 경우도 있다. 그러한 시스템에서 기계(machine)에서 기계로 이동하고 서로 다른 기계에서 서로 다른 표현을 가질 수 있는 객체를 생각하는 것이 필요하다.

요약하면 persistence는 다음과 같이 정의된다.

Persistence는 객체의 존재가 시간(즉, 객체는 객체를 생성한 무엇인가가 존재를 끝마치게 한 후에 계속해서 존재함)이나 공간(즉, 객체의 위치는 객체가 생성되었던 주소 공간에서 부터 이동됨)을 초월해서 여기저기 옮겨다니는(through) 객체의 성질(property)이다.

이상과 같이 Booch 책에서 persistence라는 의미를 설명한 내용이다. 마지막에 요약되어서 나와있지만, 객체가 시간과 공간을 초월하여 여러 프로그램간을 옮겨다닐 수 있는 성질로 표현할 수 있는데, 딱히 우리말로 한단어로 표현하기가 쉽지 않다. 프로그래머가 가장 이해하기 쉬운 단어는 '객체의 저장' 정도가 되겠지만, 이렇게 번역하기는 또한 persistence가 갖게 되는 다른 의미가 많이 상쇄되는 느낌이다. 왜냐하면 '저장' 이라는 의미가 주로 DB 저장을 뜻하는 경우가 대부분이기 때문일 것이다. 아무튼 'DB 저장' 과는 별도의 개념인 '객체 저장(persistence)' 이라는 개념이 필요할 것 같다.
저작자 표시 비영리 동일 조건 변경 허락
신고
ORM을 사용하다 보면 항상 겪는 문제지만, 다대다(many-to-many) 관계를 객체와 어떻게 매핑할 것인가이다. 물론, 객체 입장에서는 두 객체간의 Collection 문제지만, DB 입장에서는 항상 Join 테이블이 끼어 있어서 이를 해결하기간 웬만해서는 쉽지 않다.

Hibernate3는 EJB3의 JPA(Java Persistence API)의 구현체로 이에 대한 문제를 다음과 같이 해결하고 있다.

사용자 삽입 이미지

위의 그림과 같이 Category에 Item을 추가할 때마다 어떤 정보가 필요하다고 가정하자.
위의 구조를 자바 클래스에 매핑하는 데에 두가지 방법이 있다. 첫번째 방법은 join 테이블에 대한 중간의 entity 클래스를 사용하여 일대다(one-to-many) 관계로 매핑하는 것이다. 두번째 방법은 join 테이블에 대한 value-type을 사용해서 component의 집합을 이용하는 것이다.

중간의 entity를 join 테이블에 매핑
첫번째 방법은 중간의 entity 클래스인 CategorizedItem을 사용해서 Category와 Item 사이의 다대다 관계를 해결하는 것이다.
@Entity
@Table(name="CATEGORIZED_ITEM")
public class CategorizedItem {
   @Embeddable
   public static class Id implements Serializable {
      @Column(name="category_id") private Long categoryId;
      @Column(name="item_id") private Long itemId;
      public Id() {}
      public Id(Long categoryId, Long itemId) {
         this.categoryId = categoryId;
         this.itemId = itemId;
      }
      public boolean equals(Object o) {
         if (o != null && o instanceof Id) {
            Id that = (Id)o;
            return this.categoryId.equals(that.categoryId) &&
                     this.itemId.equals(that.itemId);
         } else { return false; }
      }
      public int hashCode() {
         return categoryId.hashCode() + itemId.hashCode();
      }
   }

   @EmbeddedId private Id id = new Id();
   @Column(name="added_by_user") private String username;
   @Column(name="added_on") private Date dateAdded = new Date();
   @ManyToOne
   @JoinColumn(name="item_id", insertable=false, updatable=false)
   private Item item;
   @ManyToOne
   @JoinColumn(name="category_id", insertable=false, updatable=false)
   private Category category;
   public CategorizedItem() {}
   public CategorizedItem(String username, Category category, Item item) {
      this.username = username;
      this.category = category;
      this.item = item;
      this.id.categoryId = category.getId();
      this.id.itemId = item.getId();
      category.getCategorizedItems().add(this);
      item.getCategorizedItems().add(this);
   }
   ...
}

entity 클래스는 식별자 속성을 필요로 한다. 조인 테이블의 PK는 category_id와 item_id의 복합키로 구성되어 있다. 따라서, entity 클래스는 편이를 위해서 정적인 내포 클래스로 캡슐화하여 복합키를 가진다. CategorizedItem 생성은 복합키 세팅을 같이 수행하는데, 생성자 내에 이러한 코드가 들어가 있다.

이를 매핑 XML로 표현하면 다음과 같다.
<class name="CategorizedItem" table="CATEGORIZED_ITEM" mutable="false">
   <composite-id name="id" class="CategorizedItem$Id">
      <key-property name="categoryId" acess="field" column="category_id"/>
      <key-property name="itemId" access="field" column="item_id"/>
   </composite-id>
   <property name="dateAdded" column="added_on" type="timestamp" not-null="true"/>
   <property name="username" column="added_by_user"
         type="string" not-null="true"/>
   <many-to-on name="category" column="category_id" not-null="true"
         insert="false" update="false"/>
   <many-to-on name="item" column="item_id" not-null="true"
         insert="false" update="false"/>
</class>

entity 클래스는 한번 생성되면 속성을 변경할 수 없는 immutable이다. Hibernate는 <composite-id> 필드를 직접 접근이 가능하며, 내포된 클래스에 getter나 setter가 필요하지 않다. 두개의 <many-to-one> 매핑은 insert와 update가 false로 세팅되어서 효과적으로 읽기만 가능하다. 이는 컬럼이 한번은 복합키(값에 대한 세팅시)에 다른 한번은 다대일 관계에 두번 매핑되기 때문에 필요하다.

Category와 Item entity는 CategorizedItem entity에 대해서 일대다 관계를 가진다. Category 예를 들면 다음과 같다.
<set name="categorizedItems" inverse="true">
   <key column="category_id"/>
   <one-to-many class="CategorizedItem"/>
</set>
위의 XML 매핑은 다음의 annotation과 동일한다.
@OneToMany(mappedBy="category")
private Set<CategorizedItem> categorizedItems = new HashSet<CategorizedItem>();

여기에서는 특별히 생각할 것이 없다. 일반적인 collection이 있는 양방향 일대다 관계일 뿐이다. Item도 동일한 방식으로 collection을 추가하고 관계를 맺으면 된다. 다음과 같이 category와 item 사이의 관계를 생성하고 저장한다.
CategorizedItem newLink = new CategorizedItem(aUser.getUsername(),
                                                                            aCategory, anItem);
session.save(newLink);
자바 객체에 대한 참조 무결성은 aCategory와 anItem 내의 collection 을 관리하는 CategorizedItem의 생성자에 의해서 보장된다. category와 item 사이의 관계를 제거하는 것은 다음과 같다.
aCategory.getCategorizedItems().remove(theLink);
anItem.getCategorizedItems().remove(theLink);
session.delete(theLink);

이와 같은 방법의 주요한 장점은 양방향 항해(bidirectional navigation)이 가능하다는 것이다. aCategory.getCategorizedItems() 를 통해서 category에 있는 모든 items를 접근할 수 있으며 또한 반대 방향으로 anItem.getCategorizedItems()를 통해서 접근이 가능하다. 단점은 관계를 생성하고 없애기 위해서 CategorizedItem entity 인스턴스를 관리하는 다소 복잡한 코드가 필요하다는 것이다. category와 item은 별도로 저장되고 삭제되어야 하며 CategorizedItem 클래스에 복합 식별자와 같은 특별한 코드가 필요하다. 하지만, Category와 Item의 CategorizedItem collection에 cascading 옵션을 사용해서 transitive persistence가 가능하다.

join 테이블에 추가 컬럼에 대한 두번째 방법은 중간의 entity 클래스가 필요없으며, 더 간단하다.

component의 집합으로 join 테이블 매핑
먼저 CategorizedItem 클래스를 단순화시키는데, 식별자나 복잡한 생성자가 없는 value 타입 형태로 만든다.
pulbic class CategorizedItem {
   private String username;
   private Date dateAdded = new Date();
   private Item item;
   private Category category;
   public CategorizedItem(String username, Category category, Item item) {
      this.username = username;
      this.category = category;
      this.item = item;
   }
   ...
   // getter와 setter 메소드
   // equals와 hashCode 메소드도 필요함.
}
모든 value 타입이 그렇듯이 위의 클래스도 entity에 의해서 소유된다. 소유자는 Category이며, 이 component에 대해서 collection을 가진다.
<class name="Category" table="CATEGORY">
   ...
   <set name="categorizedItems" table="CATEGORY_ITEM">
      <key column="category_id"/>
      <composite-element class="CategorizedItem">
         <parent name="category"/>
         <many-to-one name="item" column="item_id" not-null="true" class="Item"/>
         <property name="username" column="added_by_user"/>
         <property name="dateAdded" column="added_on"/>
      </composite-element>
   </set>
</class>
이는 조인 테이블에 추가 컬럼을 가지는 다대다 관계에 대한 완전한 매핑이다. <many-to-one> 요소는 Item에 대한 관계를 나타내며, <property> 매핑은 조인 테이블의 추가 컬럼을 포함한다. 데이터베이스 테이블에 단 하나의 변경이 있다. CATEGORY_ITEM 테이블은 category_id 뿐만 아니라 item_id인 모든 컬럼의 복합인 PK를 가진다. 따라서 모든 속성(여기서 many-to-one의 item_id 에 대한 설정)은 nullable 이어서는 안된다. 그렇지 않으면, 조인 테이블에 행을 식별할 수 없다.

위에 매핑에 사용자 이름 대신 User에 대한 참조를 사용해서 확장할 수 있다. 이는 조인 테이블에 USERS 에 대한 외래키로 user_id 컬럼이 추가로 필요하다. 이러한 관계를 ternary association 매핑이라고 한다.
<set name="categorizedItems" table="CATEGORY_ITEM">
   <key column="category_id"/>
   <composite-element class="CategorizedItem">
      <parent name="category"/>
      <many-to-one name="item" column="item_id" not-null="true" class="Item"/>
      <many-to-one name="user" column="user_id" not-null="true" class="User"/>
      <property name="datedAdded" column="added_on"/>
   </composite-element>
</set>
얼마나 환상적인가!

component의 collection을 사용하는 장점은 연결 객체의 명확한 내재된 생명주기이다. Category와 Item 간의 관계를 생성하기 위해서 collection에 새로운 CategorizedItem 인스턴스를 추가하면 된다. 연결을 끊으려면 collection에서 해당 요소를 없앤다. 어떤 cascading 세팅이 필요하지 않으며, Java 코드는 다음과 같이 단순해진다.
CategorizedItem aLink = new CategorizedItem(aUser.getUserName(),
                                                                   aCategory, anItem);
aCategory.getCategorizedItems().add(aLink);
aCategory.getCategorizedItems().remove(aLink);

이와 같은 방법의 단점은 양방향 항해를 할 수 있는 방법이 없다는 것이다. CategorizedItem 과 같은 component는 정의를 통해 참조를 공유하지 못한다. Item에서 CategorizedItem으로 항해할 수 없다. 하지만, 필요한 객체를 조회하는 쿼리를 작성할 수 있다.

동일한 매핑을 annotation을 사용해보자. 먼저, component 클래스를 @Embeddable로 만들고, component 컬럼과 관계 매핑을 추가한다.
@Embeddable
public class CategorizedItem {
   @org.hibernate.annotations.Parent
   private Category category;

   @ManyToOne
   @JoinColumn(name="item_id", nullable=false, updatable=false)
   private Item item;

   @ManyToOne
   @JoinColumn(name="user_id", nullable=false, updatable=false)
   private User user;

   @Temporal(TemporalType.TIMESTAMP)
   @Column(name="added_on", nullable=false, updatable=false)
   private Date dateAdded;
   ...
   // 생성자
   // getter와 setter 메소드
   // equals와 hashCode 메소드로 필요함.
}

Category 클래스에 component의 collection으로 매핑하면 다음과 같다.
@org.hibernate.annotations.CollectionOfElements
@JoinTable(
      name="CATEGORY_ITEM",
      joinColumns=@JoinColumn(name="category_id")
)
private Set<CategorizedItem> categorizedItems = new HashSet<CategorizedItem>();
이제 annotation을 사용한 ternary association이 매핑되었다. 처음에 복잡하게 보였던 것이 단 몇 줄의 annotation 메타데이터를 사용해서 줄어들었다.

JAVA Persistence With Hibernate 상세보기
Bauer, Christian/ King, Gavin 지음 | Oreilly & Associates 펴냄
저작자 표시 비영리 동일 조건 변경 허락
신고
어느 순간 무슨 이유에서든지 이 지구상의 인간이 모두 사라져버린다면 과연 지금 우리가 살고 있는 곳은 어떻게 변할까.

범위를 좁혀서 현재 내가 몸담고 있는 소프트웨어는 어떻게 될지 조금은 상상이 간다. 지금 쓰고 있는 블로그를 포함해서 웹 이라는 것이 없어질 것은 자명하다. 설혹 침팬지나 원숭이 중에 많은 진화를 거쳐 소프트웨어라는 것을 이해할 수 있는 동물이 생겨나고 그때까지 웹(인터넷) 이라는 것이 마치 과거의 유적처럼 남아있다고 한다면 이들은 과연 웹(인터넷)을 어떻게 생각할까.

재미있는 상상이다. 결국 소프트웨어는 인간과 같은 생명주기(life cycle)를 가질 것이다. 가장 최근에 생겨난 것이지만 가장 인간과 친숙하고 인간과 동고동락하는 것이 소프트웨어가 아닐까.

그런 의미에서 '소프트웨어 = 人' 이라는 공식이 성립하는 것은 아닐런지...아마도 하드웨어는 인간이 사라진 이후에도 얼마간은 남아있을 것이고, 남아있는 얼마동안은 소프트웨어도 같이 유지되겠지만, 이마저도 하드웨어가 전기가 제대로 공급되어서 제대로 작동한다는 전제에서이다.

오늘 쓰고 있는 블로그는 인터넷이라고 하는 소프트웨어 커뮤니티에 또 하나의 발자취를 남기는 것일테고, 이 발자취는 누군가에 의해서 발견될 것이다. 그 누군가는 또 다른 누군가에 의해서 발견될 것이고...
인간없는 세상은 소프트웨어 없는 세상이 될 것이고, 그 때는 발자취들이 모두 사라질 것이다. 결국 소프트웨어는 인간적일 때가 가장 인간에게 필요한 도구가 될 것이다. 인간적인 소프트웨어. 이는 AI와 같이 공상과학에 나오는 인공지능을 가진 로봇을 의미하는 것은 아니며, 人의 소프트웨어, 人에 의한 소프트웨어, 人을 위한 소프트웨어이다.

인간 없는 세상(양장) 상세보기
앨런 와이즈먼 지음 | 랜덤하우스코리아 펴냄
저작자 표시 비영리 동일 조건 변경 허락
신고

'Homo Ware' 카테고리의 다른 글

사람이 더 중요한 소프트웨어  (0) 2008.03.10
열역학 법칙  (0) 2008.03.03
지루함과 불안함의 中道  (0) 2008.03.01
고지대 적응 훈련  (0) 2008.02.26
Out of ;  (0) 2008.02.19
인간없는 세상  (0) 2008.02.12

+ Recent posts