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@7183 1b8cb986-b30d-0410-93ca-fae66ebed9b2
1116 lines
61 KiB
XML
1116 lines
61 KiB
XML
<chapter id="objectstate">
|
|
<title>객체들로 작업하기</title>
|
|
|
|
<para>
|
|
Hibernate는 기본 데이터베이스 관리 시스템의 상세로부터 개발자들을 은폐시켜줄 뿐만 아니라, 또한 객체들에 대한
|
|
<emphasis>상태 관리</emphasis>를 제공하는 하나의 완전한 객체/관계형 매핑 솔루션이다. 이것은 공통적인
|
|
JDBC/SQL 영속 계층들 내에서의 SQL <literal>문장들</literal>에 대한 관리와는 반대로, 자바 어플리케이션들에서
|
|
영속에 관한 매우 고유한 객체-지향적 관점이다.
|
|
</para>
|
|
|
|
<para>
|
|
달리 말해, Hibernate 어플리케이션 개발자들은 그들의 객체들의 <emphasis>상태</emphasis>에 대해 항상 생각해야 하고,
|
|
SQL 문장들의 실행에 대해서는 필수적이지 않다. 이 부분은 Hibernate에 의해 처리되고 시스템의 퍼포먼스를 튜닝할 때
|
|
어플리케이션 개발자와 유일하게 관련된다.
|
|
</para>
|
|
|
|
<sect1 id="objectstate-overview">
|
|
<title>Hibernate 객체 상태들</title>
|
|
|
|
<para>
|
|
Hibernate 다음 객체 상태들을 정의하고 지원한다:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>Transient</emphasis> - 만일 객체가 <literal>new</literal> 연산자를 사용하여 방금 초기화 되었다면
|
|
객체는 transient이고, 그것은 Hibernate <literal>Session</literal>과 연관되어 있지 않다. 그것은 데이터베이스
|
|
내에서 영속 표상을 갖지 않고 식별자 값이 할당되지 않았다. 만일 어플리케이션이 더 이상 참조를 소유하지 않을 경우
|
|
transient 인스턴스들은 쓰레기 수집기에 의해 파괴될 것이다. 객체를 영속화 시키는데
|
|
(그리고 이 전이(transition)에 대해 실행될 필요가 있는 SQL 문장들을 Hibernate로 하여금 처리하도록 하는데)
|
|
Hibernate <literal>Session</literal>을 사용하라.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>Persistent</emphasis> - 하나의 영속 인스턴스는 데이터베이스 내에서 하나의 표상을 갖고 하나의 식별자 값을
|
|
갖는다. 그것은 방금 저장되었거나 로드되었을 수 있지만, 정의상 그것은 <literal>Session</literal>의 범위 내에 있다.
|
|
Hibernate는 영속 상태에서 객체에 대해 행해진 임의의 변경들을 검출해낼 것이고 단위 작업이 완료될 때 그 상태를 데이터베이스와
|
|
동기화 시킬 것이다. 개발자들은 하나의 객체가 transient로 되어야 할 때 <literal>UPDATE</literal> 문장들이나
|
|
<literal>DELETE</literal> 문장들을 수작업으로 실행하지 않는다.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>Detached</emphasis> - 하나의 detached 인스턴스는 영속화 되었지만, 그것의
|
|
<literal>Session</literal>이 닫혀진 객체이다. 물론 그 객체에 대한 참조는 여전히 유효하고,
|
|
그 detached 인스턴스는 이 상태에서도 변경될 수도 있다. 하나의 detached 인스턴스는 나중에
|
|
그것(과 모두 변경들)을 다시 영속화 시켜서 새로운 <literal>Session</literal>에 다시 첨부될 수 있다.
|
|
이 특징은 사용자가 생각할 시간을 필요로 하는 장 기간 실행되는 작업 단위를 위한 프로그래밍 모형을 가능하게
|
|
해준다. 우리는 그것들을 <emphasis>어플리케이션 트랜잭션들</emphasis>, 즉 사용자의 관점의 작업 단위라고
|
|
부른다.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
이제 우리는 상태들과 상태 전이(transition)들(그리고 전이를 트리거 시키는 Hibernate 메소드들)을 상세하게 논의할 것이다.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-makingpersistent" revision="1">
|
|
<title>객체들을 영속화 시키기</title>
|
|
|
|
<para>
|
|
하나의 영속 클래스의 새로이 초기화 된 인스턴스들은 Hibernate에 의해 <emphasis>transient</emphasis>로 간주된다.
|
|
우리는 그것을 세션과 연관지어서 transient 인스턴스를 <emphasis>영속화</emphasis> 시킬 수 있다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[DomesticCat fritz = new DomesticCat();
|
|
fritz.setColor(Color.GINGER);
|
|
fritz.setSex('M');
|
|
fritz.setName("Fritz");
|
|
Long generatedId = (Long) sess.save(fritz);]]></programlisting>
|
|
|
|
<para>
|
|
만일 <literal>Cat</literal>이 생성된 식별자를 가질 경우, <literal>save()</literal>가 호출될 때
|
|
그 식별자가 생성되고 <literal>cat</literal>에 할당된다. 만일 <literal>Cat</literal>이 하나의
|
|
<literal>assigned</literal> 식별자나 하나의 composite key를 가질 경우, <literal>save()</literal>를
|
|
호출하기 전에 그 식별자가<literal>cat</literal> 인스턴스에 할당될 것이다. 당신은 또한 EJB3 초기 드래프트에서
|
|
정의된 의미로 <literal>save()</literal> 대신 <literal>persist()</literal>를 사용할 수도 있다.
|
|
</para>
|
|
|
|
<para>
|
|
다른 방법으로, 당신은 <literal>save()</literal>의 오버로드된 버전을 사용하여 식별자를 할당할 수 있다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[DomesticCat pk = new DomesticCat();
|
|
pk.setColor(Color.TABBY);
|
|
pk.setSex('F');
|
|
pk.setName("PK");
|
|
pk.setKittens( new HashSet() );
|
|
pk.addKitten(fritz);
|
|
sess.save( pk, new Long(1234) );]]></programlisting>
|
|
|
|
<para>
|
|
만일 당신이 영속화 시키는 객체가 연관된 객체들(예를 들면. 앞의 예제에 있는 <literal>kittens</literal> 콜렉션)을 갖고 있다면,
|
|
당신이 하나의 foreign 키 컬럼에 대해 하나의 <literal>NOT NULL</literal> 컨스트레인트를 갖지 않는 한,
|
|
이들 객체들은 당신이 좋아하는 임의의 순서로 영속화 되었을 수도 있다. foreign 키 컨스트레인트들을 위배하는 위험성이
|
|
결코 존재하지 않는다. 하지만 당신이 잘못된 순서로 그 객체들을 <literal>save()</literal> 시킬 경우 당신은
|
|
<literal>NOT NULL</literal> 컨스트레인트를 위배할 수도 있다.
|
|
</para>
|
|
|
|
<para>
|
|
당신이 연관된 객체들을 자동적으로 저장시키는데 Hibernate의 <emphasis>transitive persistence(전이 영속)</emphasis>
|
|
특징을 사용하는 것을 매우 좋아할 것이므로 대개 당신은 이 상세를 내버려둔다. 그때 <literal>NOT NULL</literal> 컨스트레인트
|
|
위배들이 발생되지 않을지라도 - Hibernate는 모든 것을 처리할 것이다. Transitive persistence(전이 영속)은 이 장에서
|
|
후반부에서 논의된다.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-loading">
|
|
<title>객체를 로드시키기</title>
|
|
|
|
<para>
|
|
<literal>Session</literal>의 <literal>load()</literal> 메소드들은 만일 당신이 그것(영속 인스턴스)의 식별자들을
|
|
이미 알고 있을 경우에 영속 인스턴스를 검색하는 방법을 당신에게 제공한다. <literal>load()</literal>는 하나의 클래스 객체를
|
|
취하고 그 상태를 영속(persistent) 상태로 그 클래스의 새로이 초기화 된 인스턴스 속으로 로드시킬 것이다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Cat fritz = (Cat) sess.load(Cat.class, generatedId);]]></programlisting>
|
|
|
|
<programlisting><![CDATA[// you need to wrap primitive identifiers
|
|
long pkId = 1234;
|
|
DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );]]></programlisting>
|
|
|
|
<para>
|
|
다른 방법으로 당신은 주어진 인스턴스 속으로 상태를 로드시킬 수 있다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Cat cat = new DomesticCat();
|
|
// load pk's state into cat
|
|
sess.load( cat, new Long(pkId) );
|
|
Set kittens = cat.getKittens();]]></programlisting>
|
|
|
|
<para>
|
|
만일 일치하는 데이터베이스 행이 존재하지 않을 경우에 <literal>load()</literal>가 unrecoverable(복구 불가능한) 예외상황을
|
|
던질 것임을 노트하라. 만일 클래스가 프락시를 갖도록 매핑된 경우, <literal>load()</literal>는 초기화 되지 않은 프락시를 단지
|
|
반환하고 당신이 그 프락시의 메소드를 호출하기 전까지는 실제로 데이터베이스에 접속하지 않는다. 당신이 데이터베이스로부터
|
|
객체에 대한 연관을 실제로 로드시키지 않고서 객체에 대한 연관을 생성시키고자 원할 경우에 이 특징이 매우 유용하다. 만일
|
|
<literal>batch-size</literal>가 class 매핑에 정의되는 경우 그것은 또한 다중 인스턴스들이 하나의 배치로서 로드되는 것을
|
|
허용해준다.
|
|
</para>
|
|
|
|
<para>
|
|
만일 당신이 하나의 일치하는 행이 존재하는지를 확신할 수 없을 경우, 당신은 <literal>get()</literal> 메소드를 사용해야 한다.
|
|
그것(<literal>get()</literal> 메소드)는 데이터베이스에 즉시 접속하고 만일 일치하는 행이 없을 경우 null을 반환한다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Cat cat = (Cat) sess.get(Cat.class, id);
|
|
if (cat==null) {
|
|
cat = new Cat();
|
|
sess.save(cat, id);
|
|
}
|
|
return cat;]]></programlisting>
|
|
|
|
<para>
|
|
당신은 하나의 <literal>LockMode</literal>를 사용하는, <literal>SELECT ... FOR UPDATE</literal>를 사용하여
|
|
하나의 객체를 로드시킬 수도 있다. 추가 정보는 API 문서를 보라.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);]]></programlisting>
|
|
|
|
<para>
|
|
어떤 연관된 인스턴스들이나 포함된 콜렉션들은 당신이 그 연관에 대한 케스케이드 스타일로서 <literal>lock</literal> 또는
|
|
<literal>all</literal>을 지정하도록 결정하지 않는 한, <literal>FOR UPDATE</literal>로 선택되지 않음을 노트하라.
|
|
</para>
|
|
|
|
<para>
|
|
<literal>refresh()</literal> 메소드를 사용하여, 아무때나 하나의 객체와 모든 그것의 콜렉션들을 다시 로드시키는 것이
|
|
가능하다. 데이터베이스 트리거들이 그 객체의 프로퍼티들 중 어떤 것을 초기화 시키는데 사용될 때 이것이 유용하다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[sess.save(cat);
|
|
sess.flush(); //force the SQL INSERT
|
|
sess.refresh(cat); //re-read the state (after the trigger executes)]]></programlisting>
|
|
|
|
<para>
|
|
중요한 질문이 대개 이 지점에서 나타난다: Hibernate는 데이터베이로부터 그것을 얼마나 많이 로드시키고 그리고 얼마나 많은
|
|
SQL <literal>SELECT</literal>들이 그것을 사용할 것인가? 이것은 <emphasis>페칭 방도</emphasis>에 의존하고
|
|
<xref linkend="performance-fetching"/>에 설명되어 있다.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-querying" revision="1">
|
|
<title>질의하기</title>
|
|
|
|
<para>
|
|
만일 당신이 당신이 찾고 있는 객체들에 대한 식별자들을 모를 경우, 당신은 하나의 질의를 필요로 한다. Hibernate는 사용이 쉽지만
|
|
강력한 객체 지향 질의 언어(HQL)를 지원한다. 프로그램 상의 질의 생성을 위해, Hibernate는 정교한 Criteria 및 Example 질의
|
|
특징(QBC와 QBE)를 지원한다. 당신은 또한 객체들로의 결과 셋 변환을 위한 선택적인 Hibernate의 지원으로, 당신의 데이터베이스의
|
|
native SQL 속에 당신의 질의를 표현할 수도 있다.
|
|
</para>
|
|
|
|
<sect2 id="objectstate-querying-executing">
|
|
<title>질의들을 실행하기</title>
|
|
|
|
<para>
|
|
HQL 질의와 native SQL 질의는 <literal>org.hibernate.Query</literal>의 인스턴스로 표현된다. 이 인터페이스는
|
|
파라미터 바인딩, 결과셋 핸들링을 위한, 그리고 실제 질의의 실행을 위한 메소드들을 제공한다. 당신은 항상 현재
|
|
<literal>Session</literal>을 사용하여 하나의 <literal>Query</literal>를 얻는다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[List cats = session.createQuery(
|
|
"from Cat as cat where cat.birthdate < ?")
|
|
.setDate(0, date)
|
|
.list();
|
|
|
|
List mothers = session.createQuery(
|
|
"select mother from Cat as cat join cat.mother as mother where cat.name = ?")
|
|
.setString(0, name)
|
|
.list();
|
|
|
|
List kittens = session.createQuery(
|
|
"from Cat as cat where cat.mother = ?")
|
|
.setEntity(0, pk)
|
|
.list();
|
|
|
|
Cat mother = (Cat) session.createQuery(
|
|
"select cat.mother from Cat as cat where cat = ?")
|
|
.setEntity(0, izi)
|
|
.uniqueResult();]]></programlisting>
|
|
|
|
<para>
|
|
하나의 질의는 대개 <literal>list()</literal>를 호출하여 실행되고, 질의의 결과는 메모리 내에서 하나의 콜렉션 속으로
|
|
전체적으로 로드될 것이다. 하나의 질의에 의해 검색된 엔티티 인스턴스들은 영속(persistent) 상태에 있다. 당신의 질의가
|
|
하나의 객체를 오직 반환할 것임을 당신이 알고 있을 경우에 <literal>uniqueResult()</literal> 메소드는 단축을 제공한다.
|
|
</para>
|
|
|
|
<sect3 id="objectstate-querying-executing-iterate">
|
|
<title>결과들을 반복하기</title>
|
|
|
|
<para>
|
|
종종, 당신은 <literal>iterate()</literal> 메소드를 사용하여 질의를 실행함으로써 더 나은 퍼포먼스를 성취하는
|
|
것이 가능할 수 있다. 이것은 오직 대개 질의에 의해 반환되는 실제 엔티티 인스턴스들이 이미 세션 내에 있거나
|
|
second-level 캐시 내에 있을 것임을 당신이 예상하는 경우일 것이다. 만일 그것들이 이미 캐시되지 않았다면,
|
|
<literal>iterate()</literal>는 <literal>list()</literal> 보다 더 느릴 것이고 간단한 질의에 대해
|
|
많은 데이터베이스 접속들을, 대개 오직 식별자들을 반환하는 초기 select에 대해 <emphasis>1</emphasis>번의 접속과
|
|
실제 인스턴스들을 초기화 시키는 추가적인 select들에 대해 <emphasis>n</emphasis> 번의 접속을 필요로 할 수 있다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[// fetch ids
|
|
Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate();
|
|
while ( iter.hasNext() ) {
|
|
Qux qux = (Qux) iter.next(); // fetch the object
|
|
// something we couldnt express in the query
|
|
if ( qux.calculateComplicatedAlgorithm() ) {
|
|
// delete the current instance
|
|
iter.remove();
|
|
// dont need to process the rest
|
|
break;
|
|
}
|
|
}]]></programlisting>
|
|
</sect3>
|
|
|
|
<sect3 id="objectstate-querying-executing-tuples">
|
|
<title>튜플들을 반환하는 질의들</title>
|
|
|
|
<para>
|
|
Hibernate 질의들은 때때로 객체들의 튜플들을 반환하고, 그 경우에 각각의 튜플은 배열로서 반환된다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Iterator kittensAndMothers = sess.createQuery(
|
|
"select kitten, mother from Cat kitten join kitten.mother mother")
|
|
.list()
|
|
.iterator();
|
|
|
|
while ( kittensAndMothers.hasNext() ) {
|
|
Object[] tuple = (Object[]) kittensAndMothers.next();
|
|
Cat kitten = tuple[0];
|
|
Cat mother = tuple[1];
|
|
....
|
|
}]]></programlisting>
|
|
|
|
</sect3>
|
|
|
|
<sect3 id="objectstate-querying-executing-scalar">
|
|
<title>스칼라 결과들</title>
|
|
|
|
<para>
|
|
질의들은 <literal>select</literal> 절 내에 하나의 클래스에 대한 하나의 프로퍼티를 지정할 수 있다.
|
|
그것들은 심지어 SQL 집계 함수들을 호출할 수도 있다. 프로퍼티들이나 aggregate들은
|
|
"스칼라" 결과들(그리고 영속 상태에 있는 엔티티들이 아닌 것으)로 간주된다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Iterator results = sess.createQuery(
|
|
"select cat.color, min(cat.birthdate), count(cat) from Cat cat " +
|
|
"group by cat.color")
|
|
.list()
|
|
.iterator();
|
|
|
|
while ( results.hasNext() ) {
|
|
Object[] row = results.next();
|
|
Color type = (Color) row[0];
|
|
Date oldest = (Date) row[1];
|
|
Integer count = (Integer) row[2];
|
|
.....
|
|
}]]></programlisting>
|
|
|
|
</sect3>
|
|
|
|
<sect3 id="objectstate-querying-executing-parameters">
|
|
<title>바인드 프라미터들</title>
|
|
|
|
<para>
|
|
<literal>Query</literal> 상의 메소드들은 명명된 파라미터들 또는 JDBC-스타일의 <literal>?</literal>
|
|
파라미터들에 바인딩 값들을 제공한다. <emphasis>JDBC와는 대조적으로, Hibernate 숫자 파라미터들은 0에서
|
|
시작된다.</emphasis> 명명된 파라미터들은 질의 문자열 속에서 <literal>:name</literal> 형식의 식별자들이다.
|
|
명명된 파라미터들의 장점들은 다음과 같다:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
명명된 파라미터들은 그것들이 질의 문자열 내에 발생하는 순서에 관계없다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
그것들은 동일한 질의 내에서 여러 번 발생할 수 있다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
그것은 자기-설명적이다
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<programlisting><![CDATA[//named parameter (preferred)
|
|
Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");
|
|
q.setString("name", "Fritz");
|
|
Iterator cats = q.iterate();]]></programlisting>
|
|
|
|
<programlisting><![CDATA[//positional parameter
|
|
Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");
|
|
q.setString(0, "Izi");
|
|
Iterator cats = q.iterate();]]></programlisting>
|
|
|
|
<programlisting><![CDATA[//named parameter list
|
|
List names = new ArrayList();
|
|
names.add("Izi");
|
|
names.add("Fritz");
|
|
Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)");
|
|
q.setParameterList("namesList", names);
|
|
List cats = q.list();]]></programlisting>
|
|
|
|
</sect3>
|
|
|
|
<sect3 id="objectstate-querying-executing-pagination">
|
|
<title>쪽매김</title>
|
|
|
|
<para>
|
|
만일 당신이 당신의 결과 셋에 경계(당신이 검색하고자 원하는 최대 개수 그리고/또는 당신이 검색하고자 원하는 첫 번째 행)을
|
|
지정할 필요가 있다면 당신은 <literal>Query</literal> 인터페이스의 메소드들을 사용해야 한다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Query q = sess.createQuery("from DomesticCat cat");
|
|
q.setFirstResult(20);
|
|
q.setMaxResults(10);
|
|
List cats = q.list();]]></programlisting>
|
|
|
|
<para>
|
|
Hibernate는 이 limit 질의를 당신의 DBMS의 native SQL로 번역하는 방법을 알고 있다.
|
|
</para>
|
|
|
|
</sect3>
|
|
|
|
<sect3 id="objectstate-querying-executing-scrolling">
|
|
<title>스크롤 가능한 iteration</title>
|
|
|
|
<para>
|
|
당신의 JDBC 드라이버가 스크롤 가능한 <literal>ResultSet</literal>들을 지원할 경우,
|
|
<literal>Query</literal> 인터페이스는 <literal>ScrollableResults</literal> 객체를 얻는데
|
|
사용될 수 있고, 그것은 질의 결과들에 대한 유연한 네비게이션을 허용해준다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +
|
|
"order by cat.name");
|
|
ScrollableResults cats = q.scroll();
|
|
if ( cats.first() ) {
|
|
|
|
// find the first name on each page of an alphabetical list of cats by name
|
|
firstNamesOfPages = new ArrayList();
|
|
do {
|
|
String name = cats.getString(0);
|
|
firstNamesOfPages.add(name);
|
|
}
|
|
while ( cats.scroll(PAGE_SIZE) );
|
|
|
|
// Now get the first page of cats
|
|
pageOfCats = new ArrayList();
|
|
cats.beforeFirst();
|
|
int i=0;
|
|
while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );
|
|
|
|
}
|
|
cats.close()]]></programlisting>
|
|
|
|
<para>
|
|
열려진 데이터베이스 커넥션(과 커서)가 이 기능에 필요함을 노트하고, 만일 당신이 쪽매김 기능을 작동시킬 필요가 있다면
|
|
<literal>setMaxResult()</literal>/<literal>setFirstResult()</literal>를 사용하라.
|
|
</para>
|
|
|
|
</sect3>
|
|
|
|
<sect3 id="objectstate-querying-executing-named">
|
|
<title>명명된 질의들을 구체화 시키기</title>
|
|
|
|
<para>
|
|
당신은 또한 매핑 문서 속에 명명된 질의들을 정의할 수 있다.(만일 당신의 질의가 마크업으로서 해석될 수 있는 문자들을
|
|
포함할 경우에 <literal>CDATA</literal> 섹션을 사용하는 것을 기억하라))
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<query name="eg.DomesticCat.by.name.and.minimum.weight"><![CDATA[
|
|
from eg.DomesticCat as cat
|
|
where cat.name = ?
|
|
and cat.weight > ?
|
|
] ]></query>]]></programlisting>
|
|
|
|
<para>
|
|
파라미터 바인딩과 실행은 프로그램 상으로 행해진다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Query q = sess.getNamedQuery("eg.DomesticCat.by.name.and.minimum.weight");
|
|
q.setString(0, name);
|
|
q.setInt(1, minWeight);
|
|
List cats = q.list();]]></programlisting>
|
|
|
|
<para>
|
|
실제 프로그램 코드는 사용되는 질의 언어에 독립적이고, 당신은 또한 메타데이터로 native SQL 질의들을 정의할 수도 있거나
|
|
그것들을 매핑 파일들 속에 기존 질의들을 위치지움으로써 기존 질의들을 Hibernate로 이전시킬 수도 있음을 노트하라.
|
|
</para>
|
|
|
|
</sect3>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="objectstate-filtering" revision="1">
|
|
<title>콜렉션들을 필터링 하기</title>
|
|
<para>
|
|
콜렉션 <emphasis>필터</emphasis>는 영속 콜렉션 또는 배열에 적용될 수 있는 질의의 특별한 타입이다. 질의 문자열은
|
|
현재의 콜렉션 요소를 의미하는 <literal>this</literal>를 참조할 수 있다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Collection blackKittens = session.createFilter(
|
|
pk.getKittens(),
|
|
"where this.color = ?")
|
|
.setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) )
|
|
.list()
|
|
);]]></programlisting>
|
|
|
|
<para>
|
|
반환되는 콜렉션은 하나의 bag으로 간주되고, 그것은 주어진 콜렉션에 대한 사본이다. 원래의 콜렉션은 변경되지 않는다
|
|
(이것은 이름 "filter"의 의미와는 정반대이지만, 예상되는 행위와 일치된다).
|
|
</para>
|
|
|
|
<para>
|
|
필터들은 <literal>from</literal> 절을 필요로 하지 않음을 관찰하라(필요할 경우에 필터들이 한 개의 from 절을 가질 수
|
|
있을지라도). 필터들은 콜렉션 요소들 자체들을 반환하는 것으로 한정되지 않는다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Collection blackKittenMates = session.createFilter(
|
|
pk.getKittens(),
|
|
"select this.mate where this.color = eg.Color.BLACK.intValue")
|
|
.list();]]></programlisting>
|
|
|
|
<para>
|
|
심지어 하나의 공백의 필터 질의도 예를 들어 거대한 콜렉션 내에 있는 요소들의 부분집합들을 로드시키는데 유용하다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Collection tenKittens = session.createFilter(
|
|
mother.getKittens(), "")
|
|
.setFirstResult(0).setMaxResults(10)
|
|
.list();]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="objecstate-querying-criteria" revision="1">
|
|
<title>Criteria 질의들</title>
|
|
|
|
<para>
|
|
HQL은 극히 강력하지만 몇몇 개발자들은 질의 문자열들을 빌드하기 보다, 객체 지향 API를 사용하여 동적으로 질의들을 빌드시키는
|
|
것을 선호한다. Hibernate는 이들 경우들을 위한 직관적인 <literal>Criteria</literal> query API를 제공한다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Criteria crit = session.createCriteria(Cat.class);
|
|
crit.add( Expression.eq( "color", eg.Color.BLACK ) );
|
|
crit.setMaxResults(10);
|
|
List cats = crit.list();]]></programlisting>
|
|
|
|
<para>
|
|
<literal>Criteria</literal>와 연관된 <literal>Example</literal> API 는
|
|
<xref linkend="querycriteria"/>에서 상세하게 논의된다.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="objectstate-querying-nativesql" revision="2">
|
|
<title>native SQL에서 질의들</title>
|
|
|
|
<para>
|
|
당신은 <literal>createSQLQuery()</literal>를 사용하여 SQL 속에 하나의 질의를 표현할 수 있고,
|
|
Hibernate로 하여금 결과 셋들로부터 객체들로의 매핑을 처리하도록 할수도 있다. 당신은 아무때나
|
|
<literal>session.connection()</literal>을 호출할 수 있고 직접 JDBC <literal>Connection</literal>을
|
|
사용할 수 있음을 노트하라. 만일 당신이 Hibernate API를 사용하고자 선택한 경우에 , 당신은 SQL alias들을
|
|
중괄호들 속에 포함시켜야 한다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[List cats = session.createSQLQuery(
|
|
"SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10",
|
|
"cat",
|
|
Cat.class
|
|
).list();]]></programlisting>
|
|
|
|
<programlisting><![CDATA[List cats = session.createSQLQuery(
|
|
"SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " +
|
|
"{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " +
|
|
"FROM CAT {cat} WHERE ROWNUM<10",
|
|
"cat",
|
|
Cat.class
|
|
).list()]]></programlisting>
|
|
|
|
<para>
|
|
SQL 질의들은 Hibernate 질의들처럼 명명된 파라미터들과 위치 파라미터들을 포함할 수도 있다. SQL 질의들에 대한
|
|
추가 정보는 <xref linkend="querysql"/>에서 찾을 수 있다.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-modifying" revision="1">
|
|
<title>영속 객체들을 변경하기</title>
|
|
|
|
<para>
|
|
<emphasis>트랜잭션 상의 영속 인스턴스들</emphasis> (예를들면. <literal>Session</literal>에 의해 로드되고,
|
|
저장되고, 생성되거나 질의된 객체들)은 어플리케이션에 의해 처리될 수 있고 영속 상태에 대한 임의의 변경들은
|
|
<literal>Session</literal>이 <emphasis>flush될</emphasis> 때 영속화 될 것이다(이 장의 뒷 부분에서 논의됨).
|
|
당신의 변경들을 영속화 시키기 위해 (다른 용도를 가진 <literal>update()</literal>와 같은) 특별한 메소드를 호출할
|
|
필요가 없다. 따라서 객체의 상태를 업데이트 시키는 가장 간단한 방법은 <literal>Session</literal>이 열려 있는 동안,
|
|
그것을 <literal>load()</literal>시키고 나서, 그것을 직접 처리하는 것이다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );
|
|
cat.setName("PK");
|
|
sess.flush(); // changes to cat are automatically detected and persisted]]></programlisting>
|
|
|
|
<para>
|
|
때때로 이 프로그래밍 모형은 불충분하다. 왜냐하면 그것은 동일한 세션 내에서 (객체를 로드시키는) SQL
|
|
<literal>SELECT</literal>와 (그것의 업데이트된 상태를 영속화 시키는) SQL <literal>UPDATE</literal>
|
|
양자를 필요로 할 것이기 때문이다. 그러므로 Hibernate는 detached 인스턴스들을 사용하는 대안적인 접근법을 제공한다.
|
|
</para>
|
|
|
|
<para>
|
|
<emphasis>Hibernate는 <literal>UPDATE</literal> 문장 또는 <literal>DELETE</literal> 문장의 직접적인 실행을 위한
|
|
그것 자신의 API를 제공하지 않음을 노트하라. Hibernate는 하나의 <emphasis>상태 관리</emphasis> 서비스이고, 당신은 그것을
|
|
사용할 <emphasis>문장들</emphasis>을 생각하지 말아야 한다. JDBC는 SQL 문장들을 실행시키는 완전한 API이고, 당신은
|
|
<literal>session.connection()</literal>을 호출하여 아무때나 한 개의 JDBC <literal>Connection</literal>을
|
|
얻을 수 있다. 게다가 대량 오퍼레이션의 개념은 온라인 트랜잭션 처리-지향적인 어플리케이션들을 위한 객체/관계형 매핑과 충돌한다.
|
|
하지만 Hibernate의 장래 버전들은 특별한 대용량의 오퍼레이션 기능들을 제공할 수도 있다. 몇몇 가능한 배치 오퍼레이션 트릭들에
|
|
대해서는 <xref linkend="batch"/>을 보라.</emphasis>
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-detached" revision="2">
|
|
<title>detached 객체들을 변경시키기</title>
|
|
|
|
<para>
|
|
많은 어플리케이션들은 하나의 트랜잭션 내에서 하나의 객체를 검색하고, 처리를 위한 UI 계층으로 그것을 전송하고,
|
|
그런 다음 새로운 트랜잭션 내에서 변경들을 저장할 필요가 있다. 고도의-동시성 환경에서 이런 종류의 접근법을 사용하는
|
|
어플리케이션들은 대개 작업의 "긴" 단위를 확실히 격리시키기 위해 버전화 된 데이터를 사용한다.
|
|
</para>
|
|
|
|
<para>
|
|
Hibernate는 <literal>Session.update()</literal> 메소드 또는 <literal>Session.merge()</literal> 메소드를
|
|
사용하여 detached 인스턴스들의 재첨부를 제공함으로써 이 모형을 지원한다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[// in the first session
|
|
Cat cat = (Cat) firstSession.load(Cat.class, catId);
|
|
Cat potentialMate = new Cat();
|
|
firstSession.save(potentialMate);
|
|
|
|
// in a higher layer of the application
|
|
cat.setMate(potentialMate);
|
|
|
|
// later, in a new session
|
|
secondSession.update(cat); // update cat
|
|
secondSession.update(mate); // update mate]]></programlisting>
|
|
|
|
<para>
|
|
만일 <literal>catId</literal> 식별자를 가진 <literal>Cat</literal>이 <literal>secondSession</literal>에 의해
|
|
이미 로드되었을 경우에 어플리케이션이 그것을 다시 재첨부하려고 시도할 때, 예외상황이 던져졌을 것이다.
|
|
</para>
|
|
|
|
<para>
|
|
만일 그 세션이 동일한 식별자를 가진 영속 인스턴스를 이미 포함하지 않음을 당신이 확신하는 경우에는
|
|
<literal>update()</literal>를 사용하고, 만일 당신이 세션의 상태를 고려하지 않은채로 아무때나 당신의 변경을 병합시키고자
|
|
원할 경우에는 <literal>merge()</literal>를 사용하라. 달리 말해, 당신의 detached 인스턴스들에 대한 재첨부가 실행되는
|
|
첫 번째 오퍼레이션임을 확실히 함으로써, <literal>update()</literal>는 대개 갓 만들어진 세션에서 당신이 호출하게 될
|
|
첫 번째 메소드이다.
|
|
</para>
|
|
|
|
<para>
|
|
어플리케이션은 만일 그것이 detached 인스턴스들의 상태가 또한 업데이트 되는 것을 원할 경우에<emphasis>만</emphasis>
|
|
주어진 detached 인스턴스로부터 도달 가능한 detached 인스턴스들을 개별적으로<literal>update()</literal> 시킬 것이다.
|
|
이것은 물론 <emphasis>transitive persistence(전이 영속)</emphasis>을 사용하여 자동화 될 수 있고,
|
|
<xref linkend="objectstate-transitive"/>를 보라.
|
|
</para>
|
|
|
|
<para>
|
|
<literal>lock()</literal> 메소드는 또한 하나의 객체를 새로운 세션에 대해 다시 연관시키는것을 어플리케이션에게 허용해준다.
|
|
하지만 detached 인스턴스는 변경되지 않아야 한다!
|
|
</para>
|
|
|
|
<programlisting><![CDATA[//just reassociate:
|
|
sess.lock(fritz, LockMode.NONE);
|
|
//do a version check, then reassociate:
|
|
sess.lock(izi, LockMode.READ);
|
|
//do a version check, using SELECT ... FOR UPDATE, then reassociate:
|
|
sess.lock(pk, LockMode.UPGRADE);]]></programlisting>
|
|
|
|
<para>
|
|
<literal>lock()</literal>이 여러가지 <literal>LockMode</literal>들에 사용될 수 있음을 노트하고,
|
|
상세한 것은 API 문서와 트랜잭션 처리에 관한 장을 보라. 재첨부는 <literal>lock()</literal>에 대한
|
|
유일한 쓰임새는 아니다.
|
|
</para>
|
|
|
|
<para>
|
|
긴 작업 단위에 대한 다른 모형들은 <xref linkend="transactions-optimistic"/>에서 논의된다.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-saveorupdate">
|
|
<title>자동적인 상태 검출</title>
|
|
|
|
<para>
|
|
Hibernate 사용자들은 새로운 식별자를 생성시켜서 transient 인스턴스를 저장하거나
|
|
그것의 현재 식별자와 연관된 detached 인스턴스들을 업데이트/재첨부 시키는 일반적인 용도의 메소드를 요청했다.
|
|
<literal>saveOrUpdate()</literal> 메소드는 이 기능을 구현한다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[// in the first session
|
|
Cat cat = (Cat) firstSession.load(Cat.class, catID);
|
|
|
|
// in a higher tier of the application
|
|
Cat mate = new Cat();
|
|
cat.setMate(mate);
|
|
|
|
// later, in a new session
|
|
secondSession.saveOrUpdate(cat); // update existing state (cat has a non-null id)
|
|
secondSession.saveOrUpdate(mate); // save the new instance (mate has a null id)]]></programlisting>
|
|
|
|
<para>
|
|
<literal>saveOrUpdate()</literal>의 사용 예제와 의미는 초심자들에게는 혼동스러워 보인다. 먼저, 하나의 세션에서 온
|
|
인스턴스를 또 다른 새로운 세션 내에서 사용하려고 시도하지 않는 한, 당신은 <literal>update()</literal>,
|
|
<literal>saveOrUpdate()</literal>, 또는 <literal>merge()</literal>를 사용할 필요는 없을 것이다.
|
|
몇몇 전체 어플리케이션들은 이들 메소드들 중 어느 것도 결코 사용하지 않을 것이다.
|
|
</para>
|
|
|
|
<para>
|
|
대개 <literal>update()</literal> 또는 <literal>saveOrUpdate()</literal>는 다음 시나리오에서 사용된다:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
어플리케이션이 첫 번째 세션 내에 객체를 로드시킨다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
객체가 UI 티어로 전달된다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
몇몇 변경들이 그 객체에 행해진다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
객체가 비지니스 로직 티어로 전달된다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
어플리케이션은 두 번째 세션에서 <literal>update()</literal>를 호출함으로써 이들 변경들을 영속화 시킨다
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
<literal>saveOrUpdate()</literal>는 다음을 행한다:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
만일 객체가 이 세션 내에서 이미 영속화 되어 있을 경우, 아무것도 행하지 않는다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
만일 그 세션과 연관된 또 다른 객체가 동일한 식별자를 가질 경우, 예외상황을 던진다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
만일 그 객체가 식별자 프로퍼티를 갖지 않을 경우, 그것을 <literal>save()</literal> 시킨다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
만일 객체의 식별자가 새로이 초기화 된 객체에 할당된 값을 가질 경우, 그것을 <literal>save()</literal> 시킨다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
만일 객체가 (<literal><version></literal> 또는 <literal><timestamp></literal>에 의해)
|
|
버전화 되고, version 프로퍼티 값이 새로이 초기화 된 객체에 할당된 것과 동일한 값일 경우, 그것을 <literal>save()</literal>
|
|
시킨다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
그 밖의 경우 그 객체를 <literal>update()</literal> 시킨다
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
그리고 <literal>merge()</literal>는 매우 다르다:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
만일 세션과 현재 연관된 동일한 식별자를 가진 영속 인스턴스가 존재할 경우,
|
|
주어진 객체의 상태를 영속 인스턴스 상으로 복사한다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
만일 세션과 현재 연관된 영속 인스턴스가 존재하지 않을 경우, 데이터베이스로부터 그것을 로드시키려고 시도하거나
|
|
새로운 영속 인스턴스를 생성시키려고 시도한다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
영속 인스턴스가 반환된다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
주어진 인스턴스는 세션과 연관되지 않고, 그것은 detached 상태에 머무른다
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-deleting" revision="1">
|
|
<title>영속 객체들을 삭제하기</title>
|
|
|
|
<para>
|
|
<literal>Session.delete()</literal>는 데이터베이스로부터 객체의 상태를 제거할 것이다.
|
|
물론 당신의 어플리케이션은 여전히 detached 객체에 대한 참조를 소유할 것이다. 영속 인스턴스를 transient로 만들 때
|
|
<literal>delete()</literal>를 생각하는 것이 최상이다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[sess.delete(cat);]]></programlisting>
|
|
|
|
<para>
|
|
당신은 foreign 키 컨스트레인트 위배들에 대한 위험성 없이 당신이 좋아하는 어떤 순서로 객체들을 삭제할 수도 있다.
|
|
잘못된 순서로 객체들을 삭제함으로써 foreign 키 컬럼에 대한 <literal>NOT NULL</literal> 컨스트레인트를 위배할 가능성이
|
|
여전히 존재한다. 예를 들어, 당신이 부모를 삭제하지만, 그 자식들을 삭제하는 것을 잊은 경우.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-replicating" revision="1">
|
|
<title>두 개의 다른 데이터저장소들 사이에 객체들을 복제하기</title>
|
|
|
|
<para>
|
|
영속 인스턴스들의 그래프를 취하고 식별자 값들을 다시 생성시키지 않고서 그것들을 다른 저장소 속에 영속화 시키는 것을 가능하도록
|
|
만드는 것이 종종 유용하다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[//retrieve a cat from one database
|
|
Session session1 = factory1.openSession();
|
|
Transaction tx1 = session1.beginTransaction();
|
|
Cat cat = session1.get(Cat.class, catId);
|
|
tx1.commit();
|
|
session1.close();
|
|
|
|
//reconcile with a second database
|
|
Session session2 = factory2.openSession();
|
|
Transaction tx2 = session2.beginTransaction();
|
|
session2.replicate(cat, ReplicationMode.LATEST_VERSION);
|
|
tx2.commit();
|
|
session2.close();]]></programlisting>
|
|
|
|
<para>
|
|
<literal>ReplicationMode</literal>는 <literal>replicate()</literal>가 데이터베이스 내에 있는 기존의
|
|
행들과의 충돌을 처리하게될 방법을 결정한다.
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
<literal>ReplicationMode.IGNORE</literal> - 동일한 식별자를 가진 기존 데이터베이스 행이 존재할
|
|
경우에 그 객체를 무시한다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>ReplicationMode.OVERWRITE</literal> - 동일한 식별자를 가진 어떤 기존의 데이터베이스 행을 덮어 쓴다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>ReplicationMode.EXCEPTION</literal> - 만일 동일한 식별자를 가진 기존 데이터베이스 행이 존재할 경우에
|
|
예외상황을 던진다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>ReplicationMode.LATEST_VERSION</literal> - 행의 버전 번호가 객체의 버전 번호 보다 이전의 것이면 덮어쓰고,
|
|
그 밖의 경우에 그 객체를 무시한다
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
이 특징의 쓰임새들은 다른 데이터베이스 인스턴스들 속으로 입력된 데이터 일치시키기, 제품 업그레이드 동안에 시스템 구성 정보
|
|
업데이트 하기, non-ACID 트랜잭션들 동안에 행해진 변경들을 롤백시키기 등을 포함한다.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-flushing">
|
|
<title>Session을 flush 시키기</title>
|
|
|
|
<para>
|
|
시간이 지남에 따라 <literal>Session</literal>은 JDBC 커넥션의 상태와 메모리 내에 보관된 객체들의 상태를 동기화 시키는데
|
|
필요한 SQL 문장들을 실행시킬 것이다. 이 프로세스 <emphasis>flush</emphasis>는 다음 시점들에서 디폴트로 발생한다
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
몇몇 질의들이 실행되기 전에
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>org.hibernate.Transaction.commit()</literal> 시점에서
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>Session.flush()</literal> 시점에서
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
SQL 문장들이 다음 순서로 실행 명령이 내려진다
|
|
</para>
|
|
|
|
<orderedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
|
|
대응하는 객체들이 <literal>Session.save()</literal>를 사용하여 저장되었던 것과 같은 순서로, 모든 엔티티 삽입들
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
모든 엔티티 업데이트들
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
모든 콜렉션 삭제들
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
모든 콜렉션 요소 삭제들, 업데이트들 그리고 삽입들
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
모든 콜렉션 삽입들
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
대응하는 객체들이 <literal>Session.delete()</literal>를 사용하여 삭제되었던 것과 같은 순서로 모든 엔티티 삭제들.
|
|
</para>
|
|
</listitem>
|
|
</orderedlist>
|
|
|
|
<para>
|
|
(한가지 예외는 객체들이 저장될 때 <literal>native</literal> ID 생성을 사용하는 객체들이 insert 되는 점이다.)
|
|
</para>
|
|
|
|
<para>
|
|
당신이 명시적으로 <literal>flush()</literal> 시킬 때를 제외하면, <literal>Session</literal>이 JDBC 호출들을
|
|
실행시키는 <emphasis>시점</emphasis>, 그것들이 실행되는 <emphasis>순서</emphasis>만을 절대적으로 보장하지는 않는다.
|
|
하지만 Hibernate는 <literal>Query.list(..)</literal>가 실효성이 없는 데이터를 결코 반환하지 않을 것임을 보장하거나;
|
|
그것들이 잘못된 데이터도 반환하지 않을 것임을 보장한다.
|
|
</para>
|
|
|
|
<para>
|
|
flush가 너무 자주 발생하지 않도록 디폴트 행위를 변경하는 것이 가능하다. <literal>FlushMode</literal> 클래스는 세 개의
|
|
다른 모드들을 정의한다: 오직 커밋 시(그리고 Hibernate <literal>Transaction</literal> API가 사용될 때에만) flush 모드,
|
|
설명된 루틴을 사용하는 자동적인 flush 모드, 또는 <literal>flush()</literal>가 명시적으로 호출되지 않는 한 flush 시키지
|
|
않는 모드. 마지막 모드는 오래 동안 실행되는 작업 단위에 대해 유용하고, 여기서 <literal>Session</literal>은 열려진채로
|
|
유지되고 오랜 시간 동안 연결이 해제된 채로 유지된다. (<xref linkend="transactions-optimistic-longsession"/>를 보라).
|
|
</para>
|
|
|
|
<programlisting><![CDATA[sess = sf.openSession();
|
|
Transaction tx = sess.beginTransaction();
|
|
sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state
|
|
|
|
Cat izi = (Cat) sess.load(Cat.class, id);
|
|
izi.setName(iznizi);
|
|
|
|
// might return stale data
|
|
sess.find("from Cat as cat left outer join cat.kittens kitten");
|
|
|
|
// change to izi is not flushed!
|
|
...
|
|
tx.commit(); // flush occurs]]></programlisting>
|
|
|
|
<para>
|
|
flush 동안에, 하나의 예외상황이 발생할 수도 있다(예를 들면. 만일 DML 오퍼레이션이 컨스트레인트를 위반할 경우). 예외상황들을
|
|
처리하는 것은 Hibernatem의 트랜잭션 특징에 관한 어떤 이해를 수반하며, 우리는 <xref linkend="transactions"/>에서 그것을
|
|
논의한다.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-transitive">
|
|
<title>Transitive persistence(전이 영속)</title>
|
|
|
|
<para>
|
|
특히 당신이 연관된 객체들의 그래프를 다룰 경우에, 특히 개별 객체들을 저장하고, 삭제하거나, 재첨부시키는 것이 꽤 번거롭다.
|
|
공통된 경우는 하나의 부모/자식 관계이다. 다음 예제를 검토하자:
|
|
</para>
|
|
|
|
<para>
|
|
만일 부모/자식 관계에서 자식들이 값(value) 타입(예를 들면. 주소들 또는 문자열들을 가진 하나의 콜렉션)일 경우, 그것들의 생명주기는
|
|
부모에 의존할 것이고 상태 변경들에 대해 편리한 "케스케이딩"에 더 이상의 액션이 필요하지 않을 것이다. 만일 부모가 저장될 때,
|
|
값(value)-타입의 자식 객체들도 마찬가지로 저장되고, 부모가 삭제될 때, 자식들이 삭제될 것이다. 이것은 심지어 콜렉션으로부터 하나의
|
|
자식을 제거하는 그런 오퍼레이션들에 대해서도 동작한다; Hibernate는 이것을 검출하고, 값(value)-타입의 객체들은 참조를 공유할 수
|
|
없으므로, 데이터베이스로부터 그 자식을 삭제시킨다.
|
|
</para>
|
|
|
|
<para>
|
|
이제 값(value) 타입이 아닌, 엔티티들인 부모와 자식 객체들을 가진 동일한 시나리오를 검토하자(예를 들면. 카테고리들과 아이템들,
|
|
또는 부모 고양이나 자식 고양이). 엔티티들은 그것들 자신의 생명주기를 갖고, 공유된 참조들을 지원하고 (따라서 콜렉션으로부터 하나의
|
|
엔티티를 제거하는 것은 그것이 삭제될 수 있음을 의미하지 않는다), 그리고 디폴트로 하나의 엔티티로부터 어떤 다른 연관된 엔티티들로의
|
|
상태의 케스케이딩은 존재하지 않는다. Hibernate는 디폴트로 <emphasis>도달가능성에 의한 영속성(persistence by reachability)</emphasis>을
|
|
구현하지 않는다.
|
|
</para>
|
|
|
|
<para>
|
|
- <literal>persist(), merge(), saveOrUpdate(), delete(), lock(), refresh(), evict(), replicate()</literal>를
|
|
포함하는- Hibernate 세션에 대한 각각의 기본 오퍼레이션에 대해서 하나의 대응하는 케스케이딩 스타일이 존재한다. 케스케이드 스타일들
|
|
각각은 <literal>create, merge, save-update, delete, lock, refresh, evict, replicate</literal>로 명명된다.
|
|
만일 당신이 하나의 오퍼레이션이 하나의 연관에 따라 케스케이딩되는 것을 원할 경우, 당신은 매핑 문서 내에 그것을 지시해야 한다.
|
|
예를 들면:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<one-to-one name="person" cascade="persist"/>]]></programlisting>
|
|
|
|
<para>
|
|
케스케이딩 스타일들이 결합될 수도 있다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<one-to-one name="person" cascade="persist,delete,lock"/>]]></programlisting>
|
|
|
|
<para>
|
|
당신은 <emphasis>모든</emphasis> 오퍼레이션들이 그 연관에 따라 케스케이드 되어야 함을 지정하는데
|
|
<literal>cascade="all"</literal>을 사용할 수도 있다. 디폴트<literal>cascade="none"</literal>은 오퍼레이션들이
|
|
케스케이드 되지 않을 것임을 지정한다.
|
|
</para>
|
|
|
|
<para>
|
|
특정한 케스케이드 스타일인, <literal>delete-orphan</literal>은 오직 one-to-many 연관들에만 적용되고,
|
|
<literal>delete()</literal> 오퍼레이션이 그 연관으로부터 제거되는 임의의 자식 객체에 적용되어야 함을 나타낸다.
|
|
</para>
|
|
|
|
|
|
<para>
|
|
권장사항들 :
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
하나의 <literal><many-to-one></literal> 또는 <literal><many-to-many></literal> 연관에
|
|
대해 케스케이드를 가능하게 하는 것은 대개 의미가 없다. 케스케이드는 <literal><one-to-one></literal> 연관과
|
|
<literal><one-to-many></literal> 연관에 대해 종종 유용하다.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
만일 자식 객체의 수명이 그 부모 객체의 수명에 묶여져 있을 경우, <literal>cascade="all,delete-orphan"</literal>을
|
|
지정함으로써 그것을 <emphasis>생명 주기 객체</emphasis>로 만들어라.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
그 밖의 경우, 당신은 케스케이드를 전혀 필요로 하지 않을 수 있다. 그러나 만일 당신이 동일한 트랜잭션 내에서 부모와 자식에
|
|
대해 자주 함께 작업하게 될 것이라 생각되고, 당신 스스로 타이핑 하는 것을 절약하고자 원할 경우,
|
|
<literal>cascade="persist,merge,save-update"</literal>를 사용하는 것을 고려하라.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
<literal>cascade="all"</literal>을 가진 (단일 값 연관이든 하나의 콜렉션이든) 하나의 연관을 매핑시키는 것은
|
|
그 연관을 부모의 저장/업데이트/삭제가 자식 또는 자식들의 저장/업데이트/삭제로 귀결되는 <emphasis>부모/자식</emphasis>
|
|
스타일의 관계로 마크한다.
|
|
</para>
|
|
<para>
|
|
게다가, 하나의 영속 부모로부터 하나의 자식에 대한 단순한 참조는 자식의 저장/업데이트로 귀결될 것이다. 하지만 이 메타포는 불완전하다.
|
|
그것의 부모에 의해 참조 해제되는 자식은 <literal>cascade="delete-orphan"</literal>으로 매핑된 하나의
|
|
<literal><one-to-many></literal> 연관의 경우를 제외하면, 자동적으로 삭제되지 <emphasis>않는다</emphasis>.
|
|
하나의 부모/자식 관계에 대한 케스케이딩 오퍼레이션의 정확한 의미는 다음과 같다:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
만일 부모가 <literal>persist()</literal>에 전달될 경우, 모든 자식들이 <literal>persist()</literal>에 전달된다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
만일 부모가 <literal>merge()</literal>에 전달될 경우, 모든 자식들이 <literal>merge()</literal>에 전달된다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
만일 부모가 <literal>save()</literal>, <literal>update()</literal> 또는 <literal>saveOrUpdate()</literal>에
|
|
전달될 경우, 모든 자식들이 <literal>saveOrUpdate()</literal>에 전달된다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
만일 transient 또는 detached 자식이 영속 부모에 의해 참조될 경우, 그것은 <literal>saveOrUpdate()</literal>에 전달된다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
만일 부모가 삭제될 경우, 모든 자식들이 <literal>delete()</literal>에 전달된다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
만일 자식이 영속 부모에 의해 참조 해제 될 경우, <literal>cascade="delete-orphan"</literal>이 아닌 한,
|
|
<emphasis>특별한 어떤 것도 발생하지 않는다</emphasis> - 어플리케이션은 필요한 경우에 자식을 명시적으로 삭제해야 한다 -,
|
|
<literal>cascade="delete-orphan"</literal>인 경우에 "orphaned(고아)"인 경우 자식이 삭제된다.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="objectstate-metadata">
|
|
<title>메타데이터 사용하기</title>
|
|
|
|
<para>
|
|
Hibernate는 모든 엔티티와 값(value) 타입들을 가진 매우 풍부한 메타-레벨 모형을 필요로 한다. 시간이 지남에 따라, 이 모형은
|
|
어플리케이션 그 자체에 매우 유용하다. 예를 들어, 어플리케이션은 어느 객체들이 복사되어야 하는지(예를 들면 가변적인 값(value) 타입들)
|
|
그리고 어느 것이 복사되지 말아야 하는지(예를 들면, 불변의 value 타입들과 가능한 연관된 엔티티들)를 인지하는 "스마트" deep-copy
|
|
알고리즘을 구현하는데 Hibernate의 메타데이터를 사용할 수도 있다.
|
|
</para>
|
|
<para>
|
|
Hibernate는 <literal>ClassMetadata</literal> 인터페이스와 <literal>CollectionMetadata</literal> 인터페이스
|
|
그리고 <literal>Type</literal> 계층구조를 통해 메타데이터를 노출시킨다. 메타데이터 인터페이스들의 인스턴스들은
|
|
<literal>SessionFactory</literal>로부터 얻어질 수도 있다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Cat fritz = ......;
|
|
ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class);
|
|
|
|
Object[] propertyValues = catMeta.getPropertyValues(fritz);
|
|
String[] propertyNames = catMeta.getPropertyNames();
|
|
Type[] propertyTypes = catMeta.getPropertyTypes();
|
|
|
|
// get a Map of all properties which are not collections or associations
|
|
Map namedValues = new HashMap();
|
|
for ( int i=0; i<propertyNames.length; i++ ) {
|
|
if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) {
|
|
namedValues.put( propertyNames[i], propertyValues[i] );
|
|
}
|
|
}]]></programlisting>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|