2007-06-29 19:55:14 +00:00

1293 lines
77 KiB
XML

<chapter id="performance">
<title>퍼포먼스 개선하기</title>
<sect1 id="performance-fetching" revision="2">
<title>페칭 방도들</title>
<para>
<emphasis>페칭 방도</emphasis>는 어플리케이션이 연관을 네비게이트할 필요가 있을 때 Hibernate가 연관된 객체들을 검색하는데
사용하게 될 방도이다.페치 방도들은 O/R 매핑 메타데이터 내에서 선언될 수 있거나 하나의 특정 HQL 또는 <literal>Criteria</literal>
질의에 의해 오버라이드 될 수도 있다.
</para>
<para>
Hibernate3는 다음 페칭 방도들을 정의한다:
</para>
<itemizedlist>
<listitem>
<para>
<emphasis>Join 페칭</emphasis> - Hibernate는 <literal>OUTER JOIN</literal>을 사용하여 연관된 인스턴스
또는 동일한 <literal>SELECT</literal> 내에서 콜렉션을 검색한다.
</para>
</listitem>
<listitem>
<para>
<emphasis>Select 페칭</emphasis> - 두 번째 <literal>SELECT</literal>는 연과된 엔티티 또는 콜렉션을 검색하는데
사용된다. 당신이 <literal>lazy="false"</literal>를 지정함으로써 명시적으로 lazy 페칭을 사용 불가능하게 하지 않는
한, 이 두 번째 select는 당신이 그 연관에 실제로 액세스할 때 오직 실행될 것이다.
</para>
</listitem>
<listitem>
<para>
<emphasis>Subselect 페칭</emphasis> - 두 번째 <literal>SELECT</literal>는 이전 질의 또는 페치에서 검색된
모든 엔티티들에 대해 연관된 콜렉션들을 검색하는데 사용된다. 당신이 <literal>lazy="false"</literal>를 지정하여
명시적으로 lazy 페칭을 사용 불가능하게 하지 않는 한, 이 두 번째 select는 당신이 실제로 그 연관에 접근할 때 오직 실행될
것이다.
</para>
</listitem>
<listitem>
<para>
<emphasis>Batch 페칭</emphasis> - select 페칭을 위한 최적화 방도 - Hibernate는 프라이머리 키들이나 foreign
키들의 리스트를 지정함으로써 하나의<literal>SELECT</literal> 내에서 엔티티 인스턴스들이나 콜렉션들에 대한 batch를
검색한다.
</para>
</listitem>
</itemizedlist>
<para>
Hibernate는 또한 다음 사이를 구별 짓는다:
</para>
<itemizedlist>
<listitem>
<para>
<emphasis>즉각적인 페칭</emphasis> - 소유자가 로드될 때, 연관, 콜렉션 또는 속성이 즉시 페치된다.
</para>
</listitem>
<listitem>
<para>
<emphasis>Lazy 콜렉션 페칭</emphasis> - 어플리케이션이 그 콜렉션에 대해 하나의 오퍼레이션을 호출할 때
콜렉션이 페치된다.(이것은 콜렉션들에 대해 디폴트이다.)
</para>
</listitem>
<listitem>
<para>
<emphasis>"Extra-lazy" 콜렉션 페칭</emphasis> - 콜렉션의 개별 요소들은 필요할 때
데이터베이스로부터 접근된다. Hibernate는 절대적으로 필요하지 않은 한 전체 콜렉션을 메모리 내로
페치하려고 시도하지 않는다(매우 큰 콜렉션에 적합함)
</para>
</listitem>
<listitem>
<para>
<emphasis>프락시 페칭</emphasis> - 식별자 getter가 아닌 다른 메소드가 연관된 객체에 대해 호출될 때
단일 값 연관이 페치된다.
</para>
</listitem>
<listitem>
<para>
<emphasis>"No-proxy" 페칭</emphasis> - 인스턴스 변수가 접근될 때 단일 값 연관이 페치된다.
프락시 페칭과 비교할 때, 이 접근법은 다소 덜 lazy하지만(그 연관은 심지어 유일하게 식별자가 접근될 때에도
페치된다)보다 투명하다. 왜냐하면 프락시는 어플리케이션에 가시적이지 않기 때문이다. 이 접근법은 빌드 시
바이트코드 수단을 필요로 하며 드물게 필요하다.
</para>
</listitem>
<listitem>
<para>
<emphasis>Lazy 속성 페칭</emphasis> - 인스턴스 변수가 접근될 때 속성 또는 단일 값 연관이 페치된다
이 접근법은 빌드시 바이트코드 수단을 필요로 하며 드물게 필요하다.
</para>
</listitem>
</itemizedlist>
<para>
우리는 여기서 두 개의 직교하는 개념들을 갖는다: 연관이 페치될 <emphasis></emphasis>, 그리고 그것이 페치되는
<emphasis>방법</emphasis>(사용되는 SQL). 그것들을 혼동하지 말라! 우리는 퍼포먼스를 튜팅하는데
<literal>페치</literal>를 사용한다. 우리는 특정 클래스의 어떤 detached 인스턴스 내에서 항상 이용 가능한
데이터가 무엇인지에 대한 계약을 정의하는데 <literal>lazy</literal>를 사용할 수 있다.
</para>
<sect2 id="performance-fetching-lazy">
<title>lazy 연관들로 작업하기</title>
<para>
디폴트로 Hibernate3는 콜렉션들에 대해 lazy select 페칭을 사용하고, 단일 값 연관들에 대해 lazy
프락시 페칭을 사용한다. 이들 디폴트들은 거의 모든 어플리케이션들에서 거의 모든 연관들에 대해 유의미하다.
</para>
<para>
<emphasis>노트:</emphasis> 만일 당신이 <literal>hibernate.default_batch_fetch_size</literal>를 설정하는
경우, Hibernate는 lazy 페칭을 위한 batch 페치 최적화를 사용할 것이다(이 최적화는 또한 더 많은 과립상의 레벨에서 이용 가능할
수 있다).
</para>
<para>
하지만, lazy 페칭은 당신이 알고 있어야 하는 한 가지 문제를 제기한다. 열려진 Hibernate 세션 컨텍스트 외부에서 lazy 연관에
대한 접근은 예외상황으로 귀결될 것이다. 예를 들면 :
</para>
<programlisting><![CDATA[s = sessions.openSession();
Transaction tx = s.beginTransaction();
User u = (User) s.createQuery("from User u where u.name=:userName")
.setString("userName", userName).uniqueResult();
Map permissions = u.getPermissions();
tx.commit();
s.close();
Integer accessLevel = (Integer) permissions.get("accounts"); // Error!]]></programlisting>
<para>
<literal>Session</literal>이 닫혔을 때 permissions 콜렉션이 초기화 되지 않았으므로, 그 콜렉션은 그것의 상태를 로드시킬
수가 없을 것이다. <emphasis>Hibernate 는 detached 객체들에 대한 lazy 초기화를 지원하지 않는다.</emphasis> 정정은
콜렉션으로부터 읽어들이는 코드를 커밋 바로 직전으로 이동시키는 것이다.
</para>
<para>
다른 방법으로 연관 매핑에 대해 <literal>lazy="false"</literal>를 지정함으로써, non-lazy 콜렉션 또는
non-lazy 연관을 사용할 수 있다. 하지만 lazy 초기화는 거의 모든 콜렉션들과 연관들에 대해 사용되도록 고안되어 있다.
만일 당신이 당신의 객체 모형 내에 너무 많은 non-lazy 연관들을 정의할 경우, Hibernate는 모든 트랜잭션에서 전체
데이터베이스를 메모리 속으로 페치하는 필요성을 끝내게 될 것이다!
</para>
<para>
다른 한편으로, 우리는 특정 트랜잭션 내에서 select 페칭 대신에 (고유하게 non-lazy인) join 페칭을 선택하기를 자주 원한다.
우리는 이제 페칭 방도를 맞춤화 시키는 방법을 알게 될 것이다. Hibernate3에서, 페치 방도를 선택하는 메커니즘은 단일 값 연관들과
콜렉션들에 대해 동일하다.
</para>
</sect2>
<sect2 id="performance-fetching-custom" revision="4">
<title>페치 방도들을 튜닝하기</title>
<para>
select 페칭(디폴트)은 N+1 selects 문제점들에 매우 취약해서, 우리는 매핑 문서에서 join 페칭을 사용 가능하게 하기를 원할
수도 있다:
</para>
<programlisting><![CDATA[<set name="permissions"
fetch="join">
<key column="userId"/>
<one-to-many class="Permission"/>
</set]]></programlisting>
<programlisting><![CDATA[<many-to-one name="mother" class="Cat" fetch="join"/>]]></programlisting>
<para>
매핑 문서 내에 정의된 <literal>fetch</literal> 방도는 다음에 영향을 준다:
</para>
<itemizedlist>
<listitem>
<para>
<literal>get()</literal> 또는 <literal>load()</literal>를 통한 검색
</para>
</listitem>
<listitem>
<para>
연관이 네비게이트될 때 함축적으로 발생하는 검색
</para>
</listitem>
<listitem>
<para>
<literal>Criteria</literal> 질의들
</para>
</listitem>
<listitem>
<para>
<literal>subselect</literal> 페칭이 사용될 경우에 HQL 질의들
</para>
</listitem>
</itemizedlist>
<para>
당신이 사용하는 페칭 방도가 무엇인가에 상관없이, 정의된 비-lazy 그래프가 메모리 내로 로드되는 것이 보장된다.
이것은 하나의 특별한 HQL 질의를 실행시키는데 사용되는 몇몇 즉시적인 select들로 귀결될 수 있음을 노트하라.
</para>
<para>
대개, 우리는 페칭을 맞춤화 시키는데 매핑 문서를 사용하지 않는다. 대신에, 우리는 디폴트 특징을 유지하고, HQL에서
<literal>left join fetch</literal>를 사용하여, 특정 트랜잭션에 대해 그것을 오버라이드 시킨다. 이것은
outer join을 사용하여 첫 번째 select에서 초기에 그 연관을 eagerly 페치시킬 것을 Hibernate에게 알려준다.
<literal>Criteria</literal> query API에서, 우리는 <literal>setFetchMode(FetchMode.JOIN)</literal>
사용한다.
</para>
<para>
만일 당신이 <literal>get()</literal> 또는 <literal>load()</literal>에 의해 사용된 페칭 방도를 변경시킬 수 있기를
당신이 원한다고 느낄 경우, 단순하게 <literal>Criteria</literal> 질의를 사용하라. 예를 들면:
</para>
<programlisting><![CDATA[User user = (User) session.createCriteria(User.class)
.setFetchMode("permissions", FetchMode.JOIN)
.add( Restrictions.idEq(userId) )
.uniqueResult();]]></programlisting>
<para>
(이것은 몇몇 ORM 솔루션들이 "페치 계획"이라고 부르는 것에 대한 Hibernate의 등가물이다.)
</para>
<para>
N+1 개의 select들을 가진 문제점들을 피하는 완전히 다른 방법은 second-level 캐시를 사용하는 것이다.
</para>
</sect2>
<sect2 id="performance-fetching-proxies" revision="2">
<title>Single-ended 연관 프락시</title>
<para>
콜렉션들에 대한 Lazy 페칭은 영속 콜렉션들에 대한 Hibernate 자신의 구현을 사용하여 구현된다. 하지만 다른 메커니즘은
single-ended 연관들에서 lazy 특징에 필요하다. 연관의 대상 엔티티는 프락시 되어야 한다. Hibernate는
(훌륭한 CGLIB 라이브러리를 통해) 런타임 바이트코드 증진을 사용하여 영속 객체들에 대한 lazy 초기화 프락시들을 구현한다.
</para>
<para>
디폴트로, Hibernate3는 모든 영속 클래스들에 대해 (시작 시에) 프락시들을 생성시키고 <literal>many-to-one</literal>
연관과 <literal>one-to-one</literal> 연관에 대해 lazy 페칭을 이용 가능하게 하는데 그것들을 사용한다.
</para>
<para>
매핑 파일은 그 클래스에 대한 프락시 인터페이스로서 사용할, <literal>proxy</literal> 속성을 가진, 인터페이스를 선언할
수도 있다. 디폴트로 Hibernate는 그 클래스의 서브클래스를 사용한다. <emphasis>프락시된 클래스는 최소한의 패키지 가시성
(visibility)을 가진 디폴트 생성자를 구현해야 함을 노트하라. 우리는 모든 영속 클래스들에 대해 이 생성자를 권장한다!</emphasis>
</para>
<para>
다형성 클래스들에 대해 이 접근법을 확장할 때 의식해야 하는 몇몇 난처함들이 존재한다. 예를 들면.
</para>
<programlisting><![CDATA[<class name="Cat" proxy="Cat">
......
<subclass name="DomesticCat">
.....
</subclass>
</class>]]></programlisting>
<para>
첫 번째로, 심지어 기본 인스턴스가 <literal>DomesticCat</literal>의 인스턴스인 경우조차도,
<literal>Cat</literal>의 인스턴스들은 결코 <literal>DomesticCat</literal>으로 타입캐스트가 가능하지 않을 것이다:
</para>
<programlisting><![CDATA[Cat cat = (Cat) session.load(Cat.class, id); // instantiate a proxy (does not hit the db)
if ( cat.isDomesticCat() ) { // hit the db to initialize the proxy
DomesticCat dc = (DomesticCat) cat; // Error!
....
}]]></programlisting>
<para>
두번째로, 프락시 <literal>==</literal>를 파기할 가능성이 있다.
</para>
<programlisting><![CDATA[Cat cat = (Cat) session.load(Cat.class, id); // instantiate a Cat proxy
DomesticCat dc =
(DomesticCat) session.load(DomesticCat.class, id); // acquire new DomesticCat proxy!
System.out.println(cat==dc); // false]]></programlisting>
<para>
하지만, 그 경우는 보이는 만큼 그렇게 나쁘지는 않다. 심지어 우리가 이제 다른 프락시 객체들에 대한 두 개의 참조를 가질지라도,
기본 인스턴스는 여전히 동일한 객체들일 것이다:
</para>
<programlisting><![CDATA[cat.setWeight(11.0); // hit the db to initialize the proxy
System.out.println( dc.getWeight() ); // 11.0]]></programlisting>
<para>
세번째로, 당신은 <literal>final</literal> 클래스 또는 임의의 <literal>final</literal> 메소드들을 가진 클래스에
대해 CGLIB 프락시를 사용하지 않을 수 있다.
</para>
<para>
마지막으로, 만일 당신의 영속 객체가 초기화 시에 어떤 리소스들을 필요로 할 경우(예를 들어, initializer들 또는 디폴트 생성자
내에서), 그때 그들 리소스들이 또한 프락시에 의해 획득될 것이다. 프락시 클래스는 영속 클래스에 대한 실제 서브클래스이다.
</para>
<para>
이들 문제점들은 모두 자바의 단일 상속 모형의 기본적인 제약 때문이다. 만일 당신이 이들 문제점들을 피하고자 원할 경우 당신의 영속
클래스들은 각각 그것의 비지니스 메소드들을 선언하는 인터페이스를 구현해야 한다. 당신은 매핑 파일 속에 이들 인터페이스들을
지정해야 한다. 예를 들면.
</para>
<programlisting><![CDATA[<class name="CatImpl" proxy="Cat">
......
<subclass name="DomesticCatImpl" proxy="DomesticCat">
.....
</subclass>
</class>]]></programlisting>
<para>
여기서 <literal>CatImpl</literal><literal>Cat</literal> 인터페이스를 구현하고 <literal>DomesticCatImpl</literal>
<literal>DomesticCat</literal> 인터페이스를 구현한다. 그때 <literal>Cat</literal><literal>DomesticCat</literal>
인스턴스들에 대한 프락시들은 <literal>load()</literal> 또는 <literal>iterate()</literal>에 의해 반환될 수 있다.
(<literal>list()</literal>가 대개 프락시들을 반환하지 않음을 노트하라.)
</para>
<programlisting><![CDATA[Cat cat = (Cat) session.load(CatImpl.class, catid);
Iterator iter = session.iterate("from CatImpl as cat where cat.name='fritz'");
Cat fritz = (Cat) iter.next();]]></programlisting>
<para>
관계들은 또한 lazy 초기화 된다. 이것은 당신이 임의의 프로퍼티들을 <literal>CatImpl</literal> 타입이 아닌
<literal>Cat</literal> 타입으로 선언해야 함을 의미한다.
</para>
<para>
어떤 오퍼레이션들은 프락시 초기화를 필요로 하지 <emphasis>않는다</emphasis>
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
<literal>equals()</literal>, 만일 영속 클래스가 <literal>equals()</literal>를 오버라이드 시키지 않는 경우
</para>
</listitem>
<listitem>
<para>
<literal>hashCode()</literal>, 만일 영속 클래스가<literal>hashCode()</literal>를 오버라이드 시키지
않는 경우
</para>
</listitem>
<listitem>
<para>
식별자 getter 메소드
</para>
</listitem>
</itemizedlist>
<para>
Hibernate는 <literal>equals()</literal> 또는 <literal>hashCode()</literal>를 오버라이드 시키는 영속 클래스들을
검출할 것이다.
</para>
<para>
디폴트 <literal>lazy="proxy"</literal> 대신에 <literal>lazy="no-proxy"</literal>를 선택하여,
우리는 타입캐스팅과 연관된 문제점들을 피할 수 있다. 하지만 우리는 빌드 시 바이트코드 수단을 필요로 할 것이고,
모든 연산들은 즉각적인 프락시 초기화로 귀결될 것이다.
</para>
</sect2>
<sect2 id="performance-fetching-initialization" revision="1">
<title>콜렉션들과 프락시들을 초기화 시키기</title>
<para>
만일 초기화 되지 않은 콜렉션이나 프락시가 <literal>Session</literal> 영역의 외부에서 접근될 경우에, 예를 들어 콜렉션을
소유하거나 프락시에 대한 참조를 가진 엔티티가 detached 상태에 있을 때, <literal>LazyInitializationException</literal>
Hibernate에 의해 던져질 것이다.
</para>
<para>
때때로 우리는<literal>Session</literal>을 닫기 전에 프락시 또는 콜렉션이 초기화 됨을 확실히 할 필요가 있다. 물론 우리는
예를 들어 <literal>cat.getSex()</literal> 또는 <literal>cat.getKittens().size()</literal>를 호출하여 항상
초기화를 강제시킬 수 있다. 그러나 그것은 코드의 독자들에게는 혼동스럽고 일반적인 코드로 편의적이지 않다.
</para>
<para>
static 메소드들 <literal>Hibernate.initialize()</literal><literal>Hibernate.isInitialized()</literal>
lazy 초기화 된 콜렉션들이나 프락시들에 대해 작업하는 편리한 방법을 어플리케이션에 제공한다. <literal>Hibernate.initialize(cat)</literal>
그것의 <literal>Session</literal>이 여전히 열려져 있는 한 프락시 <literal>cat</literal>의 초기화를 강제할 것이다.
<literal>Hibernate.initialize( cat.getKittens())</literal>는 kittens의 콜렉션에 대해 유사한 효과를 갖는다.
</para>
<para>
또 다른 옵션은 모든 필요한 콜렉션들과 프락시들이 로드되기 전까지 <literal>Session</literal>을 열린 채로 유지하는 것이다.
몇몇 어플리케이션 아키텍처들, 특히 Hibernate를 사용하여 데이터에 접근하는 코드, 그리고 다른 어플리케이션 계층들이나 다른
물리적 프로세스들 내에서 그것을 사용하는 코드에서, 그것은 콜렉션이 초기화 될 때 <literal>Session</literal>이 열려져
있음을 확실히 하는 문제일 수 있다. 이 쟁점을 다루는 두 가지 기본 방법들이 존재한다:
</para>
<itemizedlist>
<listitem>
<para>
웹 기반 어플리케이션에서, 서블릿 필터는 뷰 렌더링이 완료되는, 사용자 요청의 바로 끝에서만 <literal>Session</literal>
닫는데 사용될 수 있다(<emphasis>Open Session in View</emphasis> 패턴). 물론 이것은 당신의 어플리케이션
인프라스트럭처의 예외상황 처리의 정정에 관한 무거운 요구를 부과한다.
뷰 렌더링 동안에 하나의 예외상황이 발생할때에도 사용자에게 반환되기 전에 <literal>Session</literal>이 닫혀지고
트랜잭션이 종료되는 것은 지극히 중요하다. 이 "Open Session in View" 패턴에 관한 예제들은 Hibernate 위키를 보라.
</para>
</listitem>
<listitem>
<para>
별도의 비지니스 티어를 가진 어플리케이션에서, 비지니스 로직은 반환 전에 웹 티어에 필요한 모든 콜렉션들을
"준비"해야 한다. 이것은 비지니스 티어가 모든 데이터를 로드시키고 이미 초기화된 모든 데이터를 특정 쓰임새에
필요한 프리젠테이션/웹 티어로 반환해야 함을 의미한다. 대개 어플리케이션은 웹 티어에 필요하게 될 각각의 콜렉션에
대해 <literal>Hibernate.initialize()</literal>를 호출하거나(이 호출은 세션이 닫히기 전에 발생해야 한다)
또는 <literal>FETCH</literal> 절을 갖거나 또는 <literal>Criteria</literal> 내에
<literal>FetchMode.JOIN</literal>을 가진 Hibernate 질의를 사용하여 콜렉션을 열심히 검색한다. 이것은
대개 당신이 <emphasis>Session Facade</emphasis> 대신 <emphasis>Command</emphasis> 패턴을 채택할
경우에 더 쉽다.
</para>
</listitem>
<listitem>
<para>
당신은 또한 초기화 되지 않은 콜렉션들(또는 다른 프락시들)에 접근하기 전에 <literal>merge()</literal> 또는
<literal>lock()</literal>으로 앞서 로드된 객체를 새로운 <literal>Session</literal>n에 첨부할 수도 있다.
아니다. Hibernate는 이것을 자동적으로 행하지 않고, 확실히 자동적으로 행하지 <emphasis>않을 것이다</emphasis>.
왜냐하면 그것은 특별한 목적을 위한 트랜잭션 의미를 도입할 것이기 때문이다!
</para>
</listitem>
</itemizedlist>
<para>
때때로 당신은 거대한 콜렉션을 초기화 시키는 것을 원하지 않지만, 여전히 (그것의 사이즈와 같은) 그것에 대한 어떤 정보 또는
데이터의 부분집합을 필요로 한다.
</para>
<para>
당신은 그것을 초기화 시키지 않고서 콜렉션의 사이즈를 얻는데 콜렉션 필터를 사용할 수 있다:
</para>
<programlisting><![CDATA[( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()]]></programlisting>
<para>
<literal>createFilter()</literal> 메소드는 또한 전체 콜렉션을 초기화 시킬 필요 없이 콜렉션의 부분집합들을 효율적으로
검색하는데 사용된다:
</para>
<programlisting><![CDATA[s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();]]></programlisting>
</sect2>
<sect2 id="performance-fetching-batch">
<title>batch 페칭 사용하기</title>
<para>
Hibernate는 배치 페칭을 효율적으로 사용할 수 있다. 즉 하나의 프락시가 액세스 될 경우에 Hibernate는 몇몇 초기화 되지 않은
프락시들을 로드시킬 수 있다(또는 콜렉션들). batch 페칭은 lazy select 페칭 방도에 대한 최적화이다. 당신이 batch
페칭을 튜닝시킬 수 있는 두 가지 방법들이 존재한다: 클래스 레벨에서 그리고 콜렉션 레벨에서.
</para>
<para>
클래스들/엔티티들에 대한 batch 페칭은 이해하기가 더 쉽다. 당신이 실행 시에 다음 상황에 처한다고 상상하라: 당신은 하나의
<literal>Session</literal> 속에 로드된 25개의 <literal>Cat</literal> 인스턴스들을 갖고 있고, 각각의
<literal>Cat</literal>은 그것의 <literal>소유자</literal> 즉, <literal>Person</literal>에 대한 참조를
갖고 있다. <literal>Person</literal> 클래스는 프락시 <literal>lazy="true"</literal>로서 매핑된다. 만일
당신이 이제 모든 cat들을 통해 반복하고 각각의 cat에 대해 <literal>getOwner()</literal>를 호출할 경우, Hibernate는
프락시된 소유자들을 검색하기 위해 25개의 <literal>SELECT</literal> 문장들을 디폴트로 실행시킬 것이다. 당신은
<literal>Person</literal> 매핑에서 <literal>batch-size</literal>를 지정함으로써 이 동작을 튜닝시킬 수 있다:
</para>
<programlisting><![CDATA[<class name="Person" batch-size="10">...</class>]]></programlisting>
<para>
Hibernate는 이제 세 개의 질의들 만을 실행시킬 것이고, 그 패턴은 10,10, 5 이다.
</para>
<para>
당신은 또한 콜렉션들에 대해 batch 페칭을 이용 가능하게 할 수도 있다. 예를 들어, 만일 각각의 <literal>Person</literal>
<literal>Cat</literal>들을 가진 lazy 콜렉션을 갖고, 10개의 person들이 <literal>Sesssion</literal> 내에 현재
로드되어 있을 경우, 모든 person들에 대한 반복은 10개의 <literal>SELECT</literal>들을 생성시킬 것이고, <literal>getCats()</literal>
대한 매번의 호출에 대해 하나의 <literal>SELECT</literal>를 생성시킬 것이다. 만일 당신이 <literal>Person</literal>
매핑에서 <literal>cats</literal> 콜렉션에 대해 batch 페칭을 사용가능하게 할 경우, Hibernate는 콜렉션들을 미리-페치
시킬 수 있다:
</para>
<programlisting><![CDATA[<class name="Person">
<set name="cats" batch-size="3">
...
</set>
</class>]]></programlisting>
<para>
<literal>batch-size</literal> 8로서, Hibernate는 4개의 SELECT들에서 3, 3, 3, 1 개의 콜렉션들을 로드시킬 것이다.
다시 그 속성의 값은 특정 <literal>Session</literal> 내에서 초기화 되지 않은 콜렉션들의 예상되는 개수에 의존한다.
</para>
<para>
만일 당신이 항목들의 포개진 트리를 가질 경우, 예를 들어 전형적인 bill-of-materials 패턴인 경우, (비록
<emphasis>내포된 set</emphasis> 또는 <emphasis>실체화된 경로(materialized path)</emphasis>
주로-읽기-트리들에 대해 더 좋은 옵션일 수 있을지라도) 콜렉션들에 대한 batch 페칭이 특히 유용하다.
</para>
</sect2>
<sect2 id="performance-fetching-subselect">
<title>subselect 페칭 사용하기</title>
<para>
만일 한 개의 lazy 콜렉션이나 단일 값 프락시가 페치되어야 한다면, Hibernate는 하나의 subselect 내에서 원래의
질의를 다시 실행하여 그것들 모두를 로드시킨다. 이것은 조각난 로딩 없이 batch 페칭과 동일한 방식으로 동작한다.
</para>
<!-- TODO: 이것에 대해 더 많이 작성할 것 -->
</sect2>
<sect2 id="performance-fetching-lazyproperties">
<title>lazy 프로퍼티 페칭 사용하기</title>
<para>
Hibernate3은 개별적인 프로퍼티들에 대한 lazy 페칭을 지원한다. 이 최적화 기술은 또한 <emphasis>fetch groups</emphasis>
으로 알려져 있다. 이것이 대개 마케팅 특징임을 노트하길 바란다. 왜냐하면 실제로 행 읽기를 최적화 시키는 것이 컬럼 읽기에 대한
최적화 보다 훨씬 더 중요하기 때문이다. 하지만 리거시 테이블들이 수백 개의 컬럼들을 갖고 데이터 모형이 개선될 수 없을 때,
오직 클래스의 몇몇 프로퍼티들을 로드시키는 것 만이 유용할 수도 있다.
</para>
<para>
lazy 프로퍼티 로딩을 이용가능하게 하려면, 당신의 특정 property 매핑들에 대해 <literal>lazy</literal> 속성을 설정하라:
</para>
<programlisting><![CDATA[<class name="Document">
<id name="id">
<generator class="native"/>
</id>
<property name="name" not-null="true" length="50"/>
<property name="summary" not-null="true" length="200" lazy="true"/>
<property name="text" not-null="true" length="2000" lazy="true"/>
</class>]]></programlisting>
<para>
Lazy property 로딩은 빌드 시 바이트코드 수단을 필요로 한다! 만일 당신의 영속 클래스들이 개선되지 않을 경우,
Hibernate는 조용하게 lazy 프로퍼티 설정들을 무시하고 즉각적인 페칭으로 후퇴할 것이다.
</para>
<para>
bytecode 수단으로, 다음 Ant 태스크를 사용하라:
</para>
<programlisting><![CDATA[<target name="instrument" depends="compile">
<taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">
<classpath path="${jar.path}"/>
<classpath path="${classes.dir}"/>
<classpath refid="lib.class.path"/>
</taskdef>
<instrument verbose="true">
<fileset dir="${testclasses.dir}/org/hibernate/auction/model">
<include name="*.class"/>
</fileset>
</instrument>
</target>]]></programlisting>
<para>
불필요한 컬럼 읽기를 피하는 다른 (더 좋은?) 방법은 적어도 읽기 전용 트랜잭션의 경우에 HQL 질의 또는 Criteria 질의의
투사(projection) 특징들을 사용하는 것이다. 이것은 빌드 시 바이트코드 처리에 대한 필요성을 피하게 해주고 확실히 선호되는
해결책이다.
</para>
<para>
당신은 HQL에서 <literal>fetch all properties</literal>를 사용하여 프로퍼티들에 대한 통상의 eager 페칭을
강제시킬 수 있다.
</para>
</sect2>
</sect1>
<sect1 id="performance-cache" revision="1">
<title>두번째 레벨 캐시</title>
<para>
Hibernate <literal>Session</literal>은 영속 데이터에 대한 트랜잭션 레벨 캐시이다. class-by-class와
collection-by-collection 기반 위에 클러스터 또는 JVM-레벨(<literal>SessionFactory</literal>-레벨) 캐시를 구성하는
것이 가능하다. 당신은 클러스터링 된 캐시 속에 플러그인 할 수도 있다. 주의하라. 캐시들은 (비록 그것들이 캐시된 데이터를 정기적으로
만료되도록 구성되어 있을지라도) 또 다른 어플리케이션에 의해 영속 저장소에 대해 행해진 변경들을 결코 알지 못한다.
</para>
<para revision="1">
디폴트로, Hibernate는 JVM-레벨의 캐싱에 EHCache를 사용한다. (JCS 지원은 이제 진부하게 되었고 Hibernate의 장래 버전에서
제거될 것이다.) 당신은 <literal>hibernate.cache.provider_class</literal> 프로퍼티를 사용하여
<literal>org.hibernate.cache.CacheProvider</literal>를 구현하는 클래스의 이름을 지정함으로써 다른 구현을 선택할 수도
있다.
You have the option to tell Hibernate which caching implementation to use by
specifying the name of a class that implements <literal>org.hibernate.cache.CacheProvider</literal>
using the property <literal>hibernate.cache.provider_class</literal>. Hibernate
comes bundled with a number of built-in integrations with open-source cache providers
(listed below); additionally, you could implement your own and plug it in as
outlined above. Note that versions prior to 3.2 defaulted to use EhCache as the
default cache provider; that is no longer the case as of 3.2.
당신은 <literal>hibernate.cache.provider_class</literal> 프로퍼티를 사용하여
<literal>org.hibernate.cache.CacheProvider</literal>를 구현하는 클래스의 이름을 지정함으로써 어느 캐싱 구현을
사용할 것인지를 Hibernate에게 알려주는 옵션을 갖는다. Hibernate는 (아래에 열거된) 오픈-소스 프로바이더들을 가진
많은 빌드되어 있는 통합들을 번들로 갖고 있다; 추가적으로 당신은 위에서 언급했듯이 그것에 당신 자신의 것을 구현할 수 있고 그것에
플러그 시킬 수 있다. 3.2 이번 버전들은 디플트 캐시 프로바이더로서 EhCache를 사용하도록 디포릍로 내장되어 있음을 노트하라;
버전 3.2의 경우에 그것은 더이상 디폴트 내장이 아니다.
</para>
<table frame="topbot" id="cacheproviders" revision="1">
<title>캐시 프로바이더들</title>
<tgroup cols='5' align='left' colsep='1' rowsep='1'>
<colspec colname='c1' colwidth="1*"/>
<colspec colname='c2' colwidth="3*"/>
<colspec colname='c3' colwidth="1*"/>
<colspec colname='c4' colwidth="1*"/>
<colspec colname='c5' colwidth="1*"/>
<thead>
<row>
<entry>캐시</entry>
<entry>프로바이더 클래스</entry>
<entry>타입</entry>
<entry>클러스터 안전</entry>
<entry>질의 캐시 지원</entry>
</row>
</thead>
<tbody>
<row>
<entry>Hashtable (제품 용도로 고안되어 있지 않음)</entry>
<entry><literal>org.hibernate.cache.HashtableCacheProvider</literal></entry>
<entry>memory</entry>
<entry></entry>
<entry>yes</entry>
</row>
<row>
<entry>EHCache</entry>
<entry><literal>org.hibernate.cache.EhCacheProvider</literal></entry>
<entry>memory, disk</entry>
<entry></entry>
<entry>yes</entry>
</row>
<row>
<entry>OSCache</entry>
<entry><literal>org.hibernate.cache.OSCacheProvider</literal></entry>
<entry>memory, disk</entry>
<entry></entry>
<entry>yes</entry>
</row>
<row>
<entry>SwarmCache</entry>
<entry><literal>org.hibernate.cache.SwarmCacheProvider</literal></entry>
<entry>clustered (ip multicast)</entry>
<entry>yes (clustered invalidation)</entry>
<entry></entry>
</row>
<row>
<entry>JBoss TreeCache</entry>
<entry><literal>org.hibernate.cache.TreeCacheProvider</literal></entry>
<entry>clustered (ip multicast), transactional</entry>
<entry>yes (replication)</entry>
<entry>yes (clock sync req.)</entry>
</row>
</tbody>
</tgroup>
</table>
<sect2 id="performance-cache-mapping" revision="2">
<title>Cache 매핑들</title>
<para>
클래스 또는 콜렉션 매핑의 <literal>&lt;cache&gt;</literal> 요소는 다음 형식을 갖는다:
</para>
<programlistingco>
<areaspec>
<area id="cache1" coords="2 70"/>
<area id="cache2" coords="3 70"/>
<area id="cache3" coords="4 70"/>
</areaspec>
<programlisting><![CDATA[<cache
usage="transactional|read-write|nonstrict-read-write|read-only"
region="RegionName"
include="all|non-lazy"
/>]]></programlisting>
<calloutlist>
<callout arearefs="cache1">
<para>
<literal>usage</literal>(필수) 캐싱 방도를 지정한다:
<literal>transactional</literal>,
<literal>read-write</literal>,
<literal>nonstrict-read-write</literal> 또는
<literal>read-only</literal>
</para>
</callout>
<callout arearefs="cache2">
<para>
<literal>region</literal> (옵션, 디폴트는 class 또는 콜렉션 role 이름)
second level 캐시 영역의 이름을 지정한다
</para>
</callout>
<callout arearefs="cache3">
<para>
<literal>include</literal> (옵션, 디폴트는 <literal>all</literal>)
<literal>non-lazy</literal><literal>lazy="true"</literal>로 매핑된 엔티티의
프로퍼티들을 지정하며 속성-레벨 lazy 페칭이 이용 가능할 때 키시될 수 없다
</para>
</callout>
</calloutlist>
</programlistingco>
<para>
다른 방법으로 (선호적으로?), 당신은 <literal>hibernate.cfg.xml</literal> 내에 <literal>&lt;class-cache&gt;</literal>
<literal>&lt;collection-cache&gt;</literal> 요소들을 지정할 수도 있다.
</para>
<para>
<literal>usage</literal> 속성은 <emphasis> 캐시 동시성 방도</emphasis>를 지정한다.
</para>
</sect2>
<sect2 id="performance-cache-readonly">
<title>방도: 읽기 전용</title>
<para>
당신의 어플리케이션이 영속 클래스의 인스턴스들을 읽어들일 필요가 있지만 결코 변경할 필요가 없을 경우에 <literal>read-only</literal>
캐시가 사용될 수 있다. 이것은 가장 간단한 최상의 퍼포먼스를 위한 방도이다. 그것은 클러스터 내 사용에는 완벽하게 안전하다.
</para>
<programlisting><![CDATA[<class name="eg.Immutable" mutable="false">
<cache usage="read-only"/>
....
</class>]]></programlisting>
</sect2>
<sect2 id="performance-cache-readwrite">
<title>방도: 읽기/쓰기</title>
<para>
어플리케이션이 데이터를 업데이트 할 필요가 있을 경우, <literal>read-write</literal> 캐시가 적절하다. 만일
직렬화 가능한(serializable) 트랜잭션 격리 레벨이 필요한 경우에는 이 캐시 방도가 결코 사용되지 말아야 한다. 만일 캐시가 JTA
환경에서 사용될 경우, 당신은 JTA <literal>TransactionManager</literal>를 얻는 방도를 명명하는
<literal>hibernate.transaction.manager_lookup_class</literal> 프로퍼티를 지정해야 한다. 다른 환경들에서,
당신은<literal>Session.close()</literal> 또는 <literal>Session.disconnect()</literal>가 호출될 때
트랜잭션이 완료되는 것을 확실히 해야 한다. 만일 당신이 클러스터 내에 이 방도를 사용하고자 원할 경우, 당신은 기본 캐시 구현이
잠금을 지원하도록 하는 것을 확실히 해야 한다. 미리 만들어진 캐시 프로바이더들은 그렇게 행하지 <emphasis>않는다</emphasis>.
</para>
<programlisting><![CDATA[<class name="eg.Cat" .... >
<cache usage="read-write"/>
....
<set name="kittens" ... >
<cache usage="read-write"/>
....
</set>
</class>]]></programlisting>
</sect2>
<sect2 id="performance-cache-nonstrict">
<title>방도: 엄격하지 않은 읽기/쓰기</title>
<para>
만일 어플리케이션이 오직 데이터를 자주 업데이트할 필요가 있고(예를 들어, 만일 두 개의 트랜잭션들이 동시에 동일한 항목을 업데이트
하려고 시도하는 정말 있음직하지 않은 경우) 그리고 엄격한 트랜잭션 격리가 필요하지 않은 경우, <literal>nonstrict-read-write</literal>
캐시가 적절할 수 있다. 만일 그 캐시가 JTA 환경에서 사용될 경우, 당신은 <literal>hibernate.transaction.manager_lookup_class</literal>
지정해야 한다. 다른 환경들에서, 당신은 <literal>Session.close()</literal> 또는 <literal>Session.disconnect()</literal>가 호출될 때
트랜잭션이 완료되도록 확실히 해야 한다.
</para>
</sect2>
<sect2 id="performance-cache-transactional">
<title>방도: transactional</title>
<para>
<literal>transactional</literal> 캐시 방도는 JBoss TreeCache와 같은 전체 트랜잭션적인 캐시 프로바이더들에 대한
지원을 제공한다. 그런 캐시는 오직 JTA 환경 내에서 사용될 수 있고 당신은
<literal>hibernate.transaction.manager_lookup_class</literal>를 지정해야 한다.
</para>
</sect2>
<para>
캐시 프로바이더들 중 어느 것도 모든 캐시 동시성 방도들을 지원하지 않는다. 다음 테이블은 어느 프로바이더들이 어느 동시성 방도들과
호환되는지를 보여준다.
</para>
<table frame="topbot">
<title>캐시 동시성 방도 지원</title>
<tgroup cols='5' align='left' colsep='1' rowsep='1'>
<colspec colname='c1' colwidth="1*"/>
<colspec colname='c2' colwidth="1*"/>
<colspec colname='c3' colwidth="1*"/>
<colspec colname='c4' colwidth="1*"/>
<colspec colname='c5' colwidth="1*"/>
<thead>
<row>
<entry>캐시</entry>
<entry>읽기 전용</entry>
<entry>엄격하지 않은 읽기-쓰기</entry>
<entry>읽기-쓰기</entry>
<entry>transactional</entry>
</row>
</thead>
<tbody>
<row>
<entry>Hashtable (제품용으로 고안되지 않음)</entry>
<entry></entry>
<entry></entry>
<entry></entry>
<entry></entry>
</row>
<row>
<entry>EHCache</entry>
<entry></entry>
<entry></entry>
<entry></entry>
<entry></entry>
</row>
<row>
<entry>OSCache</entry>
<entry></entry>
<entry></entry>
<entry></entry>
<entry></entry>
</row>
<row>
<entry>SwarmCache</entry>
<entry></entry>
<entry></entry>
<entry></entry>
<entry></entry>
</row>
<row>
<entry>JBoss TreeCache</entry>
<entry></entry>
<entry></entry>
<entry></entry>
<entry></entry>
</row>
</tbody>
</tgroup>
</table>
</sect1>
<sect1 id="performance-sessioncache" revision="2">
<title>캐시들을 관리하기</title>
<para>
당신이 객체를 <literal>save()</literal>, <literal>update()</literal> 또는 <literal>saveOrUpdate()</literal>
전달할 때마다 그리고 당신이 <literal>load()</literal>, <literal>get()</literal>, <literal>list()</literal>,
<literal>iterate()</literal> 또는 <literal>scroll()</literal>을 사용하여 객체를 검색할 때마다, 그 객체는
<literal>Session</literal>의 내부 캐시에 추가된다.
</para>
<para>
<literal>flush()</literal>가 차후적으로 호출될 때, 그 객체의 상태는 데이터베이스와 동기화 될 것이다. 만일 당신이 이 동기화가
발생되는 것을 원하지 않거나 만일 당신이 대량의 객체들을 처리 중이고 메모리를 효율적으로 관리할 필요가 있을 경우, <literal>evict()</literal>
메소드는 first-level 캐시로부터 그 객체와 그것의 콜렉션들을 제거하는데 사용될 수 있다.
</para>
<programlisting><![CDATA[ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set
while ( cats.next() ) {
Cat cat = (Cat) cats.get(0);
doSomethingWithACat(cat);
sess.evict(cat);
}]]></programlisting>
<para>
<literal>Session</literal>은 또한 인스턴스가 세션 캐시에 속하는지 여부를 결정하는데 <literal>contains()</literal>
메소드를 제공한다.
</para>
<para>
세션 캐시로부터 모든 객체들을 완전하게 퇴거시키기 위해, <literal>Session.clear()</literal>를 호출하라.
</para>
<para>
second-level 캐시의 경우, 하나의 인스턴스, 전체 클래스, 콜렉션 인스턴스 또는 전체 콜렉션 role의 캐시된 상태를 퇴거시키는
<literal>SessionFactory</literal> 상에 정의된 메소드들이 존재한다.
</para>
<programlisting><![CDATA[sessionFactory.evict(Cat.class, catId); //evict a particular Cat
sessionFactory.evict(Cat.class); //evict all Cats
sessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens
sessionFactory.evictCollection("Cat.kittens"); //evict all kitten collections]]></programlisting>
<para>
<literal>CacheMode</literal>는 특정 세션이 second-level 캐시와 어떻게 상호작용하는지를 제어한다
</para>
<itemizedlist>
<listitem>
<para>
<literal>CacheMode.NORMAL</literal> - second-level 캐시로부터 아이템들을 읽어들이고 second-level 캐시로
아이템들을 기록한다
</para>
</listitem>
<listitem>
<para>
<literal>CacheMode.GET</literal> - second-level 캐시로부터 아이템들을 읽어들이지만, 데이터를 업데이트할 때를 제외하면
second-level 캐시로 기록하지 않는다
</para>
</listitem>
<listitem>
<para>
<literal>CacheMode.PUT</literal> - 아이템들을 second-level 캐시에 기록하지만, second-level 캐시로부터 읽어들이지
않는다
</para>
</listitem>
<listitem>
<para>
<literal>CacheMode.REFRESH</literal> - 아이템들을 second-level 캐시로기록하지만, second-level 캐시로부터
읽어들이지 않고, 데이터베이스로부터 읽어들인 모든 아이템들에 대한 second-level 캐시의 갱신을 강제시켜,
<literal>hibernate.cache.use_minimal_puts</literal>의 효과를 무시한다
</para>
</listitem>
</itemizedlist>
<para>
second-level 캐시 또는 질의 캐시 영역의 내용물을 브라우징하려면 <literal>Statistics</literal> API를 사용하라:
</para>
<programlisting><![CDATA[Map cacheEntries = sessionFactory.getStatistics()
.getSecondLevelCacheStatistics(regionName)
.getEntries();]]></programlisting>
<para>
당신은 통계를 이용 가능하게 하고, 선택적으로 Hibernate로 하여금 캐시 엔트리들을 보다 인간에게 이해가능한 형식으로 유지시키도록
강제시키는 것이 필요할 것이다:
</para>
<programlisting><![CDATA[hibernate.generate_statistics true
hibernate.cache.use_structured_entries true]]></programlisting>
</sect1>
<sect1 id="performance-querycache" revision="1">
<title>질의 캐시</title>
<para>
질의 결과 셋들이 또한 캐시될 수도 있다. 이것은 동일한 파라미터들을 가지고 자주 실행되는 질의들에만 유용하다.
질의 캐시를 사용하기 위해 당신은 먼저 그것을 이용 가능하도록 해야 한다:
</para>
<programlisting><![CDATA[hibernate.cache.use_query_cache true]]></programlisting>
<para>
이 설정은 두 개의 새로운 캐시 영역들 - 캐시된 질의 결과 셋들을 보관하는 것
(<literal>org.hibernate.cache.StandardQueryCache</literal>), 질의 가능한 테이블들에 대한 가장
최신 업데이트들에 대한 timestamp들을 보관하는 다른 것
(<literal>org.hibernate.cache.UpdateTimestampsCache</literal>)-의 생성을 강제한다 . 질의 캐시는
결과 셋 내에 실제 엔티티들의 상태를 캐시시키지 않음을 노트하라; 그것은 오직 식별자 값들과 값 타입의 결과들
만을 캐시시킨다. 따라서 질의 캐시는 항상 second-level 캐시와 함께 사용되어야 한다.
</para>
<para>
대부분의 질의들은 캐싱으로부터 이점이 없기에, 디폴트로 질의들은 캐시되지 않는다. 캐싱을 이용 가능하도록 하려면,
<literal>Query.setCacheable(true)</literal>를 호출하라. 이 호출은 기존 캐시 결과들을 찾는 것을 질의에게
허용해주거나 질의가 실행될 때 그것의 결과들을 캐시에 추가하는 것을 허용해준다.
</para>
<para>
만일 당신이 질의 캐시 만료 정책들에 대한 세밀한 제어를 필요로 할 경우, 당신은
<literal>Query.setCacheRegion()</literal>을 호출함으로써 특별한 질의에 대해 명명되니 캐시 영역을
지정할 수도 있다.
</para>
<programlisting><![CDATA[List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
.setEntity("blogger", blogger)
.setMaxResults(15)
.setCacheable(true)
.setCacheRegion("frontpages")
.list();]]></programlisting>
<para>
만일 질의가 그것의 질의 캐시 영역의 갱신을 강제시켜야 하는 경우에, 당신은 <literal>Query.setCacheMode(CacheMode.REFRESH)</literal>
호출해야 한다. 이것은 기본 데이터가 별도의 프로세스를 통해 업데이트되었고(예를 들면, Hibernate를 통해 변경되지 않았고)
특정 질의 결과 셋들을 선택적으로 갱신하는 것을 어플리케이션에게 허용해주는 경우들에서 특별히 유용하다. 이것은
<literal>SessionFactory.evictQueries()</literal>를 통해 질의 캐시 영역을 퇴거시키는 보다 효과적인 대안이다.
</para>
</sect1>
<sect1 id="performance-collections">
<title>콜렉션 퍼포먼스 이해하기</title>
<para>
우리는이미 콜렉션들에 관해 얘기하는데 꽤 많은 시간을 소요했다. 이 절에서 우리는 콜렉션들이 실행 시에 어떻게 행위하는지에 관한
한 쌍의 쟁점들을 조명할 것이다.
</para>
<sect2 id="performance-collections-taxonomy">
<title>분류</title>
<para>Hibernate는 세 가지 기본적인 종류의 콜렉션들을 정의한다:</para>
<itemizedlist>
<listitem>
<para>값들을 가진 콜렉션들</para>
</listitem>
<listitem>
<para>one to many 연관들</para>
</listitem>
<listitem>
<para>many to many 연관들</para>
</listitem>
</itemizedlist>
<para>
이 분류는 여러 가지 테이블과 foreign key 관계들을 구별짓지만 우리가 관계형 모형에 대해 알 필요가 있는 모든 것을 우리에게
말해주지 않는다. 관계형 구조와 퍼포먼스 특징들을 완전하게 이해하기 위해, 우리는 또한 콜렉션 행들을 업데이트하거나 삭제하기
위해 Hibernate에 의해 사용되는 프라이머리 키의 구조를 검토해야 한다. 이것은 다음 분류를 제안한다:
</para>
<itemizedlist>
<listitem>
<para>인덱싱 된 콜렉션들 </para>
</listitem>
<listitem>
<para>set들</para>
</listitem>
<listitem>
<para>bag들</para>
</listitem>
</itemizedlist>
<para>
모든 인덱싱된 콜렉션들(map들, list들, array들)은 <literal>&lt;key&gt;</literal>
<literal>&lt;index&gt;</literal> 컬럼들로 이루어진 프라이머리 키를 갖는다. 이 경우에 콜렉션 업데이트들은 대개
극히 효율적이다 - Hibernate가 그것을 업데이트나 삭제를 시도할 때 프라이머리 키는 효율적으로 인덱싱될 수 있고 특정 행은
효율적으로 위치지워질 수 있다.
</para>
<para>
Set들은 <literal>&lt;key&gt;</literal>와 요소 컬럼들로 구성된 프라이머리 키를 갖는다. 이것은 몇몇 유형의 콜렉션 요소,
특히 composite 요소들 또는 대형 텍스트 또는 바이너리 필드들에 대해 덜 효율적일 수 있다; 데이터베이스는 복잡한 프라이머리
키를 효율적으로 인덱싱하는 것이 불가능할 수도 있다. 반면에 one to many 또는 many to many 연관들의 경우, 특히 합성
식별자들의 경우에는 효율적일 수 있을 것 같다.(부수-노트: 만일 당신이 당신을 위한 <literal>&lt;set&gt;</literal>
프라이머리 키를 실제로 생성시키기 위해 <literal>SchemaExport</literal>를 원한다면 당신은 모든 컬럼들을
<literal>not-null="true"</literal>로 선언해야 한다.)
</para>
<para>
<literal>&lt;idbag&gt;</literal> 매핑들은 대용 키를 정의하여서, 그것들은 항상 업데이트에 매우 효율적이다. 사실,
그것들은 최상의 경우이다.
</para>
<para>
Bag들은 가장 나쁜 경우이다. 왜냐하면 하나의 bag은 중복 요소 값들을 허용하고 인덱스 컬럼을 갖지 않기 때문에, 프라이머리 키가
정의될 수 없다. Hibernate는 중복 행들 사이를 구분 짓는 방법을 갖고 있지 않다. Hibernate는 그것이 변경될 때마다
(한 개의 DELETE로) 콜렉션을 완전하게 제거하고 다시 생성시킴으로써 이 문제를 해결한다. 이것은 매우 비효율적이다.
</para>
<para>
one-to-many 연관의 경우, "프라이머리 키"는 데이터베이스 테이블의 물리적인 프라이머리 키가 아닐 수도 있지만- 이 경우에서도
위의 분류는 여전히 유용하다. (그것은 여전히 Hibernate가 콜렉션의 개별 행들을 어떻게 "위치지우는"지를 반영한다.)
</para>
</sect2>
<sect2 id="performance-collections-mostefficientupdate">
<title>List, map, idbag, set들은 update에 가장 효율적인 콜렉션들이다</title>
<para>
위의 논의에서, 인덱싱된 콜렉션들과 (대개) set들이 요소들을 추가하고, 제거하고 업데이트함에 있어 가장 효율적인 오퍼레이션을
허용해준다.
</para>
<para>
아마 인덱싱 된 콜렉션들이 many to many 연관들을 위한 또는 값들을 가진 콜렉션들을 위한 set들에 대해 갖고 있는 하나 이상의
장점들이 존재한다. <literal>Set</literal>의 구조 때문에, Hibernate는 요소가 "변경"될 때 행을 <literal>UPDATE</literal>
하지 않는다. <literal>Set</literal>에 대한 변경들은 항상 (개별 행들에 대한) <literal>INSERT</literal>
<literal>DELETE</literal>를 통해 동작한다. 다시 이 검토는 one to many 연관들에 적용되지 않는다.
</para>
<para>
배열들이 lazy 될 수 없음을 관찰 한 후에, 우리는 list들, map들, 그리고 idbag들이 단독이 아닌 set들을 가진 가장 퍼포먼스가
좋은(non-inverse) 콜렉션 타입들임을 결론 지을 것이다. Set들은 Hibernate 어플리케이션들에서 가장 공통된 종류의 콜렉션이
될 것이라 예상된다. 이것은 "set" 의미가 관계형 모형에서 가장 고유한 것이기 때문이다.
</para>
<para>
하지만, 잘 설계된 Hibernate 도메인 모형들에서, 우리는 대개 대부분의 콜렉션들이 사실 <literal>inverse="true"</literal>
가진 one-to-many 연관들임을 보게 된다. 이들 연관들의 경우, 업데이트는 연관의 many-to-one 엔드에 의해 처리되고, 따라서
콜렉션 업데이트 퍼포먼스에 대한 검토들은 단순히 적용되지 않는다.
</para>
</sect2>
<sect2 id="performance-collections-mostefficentinverse">
<title>Bag들과 list들은 가장 효율적인 inverse 콜렉션들이다</title>
<para>
단지 당신이 영원히 bag들을 버리기 전에, bag들(과 또한 list들)이 set들보다 훨씬 더 성능이 좋은 특별한 경우들이 존재한다.
<literal>inverse="true"</literal>를 가진 콜렉션들(예를 들어, 표준 양방향 one-to-many 관계 특질)의 경우, 우리는
bag 요소들을 초기화(페치) 시킬 필요 없이 bag 또는 list에 요소들을 추가시킬 수 있다! 이것은 <literal>Collection.add()</literal>
또는 <literal>Collection.addAll()</literal>이 (<literal>Set</literal>과는 달리) 항상 bag 또는
<literal>List</literal>에 대해 true를 반환해야하기 때문이다. 이것은 훨씬 다음 공통적인 코드를 더 빠르게 만들 수 있다.
</para>
<programlisting><![CDATA[Parent p = (Parent) sess.load(Parent.class, id);
Child c = new Child();
c.setParent(p);
p.getChildren().add(c); //no need to fetch the collection!
sess.flush();]]></programlisting>
</sect2>
<sect2 id="performance-collections-oneshotdelete">
<title>원 샷 delete</title>
<para>
종종 콜렉션 요소들을 하나씩 삭제하는 것은 극히 비효율적일 수 있다! Hibernate는 완전하게 바보가 아니어서, 그것은 새로운 공백의
콜렉션의 경우(예를 들어 당신이 <literal>list.clear()</literal>를 호출했을 경우)에 그것을 행하지 않을 것임을 알고 있다.
이 경우에, Hibernate는 하나의 <literal>DELETE</literal> 명령을 내릴 것이고 우리는 모두 행했다!
</para>
<para>
우리가 길이 20인 하나의 콜렉션에 한 개의 요소를 추가하고 그런 다음 두 개의 요소들을 제거한다고 가정하자. Hibernate는
(콜렉션이 bag가 아닌 한) 한 개의 <literal>INSERT</literal> 문장과 두 개의 <literal>DELETE</literal> 문장을 명령
내릴 것이다. 이것은 확실히 마음에 든다.
</para>
<para>
하지만, 우리가 두 개의 요소들을 남겨둔채 18 개의 요소들을 제거하고 나서 세 개의 새로운 요소들을 추가한다고 가정하자.
두 가지 가능한 처리 방법들이 존재한다.
</para>
<itemizedlist>
<listitem>
<para>하나씩 열 여덟 개의 행들을 삭제한 다음에 세 개의 행들을 삽입시킨다</para>
</listitem>
<listitem>
<para>
(한 개의 SQL <literal>DELETE</literal>로)전체 콜렉션을 삭제하고 모든 다섯개의 현재 요소들을 (하나씩) insert 시킨다
</para>
</listitem>
</itemizedlist>
<para>
Hibernate는 두 번째 옵션이 아마 이 경우에 더 빠르다는 점을 알 만큼 충분히 영리하지 않다.(그리고 Hibernate가 그렇게 영리해지는 것을 희망
하는 것은 가능하지 않을 것이다; 그런 특징은 데이터베이스 트리거들 등을 혼동스럽게 할 수도 있다.)
</para>
<para>
다행히, 당신은 원래의 콜렉션을 폐기시키고(예를 들어 참조 해제하고) 모든 현재 요소들을 가진 새로이 초기화된 콜렉션을
반환함으로써 아무때든지 이 특징을 강제시킬 수 있다. 이것은 시간이 흐름에 따라 매우 유용하고 강력해질 수 있다.
</para>
<para>
물론 단 한번의 삭제(one-shot-delete)는 <literal>inverse="true"</literal>로 매핑된 콜렉션들에 적용되지 않는다.
</para>
</sect2>
</sect1>
<sect1 id="performance-monitoring" revision="1">
<title>퍼포먼스 모니터링하기</title>
<para>
최적화는 퍼포먼스 관련 숫자들에 대한 모니터링과 접근 없이는 많이 사용되지 않는다. Hibernate는 그것의 내부적인 오퍼레이션들에 대한
전체 영역의 특징들을 제공한다. Hibernate에서 Statistics는 <literal>SessionFactory</literal>에 대해 이용 가능하다.
</para>
<sect2 id="performance-monitoring-sf" revision="2">
<title>SessionFactory 모니터링 하기</title>
<para>
당신은 두 가지 방법들로 <literal>SessionFactory</literal> metrics에 접근할 수 있다. 당신의 첫 번째 옵션은
<literal>sessionFactory.getStatistics()</literal>를 호출하고 당신 스스로 <literal>Statistics</literal>
읽거나 디스플레이 하는 것이다.
</para>
<para>
만일 당신이 <literal>StatisticsService</literal> MBean을 이용 가능하도록 할 경우 Hibernate는 또한 metrics를
발표하는데 JMX를 사용할 수 있다. 당신은 모든 당신의<literal>SessionFactory</literal>에 대해 한 개의 MBean 또는
팩토리 당 한 개를 이용 가능하게 할 수 있다. 최소한의 구성 예제들은 다음 코드를 보라:
</para>
<programlisting><![CDATA[// MBean service registration for a specific SessionFactory
Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "myFinancialApp");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
StatisticsService stats = new StatisticsService(); // MBean implementation
stats.setSessionFactory(sessionFactory); // Bind the stats to a SessionFactory
server.registerMBean(stats, on); // Register the Mbean on the server]]></programlisting>
<programlisting><![CDATA[// MBean service registration for all SessionFactory's
Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "all");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
StatisticsService stats = new StatisticsService(); // MBean implementation
server.registerMBean(stats, on); // Register the MBean on the server]]></programlisting>
<para>
TODO: 이것은 의미가 없다: 첫번째 경우에, 우리는 직접 MBean을 검색하고 사용한다.
두 번째 경우에 우리는 JNDI 이름을 사용하기 전에 세션 팩토리가 보관하고 있는 JNDI 이름을 부여해야 한다.
<literal>hibernateStatsBean.setSessionFactoryJNDIName("my/JNDI/Name")</literal>을 사용하라.
</para>
<para>
당신은 <literal>SessionFactory</literal>에 대한 모니터링을 (비)활성화 시킬 수 있다
</para>
<itemizedlist>
<listitem>
<para>
구성 시 : <literal>hibernate.generate_statistics</literal>, 디폴트는 <literal>false</literal>
</para>
</listitem>
</itemizedlist>
<itemizedlist>
<listitem>
<para>
실행 시 : <literal>sf.getStatistics().setStatisticsEnabled(true)</literal> 또는
<literal>hibernateStatsBean.setStatisticsEnabled(true)</literal>
</para>
</listitem>
</itemizedlist>
<para>
Statistics(통계량들)은 <literal>clear()</literal> 메소드를 사용하여 프로그래밍 방식으로 재설정 될 수 있다. 요약은
<literal>logSummary()</literal> 메소드를 사용하여 logger(info 레벨)에게 전송될 수 있다.
</para>
</sect2>
<sect2 id="performance-monitoring-metrics" revision="1">
<title>Metrics</title>
<para>
Hibernate는 매우 기본적인 것에서부터 어떤 시나리오들에만 관련된 전문 정보에 이르는 많은 metrics를 제공한다.
모든 이용 가능한 카운터들은 <literal>Statistics</literal> interface API에서 3개의 카테고리로 설명되어 있다:
</para>
<itemizedlist>
<listitem>
<para>
열려진 세션들의 개수, 검색된 JDBC 커넥션들의 개수 등과 같은 일반적인 <literal>Session</literal> 사용에
관련된 metrics.
</para>
</listitem>
<listitem>
<para>
전체적으로 엔티티들, 콜렉션들, 질의들, 그리고 캐시들에 관련된 metrics(전역 metrics로 알려져 있음),
</para>
</listitem>
<listitem>
<para>
특정한 엔티티, 콜렉션, 질의 또는 캐시 영역에 관련된 상세 metrics.
</para>
</listitem>
</itemizedlist>
<para>
예를 들어 당신은 엔티티, 콜렉션, 질의들의 캐시 성공율 및 실패율, put(역자 주, 캐시 시도, putt) 비율, 콜렉션들과 질의들,
그리고 평균 질의 요구 시간 등을 찾을 수 있다. 수 밀리초들가 자바에서 근사치에 종속됨을 의식하라. Hibernate는 JVM 정밀도에
묶여 있고, 몇몇 플랫폼들에서 이것은 심지어 약 10초가 될 수도 있다.
</para>
<para>
간단한 getter들은 (예를 들어 특정 엔티티, 콜렉션, 캐시 영역에 묶이지 않은) 전역 metrics에 접근하는데 사용된다. 당신은
그것(특정 엔티티, 콜렉션, 또는 캐시 영역)의 이름을 통해, 그리고 질의들에 대한 그것의 HQL 또는 SQL 표현을 통해 특정 엔티티,
콜렉션, 또는 캐시 영역의 metrics에 접근할수 있다. 추가 정보는 <literal>Statistics</literal>, <literal>EntityStatistics</literal>,
<literal>CollectionStatistics</literal>, <literal>SecondLevelCacheStatistics</literal>, 그리고
<literal>QueryStatistics</literal>를 참조하라. 다음 코드는 간단한 예제를 보여준다:
</para>
<programlisting><![CDATA[Statistics stats = HibernateUtil.sessionFactory.getStatistics();
double queryCacheHitCount = stats.getQueryCacheHitCount();
double queryCacheMissCount = stats.getQueryCacheMissCount();
double queryCacheHitRatio =
queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount);
log.info("Query Hit ratio:" + queryCacheHitRatio);
EntityStatistics entityStats =
stats.getEntityStatistics( Cat.class.getName() );
long changes =
entityStats.getInsertCount()
+ entityStats.getUpdateCount()
+ entityStats.getDeleteCount();
log.info(Cat.class.getName() + " changed " + changes + "times" );]]></programlisting>
<para>
모든 엔티티들, 콜렉션들, 콜렉션들,질의들 그리고 영역 캐시들에 대해 작업하기 위해, 당신은 다음 메소드들로서 엔티티들,
콜렉션들, 질의들, 그리고 영역 캐시들에 대한 이름들의 목록을 검색할 수 있다: <literal>getQueries()</literal>,
<literal>getEntityNames()</literal>, <literal>getCollectionRoleNames()</literal>, 그리고
<literal>getSecondLevelCacheRegionNames()</literal>.
</para>
</sect2>
</sect1>
</chapter>