mirror of
https://github.com/hibernate/hibernate-orm
synced 2025-03-03 08:19:15 +00:00
git-svn-id: https://svn.jboss.org/repos/hibernate/trunk/Hibernate3/doc@7183 1b8cb986-b30d-0410-93ca-fae66ebed9b2
343 lines
17 KiB
XML
343 lines
17 KiB
XML
<chapter id="example-parentchild">
|
|
<title>예제: 부모/자식</title>
|
|
|
|
<para>
|
|
새로운 사용자들이 Hibernate로 행하고자 시도하는 바로 첫 번째 것들 중 하나는 부모/자식 타입의 관계를 모형화 시키는 것이다. 이것에 대한
|
|
두 가지 다른 접근법들이 존재한다. 여러가지 이유들로 인해 특히 새로운 사용자들에게 가장 편한 접근법은 <literal>Parent</literal>로부터
|
|
<literal>Child</literal>로의 <literal><one-to-many></literal> 연관을 가진 엔티티 클래스들로서 <literal>Parent</literal>와
|
|
<literal>Child</literal> 양자를 모형화 시키는 것이다. (다른 접근법은 <literal>Child</literal>를
|
|
<literal><composite-element></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><one-to-many></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><composite-element></literal> 매핑들의 경우에는 존재하지 않는다. 불행히도, composite 요소 클래스들에
|
|
대한 두 개의 커다란 제약들이 존재한다: composite 요소들은 콜렉션들을 소유하지 않고, 그것들은 유일한 부모가 아닌 다른 어떤
|
|
엔티티의 자식일 수는 없다.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter> |