Hibernate Core와 Java Persistence and EJB 3.0

Hibernate Core Java Persistence and EJB 3.0
Persistence 클래스는 프록시 기반의 Lazy Loading을 사용할 때에만 아규먼트가 없는 public 또는 protected의 생성자를 필요로 한다. JPA는 모든 Entity 클래스들이 반드시 아규먼트가 없는 public 또는 protected의 생성자를 가지고 있어야만 한다.
Persistent 콜렉션은 반드시 인터페이스 타입이어야 하며, JDK가 제공하는 모든 인터페이스를 지원한다. 마찬가지로 모두 인터페이스 타입이어야 하며, 정렬된 콜렉션을 제외한 일부 인터페이스만 완전히 이식가능한 형태로 사용할 수 있다.
Persistent 속성들은 실행시에 accessor나 필드를 통해서 접근할 수 있으며, 혹은 완전히 다른 전략을 적용할 수도 있다. 마찬가지로 accessor나 필드를 통해서 접근가능하지만, 이식성이 필수라면 두 방법 모두 그렇치 않다.
XML 메타데이터 형식으로 모든 하이버네이트 맵핑 옵션을 사용할 수 있다. JPA 애노테이션은 기본적이고 가장 발전된 형태의 맵핑 옵션을 모두 제공한다. 하이버네이트 애노테이션은 별도의 맵핑과 튜닝을 필요로 한다.
XML 맵핑 메타데이터는 전역적으로 설정될 수 있으며 XML placeholder를 사용해서 메타데이터를 독립적으로 유지할 수 있다. 전역적인 메타데이터는 org.xml 메타데이터 파일에 두어야 이식성을 유지할 수 있다.

XML 도메인 모델

특징

  • The XML data model is weak, its type system is complex and underpowered.
  • its data integrity is almost completely procedural.
  • it introduces hierarchical data structures that were outdated decades ago.
  • However, data in XML format is attractive to work with in Java; we have nice tools.

XML로 도메인 모델 뽑아내기

DOM4J로 도메인 모델 뽑아내기
Session dom4jSession = session.getSession(EntityMode.DOM4J);
Element userXML =
(Element) dom4jSession.load(User.class, storedUserId);
  • Element 클래스는 Dom4J API
화면에 출력하기
try {
OutputFormat format = OutputFormat.createPrettyPrint();
XMLWriter writer = new XMLWriter( System.out, format);
writer.write( userXML );
} catch (IOException ex) {
throw new RuntimeException(ex);
}

맵핑할 때 node 속성 사용하기

  • <class> 나 <property> 맵핑에서의 node 속성(node=”name”)은 XML 엘리먼트의 이름을 정의한다.
  • <property> 맵핑에서 node 속성(node=”@name”)은 XML 엘리먼트의 속성을 정의한다.
  • <property> 맵핑에서의 node 속성(node=”name/@attname”)은 name이라는 자식 엘리먼트에 attname이라는 속성을 정의한다.
  • 위의 속성을 사용해서 맵핑하면, 도메인 모델을 표현할 XML을 원하는 형태로 정의할 수 있다.

Map 도메인 모델

특징

  • A dynamic domain model is a model that is dynamically typed.
<hibernate-mapping>
<class entity-name="ItemEntity" table="ITEM_ENTITY">
<id name="id" type="long" column="ITEM_ID">
<generator class="native" />
</id>
<property name="initialPrice" type="big_decimal"
column="INIT_PRICE" />
<property name="description" type="string" column="DESCRIPTION" />
<many-to-one name="seller" entity-name="UserEntity"
column="USER_ID" />
</class>
<class entity-name="UserEntity" table="USER_ENTITY">
<id name="id" type="long" column="USER_ID">
<generator class="native" />
</id>
<property name="username" type="string" column="USERNAME" />
<bag name="itemsForSale" inverse="true" cascade="all">
<key column="USER_ID" />
<one-to-many entity-name="ItemEntity" />
</bag>
</class>
</hibernate-mapping>
  • 다수의 도메인 맵핑을 하나의 파일 안에 두고 있다. 자바 클래스를 맵핑하는 것이 아니기 때문에 메타데이터를 어떻게 구성하던 맘대로 하라.
  • <class name=”…”> 대신에 <class entity-name=”…”> 를 사용했으며, 정적인 도메인 모델과 구분하기 위해 Entity를 접미어로 사용했다.
  • <many-to-one> 와 <one-to-many> 에서 entity-name에 설정한 이름을 사용했다.

동적인 맵 사용하기

동적인 맵 사용 예제
Map user = new HashMap();
user.put("username", "johndoe");

Map item1 = new HashMap();
item1.put("description", "An item for auction");
item1.put("initialPrice", new BigDecimal(99));
item1.put("seller", user);

Map item2 = new HashMap();
item2.put("description", "Another item for auction");
item2.put("initialPrice", new BigDecimal(123));
item2.put("seller", user);

Collection itemsForSale = new ArrayList();
itemsForSale.add(item1);
itemsForSale.add(item2);
user.put("itemsForSale", itemsForSale);

session.save("UserEntity", user);
  • Entity 하나 당 HashMap 객체 하나에 대응한다.
  • ArrayList는 bag을 맵핑하려고 사용했다.
  • Session.save(entity-name, HashMap)를 사용해서 저장했다.
  • cascading에 의해서 user와 user와 연관을 맺고 있는 두 개의 Item 역시 DB에 저장됐다.
  • Set을 사용하면 안 된다. Map은 equals()를 할 때 키값을 가지고 비교하기 때문에, Set을 사용하면 여러 개의 Entity를 넣을 수 없다.

정적인 엔티티 모드와 동적인 엔티티 모드 혼용하기

  • 맵핑할 때, <class name=”model.UserPojo” entity-name=”UserEntity” table=”USER_ENTITY”> 이런식으로 클래스이름과 논리적인 이름을 모두 설정해 준다.
  • Session을 사용할 때는 entity-name을 사용해야 한다.
  • Session의 결과로 받는 값은 기본으로 POJO 객체다.
  • 둘 모두 <property name=”default_entity_mode”>dynamic-map</property> 를 사용해서 Map을 사용하도록 설정할 수 있다.
  • 특정 세션만 Map을 사용하도록 설정할 수 있다. Session dynamicSession = session.getSession(EntityMode.MAP);
  • Note that you can’t link a map with a POJO instance. 당근이지.

하나의 클래스 여러번 맵핑하기

  • 맵핑 할 때 entity-name에 다른 값을 주면, 같은 클래스를 사용해서 서로 다른 테이블로 맵핑할 수 있다.
<hibernate-mapping>
<class name="model.Item" entity-name="ItemAuction"
table="ITEM_AUCTION">
<id name="id" column="ITEM_AUCTION_ID">...</id>
<property name="description" column="DESCRIPTION" />
<property name="initialPrice" column="INIT_PRICE" />
</class>
<class name="model.Item" entity-name="ItemSale" table="ITEM_SALE">
<id name="id" column="ITEM_SALE_ID">...</id>
<property name="description" column="DESCRIPTION" />
<property name="salesPrice" column="SALES_PRICE" />
</class>
</hibernate-mapping>

Using placeholders

특징

  • you can call any native SQL function or keyword your database system supports.
  • The disadvantage of putting native SQL in your mapping metadata is lost database portability.
  • One way to deal with this issue is to use a placeholder in
    your XML file that is replaced during build when the mapping files are
    copied to the target directory.

Placeholder

<id name="id" column="CATEGORY_ID" type="long">
<generator class="&idgenerator;"/>
</id>
  • &idgenerator; 값을 Entity placeholder라고 부른다. 이 값을 XML 파일의 상단에 문서 타입 정의의 일부분으로 선언할 수 있다.
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping SYSTEM
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"
[
<!ENTITY idgenerator "auction.custom.MyOracleGenerator">
]>
  • 맵핑 파일을 읽어들일 때, XML 파서는 placeholder를 위에 선언한 값으로 대체한다.
  • 선언 부분을 별도의 DTD 파일로 한 단계 더 빼낼 수 있고, 원래 자리에는 dtd 파일을 참조하도록 global 옵션을 사용할 수 있다.
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping SYSTEM
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"
[
<!ENTITY % globals SYSTEM "classpath://persistence/globals.dtd">
%globals;
]>
  • 모든 전역적인 옵션을 DTD 파일로 빼낼 수 있다.
  • 다수의 속성이 여러 클래스에 중복되서 나타나지만, 상속을 사용할 수 없을 때는 XML 덩어리를 별도의 파일로 빼내서 다른 XMl 파일들에 포함 시킬 수도 있다.