mirror of
https://github.com/hibernate/hibernate-orm
synced 2025-03-03 16:29:13 +00:00
git-svn-id: https://svn.jboss.org/repos/hibernate/trunk/Hibernate3/doc@7408 1b8cb986-b30d-0410-93ca-fae66ebed9b2
1140 lines
65 KiB
XML
1140 lines
65 KiB
XML
<chapter id="tutorial">
|
|
<title>Hibernate 개요</title>
|
|
|
|
<sect1 id="tutorial-intro">
|
|
<title>머리말</title>
|
|
|
|
<para>
|
|
이 장은 Hibernate 초심자를 위한 개론적인 튜토리얼이다. 우리는 메모리-내 데이터베이스를
|
|
사용하는 간단한 명령 라인 어플리케이션으로 시작하고 단계들을 이해하도록 쉽게 그것을 개발한다.
|
|
|
|
</para>
|
|
|
|
<para>
|
|
이 튜토리얼은 Hibernate 신규 사용자들을 의도하고 있지만 Java와 SQL 지식을 필요로 한다.
|
|
그것은 Michael Gloegl이 작성한 튜토리얼에 기초한다.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="tutorial-firstapp">
|
|
<title>파트 1 - 첫 번째 Hibernate 어플리케이션</title>
|
|
|
|
<para>
|
|
먼저, 우리는 한 개의 간단한 콘솔-기반 Hibernate 어플리케이션을 생성시킬 것이다. 우리는 메모리-내
|
|
데이터베이스(HSQL DB)를 사용하므로, 우리는 어떤 데이터베이스 서버를 설치하지 않아도 된다.
|
|
</para>
|
|
|
|
<para>
|
|
우리가 우리가 출석을 원하는 이벤트들을 저장할 수 있는 작은 데이터베이스 어플리케이션과 이들
|
|
이벤트들의 호스트들에 대한 정보를 필요로 한다고 가정하자.
|
|
</para>
|
|
|
|
<para>
|
|
우리가 행할 첫 번째 것은 우리의 개발 디렉토리를 설정하고, 우리가 필요로 하는 모든 Java 라이브러리들을
|
|
그것 속에 집어 넣는 것이다. Hibernate 웹 사이트로부터 Hibernate 배포본을 내려 받아라. 패키지를
|
|
추출해내고 <literal>/lib</literal> 속에서 발견되는 모든 필요한 라이브러리들을 당신의 새로운 개발 작업
|
|
디렉토리의 <literal>/lib</literal> 디렉토리 속에 위치지워라. 그것은 다음과 같을 것이다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[.
|
|
+lib
|
|
antlr.jar
|
|
cglib-full.jar
|
|
asm.jar
|
|
asm-attrs.jars
|
|
commons-collections.jar
|
|
commons-logging.jar
|
|
hibernate3.jar
|
|
jta.jar
|
|
dom4j.jar
|
|
log4j.jar ]]></programlisting>
|
|
|
|
<para>
|
|
이것은 Hibernate에 필요한 최소한의 세트이다(우리는 또한 메인 아카이브인 hibernate3.jar를
|
|
복사했음을 노트하라). 필수적인 제 3의 라이브러리와 선택적인 제 3의 라이브러리들에 대한 추가 정보는
|
|
Hibernate 배포본의 <literal>lib/</literal> 디렉토리 속에 있는 <literal>README.txt</literal> 파일을 보라.
|
|
(실제로, Log4j는 필수적이지는 않지만 많은 개발자들에 의해 선호된다.)
|
|
</para>
|
|
|
|
<para>
|
|
다음으로 우리는 우리가 데이터베이스 속에 저장시키고자 원하는 이벤트를 표현하는 한 개의 클래스를 생성시킨다.
|
|
</para>
|
|
|
|
<sect2 id="tutorial-firstapp-firstclass">
|
|
<title>첫 번째 클래스</title>
|
|
|
|
<para>
|
|
우리의 첫 번째 영속 클래스는 몇몇 프로퍼티들을 가진 간단한 자바빈즈 클래스이다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[import java.util.Date;
|
|
|
|
public class Event {
|
|
private Long id;
|
|
|
|
private String title;
|
|
private Date date;
|
|
|
|
Event() {}
|
|
|
|
public Long getId() {
|
|
return id;
|
|
}
|
|
|
|
private void setId(Long id) {
|
|
this.id = id;
|
|
}
|
|
|
|
public Date getDate() {
|
|
return date;
|
|
}
|
|
|
|
public void setDate(Date date) {
|
|
this.date = date;
|
|
}
|
|
|
|
public String getTitle() {
|
|
return title;
|
|
}
|
|
|
|
public void setTitle(String title) {
|
|
this.title = title;
|
|
}
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
당신은 이 클래스가 프로퍼티 getter와 setter 메소드들에 대한 표준 자바빈즈 명명법들 뿐만 아니라 필드들에 대한
|
|
private 가시성을 사용하고 있음을 알 수 있다. 이것은 권장되는 설계이지만, 필수적이지는 않다. Hibernate는
|
|
또한 필드들에 직접 접근할 수 있으며, accessor 메소드들의 이점은 강건한 리팩토링이다.
|
|
</para>
|
|
|
|
<para>
|
|
<literal>id</literal> 프로퍼티는 특별한 이벤트를 위한 유일 식별자를 소유한다. 만일 우리가 Hibernate의
|
|
전체 특징을 사용하고자 원할 경우 모든 영속 엔티티 클래스들(보다 덜 중요한 종속 클래스들 또한 존재한다)은 그런
|
|
식별자 프로퍼티를 필요로 할 것이다. 사실 대부분의 어플리케이션들(특히 웹 어플리케이션들)은 식별자에 의해
|
|
객체들을 구분지을 필요가 있어서, 당신은 이것을 어떤 제한이라기 보다는 하나의 특징으로 간주할 것이다. 하지만
|
|
우리는 대개 객체의 항등(identity)를 처리하지 않으므로, setter 메소드는 private이어야 한다. 객체가 저장될 때,
|
|
Hibernate는 단지 식별자를 할당할 것이다. 당신은 Hibernate가 public, private, protected 접근자 메소드들
|
|
뿐만 아니라 (public, private, protected) 필드들에도 직접 접근할 수 있음을 알 수 있다. 선택은 당신에게 달려
|
|
있으며, 당신은 당신의 어플리케이션 설계에 적합하도록 그것을 부합시킬 수 있다.
|
|
</para>
|
|
|
|
<para>
|
|
아규먼트 없는 생성자는 모든 영속 클래스들에 대한 필요조건이다; Hibernate는 당신을 위해 Java Reflection을
|
|
사용하여 객체들을 생성시켜야 한다. 하지만 생성자는 private 일 수 있고, 패키지 가시성은 런타임 프락시 생성과
|
|
바이트코드 방편 없는 효율적인 데이터 검색에 필요하다.
|
|
</para>
|
|
|
|
<para>
|
|
이 Java 노스 파일을 개발 폴더 내의 <literal>src</literal>로 명명된 디렉토리 속에 있는 위치지워라. 이제
|
|
그 디렉토리는 다음과 같을 것이다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[.
|
|
+lib
|
|
<Hibernate and third-party libraries>
|
|
+src
|
|
Event.java]]></programlisting>
|
|
|
|
<para>
|
|
다음 단계에서, 우리는 Hiberante에게 이 영속 클래스에 대해 알려 준다.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-firstapp-mapping">
|
|
<title>매핑 파일</title>
|
|
|
|
<para>
|
|
Hibernate는 영속 크래스들에 대한 객체들을 로드시키고 저장시키는 방법을 알 필요가 있다.
|
|
이곳은 Hibernate 매핑 파일이 역할을 행하는 곳이다. 매핑 파일은 Hibernate가 접근해야 하는
|
|
데이터베이스 내의 테이블이 무엇인지, 그리고 그것이 사용해야 하는 그 테이블 내의 컬럼들이
|
|
무엇인지를 Hibernate에게 알려준다.
|
|
</para>
|
|
|
|
<para>
|
|
매핑 파일의 기본 구조는 다음과 같다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<?xml version="1.0"?>
|
|
<!DOCTYPE hibernate-mapping PUBLIC
|
|
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
|
|
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
|
|
|
|
<hibernate-mapping>
|
|
[...]
|
|
</hibernate-mapping>]]></programlisting>
|
|
|
|
<para>
|
|
Hibernate DTD는 매우 정교하다. 당신은 당신의 편집기 또는 IDE 내에서 XML 매핑 요소들과 속성들에
|
|
대한 자동 완성 기능을 위해 그것을 사용할 수 있다. 당신은 또한 당신의 텍스트 편집기 내에 DTD 파일을
|
|
열 수 있을 것이다 - 그것은 모든 요소들과 속성들에 대한 전체상을 얻고 디폴트들 뿐만 아니라 몇몇 주석들을
|
|
보는 가장 손쉬운 방법이다. Hibernate는 웹으로부터 DTD 파일을 로드시키지 않지만, 먼저 어플리케이션의
|
|
classpath 경로로부터 그것을 먼저 룩업할 것임을 노트하라. DTD 파일은 <literal>hibernate3.jar</literal>
|
|
속에 포함되어 있을 뿐만 아니라 Hibernate 배포본의 <literal>src/</literal> 디렉토리 속에 포함되어
|
|
있다.
|
|
</para>
|
|
|
|
<para>
|
|
우리는 코드를 간략화 시키기 위해 장래의 예제에서 DTD 선언을 생략할 것이다. 그것은 물론 옵션이 아니다.
|
|
</para>
|
|
|
|
<para>
|
|
두 개의 <literal>hibernate-mapping</literal> 태그들 사이에 <literal>class</literal> 요소를
|
|
포함시켜라. 모든 영속 엔티티 클래스들(다시금 종속 클래스들일 수 있고, 그것은 첫번째-급의 엔티티들이 아니다)은
|
|
SQL 데이터베이스 내의 테이블에 대한 그런 매핑을 필요로 한다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<hibernate-mapping>
|
|
|
|
<class name="Event" table="EVENTS">
|
|
|
|
</class>
|
|
|
|
</hibernate-mapping>]]></programlisting>
|
|
|
|
<para>
|
|
지금까지 우리는 그 테이블 내에 있는 한 행에 의해 표현된 각각의 인스턴스인, 클래스의 객체를 영속화 시키고
|
|
로드시키는 방법을 Hibernate에게 알려주었다. 이제 우린느 테이블 프라이머리 키에 대한 유일 식별자 프로퍼티 매핑을
|
|
계속 행한다. 게다가 우리는 이 식별자를 처리하는 것에 주의를 기울이고자 원하지 않으므로, 우리는 대용 키 프라이머리 키
|
|
컬럼에 대한 Hibernate의 식별자 생성 방도를 구성한다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<hibernate-mapping>
|
|
|
|
<class name="Event" table="EVENTS">
|
|
<id name="id" column="EVENT_ID">
|
|
<generator class="increment"/>
|
|
</id>
|
|
</class>
|
|
|
|
</hibernate-mapping>]]></programlisting>
|
|
|
|
<para>
|
|
<literal>id</literal> 요소는 식별자 프로퍼티의 선언이고, <literal>name="id"</literal>는
|
|
Java 프로퍼티의 이름을 선언한다 - Hibernate는 그 프로퍼티에 접근하는데 getter 및 setter 메소드들을
|
|
사용할 것이다. column 속성은 우리가 <literal>EVENTS</literal> 테이블의 어느 컬럼을 이 프라이머리 키로
|
|
사용하는지를 Hibernate에게 알려준다. 내포된 <literal>generator</literal> 요소는 식별자 생성 방도를
|
|
지정하며, 이 경우에 우리는 <literal>increment</literal>를 사용했고, 그것은 대개 테스팅(과 튜토리얼들)에
|
|
유용한 매우 간단한 메모리-내 숫자 증가 방법이다. Hibernate는 또한 전역적으로 유일한 데이터베이스에 의해 생성된
|
|
식별자 뿐만 아니라 어플리케이션에 의해 할당된 식별자(또는 당신이 확장으로 작성한 어떤 방도)를 지원한다.
|
|
</para>
|
|
|
|
<para>
|
|
마지막으로 우리는 매핑 파일 속에서 클래스의 영속 프로퍼티들에 대한 선언들을 포함한다. 디폴트로, 클래스의
|
|
프로퍼티들은 영속적인 것으로 간주되지 않는다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[
|
|
<hibernate-mapping>
|
|
|
|
<class name="Event" table="EVENTS">
|
|
<id name="id" column="EVENT_ID">
|
|
<generator class="increment"/>
|
|
</id>
|
|
<property name="date" type="timestamp" column="EVENT_DATE"/>
|
|
<property name="title"/>
|
|
</class>
|
|
|
|
</hibernate-mapping>]]></programlisting>
|
|
|
|
<para>
|
|
<literal>id</literal> 요소의 경우처럼, <literal>property</literal> 요소의 <literal>name</literal>
|
|
속성은 사용할 getter 및 setter 메소드들이 어느 것인지를 Hibernate에게 알려준다.
|
|
</para>
|
|
|
|
<para>
|
|
<literal>date</literal> 프로퍼티 매핑은 <literal>column</literal> 속성을 포함하는데,
|
|
왜 <literal>title</literal>은 <literal>column</literal> 속성을 포함하지 않는가?
|
|
<literal>column</literal> 속성이 없을 경우 Hibernate는 디폴트로 컬럼 이름으로서 프로퍼티 이름을
|
|
사용한다. 이것은 에 대해 잘 동작한다. 하지만 <literal>date</literal>는 대부분의 데이터베이스에서
|
|
예약된 키워드이어서, 우리는 그것을 다른 이름으로 더 좋게 매핑 시킨다.
|
|
</para>
|
|
|
|
<para>
|
|
다음 흥미로운 점은 <literal>title</literal> 매핑 또한 <literal>type</literal> 속성을 갖지 않는다.
|
|
우리가 매핑파일들 속에서 선언하고 사용하는 타입들은 당신이 예상하는 Java 데이터 타입들이 아니다. 그것들은
|
|
또한 SQL 데이터베이스 타입들도 아니다. 이들 타입들은 이른바 <emphasis>Hibernate 매핑 타입들</emphasis>,
|
|
즉 Java 타입들로부터 SQL 타입들로 변환될 수 있고 반대로 SQL 타입들로부터 Java 타입들로 매핑될 수 있는 컨버터들이다.
|
|
다시말해, <literal>type</literal> 속성이 매핑 속에 존재하지 않을 경우 Hibernate는 정확환 변환 및 매핑 타입
|
|
그 자체를 결정하려고 시도할 것이다. 몇몇 경우들에서 (Java 클래스에 대한 Reflection을 사용하는) 이 자동적인 검출은
|
|
당신이 예상하거나 필요로 하는 디폴트를 갖지 않을 수도 있다. 이것은 <literal>date</literal> 프로퍼티를 가진
|
|
경우이다. Hibernate는 그 프로퍼티가 SQL <literal>date</literal> 컬럼, <literal>timestamp</literal>
|
|
컬럼 또는 <literal>time</literal> 컬럼 중 어느 것으로 매핑되어야 하는지를 알 수가 없다. 우리는 <literal>timestamp</literal>를
|
|
가진 프로퍼티를 매핑함으로써 전체 날짜와 시간 정보를 보존하고 싶다고 선언한다.
|
|
</para>
|
|
|
|
<para>
|
|
다음 매핑 파일은 <literal>Event</literal> Java 클래스 소스 파일과 같은 디렉토리 속에
|
|
<literal>Event.hbm.xml</literal>로서 저장될 것이다. 매핑 파일들에 대한 네이밍은 임의적일 수
|
|
있지만, 접미사 <literal>hbm.xml</literal>은 Hibernate 개발자 공동체 내에서 컨벤션이 되었다.
|
|
디렉토리 구조는 이제 다음과 같을 것이다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[.
|
|
+lib
|
|
<Hibernate and third-party libraries>
|
|
+src
|
|
Event.java
|
|
Event.hbm.xml]]></programlisting>
|
|
|
|
<para>
|
|
우리는 Hibernate의 메인 구성을 계속 행한다.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-firstapp-configuration">
|
|
<title>Hibernate 구성</title>
|
|
|
|
<para>
|
|
우리는 이제 적절한 곳에 한 개의 영속 클래스와 그것의 매핑 파일을 갖고 있다. Hibernate를 구성할 차례이다.
|
|
우리가 이것을 행하기 전에, 우리는 데이터베이스를 필요로 할 것이다. 자바 기반의 메모리-내 SQL DBMS인
|
|
HSQL DB는 HSQL DB 웹 사이트에서 내려받을 수 있다. 실제로, 당신은 이 다운로드에서 오직 <literal>hsqldb.jar</literal>
|
|
만을 필요로 한다. 개발 폴더의 <literal>lib/</literal> 디렉토리 속에 이 파일을 위치지워라.
|
|
</para>
|
|
|
|
<para>
|
|
개발 디렉토리의 루트에 <literal>data</literal>로 명명된 디렉토리를 생성시켜라 - 이 디렉토리는
|
|
HSQL DB가 그것의 데이터 파일들을 저장하게 될 장소이다.
|
|
</para>
|
|
|
|
<para>
|
|
Hibernate는 당신의 어플리케이션 내에서 이 데이터베이스에 연결하는 계층이고, 따라서 그것은 커넥션 정보를
|
|
필요로 한다. 커넥션들은 마찬가지로 구성되어야 하는 하나의 JDBC 커넥션 풀을 통해 행해진다. Hibernate
|
|
배포본은 몇몇 오픈 소스 JDBC 커넥션 풀링 도구들을 포함하고 있지만, 이 튜토리얼에서는 Hibernate에 의해
|
|
미리 빌드된 커넥션 풀링을 사용할 것이다. 당신이 필수 라이브러리를 당신의 classpath 속에 복사해야 하고
|
|
만일 당신이 제품-특징의 제3의 JDBC 풀링 소프트웨어를 사용하고자 원할 경우에는 다른 커넥션 풀링 설정들을
|
|
사용해야 함을 노트하라.
|
|
</para>
|
|
|
|
<para>
|
|
Hibernate의 구성을 위해, 우리는 한 개의 간단한 <literal>hibernate.properties</literal> 파일,
|
|
한 개의 약간 더 세련된 <literal>hibernate.cfg.xml</literal> 파일, 또는 심지어 완전한 프로그램
|
|
상의 설정을 사용할 수 있다. 대부분의 사용자들은 XMl 구성 파일을 선호한다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<?xml version='1.0' encoding='utf-8'?>
|
|
<!DOCTYPE hibernate-configuration PUBLIC
|
|
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
|
|
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
|
|
|
|
<hibernate-configuration>
|
|
|
|
<session-factory>
|
|
|
|
<!-- Database connection settings -->
|
|
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
|
|
<property name="connection.url">jdbc:hsqldb:data/tutorial</property>
|
|
<property name="connection.username">sa</property>
|
|
<property name="connection.password"></property>
|
|
|
|
<!-- JDBC connection pool (use the built-in) -->
|
|
<property name="connection.pool_size">1</property>
|
|
|
|
<!-- SQL dialect -->
|
|
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
|
|
|
|
<!-- Echo all executed SQL to stdout -->
|
|
<property name="show_sql">true</property>
|
|
|
|
<!-- Drop and re-create the database schema on startup -->
|
|
<property name="hbm2ddl.auto">create</property>
|
|
|
|
<mapping resource="Event.hbm.xml"/>
|
|
|
|
</session-factory>
|
|
|
|
</hibernate-configuration>]]></programlisting>
|
|
|
|
<para>
|
|
이 XML 구성이 다른 DTD를 사용함을 노트하라. 우리는 Hibernate의 <literal>SessionFactory</literal>
|
|
-특정 데이터베이스에 대해 책임이 있는 전역 팩토리-를 구성한다. 만일 당신이 여러 데이터베이스들을
|
|
갖고 있다면, (보다 쉬운 시작을 위해) 몇 개의 구성 파일들 속에 여러 개의 <literal><session-factory></literal>
|
|
구성들을 사용하라.
|
|
</para>
|
|
|
|
<para>
|
|
처음 네 개의 <literal>property</literal> 요소들은 JDBC 커넥션을 위한 필수 구성을 포함한다.
|
|
dialect <literal>property</literal> 요소는 Hibernate가 발생시키는 특별한 SQL 이형(異形)을
|
|
지정한다.
|
|
<literal>hbm2ddl.auto</literal> 옵션은 -직접 데이터베이스 속으로- 데이터베이스 스키마의 자동적인
|
|
생성을 활성화 시킨다. 물론 이것은 (config 옵션을 제거함으로써) 비활성화 시킬 수 있거나
|
|
<literal>SchemaExport</literal> Ant 태스크의 도움으로 파일로 리다이렉트 될 수 있다. 마지막으로
|
|
우리는 영속 클래스들을 위한 매핑 파일(들)을 추가시킨다.
|
|
</para>
|
|
|
|
<para>
|
|
이 파일을 소스 디렉토리 속으로 복사하고, 따라서 그것은 classpath의 루트에서 끝날 것이다. Hibernate는
|
|
시작 시에 classpath의 루트에서 <literal>hibernate.cfg.xml</literal>로 명명된 파일을 자동적으로
|
|
찾는다.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-firstapp-ant">
|
|
<title>Ant로 빌드하기</title>
|
|
|
|
<para>
|
|
우리는 이제 Ant로 튜토리얼을 빌드할 것이다. 당신은 Ant를 설치할 필요가 있을 것이다 -
|
|
<ulink url="http://ant.apache.org/bindownload.cgi">Ant 내려받기 페이지</ulink>에서
|
|
Ant를 얻어라. Ant를 설치하는 방법은 여기서 다루지 않을 것이다. <ulink url="http://ant.apache.org/manual/index.html">Ant 매뉴얼</ulink>을
|
|
참조하길 바란다. 당신이 Ant를 설치한 후에, 우리는 빌드파일 생성을 시작할 수 있다. 그것은 <literal>build.xml</literal>로
|
|
명명되고 개발 디렉토리 속에 직접 위치될 것이다.
|
|
</para>
|
|
|
|
<note>
|
|
<title>Ant 설치</title>
|
|
<para>
|
|
디폴트로 Ant 배포본은 (FAQ에 설명되었듯이) 깨어져 있으며, 예를 들어, 만일 당신이 당신의 빌드 파일 내부에서
|
|
JUnit를 사용하고자 원할 경우 당신에 의해 정정되어야 한다. JUnit 태스크 작업을 행하기 위해(우리는 이
|
|
튜토리얼에서 그것을 필요로 하지 않을 것이다), junit.jar를 <literal>ANT_HOME/lib</literal>에 복사하거나
|
|
<literal>ANT_HOME/lib/ant-junit.jar</literal> 플러그인 스텁을 제거하라.
|
|
</para>
|
|
</note>
|
|
|
|
<para>
|
|
기본 빌드 파일은 다음과 같다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<project name="hibernate-tutorial" default="compile">
|
|
|
|
<property name="sourcedir" value="${basedir}/src"/>
|
|
<property name="targetdir" value="${basedir}/bin"/>
|
|
<property name="librarydir" value="${basedir}/lib"/>
|
|
|
|
<path id="libraries">
|
|
<fileset dir="${librarydir}">
|
|
<include name="*.jar"/>
|
|
</fileset>
|
|
</path>
|
|
|
|
<target name="clean">
|
|
<delete dir="${targetdir}"/>
|
|
<mkdir dir="${targetdir}"/>
|
|
</target>
|
|
|
|
<target name="compile" depends="clean, copy-resources">
|
|
<javac srcdir="${sourcedir}"
|
|
destdir="${targetdir}"
|
|
classpathref="libraries"/>
|
|
</target>
|
|
|
|
<target name="copy-resources">
|
|
<copy todir="${targetdir}">
|
|
<fileset dir="${sourcedir}">
|
|
<exclude name="**/*.java"/>
|
|
</fileset>
|
|
</copy>
|
|
</target>
|
|
|
|
</project>]]></programlisting>
|
|
|
|
<para>
|
|
이것은 <literal>.jar</literal>로 끝나는 lib 디렉토리 내에 있는 모든 파일들을 컴파일에 사용되는 classpath에
|
|
추가하도록 Ant에게 알려줄 것이다. 그것은 또한 모든 비-Java 소스 파일들을 대상 디렉토리로 복사할 것이다. 예를 들면,
|
|
구성 및 Hibernate 매핑 파일들. 만일 당신이 Ant를 이제 실행할 경우, 당신은 다음 출력을 얻게 될 것이다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[C:\hibernateTutorial\>ant
|
|
Buildfile: build.xml
|
|
|
|
copy-resources:
|
|
[copy] Copying 2 files to C:\hibernateTutorial\bin
|
|
|
|
compile:
|
|
[javac] Compiling 1 source file to C:\hibernateTutorial\bin
|
|
|
|
BUILD SUCCESSFUL
|
|
Total time: 1 second ]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-firstapp-helpers">
|
|
<title>시작과 helper들</title>
|
|
|
|
<para>
|
|
몇몇 <literal>Event</literal> 객체들을 로드시키고 저장할 차례이지만, 먼저 우리는 어떤 인프라스트럭처
|
|
코드로 설정을 완료해야 한다. 우리는 Hibernate를 시작해야 한다. 이 시작은 전역 <literal>SessionFactory</literal>
|
|
객체를 빌드하고 어플리케이션 내에서 용이한 접근을 위해 그것을 어떤 곳에 저장하는 것을 포함한다.
|
|
<literal>SessionFactory</literal>는 새로운 <literal>Session</literal>들을 열 수 있다.
|
|
<literal>Session</literal>은 작업의 단일-쓰레드 단위를 표현하며, <literal>SessionFactory</literal>는
|
|
한번 초기화 되는 하나의 thread-safe 전역 객체이다.
|
|
</para>
|
|
|
|
<para>
|
|
우리는 시작을 처리하고 <literal>Session</literal> 처리를 편리하게 해주는 <literal>HibernateUtil</literal>
|
|
helper 클래스를 생성시킬 것이다. 이른바 <emphasis>ThreadLocal Session</emphasis> 패턴이 여기서
|
|
유용하며, 우리는 현재의 작업 단위를 현재의 쓰레드와 연관지워 유지한다. 구현을 살펴보자:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[import org.hibernate.*;
|
|
import org.hibernate.cfg.*;
|
|
|
|
public class HibernateUtil {
|
|
|
|
public static final SessionFactory sessionFactory;
|
|
|
|
static {
|
|
try {
|
|
// Create the SessionFactory from hibernate.cfg.xml
|
|
sessionFactory = new Configuration().configure().buildSessionFactory();
|
|
} catch (Throwable ex) {
|
|
// Make sure you log the exception, as it might be swallowed
|
|
System.err.println("Initial SessionFactory creation failed." + ex);
|
|
throw new ExceptionInInitializerError(ex);
|
|
}
|
|
}
|
|
|
|
public static final ThreadLocal session = new ThreadLocal();
|
|
|
|
public static Session currentSession() throws HibernateException {
|
|
Session s = (Session) session.get();
|
|
// Open a new Session, if this thread has none yet
|
|
if (s == null) {
|
|
s = sessionFactory.openSession();
|
|
// Store it in the ThreadLocal variable
|
|
session.set(s);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
public static void closeSession() throws HibernateException {
|
|
Session s = (Session) session.get();
|
|
if (s != null)
|
|
s.close();
|
|
session.set(null);
|
|
}
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
이 클래스는 (클래스가 로드될 때 JVM에 의해 한번 호출되는) 그것의 static 초기자 내에 전역 <literal>SessionFactory</literal>를
|
|
산출할 뿐만 아니라 또한 현재 쓰레드에 대한 <literal>Session</literal>을 소유하는 <literal>ThreadLocal</literal> 변수를
|
|
갖는다. 당신이 <literal>HibernateUtil.currentSession()</literal>을 호출하는 시점에는 문제가 없으며,
|
|
그것은 항상 동일 쓰레드 내에 동일한 Hibernate 작업 단위를 반환할 것이다. <literal>HibernateUtil.closeSession()</literal>에
|
|
대한 호출은 쓰레드와 현재 연관되어 있는 작업 단위를 종료시킨다.
|
|
</para>
|
|
|
|
<para>
|
|
당신이 이 helper를 사용하기 전에 thread-local 변수들에 대한 Java 개념을 확실히 이해하도록 하라.
|
|
보다 강력한 <literal>HibernateUtil</literal> helper는 http://caveatemptor.hibernate.org/에
|
|
있는 <literal>CaveatEmptor</literal> 뿐만 아니라 "Hibernate in Action" 책에서 찾을 수 있다.
|
|
당신이 J2EE 어플리케이션 서버 내에 Hibernate를 배치할 경우에 이 클래스는 필수적이지 않다: 하나의
|
|
<literal>Session</literal>은 현재의 JTA 트랜잭션에 자동적으로 바인드 될 것이고 당신은 JNDI를 통해
|
|
<literal>SessionFactory</literal>를 룩업할 수 있다. 만일 당신이 JBoss AS를 사용할 경우,
|
|
Hibernate는 관리되는 시스템 서비스로서 배치될 수 있고 <literal>SessionFactory</literal>를
|
|
JNDI 이름에 자동적으로 바인드시킬 수 있을 것이다.
|
|
</para>
|
|
|
|
<para>
|
|
개발 소스 디렉토리 속에 <literal>HibernateUtil.java</literal> 를 위치지우고, 다음으로 <literal>Event.java</literal>를
|
|
위치지워라:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[.
|
|
+lib
|
|
<Hibernate and third-party libraries>
|
|
+src
|
|
Event.java
|
|
Event.hbm.xml
|
|
HibernateUtil.java
|
|
hibernate.cfg.xml
|
|
+data
|
|
build.xml]]></programlisting>
|
|
|
|
<para>
|
|
이것은 문제 없이 다시 컴파일 될 것이다. 우리는 마지막으로 로깅 시스템을 구성할 필요가 있다 - Hibernate는
|
|
commons logging를 사용하고 Log4j와 JDK 1.4 사이의 선택은 당신의 몫으로 남겨둔다. 대부분의 개발자들은
|
|
Log4j를 선호한다: Hibernate 배포본에 있는 <literal>log4j.properties</literal>(이것은 디렉토리
|
|
<literal>etc/</literal> 내에 있다)를 <literal>src</literal> 디렉토리로 복사하고, 다음으로
|
|
<literal>hibernate.cfg.xml</literal>을 복사하라. 예제 구성을 살펴보고 당신이 더 많은 verbose 출력을
|
|
원할 경우에 설정들을 변경하라. 디폴트로 Hibernate 시작 메시지는 stdout 상에 보여진다.
|
|
</para>
|
|
|
|
<para>
|
|
튜토리얼 인프라스트럭처는 완전하다 - 그리고 우리는 Hibernate로 어떤 실제 작업을 행할 준비가 되어 있다.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-firstapp-workingpersistence">
|
|
<title>객체 로딩과 객체 저장</title>
|
|
|
|
<para>
|
|
마지막으로 우리는 객체들을 로드시키고 저장하는데 Hibernate를 사용할 수 있다. 우리는 한 개의 <literal>main()</literal>
|
|
메소드를 가진 한 개의 <literal>EventManager</literal> 클래스를 작성한다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[import org.hibernate.Transaction;
|
|
import org.hibernate.Session;
|
|
|
|
import java.util.Date;
|
|
|
|
public class EventManager {
|
|
|
|
public static void main(String[] args) {
|
|
EventManager mgr = new EventManager();
|
|
|
|
if (args[0].equals("store")) {
|
|
mgr.createAndStoreEvent("My Event", new Date());
|
|
}
|
|
|
|
HibernateUtil.sessionFactory.close();
|
|
}
|
|
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
우리는 명령 라인으로부터 몇몇 아규먼트들을 읽어들이고, 만일 첫 번째 아규먼트가 "store"이면, 우리는
|
|
새로운 Event를 생성시키고 저장시킨다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[private void createAndStoreEvent(String title, Date theDate) {
|
|
Session session = HibernateUtil.currentSession();
|
|
Transaction tx = session.beginTransaction();
|
|
|
|
Event theEvent = new Event();
|
|
theEvent.setTitle(title);
|
|
theEvent.setDate(theDate);
|
|
|
|
session.save(theEvent);
|
|
|
|
tx.commit();
|
|
HibernateUtil.closeSession();
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
우리는 한 개의 새로운 <literal>Event</literal> 객체를 생성시키고, 그것을 Hibernate에게 건네준다.
|
|
Hibernate는 이제 SQL을 처리하고 데이터베이스 상에서 <literal>INSERT</literal>들을 실행시킨다.
|
|
-우리가 이것을 실행하기 전에 코드를 처리하는- <literal>Session</literal>과 <literal>Transaction</literal>을
|
|
살펴보자.
|
|
</para>
|
|
|
|
<para>
|
|
<literal>Session</literal>은 한 개의 작업 단위이다. 당신은 우리가 추가적인 API, <literal>Transaction</literal>dmf
|
|
갖고 있음에 놀랐을 수도 있다. 이것은 작업 단위가 한 개의 데이터베이스 트랜잭션보다 "더 긴" 경우일 수 있음을 의미한다
|
|
-웹 어플리케이션 속에서 여러 개의 Http 요청/응답 주기들(예를 들면 마법사 대화상자)에 걸치는 작업 단위를 상상하라.
|
|
지금 우리는 그것들을 간단하게 유지할 것이고 <literal>Session</literal>과 <literal>Transaction</literal>
|
|
사이에 한 개의 one-to-one 입상을 가정할 것이다.
|
|
</para>
|
|
|
|
<para>
|
|
<literal>Transaction.begin()</literal>과 <literal>commit()</literal>은 무엇을 행하는가?
|
|
잘못되는 경우들에서 <literal>rollback()</literal>은 어디에 있는가? Hibernate
|
|
<literal>Transaction</literal> API는 실제 옵션이지만, 우리는 편의와 이식성을 위해 그것을 사용한다.
|
|
(예를 들어 <literal>session.connection.commit()</literal>을 호출함으로써)만일 당신 자신이
|
|
데이터베이스 트랜잭션을 처리했다면, 당신은 이 직접적인 관리되지 않는 JDBC로 특별한 배치 환경에 그 코드를
|
|
바인드시켰다. 당신의 Hibernate 구성 속에서 <literal>Transaction</literal>을 위한 팩토리를 설정함으로써,
|
|
당신은 당신의 영속 계층을 어디에서나 배치할 수 있다. 트랜잭션 처리와 관할에 대한 추가 정보는
|
|
<xref linkend="transactions"/>를 살펴보라. 우리는 또한 이 예제에서 오류 핸들링과 롤백을 생략했다.
|
|
</para>
|
|
|
|
<para>
|
|
이 첫 번째 루틴을 실행하기 위해서 우리는 호출 가능한 대상을 Ant 빌드 파일에 추가해야 한다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<target name="run" depends="compile">
|
|
<java classname="EventManager" classpathref="libraries">
|
|
<classpath path="${targetdir}"/>
|
|
<arg value="${action}"/>
|
|
</java>
|
|
</target>]]></programlisting>
|
|
|
|
<para>
|
|
<literal>action</literal> 아규먼트의 값은 대상을 호출할 때 명령 라인 상에서 설정된다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[C:\hibernateTutorial\>ant run -Daction=store]]></programlisting>
|
|
|
|
<para>
|
|
컴파일, 구성에 따른 Hibernate 시작 후에, 당신은 많은 로그 출력을 보게 될 것이다. 끝에서 당신은
|
|
다음 라인을 발견할 것이다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)]]></programlisting>
|
|
|
|
<para>
|
|
이것은 Hibernate에 의해 실행된 <literal>INSERT</literal>이고, 물음표 기호는 JDBC 바인드
|
|
파라미터들을 나타낸다. 아규먼트로서 바인드 된 값들을 보거나 장황한 로그를 줄이려면 당신의
|
|
<literal>log4j.properties</literal>를 체크하라.
|
|
</para>
|
|
|
|
<para>
|
|
이제 우리는 마찬가지로 저장된 이벤트들을 열거하고자 원하며, 우리는 main 메소드에 한 개의 옵션을
|
|
추가한다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[if (args[0].equals("store")) {
|
|
mgr.createAndStoreEvent("My Event", new Date());
|
|
}
|
|
else if (args[0].equals("list")) {
|
|
List events = mgr.listEvents();
|
|
for (int i = 0; i < events.size(); i++) {
|
|
Event theEvent = (Event) events.get(i);
|
|
System.out.println("Event: " + theEvent.getTitle() +
|
|
" Time: " + theEvent.getDate());
|
|
}
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
우리는 또한 새로운 <literal>listEvents() method</literal> 메소드를 추가 시킨다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[private List listEvents() {
|
|
Session session = HibernateUtil.currentSession();
|
|
Transaction tx = session.beginTransaction();
|
|
|
|
List result = session.createQuery("from Event").list();
|
|
|
|
tx.commit();
|
|
session.close();
|
|
|
|
return result;
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
여기서 우리가 행할 것은 데이터베이스로부터 모든 존재하는 <literal>Event</literal> 객체들을 로드시키기
|
|
위해 HQL (Hibernate Query Language) 질의를 사용하는 것이다. Hibernate는 적절한 SQL을 생성시킬
|
|
것이고, 그것을 데이터베이스로 전송하고 데이터를 <literal>Event</literal> 객체들에 거주시킬 것이다.
|
|
당신은 물론 HQL로서 보다 복잡한 질의들을 생성시킬 수 있다.
|
|
</para>
|
|
|
|
<para>
|
|
만일 당신이 이제 <literal>-Daction=list</literal> 옵션으로 Ant를 호출할 경우, 당신은 당신이 지금까지
|
|
저장시켰던 이벤트들을 보게 될 것이다. 당신은 이것이 동작하지 않아서 놀랄 수도 있고, 어쨌든 만일 당신이 이 튜토리얼을
|
|
단계적으로 따랐다면 - 그 결과는 항상 공백일 것이다. 이 이유는 Hibernate에서 <literal>hbm2ddl.auto</literal>
|
|
바꿈 때문이다 : Hibernate는 매번 실행 시에 데이터베이스를 다시 생성시킬 것이다. 그 옵션을 제거하여 그것을
|
|
사용하지 않도록 하면, 당신이 몇 번의 <literal>store</literal> 액션을 호출한 후에 당신은 당신의 목록 속에서
|
|
결과들을 보게 될 것이다. 물론 당신이 행하는 첫 번째 <literal>저장(store)</literal>은 결정되는 옵션을 가져야 하므로,
|
|
당신은 생성된 초기 테이블들을 얻는다. 자동적인 스키마 생성과 내보내기(export)는 대개 단위 테스팅에서 유용하다.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="tutorial-associations">
|
|
<title>파트 2 - 연관들을 매핑하기</title>
|
|
|
|
<para>
|
|
우리는 한 개의 영속 엔티티 클래스를 한 개의 테이블로 매핑했다. 이것 위에서 빌드하고 몇몇 클래스 연관들을 추가시키자. 먼저
|
|
우리는 우리의 어플리케이션에 사람들을 추가하고 그들이 참여하는 이벤트들의 목록을 저장할 것이다.
|
|
</para>
|
|
|
|
<sect2 id="tutorial-associations-mappinguser">
|
|
<title>Person 클래스 매핑하기</title>
|
|
|
|
<para>
|
|
클래스의 첫 번째 장면은 간단하다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[public class Person {
|
|
|
|
private Long id;
|
|
private int age;
|
|
private String firstname;
|
|
private String lastname;
|
|
|
|
Person() {}
|
|
|
|
// Accessor methods for all properties, private setter for 'id'
|
|
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
<literal>Person.hbm.xml</literal>로 명명된 새로운 매핑 파일을 생성시켜라:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<hibernate-mapping>
|
|
|
|
<class name="Person" table="PERSON">
|
|
<id name="id" column="PERSON_ID">
|
|
<generator class="increment"/>
|
|
</id>
|
|
<property name="age"/>
|
|
<property name="firstname"/>
|
|
<property name="lastname"/>
|
|
</class>
|
|
|
|
</hibernate-mapping>]]></programlisting>
|
|
|
|
<para>
|
|
마지막으로 새로운 매핑을 Hibernate의 구성에 추가하라:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[ <mapping resource="Event.hbm.xml"/>
|
|
<mapping resource="Person.hbm.xml"/>
|
|
]]></programlisting>
|
|
|
|
<para>
|
|
이제 우리는 이들 두 개의 엔티티들 사이에 한 개의 연관을 생성시킬 것이다. 명백하게, 개인들은 이벤트들에
|
|
참여할 수 있고, 이벤트들은 참여자들을 갖는다. 우리가 다루어야 하는 설계 질문들은 다음과 같다 :
|
|
방향성(directionality), 다중성(multiplicity), 그리고 콜렉션 특징.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-associations-unidirset">
|
|
<title>단방향 Set-기반의 연관</title>
|
|
|
|
<para>
|
|
우리는 <literal>Person</literal> 클래스에 이벤트들을 가진 한 개의 콜렉션을 추가할 것이다. 그 방법으로 우리는
|
|
명시적인 질의를 실행시키지 않고서 -<literal>aPerson.getEvents()</literal>를 호출하여- 특정 개인에 대한
|
|
이벤트들을 쉽게 네비게이트할 수 있다. 우리는 하나의 Java 콜렉션, <literal>Set</literal>를 사용한다. 왜냐하면
|
|
그 콜렉션은 중복 요소들을 포함하기 않을 것이고 그 순서가 우리와 관련되어 있지 않기 때문이다.
|
|
</para>
|
|
|
|
<para>
|
|
지금까지 우리는 <literal>Set</literal>으로 구현된 단방향이고 다중 값을 가진 연관들을 설계했다.
|
|
Java 클래스들 속에 이를 위한 코드를 작성하고 그런 다음 그것을 매핑시키자:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[public class Person {
|
|
|
|
private Set events = new HashSet();
|
|
|
|
public Set getEvents() {
|
|
return events;
|
|
}
|
|
|
|
public void setEvents(Set events) {
|
|
this.events = events;
|
|
}
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
우리가 이 연관을 매핑하기 전에, 다른 측에 대해 생각하라. 명백하게 우리는 이것을 단지 단방향으로 유지시킬 수 있다.
|
|
또는 우리가 그것을 양방향으로 네비게이트하는 것-예를 들어 <literal>anEvent.getParticipants()</literal>-이
|
|
가능하도록 원할 경우에, <literal>Event</literal>측 상에 또 다른 콜렉션을 생성시킬 수 있다. 이것은 당신에게
|
|
남겨진 설계 선택이지만, 이 논의에서 명료한 점은 연관의 다중성이다: 양 측 상에서 "다중" 값을 갖는 경우, 우리는 이것을
|
|
<emphasis>many-to-many</emphasis> 연관이라고 명명한다. 그러므로 우리는 Hibernate의 many-to-many 매핑을
|
|
사용한다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<class name="Person" table="PERSON">
|
|
<id name="id" column="PERSON_ID">
|
|
<generator class="increment"/>
|
|
</id>
|
|
<property name="age"/>
|
|
<property name="firstname"/>
|
|
<property name="lastname"/>
|
|
|
|
<set name="events" table="PERSON_EVENT">
|
|
<key column="PERSON_ID"/>
|
|
<many-to-many column="EVENT_ID" class="Event"/>
|
|
</set>
|
|
|
|
</class>]]></programlisting>
|
|
|
|
<para>
|
|
Hibernate는 모든 종류의 콜렉션 매핑들, 가장 공통적인 <literal><set></literal>을 지원한다. many-to-many
|
|
연관 (또는 <emphasis>n:m</emphasis> 엔티티 관계)의 경우, 한 개의 연관 테이블이 필요하다. 이 테이블 속에 있는 각각의
|
|
행은 한 명의 개인과 한 개의 이벤트 사이의 링크를 표현한다. 테이블 이름은 <literal>set</literal> 요소의 <literal>table</literal>
|
|
속성으로 구성된다. 연관 내의 식별자 컬럼 이름은 개인 측에 대해 <literal><key></literal> 요소로 정의되고
|
|
이벤트 측에 대한 컬럼 이름은 <literal><many-to-many></literal>의 <literal>column</literal> 속성으로
|
|
정의된다. 당신은 또한 당신의 콜렉션 내에 있는 객체들의 클래스(정확하게 : 참조들을 가진 콜렉션의 다른 측 상에 있는 클래스)를
|
|
Hibernate에게 알려주어야 한다
|
|
</para>
|
|
|
|
<para>
|
|
따라서 이 매핑을 위한 데이터베이스 스키마는 다음과 같다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[
|
|
_____________ __________________
|
|
| | | | _____________
|
|
| EVENTS | | PERSON_EVENT | | |
|
|
|_____________| |__________________| | PERSON |
|
|
| | | | |_____________|
|
|
| *EVENT_ID | <--> | *EVENT_ID | | |
|
|
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID |
|
|
| TITLE | |__________________| | AGE |
|
|
|_____________| | FIRSTNAME |
|
|
| LASTNAME |
|
|
|_____________|
|
|
]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-associations-working">
|
|
<title>연관들에 작업하기</title>
|
|
|
|
<para>
|
|
<literal>EventManager</literal> 속에 있는 한 개의 새로운 메소드 내에 몇몇 사람들과 이벤트들을 함께 가져오자:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[private void addPersonToEvent(Long personId, Long eventId) {
|
|
Session session = HibernateUtil.currentSession();
|
|
Transaction tx = session.beginTransaction();
|
|
|
|
Person aPerson = (Person) session.load(Person.class, personId);
|
|
Event anEvent = (Event) session.load(Event.class, eventId);
|
|
|
|
aPerson.getEvents().add(anEvent);
|
|
|
|
tx.commit();
|
|
HibernateUtil.closeSession();
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
<literal>Person</literal>과 <literal>Event</literal>를 로드시킨 후에, 정규 콜렉션 메소드들을 사용하여
|
|
콜렉션을 간단하게 변경하라. 당신이 알 수 있듯이, <literal>update()</literal> 또는 <literal>save()</literal>에
|
|
대한 명시적인 호출이 존재하지 않고, 변경되었고 저장할 필요가 있는 콜렉션을 Hibernate가 자동적으로 검출해낸다. 이것은
|
|
<emphasis>자동적인 dirty 체킹</emphasis>이라 불려지며, 당신은 또한 당신의 임의의 객체들에 대한 name 또는 date
|
|
프로퍼티를 변경함으로써 그것을 시도할 수 있다. 그것들이 <emphasis>영속(persistent)</emphasis> 상태에 있는 동안, 즉 특정 Hibernate
|
|
<literal>Session</literal>에 바인드되어 있는 동안(예를 들면. 그것들은 작업 단위 속에 방금 로드되었거나 저장되었다),
|
|
Hibernate는 임의의 변경들을 모니터링하고 쓰기 이면의 형태로 SQL을 실행시킨다. 메모리 상태를 데이터베이스와 동기화 시키는
|
|
과정은 대개 오직 작업 단위의 끝에서이고, <emphasis>flushing</emphasis>이라 명명된다.
|
|
</para>
|
|
|
|
<para>
|
|
물론 당신은 다른 작업 단위 속에 개인과 이벤트를 로드시킬 수 도 있다. 또는 당신은 하나의 객체그 영속 상태에 있지 않을 때
|
|
<literal>Session</literal>의 외부에서 객체를 변경시킬 수도 있다(만일 객체가 이전에 영속화 되었다면, 우리는 이 상태를
|
|
<emphasis>detached</emphasis>라고 부른다). (매우 사실적이지 않은) 코드 내에서 이것은 다음과 같을 수 있다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[ private void addPersonToEvent(Long personId, Long eventId) {
|
|
|
|
Session session = HibernateUtil.currentSession();
|
|
Transaction tx = session.beginTransaction();
|
|
|
|
Person aPerson = (Person) session.load(Person.class, personId);
|
|
Event anEvent = (Event) session.load(Event.class, eventId);
|
|
|
|
tx.commit();
|
|
HibernateUtil.closeSession();
|
|
|
|
aPerson.getEvents().add(anEvent); // aPerson is detached
|
|
|
|
Session session2 = HibernateUtil.currentSession();
|
|
Transaction tx2 = session.beginTransaction();
|
|
|
|
session2.update(aPerson); // Reattachment of aPerson
|
|
|
|
tx2.commit();
|
|
HibernateUtil.closeSession();
|
|
}
|
|
]]></programlisting>
|
|
|
|
<para>
|
|
<literal>update</literal>에 대한 호출은 한 개의 detached 객체를 영속화 시키고, 당신은 그것이 새로운 작업 단위에
|
|
바인드된다고 말할 수 있고, 따라서 detached 동안에 당신이 그것에 대해 행한 임의의 변경들이 데이터베이스에 저장될 수 있다.
|
|
</para>
|
|
|
|
<para>
|
|
물론, 이것은 우리의 현재 상황에서 많이 사용되지 않지만, 그것은 당신이 당신 자신의 어플리케이션 속으로 설계할 수 있는 중요한
|
|
개념이다. 지금 한 개의 새로운 액션을 <literal>EventManager</literal>의 main 메소드에 추가하고 명령 라인에서 그것을
|
|
호출하여 이 연습을 완료하라. 만일 당신이 한명의 개인과 한 개의 이벤트에 대한 식별자들을 필요로 할 경우 - <literal>save()</literal>
|
|
메소드가 그것을 반환시킨다.
|
|
</para>
|
|
|
|
<para>
|
|
이것은 두 개의 동등하게 중요한 클래스들, 두 개의 엔티티들 사이에서 한 개의 연관에 관한 예제였다. 앞서 언급했듯이, 전형적인 모형 내에는
|
|
다른 클래스들과 타이들이 존재하는데, 대개 "덜 중요하다". 당신은 이미 <literal>int</literal> 또는 <literal>String</literal>과
|
|
같은 어떤 것을 이미 보았다. 우리는 이들 클래스들을 <emphasis>값 타입들(value types)</emphasis>이라 명명하고, 그들 인스턴스들은
|
|
특정 엔티티에 <emphasis>의존한다(depend)</emphasis>. 이들 타입들을 가진 인스턴스들은 그것들 자신의 식별성(identity)를 갖지 않거나,
|
|
그것들은 엔티티들 사이에서 공유되지도 않는다(두개의 person들은 심지어 그것들이 같은 첫 번째 이름을 갖는 경우에도 동일한 <literal>firstname</literal>을
|
|
참조하지 않는다 ). 물론 값 타입들은 JDK 내에서 발견될 뿐만 아니라(사실, Hibernate 어플리케이션에서 모든 JDK 클래스들은 값 타입들로 간주된다),
|
|
당신은 또한 당신 스스로 종속 클래스들, 예를 들면 <literal>Address</literal> 또는 <literal>MonetaryAmount</literal>을 작성할 수 있다.
|
|
</para>
|
|
|
|
<para>
|
|
당신은 또한 값 타입들을 설계할 수 있다. 이것은 다른 엔티티들에 대한 참조들을 가진 콜렉션과는 개념적으로 매우 다르지만,
|
|
Java에서는 대개 동일한 것으로 보여진다.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-associations-valuecollections">
|
|
<title>값들을 가진 콜렉션</title>
|
|
|
|
<para>
|
|
우리는 값 타입의 객체들을 가진 한 개의 콜렉션을 <literal>Person</literal> 엔티티에 추가시킨다. 우리는 email 주소를
|
|
저장하고자 원하므로, 우리가 사용하는 타입은 <literal>String</literal>이고, 그 콜렉션은 다시 한 개의 <literal>Set</literal>이다:
|
|
</para>
|
|
<programlisting><![CDATA[private Set emailAddresses = new HashSet();
|
|
|
|
public Set getEmailAddresses() {
|
|
return emailAddresses;
|
|
}
|
|
|
|
public void setEmailAddresses(Set emailAddresses) {
|
|
this.emailAddresses = emailAddresses;
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
이 <literal>Set</literal>에 대한 매핑은 다음과 같다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="emailAddresses" table="PERSON_EMAIL_ADDR">
|
|
<key column="PERSON_ID"/>
|
|
<element type="string" column="EMAIL_ADDR"/>
|
|
</set>]]></programlisting>
|
|
|
|
<para>
|
|
앞의 매핑과 비교한 차이점은 <literal>element</literal> 부분인데, 그것은 그 콜렉션이 또 다른 엔티티에 대한
|
|
참조들을 포함하지 않을 것이지만 <literal>String</literal>(소문자 이름은 그것이 Hibernate 매핑 타입/변환자임을
|
|
당신에게 말해준다) 타입의 요소들을 가진 한 개의 콜렉션을 포함할 것임을 Hibernate에게 알려준다. 일단 다시 <literal>set</literal>
|
|
요소의 <literal>table</literal> 속성은 그 콜렉션에 대한 테이블 이름을 결정한다. <literal>key</literal> 요소는
|
|
콜렉션 테이블 내에서 foreign-key 컬럼 이름을 정의한다. <literal>element</literal> 요소 내에 있는 <literal>column</literal>
|
|
속성은 <literal>String</literal> 값들이 실제로 저장될 컬럼 이름을 정의한다.
|
|
</para>
|
|
|
|
<para>
|
|
업데이트된 스키마를 살펴보라:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[
|
|
_____________ __________________
|
|
| | | | _____________
|
|
| EVENTS | | PERSON_EVENT | | | ___________________
|
|
|_____________| |__________________| | PERSON | | |
|
|
| | | | |_____________| | PERSON_EMAIL_ADDR |
|
|
| *EVENT_ID | <--> | *EVENT_ID | | | |___________________|
|
|
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | <--> | *PERSON_ID |
|
|
| TITLE | |__________________| | AGE | | *EMAIL_ADDR |
|
|
|_____________| | FIRSTNAME | |___________________|
|
|
| LASTNAME |
|
|
|_____________|
|
|
]]></programlisting>
|
|
|
|
<para>
|
|
당신은 콜렉션 테이블의 프라이머리 키가 사실은 두 컬럼들을 사용하는 한 개의 합성 키(composite key)임을 알 수 있다.
|
|
이것은 또한 개인에 대해 email 주소가 중복될 수 없음을 의미하며, 그것은 정확하게 우리가 Java에서 set을 필요로 하는
|
|
의미론이다.
|
|
</para>
|
|
|
|
<para>
|
|
마치 개인들과 이벤트들을 링크시켜서 이전에 우리가 행했던 것처럼 이제 당신은 요소들을 시도하고 이 콜렉션에 추가할 수 있다.
|
|
그것은 Java에서 동일한 코드이다.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-associations-bidirectional">
|
|
<title>Bi-directional associations</title>
|
|
|
|
<para>
|
|
다음으로 우리는 양방향 연관을 매핑시킬 예정이다-개인과 이벤트 사이에 연관을 만드는 것은 Java에서 양 측들에서 동작한다.
|
|
물론 데이터베이스 스키마는 변경되지 않고, 우리는 여전히 many-to-many 다중성을 갖는다. 관계형 데이터베이스는
|
|
네트웍 프로그래밍 언어 보다 훨씬 더 유연하여서, 그것은 네비게이션 방향과 같은 어떤 것을 필요로 하지 않는다 - 데이터는
|
|
어떤 가능한 바업ㅂ으로 보여질 수 있고 검색될 수 있다.
|
|
</para>
|
|
|
|
<para>
|
|
먼저, 참여자들을 가진 한 개의 콜렉션을 <literal>Event</literal> Event 클래스에 추가시켜라:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[private Set participants = new HashSet();
|
|
|
|
public Set getParticipants() {
|
|
return participants;
|
|
}
|
|
|
|
public void setParticipants(Set participants) {
|
|
this.participants = participants;
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
이제 <literal>Event.hbm.xml</literal> 내에 연관의 이 쪽도 매핑하라.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="participants" table="PERSON_EVENT" inverse="true">
|
|
<key column="EVENT_ID"/>
|
|
<many-to-many column="PERSON_ID" class="Person"/>
|
|
</set>]]></programlisting>
|
|
|
|
<para>
|
|
당신이 볼 수 있듯이, 이것들은 두 매핑 문서들 내에서 정규 <literal>set</literal> 매핑들이다. <literal>key</literal>와
|
|
<literal>many-to-many</literal>에서 컬럼 이름들은 두 매핑 문서들에서 바뀌어진다. 여기서 가장 중요한 부가물은
|
|
<literal>Event</literal>의 콜렉션 매핑에 관한 <literal>set</literal> 요소 내에 있는 <literal>inverse="true"</literal>
|
|
속성이다.
|
|
</para>
|
|
|
|
<para>
|
|
이것이 의미하는 바는 Hibernate가 둘 사이의 링크에 대한 정보를 알 필요가 있을 때 다른 측-<literal>Person</literal> 클래스-를 취할
|
|
것이라는 점이다. 일단 당신이 우리의 두 엔티티들 사이에 양방향 링크가 생성되는 방법을 안다면 이것은 이해하기가 훨씬 더 쉬울 것이다.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="tutorial-associations-usingbidir">
|
|
<title>양방향 링크들에 작업하기</title>
|
|
|
|
<para>
|
|
첫 번째로 Hibernate가 정규 Java 의미론에 영향을 주지 않음을 염두에 두라. 우리는 단방향 예제에서 <literal>Person</literal>과
|
|
<literal>Event</literal> 사이에 어떻게 한 개의 링크를 생성시켰는가? 우리는 <literal>Event</literal> 타입의 인스턴스를
|
|
<literal>Person</literal> 타입의 이벤트 참조들을 가진 콜렉션에 추가시켰다. 따라서 명백하게 우리가 이 링크를 양방향으로
|
|
동작하도록 만들고자 원한다면, 우리는 다른 측 상에서 -하나의 <literal>Person</literal> 참조를 하나의 <literal>Event</literal>
|
|
내에 있는 콜렉션에 추가시킴으로써- 동일한 것을 행해야 한다. 이 "양 측 상에 링크 설정하기"는 절대적으로 필수적이고 당신은 그것을 행하는
|
|
것을 결코 잊지 말아야 한다.
|
|
</para>
|
|
|
|
<para>
|
|
많은 개발자들은 방비책을 프로그램하고 양 측들을 정확하게 설정하기 위한 하나의 링크 관리 메소드들을 생성시킨다. 예를 들면 <literal>Person</literal>에서 :
|
|
</para>
|
|
|
|
<programlisting><![CDATA[protected Set getEvents() {
|
|
return events;
|
|
}
|
|
|
|
protected void setEvents(Set events) {
|
|
this.events = events;
|
|
}
|
|
|
|
public void addToEvent(Event event) {
|
|
this.getEvents().add(event);
|
|
event.getParticipants().add(this);
|
|
}
|
|
|
|
public void removeFromEvent(Event event) {
|
|
this.getEvents().remove(event);
|
|
event.getParticipants().remove(this);
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
콜렉션에 대한 get 및 set 메소드드은 이제 protected임을 인지하라 - 이것은 동일한 패키지 내에 있는 클래스들과 서브클래스들이
|
|
그 메소드들에 접근하는 것을 허용해주지만, 그 밖의 모든 것들이 그 콜렉션들을 (물론, 대개) 직접 만지는 것을 금지시킨다. 당신은
|
|
다른 측 상에 있는 콜렉션에 대해 동일한 것을 행할 것이다.
|
|
</para>
|
|
|
|
<para>
|
|
<literal>inverse</literal> 매핑 속성은 무엇인가? 당신의 경우, 그리고 Java의 경우, 한 개의 양방향 링크는 단순히 양 측들에 대한
|
|
참조들을 정확하게 설정하는 문제이다. 하지만 Hibernate는 (컨스트레인트 위배를 피하기 위해서) SQL <literal>INSERT</literal> 문장과
|
|
<literal>UPDATE</literal> 문장을 정확하게 마련하기에 충분한 정보를 갖고 있지 않으며, 양방향 연관들을 올바르게 처리하기 위해
|
|
어떤 도움을 필요로 한다. 연관의 한 측을 <literal>inverse</literal>로 만드는 것은 기본적으로 그것을 무시하고 그것을 다른 측의
|
|
<emphasis>거울(mirror)</emphasis>로 간주하도록 Hibernate에게 알려준다. 그것은 Hibernate가 하나의 방향성 네비게이션 모형을
|
|
한 개의 SQL 스키마로 변환시킬 때 모든 쟁점들을 잘 해결하는데 필수적인 모든 것이다. 당신이 염두에 두어야 하는 규칙들은 간단하다 :
|
|
모든 양방향 연관들은 한 쪽이 <literal>inverse</literal>일 필요가 있다. one-to-many 연관에서 그것은 many-측이어야 하고,
|
|
many-to-many 연관에서 당신은 어느 측이든 선택할 수 있으며 차이점은 없다.
|
|
</para>
|
|
<!--
|
|
<para>
|
|
다음 장에서 우리는 Hibernate를 Tomcat 및 WebWork와 통합시킨다. <literal>EventManager</literal>는 우리의 성장하는
|
|
어플리케이션을 더이상 감당하지 못한다.
|
|
</para>
|
|
-->
|
|
</sect2>
|
|
</sect1>
|
|
|
|
<sect1 id="tutorial-summary">
|
|
<title>요약</title>
|
|
|
|
<para>
|
|
이 튜토리얼은 간단한 스탠드얼론 Hibernate 어플리케이션을 작성하는 기초를 다루었다.
|
|
</para>
|
|
|
|
<para>
|
|
만일 당신이 이미 Hibernate에 자신이 있다고 느낀다면, 당신이 흥미를 찾는 주제들에 대한 참조 문서 목차를 계속 브라우징하라
|
|
- 가장 많이 요청되는 것은 트랜잭션 처리(<xref linkend="transactions"/>), 페치 퍼포먼스(<xref linkend="performance"/>),
|
|
또는 API 사용법(<xref linkend="objectstate"/>), 그리고 질의 특징들(<xref linkend="objectstate-querying"/>)이다.
|
|
</para>
|
|
|
|
<para>
|
|
더 많은(특화된) 튜토리얼들에 대해서는 Hibernate 웹 사이트를 체크하는 것을 잊지 말라.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter> |