hibernate-orm/reference/ko/modules/example_parentchild.xml
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

343 lines
17 KiB
XML

<chapter id="example-parentchild">
<title>예제: 부모/자식</title>
<para>
새로운 사용자들이 Hibernate로 행하고자 시도하는 바로 첫 번째 것들 중 하나는 부모/자식 타입의 관계를 모형화 시키는 것이다. 이것에 대한
두 가지 다른 접근법들이 존재한다. 여러가지 이유들로 인해 특히 새로운 사용자들에게 가장 편한 접근법은 <literal>Parent</literal>로부터
<literal>Child</literal>로의 <literal>&lt;one-to-many&gt;</literal> 연관을 가진 엔티티 클래스들로서 <literal>Parent</literal>
<literal>Child</literal> 양자를 모형화 시키는 것이다. (다른 접근법은 <literal>Child</literal>
<literal>&lt;composite-element&gt;</literal>로 선언하는 것이다.) 이제, (Hibernate에서) one to many 연관에 대한 디폴트
의미는 composite 요소 매핑의 의미보다 부모/자식 관계의 통상적인 의미에 훨씬 덜 가깝다는 것이 판명된다. 우리는 부모/자식 관계를 효율적이고
강력하게 모형화 시키기 위해 <emphasis>케스케이드들을 가진 양방향 one to many 연관</emphasis>을 사용하는 방법을 설명할 것이다.
그것은 전혀 어렵지 않다!
</para>
<sect1 id="example-parentchild-collections">
<title>콜렉션들에 관한 노트</title>
<para>
Hibernate 콜렉션들은 그것들의 소유하고 있는 엔티티의 논리적 부분으로 간주된다; 결코 포함된 엔티티들의 부분이 아니다. 이것은
중대한 구분점이다! 그것은 다음은 다음 결과들을 갖는다:
</para>
<itemizedlist>
<listitem>
<para>
콜렉션으로부터 객체를 제거하고/콜렉션에 객체를 추가 시킬 때, 콜렉션 소유자의 버전 번호가 증가된다.
</para>
</listitem>
<listitem>
<para>
만일 콜렉션으로부터 제거되었던 객체가 하나의 값 타입의 인스턴스(예를 들어 composite 요소)이면, 그 객체는 영속상태를 끝내고
그것의 상태가 데이터베이스로부터 완전히 제거될 것이다. 마찬가지로 하나의 값 타입의 인스턴스를 콜렉션에 추가시키는 것은 그것의
상태가 즉시 영속화 되도록 강제시킬 것이다.
</para>
</listitem>
<listitem>
<para>
반면에, 만일 엔티티가 콜렉션으로부터 제거될 경우(one-to-many 또는 many-to-many 연관), 그것은 디폴트로 삭제되지 않을
것이다. 이 특징은 완전하게 일관적이다 - 다른 엔티티의 내부 상태에 대한 변경은 연관된 엔티티를 사라지도록 강제하지 않을 것이다!
마찬가지로 콜렉션에 엔티티를 추가시키는 것은 디폴트로 그 엔티티가 영속화 되도록 강제시키지 않는다.
</para>
</listitem>
</itemizedlist>
<para>
대신에 콜렉션으로의 엔티티 추가가 두 엔티티들 사이에 단지 하나의 링크를 생성시키는 반면에, 그것을 제거하는 것은 링크를 제거한다는 점이
디폴트 특징이다. 이것은 모든 종류의 경우들에 대해 매우 적절하다. 그것이 전혀 적절하지 않은 곳은 부모/자식 관계인 경우이고, 여기서
자식의 생애는 부모의 생명주기에 묶여져 있다.
</para>
</sect1>
<sect1 id="example-parentchild-bidir">
<title>양방향 one-to-many</title>
<para>
<literal>Parent</literal>로부터 <literal>Child</literal>로의 간단한 <literal>&lt;one-to-many&gt;</literal>
연관관계로 시작한다고 가정하자.
</para>
<programlisting><![CDATA[<set name="children">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>]]></programlisting>
<para>
우리가 다음 코드를 실행시켰다면
</para>
<programlisting><![CDATA[Parent p = .....;
Child c = new Child();
p.getChildren().add(c);
session.save(c);
session.flush();]]></programlisting>
<para>
Hibernate는 두 개의 SQL 문장들을 실행할 것이다:
</para>
<itemizedlist>
<listitem>
<para><literal>c</literal>에 대한 레코드를 생성시키는 <literal>INSERT</literal></para>
</listitem>
<listitem>
<para><literal>p</literal>로부터 <literal>c</literal>로의 링크를 생성시키는 <literal>UPDATE</literal></para>
</listitem>
</itemizedlist>
<para>
이것은 비효율적일 뿐만 아니라, 또한 <literal>parent_id</literal> 컬럼 상의 임의의 <literal>NOT NULL</literal>
컨스트레인트에 위배된다. 우리는 콜렉션 매핑에서 <literal>not-null="true"</literal>를 지정함으로써 null 허용 가능
컨스트레인트 위반을 정정할 수 있다:
</para>
<programlisting><![CDATA[<set name="children">
<key column="parent_id" not-null="true"/>
<one-to-many class="Child"/>
</set>]]></programlisting>
<para>
하지만 이것은 권장되는 해결책이 아니다.
</para>
<para>
이 행위의 기본 원인은 <literal>p</literal>로부터 <literal>c</literal>로의 링크(foreign key <literal>parent_id</literal>)가
<literal>Child</literal> 객체의 상태의 부분으로 간주되지 않고 그러므로 <literal>INSERT</literal>로 생성되지 않는다는
점이다. 따라서 해결책은 <literal>Child</literal> 매핑의 링크 부분을 만드는 것이다.
</para>
<programlisting><![CDATA[<many-to-one name="parent" column="parent_id" not-null="true"/>]]></programlisting>
<para>
(우리는 또한 <literal>parent</literal> 프로퍼티를 <literal>Child</literal> 클래스에 추가시킬 필요가 있다.)
</para>
<para>
이제 <literal>Child</literal> 엔티티가 링크의 상태를 관리한다는 점을 노트하고, 우리는 링크를 업데이트 시키지 말도록 콜렉션에게
통보한다. 우리는 <literal>inverse</literal> 속성을 사용한다.
</para>
<programlisting><![CDATA[<set name="children" inverse="true">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>]]></programlisting>
<para>
다음 코드는 새로운 <literal>Child</literal>를 추가시키는데 사용될 것이다
</para>
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
c.setParent(p);
p.getChildren().add(c);
session.save(c);
session.flush();]]></programlisting>
<para>
그리고 이제, 유일하게 한 개의 SQL <literal>INSERT</literal>가 실행될 것이다!
</para>
<para>
약간 거칠게, 우리는 <literal>Parent</literal><literal>addChild()</literal> 메소드를 생성시킬 수 있다.
</para>
<programlisting><![CDATA[public void addChild(Child c) {
c.setParent(this);
children.add(c);
}]]></programlisting>
<para>
이제, <literal>Child</literal>를 추가하는 코드는 다음과 같다
</para>
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
p.addChild(c);
session.save(c);
session.flush();]]></programlisting>
</sect1>
<sect1 id="example-parentchild-cascades">
<title>케스케이딩 생명주기</title>
<para>
<literal>save()</literal>에 대한 명시적인 호출은 여전히 성가시다. 우리는 케스케이딩을 사용하여 이것을 얘기할 것이다.
</para>
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>]]></programlisting>
<para>
다음은 위의 코드를 단순화 시킨다
</para>
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
Child c = new Child();
p.addChild(c);
session.flush();]]></programlisting>
<para>
유사하게, 우리는 <literal>Parent</literal>를 저장하거나 삭제할 때 자식들에 대해 반복하는 것을 필요로 하지 않는다. 다음은
데이터베이스로부터 <literal>p</literal>와 모든 그것의 자식들을 제거시킨다.
</para>
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
session.delete(p);
session.flush();]]></programlisting>
<para>
하지만, 다음 코드
</para>
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
c.setParent(null);
session.flush();]]></programlisting>
<para>
는 데이터베이스로부터 <literal>c</literal>를 제거하지 않을 것이다; 그것은 오직 <literal>p</literal>에 대한 링크만을 제거할
것이다(그리고 이 경우에 <literal>NOT NULL</literal> 컨스트레인트 위반을 일으킬 것이다 ). 당신은 명시적으로
<literal>Child</literal><literal>delete()</literal> 시킬 필요가 있다.
</para>
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
session.delete(c);
session.flush();]]></programlisting>
<para>
이제 우리의 경우에 <literal>Child</literal>는 그것의 부모 없이는 진정으로 존재할 수 없다. 따라서 만일 우리가 콜렉션으로부터
하나의 <literal>Child</literal>를 제거할 경우, 우리는 그것이 정말로 삭제되기를 원한다. 이를 위해 우리는
<literal>cascade="all-delete-orphan"</literal>을 사용해야 한다.
</para>
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all-delete-orphan">
<key column="parent_id"/>
<one-to-many class="Child"/>
</set>]]></programlisting>
<para>
노트: 비록 콜렉션 매핑이 <literal>inverse="true"</literal>를 지정할 지라도, 케스케이드들은 여전히 콜렉션 요소들을
반복함으로써 처리된다. 따라서 객체가 케스케이드에 의해 저장되고, 삭제되거나 업데이트 되는 것을 당신이 필요로 할 경우, 당신은
그것을 그 콜렉션에 추가해야 한다. 단순히 <literal>setParent()</literal>를 호출하는 것으로는 충분하지 않다.
</para>
</sect1>
<sect1 id="example-parentchild-update">
<title>케스케이드들과 <literal>unsaved-value</literal></title>
<para>
우리가 하나의 <literal>Session</literal> 속에 <literal>Parent</literal>를 로드시켰고 UI 액션에서 어떤 변경들을 행했고,
<literal>update()</literal>를 호출하여 새로운 세션에서 이들 변경들을 영속화 시키는 것을 원한다고 가정하자. <literal>Parent</literal>
자식들을 가진 콜렉션을 포함할 것이고, 케스케이딩 업데이트가 사용 가능하기 때문에, Hibernate는 어느 자식들이 새로이 초기화 되는지
그리고 어느 것이 데이터베이스에서 현재 행들을 표현하는지를 알 필요가 있다. <literal>Parent</literal><literal>Child</literal>
모두 <literal>Long</literal> 타입의 식별자 프로퍼티들을 생성시켰다고 가정하자. Hibernate는 어느 자식들이 새로운 것인지를
결정하는데 식별자와 version/timestamp 프로퍼티 값을 사용할 것이다.(<xref linkend="objectstate-saveorupdate"/>을 보라.)
<emphasis>Hibernate3에서는<literal>unsaved-value</literal>를 더이상 명시적으로 지정할 필요가 없다.</emphasis>
</para>
<para>
다음 코드는 <literal>parent</literal><literal>child</literal>를 업데이트하고 <literal>newChild</literal>
삽입시킬 것이다.
</para>
<programlisting><![CDATA[//parent and child were both loaded in a previous session
parent.addChild(child);
Child newChild = new Child();
parent.addChild(newChild);
session.update(parent);
session.flush();]]></programlisting>
<para>
물론 그것은 생성되는 식별자의 경우에는 모두 매우 좋지만, 할당되는 식별자들과 composite 식별자들에 대해서는 어떠한가? 이것은 보다
어렵다. 왜냐하면 Hibernate는 (사용자에 의해 할당된 식별자를 가진) 새로이 초기화 된 객체와 이전 세션에서 로드되었던 객체 사이를
구별짓는데 식별자 프로퍼티를 사용할 수 없기 때문이다. 이 경우에, Hibernate는 timestamp 프로퍼티 또는 version 프로퍼티를
사용하거나 실제로 second-level 캐시를 질의하거나 가장 나쁜 경우에는 행이 존재하는지를 알기 위해 데이터베이스를 질의할 것이다.
</para>
<!-- undocumenting
<para>
There is one further possibility. The <literal>Interceptor</literal> method named
<literal>isUnsaved()</literal> lets the application implement its own strategy for distinguishing
newly instantiated objects. For example, you could define a base class for your persistent classes.
</para>
<programlisting><![CDATA[public class Persistent {
private boolean _saved = false;
public void onSave() {
_saved=true;
}
public void onLoad() {
_saved=true;
}
......
public boolean isSaved() {
return _saved;
}
}]]></programlisting>
<para>
(The <literal>saved</literal> property is non-persistent.)
Now implement <literal>isUnsaved()</literal>, along with <literal>onLoad()</literal>
and <literal>onSave()</literal> as follows.
</para>
<programlisting><![CDATA[public Boolean isUnsaved(Object entity) {
if (entity instanceof Persistent) {
return new Boolean( !( (Persistent) entity ).isSaved() );
}
else {
return null;
}
}
public boolean onLoad(Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
if (entity instanceof Persistent) ( (Persistent) entity ).onLoad();
return false;
}
public boolean onSave(Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
if (entity instanceof Persistent) ( (Persistent) entity ).onSave();
return false;
}]]></programlisting>
<para>
Don't worry; in Hibernate3 you don't need to write any of this kind of code if you don't want to.
</para>
-->
</sect1>
<sect1 id="example-parentchild-conclusion">
<title>결론</title>
<para>
여기에 숙지할 것이 약간 있고 그것은 처음에는 혼동스러운 것처럼 보일 수 있다. 하지만 실제로 그것은 모두 매우 좋게 동작한다. 대부분의
Hibernate 어플리케이션들은 많은 장소들에서 부모/자식 패턴을 사용한다.
</para>
<para>
우리는 첫 번째 단락에서 대안을 언급했다. 위의 쟁점들 중 어느 것도 정확하게 부모/자식 관계의 의미를 가진,
<literal>&lt;composite-element&gt;</literal> 매핑들의 경우에는 존재하지 않는다. 불행히도, composite 요소 클래스들에
대한 두 개의 커다란 제약들이 존재한다: composite 요소들은 콜렉션들을 소유하지 않고, 그것들은 유일한 부모가 아닌 다른 어떤
엔티티의 자식일 수는 없다.
</para>
</sect1>
</chapter>