JongDae Kim 7672eaa82b *** empty log message ***
git-svn-id: https://svn.jboss.org/repos/hibernate/trunk/Hibernate3/doc@7183 1b8cb986-b30d-0410-93ca-fae66ebed9b2
2005-06-18 12:39:09 +00:00

1139 lines
64 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>&lt;session-factory&gt;</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> 액션을 호출한 후에 당신은 당신의 목록 속에서
결과들을 보게 될 것이다. 자동적인 스키마 생성과 내보내기(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>&lt;set&gt;</literal>을 지원한다. many-to-many
연관 (또는 <emphasis>n:m</emphasis> 엔티티 관계)의 경우, 한 개의 연관 테이블이 필요하다. 이 테이블 속에 있는 각각의
행은 한 명의 개인과 한 개의 이벤트 사이의 링크를 표현한다. 테이블 이름은 <literal>set</literal> 요소의 <literal>table</literal>
속성으로 구성된다. 연관 내의 식별자 컬럼 이름은 개인 측에 대해 <literal>&lt;key&gt;</literal> 요소로 정의되고
이벤트 측에 대한 컬럼 이름은 <literal>&lt;many-to-many&gt;</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>