JongDae Kim a6607faacc v31final
git-svn-id: https://svn.jboss.org/repos/hibernate/trunk/Hibernate3/doc@9072 1b8cb986-b30d-0410-93ca-fae66ebed9b2
2006-01-17 04:13:35 +00:00

295 lines
17 KiB
XML

<chapter id="batch">
<title>Batch 처리</title>
<para>
Hibernate를 사용하여 데이터베이스 내에서 100 000 개의 행들을 삽입시키는 본래의 접근법은 다음과 같다:
</para>
<programlisting><![CDATA[Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.save(customer);
}
tx.commit();
session.close();]]></programlisting>
<para>
이것은 50 000번째 행 가까운 곳에서 <literal>OutOfMemoryException</literal>으로 떨어질 것이다. 그것은 Hibernate가
session-level 캐시 속에 모든 새로이 삽입된 <literal>Customer</literal> 인스턴스들을 캐시시키기 때문이다.
</para>
<para>
이 장에서 우리는 이 문제를 피하는 방법을 당신에게 보여줄 것이다. 하지만 먼저 당신이 배치 처리를 행하는 중이라면, 당신이 적당한 퍼포먼스를
성취하려고 할 경우에 당신이 JDBC 배치 사용을 가능하게 하는 것은 절대적으로 필요하다. JDBC 배치 사이즈를 적당한 숫자(10-50)로 설정하라:
</para>
<programlisting><![CDATA[hibernate.jdbc.batch_size 20]]></programlisting>
<para>
당신은 또한 second-level 캐시를 가진 상호작용이 완전하게 불가능한 프로세스 내에서 이런 종류의 작업을 행하고 싶어할 수도 있다:
You also might like to do this kind of work in a process where interaction with
the second-level cache is completely disabled:
</para>
<programlisting><![CDATA[hibernate.cache.use_second_level_cache false]]></programlisting>
<para>
하지만 이것은 절대적으로 필요하지 않다. 왜냐하면 우리는 second-level 캐시와의 상호작용을 불가능하도록 하기 위해
명시적으로 <literal>CacheMode</literal>를 설정할 수 있기 때문이다.
</para>
<sect1 id="batch-inserts">
<title>Batch inserts</title>
<para>
새로운 객체들을 영속화 시킬 때, 당신은 first-level 캐시의 사이즈를 제어하기 위해 세션을 정기적으로 <literal>flush()</literal>
시키고 나서 <literal>clear()</literal> 시켜야 한다.
</para>
<programlisting><![CDATA[Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.save(customer);
if ( i % 20 == 0 ) { //20, same as the JDBC batch size
//flush a batch of inserts and release memory:
session.flush();
session.clear();
}
}
tx.commit();
session.close();]]></programlisting>
</sect1>
<sect1 id="batch-update" >
<title>Batch updates</title>
<para>
데이터 검색과 업데이트의 경우 동일한 개념들이 적용된다. 게다가 당신은 많은 데이터 행들을 반환하는 질의들에 대해 서버-측 커서들의 장점을
취하는데 <literal>scroll()</literal>을 사용할 필요가 있다.
</para>
<programlisting><![CDATA[Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
ScrollableResults customers = session.getNamedQuery("GetCustomers")
.setCacheMode(CacheMode.IGNORE)
.scroll(ScrollMode.FORWARD_ONLY);
int count=0;
while ( customers.next() ) {
Customer customer = (Customer) customers.get(0);
customer.updateStuff(...);
if ( ++count % 20 == 0 ) {
//flush a batch of updates and release memory:
session.flush();
session.clear();
}
}
tx.commit();
session.close();]]></programlisting>
</sect1>
<sect1 id="batch-statelesssession">
<title>StatelessSession 인터페이스</title>
<para>
다른 방법으로 Hibernate는 분리된(detached) 객체들의 형식으로 데이터베이스로 그리고 데이터베이스로부터
데이터를 스트리밍하는데 사용될 수 있는 하나의 명령 지향 API를 제공한다. <literal>StatelessSession</literal>
그것과 연관된 영속 컨텍스트를 갖지 않고 많은 보다 높은 레벨의 생명 주기 의미를 제공하지 않는다.
특히 하나의 상태 없는 세션은 첫 번째 레벨 캐시를 구현하지 않거나 임의의 두 번째 레벨 캐시 또는 질의 캐시와
상호작용하지 않는다. 그것은 전통적인 쓰기-이면(write-behind)의 체킹이나 자동적인 dirty 체킹을 구현하지 않는다.
하나의 상태 없는 세션을 사용하여 수행된 연산들은 연관된 인스턴스들에 대해 케스케이드 되지 않는다. 콜렉션들은
상태 없는 세션에 의해 무시된다. 상태없는 세션을 통해 수행된 연산들은 Hibernate의 이벤트 모형과 인터셉터들을
우회한다. 상태없는 세션들은 첫번째-레벨의 캐시의 부족으로 인해 데이터 alias 효과들에 취약하다. 상태없는 세션은
기본 JDBC에 훨씬 더 가까운 하나의 보다 낮은 레벨의 추상화(abstraction)이다.
</para>
<programlisting><![CDATA[StatelessSession session = sessionFactory.openStatelessSession();
Transaction tx = session.beginTransaction();
ScrollableResults customers = session.getNamedQuery("GetCustomers")
.scroll(ScrollMode.FORWARD_ONLY);
while ( customers.next() ) {
Customer customer = (Customer) customers.get(0);
customer.updateStuff(...);
session.update(customer);
}
tx.commit();
session.close();]]></programlisting>
<para>
이 코드 예제에서, 질의에 의해 반환된 <literal>Customer</literal> 인스턴스들은 곧바고 분리된다(detached).
그것들은 임의의 영속 컨텍스트와 결코 연관되지 않는다.
</para>
<para>
<literal>StatelessSession</literal> 인터페이스에 의해 정의된
<literal>insert(), update()</literal> 그리고 <literal>delete()</literal> 연산들은 직접적인
데이터베이스 저급 연산들로 갖주되고, 그것은 각각 하나의 SQL <literal>INSERT, UPDATE</literal> 또는
<literal>DELETE</literal>의 즉각적인 실행으로 귀결된다. 따라서 그것들은 <literal>Session</literal>
인터페이스에 의해 정의된 <literal>save(), saveOrUpdate()</literal><literal>delete()</literal>
연산들과는 매우 다른 의미들을 갖는다.
</para>
</sect1>
<sect1 id="batch-direct" revision="2">
<title>DML-스타일 연산들</title>
<para>
이미 논의했듯이, 자동적이고 투명한 객체/관계형 매핑은 객체 상태에 대한 관리에 관계된다. 이것은 객체 상태가 메모리 내에서 이용 가능함을
의미하므로, (SQL <literal>Data Manipulation Language</literal>(DML) 문장들 : <literal>INSERT</literal>,
<literal>UPDATE</literal>, <literal>DELETE</literal>를 사용하여) 데이터베이스에서 직접 데이터를
업데이트하거나 삭제하는 것은 메모리 내 상태에 영향을 주지 않을 것이다. 하지만 Hibernate는 Hibernate Query Language
(<xref linkend="queryhql">HQL</xref>)를 통해 실행되는 대량 SQL-스타일의 DML 문장 실행을 위한 메소드들을 제공한다.
</para>
<para>
<literal>UPDATE</literal><literal>DELETE</literal> 문장들의 유사-구문은 다음과 같다:
<literal>( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)?</literal>. 노트할 몇 가지 :
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
from-절에서, FROM 키워드는 옵션이다
</para>
</listitem>
<listitem>
<para>
from-절 내에 한 개의 명명된 엔티티가 오직 존재할 수 있다; 그것은 선택적으로 alias될 수 있다. 만일 엔티티
이름이 alias되면, 그때 임의의 프로퍼티 참조들은 그 alias를 사용하여 수식되어야 한다; 만일 엔티티 이름이
alias되지 않을 경우, 임의의 프로퍼티 참조들에 대해 수식되는 것은 규칙에 어긋난다.
</para>
</listitem>
<listitem>
<para>
<xref linkend="queryhql-joins-forms">join들</xref>은 (함축적이든 명시적이든)
대량 HQL 질의 속에 지정될 수 없다. 서브-질의들이 where-절에 사용될 수 있다;
서브질의들 그 자신들은 조인들을 포함할 수 있다.
</para>
</listitem>
<listitem>
<para>
where-절 또한 옵션이다.
</para>
</listitem>
</itemizedlist>
<para>
하나의 예제로서, 한 개의 HQL <literal>UPDATE</literal>를 실행하기 위해, <literal>Query.executeUpdate()</literal>
메소드(이 메소드는 JDBC의 <literal>PreparedStatement.executeUpdate()</literal>와 유사하게 명명된다)를 사용하라:
</para>
<programlisting><![CDATA[Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName";
// or String hqlUpdate = "update Customer set name = :newName where name = :oldName";
int updatedEntities = s.createQuery( hqlUpdate )
.setString( "newName", newName )
.setString( "oldName", oldName )
.executeUpdate();
tx.commit();
session.close();]]></programlisting>
<para>
한 개의 HQL <literal>DELETE</literal>를 실행하기 위해, 동일한 <literal>Query.executeUpdate()</literal> 메소드를
사용하라:
</para>
<programlisting><![CDATA[Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlDelete = "delete Customer c where c.name = :oldName";
// or String hqlDelete = "delete Customer where name = :oldName";
int deletedEntities = s.createQuery( hqlDelete )
.setString( "oldName", oldName )
.executeUpdate();
tx.commit();
session.close();]]></programlisting>
<para>
<literal>Query.executeUpdate()</literal> 메소드에 의해 반환되는 <literal>int</literal> 값은 그 오퍼레이션에 의해
영향받은 엔티티들의 개수를 나타낸다. 이것이 데이터베이스 내에서 영향받은 행들의 개수와 상관이 있는지 없는지 여부를 살펴보자. HQL
대량 오퍼레이션은 예를 들어 joined-subclass의 경우에 실행 중인 여러 개의 실제 SQL 문장들로 귀결될 수 있다. 반환되는 숫자는
그 문장에 의해 영향받은 실제 엔티티들의 개수를 나타낸다. joined-subclass 예제로 되돌아가면, 서브 클래스들 중 하나에 대한 삭제는
단지 그 서브클래스가 매핑되어 있는 테이블에 대한 삭제 뿐만 아니라 또한 "루트" 테이블과 상속 계층에서 더 내려온 잠정적으로
조인된-서브클래스 테이블들에 대한 삭제들로 귀결될 수 있다.
</para>
<para>
장래의 배포본들에서 전달될 대량 HQL 오퍼레이션들에 대한 몇 가지 제한들이 현재 존재함을 노트하라; 상세한 것은 JIRA 로드맵을 참조하라.
<literal>INSERT</literal> 문장들을 위한 유사-구문은 다음과 같다:
<literal>INSERT INTO EntityName properties_list select_statement</literal>. 노트할 몇 가지:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
오직 INSERT INTO ... SELECT ... 형식 만일 지원된다; INSERT INTO ... VALUES ... 형식은 지원되지 않는다.
</para>
<para>
properties_list는 SQL <literal>INSERT</literal> 내에서 <literal>column speficiation</literal>과 유사하다.
매핑된 상속에 참여하는 엔티티들의 경우, 그 주어진 클래스-레벨 상에 직접 정의된 프로퍼티들 만이 properties_list에 사용될 수 있다.
슈퍼클래스 프로퍼티들은 허용되지 않는다; 그리고 서브클래스 프로퍼티들은 의미가 없다. 달리 말해 <literal>INSERT</literal> 문장들은
본래적으로 다형적이지 않다.
</para>
</listitem>
<listitem>
<para>
select_statement는 반환 타입들이 insert에 의해 기대되는 타입들과 일치해야 한다는 단서 하에 임의의 유효한 HQL select 질의일 수
있다. 현재 이것은 체크를 데이터베이스로 이관시키는 것을 허용하기 보다는 질의 컴파일 동안에 체크된다. 하지만 이것은
<emphasis>equal</emphasis>과는 대조적으로 <emphasis>등가인(equivalent)</emphasis> Hibernate <literal>Type</literal>
사이에서 문제점들을 일으킬 수도 있음을 노트하라. 비록 데이터베이스가 구별짓지 않을 수 있거나 변환을 처리할 수 있을 지라도, 이것은
<literal>org.hibernate.type.DateType</literal>로서 정의된 프로퍼티와 <literal>org.hibernate.type.TimestampType</literal>으로
정의된 프로퍼티 사이에 불일치 쟁점들을 일으킨다.
</para>
</listitem>
<listitem>
<para>
id 프로퍼티의 경우, insert 문장은 당신에게 두 개의 옵션을 준다. 당신은 properties_list 내에 id 프로퍼티를 명시적으로
지정할 수 있거나(그것의 값이 대응하는 select 표현식으로부터 얻어진 경우) 또는 properties_list에서 그것을 생략할 수도 있다
(산출된 값이 사용되는 경우). 이 후자의 옵션은 데이터베이스 내에서 연산되는 id 연산자들을 사용할 때에만 이용 가능하다;
임의의 "메모리 내" 타입 연산자들과 함께 이 옵션을 사용하려고 시도하는 것은 파싱 동안에 예외상황을 일으킬 것이다. 이 논의의
목적 상, 데이터베이스 내 산출자(generator)들은 <literal>org.hibernate.id.SequenceGenerator</literal>
(그리고 그것의 서브클래스들) 그리고 임의의 <literal>org.hibernate.id.PostInsertIdentifierGenerator</literal>
구현자들이라고 간주됨을 노트하라. 여기서 가장 주목할 만한 예외상황은 그것이 그것의 값들을 얻기 위한 select 가능한 방법을
노출시키지 않기 때문ㅇ에 사용될 수 없는 <literal>org.hibernate.id.TableHiLoGenerator</literal>이다.
</para>
</listitem>
<listitem>
<para>
<literal>version</literal> 또는 <literal>timestamp</literal>로서 매핑된 프로퍼티들의 경우에,
insert 문장은 당신에게 두 개의 옵션들을 준다. 당신은 properties_list 내에 그 프로퍼티를 지정할 수 있거나
(그 것의 값이 대응하는 select 표현식으로부터 얻어진 경우) 또는 properties_list에서 그것을 생략할 수 있다
(<literal>org.hibernate.type.VersionType</literal>에 의해 정의된 <literal>seed value</literal>
값이 사용되는 경우).
</para>
</listitem>
</itemizedlist>
<para>
예제 HQL <literal>INSERT</literal> 문장 실행:
</para>
<programlisting><![CDATA[Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ...";
int createdEntities = s.createQuery( hqlInsert )
.executeUpdate();
tx.commit();
session.close();]]></programlisting>
</sect1>
</chapter>