mirror of
https://github.com/hibernate/hibernate-orm
synced 2025-03-01 07:19:15 +00:00
git-svn-id: https://svn.jboss.org/repos/hibernate/core/branches/Branch_3_2@11779 1b8cb986-b30d-0410-93ca-fae66ebed9b2
1293 lines
77 KiB
XML
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><cache></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><class-cache></literal>와
|
|
<literal><collection-cache></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><key></literal>와
|
|
<literal><index></literal> 컬럼들로 이루어진 프라이머리 키를 갖는다. 이 경우에 콜렉션 업데이트들은 대개
|
|
극히 효율적이다 - Hibernate가 그것을 업데이트나 삭제를 시도할 때 프라이머리 키는 효율적으로 인덱싱될 수 있고 특정 행은
|
|
효율적으로 위치지워질 수 있다.
|
|
</para>
|
|
|
|
<para>
|
|
Set들은 <literal><key></literal>와 요소 컬럼들로 구성된 프라이머리 키를 갖는다. 이것은 몇몇 유형의 콜렉션 요소,
|
|
특히 composite 요소들 또는 대형 텍스트 또는 바이너리 필드들에 대해 덜 효율적일 수 있다; 데이터베이스는 복잡한 프라이머리
|
|
키를 효율적으로 인덱싱하는 것이 불가능할 수도 있다. 반면에 one to many 또는 many to many 연관들의 경우, 특히 합성
|
|
식별자들의 경우에는 효율적일 수 있을 것 같다.(부수-노트: 만일 당신이 당신을 위한 <literal><set></literal>의
|
|
프라이머리 키를 실제로 생성시키기 위해 <literal>SchemaExport</literal>를 원한다면 당신은 모든 컬럼들을
|
|
<literal>not-null="true"</literal>로 선언해야 한다.)
|
|
</para>
|
|
|
|
<para>
|
|
<literal><idbag></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> |