mirror of
https://github.com/hibernate/hibernate-orm
synced 2025-03-03 16:29:13 +00:00
git-svn-id: https://svn.jboss.org/repos/hibernate/trunk/Hibernate3/doc@7183 1b8cb986-b30d-0410-93ca-fae66ebed9b2
817 lines
60 KiB
XML
817 lines
60 KiB
XML
<chapter id="transactions" revision="1">
|
|
<title>트랜잭션들과 동시성</title>
|
|
|
|
<para>
|
|
Hibernate와 동시성 제어에 대한 가장 중요한 점은 이해하기가 매우 쉽다는 점이다. Hibernate는 어떤 추가적인 잠금 행위 없이
|
|
JDBC 커넥션들과 JTA 리소스들을 직접 사용한다. 우리는 당신의 데이터베이스 관리 시스템의 JDBC, ANSI, 그리고 트랜잭션 격리 명세에
|
|
약간의 시간을 할애할 것을 매우 권장한다. Hibernate는 단지 자동적인 버전화를 추가하지만 메모리 내에서 객체들을 잠그지 않거나 당신의
|
|
데이터베이스 트랜잭션들의 격리 레벨을 변경시키지 않는다. 기본적으로, 당신이 당신의 데이터베이스 리소스들에 직접 JDBC(또는 JTA/CMT)를
|
|
사용하는 것처럼 Hibernate를 사용하라.
|
|
</para>
|
|
|
|
<para>
|
|
하지만, 자동적인 버전화에 덧붙여, Hibernate는 또한 <literal>SELECT FOR UPDATE</literal> 구문을 사용하여,
|
|
행들에 대한 pessimistic 잠금을 위한 (마이너) API를 제공한다. 이 API는 이 장의 뒷 부분에서 논의된다.
|
|
</para>
|
|
|
|
<para>
|
|
우리는 <literal>Configuration</literal>, <literal>SessionFactory</literal>, <literal>Session</literal>,
|
|
알갱이를 가진 Hibernate에서의 동시성 제어 뿐만 아니라 데이터베이스 트랜잭션과 긴 어플리케이션 트랜잭션에 대한 논의를 시작한다.
|
|
</para>
|
|
|
|
<sect1 id="transactions-basics">
|
|
<title>세션 영역과 트랜잭션 영역</title>
|
|
|
|
<para>
|
|
<literal>SessionFactory</literal>는 모든 어플리케이션 쓰레드들에 의해 공유되도록 고안된 생성에 비용이 드는,
|
|
쓰레드안전(threadsafe) 객체이다. 그것은 대개 어플리케이션 시작 시에 <literal>Configuration</literal> 인스턴스로부터
|
|
한번 생성된다.
|
|
</para>
|
|
|
|
<para>
|
|
<literal>Session</literal>은 단일 비지니스 프로세스, 하나의 작업 단위를 위해 한번만 사용되고 나서 폐기될 예정인,
|
|
비용이 들지 않는, 쓰레드 안전하지 않은 객체이다. <literal>Session</literal>은 그것이 필요하지 않으면 JDBC
|
|
<literal>Connection</literal>(또는 <literal>Datasource</literal>)를 얻지 않을 것이어서, 데이터 접근이
|
|
특별한 요청에 서비스할 필요가 있을 것이라고 당신이 확신하지 않을 경우에 당신은 <literal>Session</literal>을 안전하게
|
|
열고 닫을 수 있다. (이것은 당신이 요청 인터셉션을 사용하여 다음 패턴들 중 어떤 것을 구현하자마자 중요하게 된다.)
|
|
</para>
|
|
|
|
<para>
|
|
이 그림을 완성하기 위해 당신은 또한 데이터베이스 트랜재션들에 대해 생각해야 한다. 데이터베이스 트랜잭션은 데이터베이스에서
|
|
잠금 다툼을 줄이기 위해 가능한 짧아야 한다. 긴 데이터베이스 트랜잭션들은 당신의 어플리케이션이 고도의 동시성 로드로의 가용성을
|
|
높이는 것을 방해할 것이다.
|
|
</para>
|
|
|
|
<para>
|
|
하나의 작업 단위의 영역은 무엇인가? 하나의 Hibernate <literal>Session</literal>은 몇몇 데이터베이스 트랜잭션들에
|
|
걸칠 수 있는가 또는 이것은 영역들의 one-to-one 관계인가? 당신은 언제 <literal>Session</literal>을 열고 닫는가
|
|
그리고 당신은 데이터베이스 트랜잭션 경계들을 어떻게 한정하는가?
|
|
</para>
|
|
|
|
<sect2 id="transactions-basics-uow">
|
|
<title>작업 단위</title>
|
|
|
|
<para>
|
|
첫번째로, <emphasis>session-per-operation</emphasis> anti-패턴을 사용하지 말라. 즉, 단일 쓰레드 내에서
|
|
모든 간단한 데이터베이스 호출에 대해 <literal>Session</literal>을 열고 닫지 말라! 물론 같은 것이 데이터베이스
|
|
트랜잭션들에 대해서도 참이다. 어플리케이션 내의 데이터베이스 호출들은 계획된 순서를사용하여 행해지며, 그것들은 원자
|
|
작업 단위 속으로 그룹지워진다. (이것은 또한 모든 하나의 SQL 문장 뒤의 auto-commit(자동-커밋)이 어플리케이션 내에서
|
|
무용지물임을 의미하고, 이 모드가 SQL 콘솔 작업을 돕도록 고안되었음을 노트하라. Hibernate는
|
|
의미하고, 이 모드는 Hibernate는 즉시 자동-커밋 모드를 사용 불가능하게 하거나, 어플리케이션 서버가 그렇게 행하고,
|
|
즉시 자동-커밋시키는 것을 사용불가능하게 하거나 ,그렇게 행하는 것을 기대한다.)
|
|
</para>
|
|
|
|
<para>
|
|
다중 사용자 클라이언트/서버 어플리케이션에서 가장 공통된 패턴은 <emphasis>session-per-request</emphasis>이다.
|
|
이 모형에서, 클라이언트로부터의 요청은 (Hibernate 영속 계층이 실행되는) 서버로 전송되고, 새로운 Hibernate
|
|
<literal>Session</literal>이 열려지고, 모든 데이터베이스 오퍼레이션들이 이 작업 단위 내에서 실행된다. 일단 그 작업이
|
|
완료되었다면(그리고 클라이언트에 대한 응답이 준비되었다면), 그 세션은 flush 되고 닫혀진다. 당신은 또한 당신이
|
|
<literal>Session</literal>을 열고 닫을 때 그것을 시작하고 커밋시켜서 클라이언트 요청에 서비스하는데 한 개의 데이터베이스
|
|
트랜잭션을 사용하게 될 것이다. 둘 사이의 관계는 일대일 대응이고 이 모형은 많은 어플리케이션들에서 완전하게 적합하다.
|
|
</para>
|
|
|
|
<para>
|
|
도전점은 구현에 놓여있다: <literal>Session</literal>과 트랜잭션이 정확하게 시작되고 끝나야 할 뿐만 아니라, 그것들은
|
|
또한 데이터 접근 오퍼레이션들에 대해 접근 가능해야 한다. 작업의 단위에 대한 경계 구분은 하나의 요청이 서버에 도착하고
|
|
응답이 전송되기 전에 실행되는 인터셉터(예를 들면 <literal>ServletFilter</literal>)를 사용하여 이상적으로 구현된다.
|
|
우리는 하나의 <literal>ThreadLocal</literal> 변수를 사용하여 그 <literal>Session</literal>을 요청에 서비스하는
|
|
쓰레드에 바인드 시킬 것을 권장한다. 이것은 이 쓰레드 내에서 실행되는 모든 코드에서 (static 변수에 접근하는 것처럼) 쉽게
|
|
접근을 허용해준다. 당신이 선택하는 데이터베이스 트랜잭션 경계 구분 메커니즘에 따라, 당신은 또한 <literal>ThreadLocal</literal>
|
|
변수 내에 트랜잭션 컨텍스트를 유지할 수도 있다. 이것을 위한 구현 패턴들은<emphasis>ThreadLocal Session</emphasis>
|
|
및 <emphasis> 뷰 내의 Open Session</emphasis>으로 알려져 있다. 당신은 이것을 구현하기 위해 이 문서의 앞 쪽에 보였던
|
|
<literal>HibernateUtil</literal> helper 클래스를 쉽게 확장할 수 있다. 물론 당신은 당신의 환경에서 인터셉터를 구현하고 그것을
|
|
설정하는 방법을 찾아야 한다. 팁들과 예제들은 Hibernate 웹 사이트를 보라.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="transactions-basics-apptx">
|
|
<title>어플리케이션 트랜잭션들</title>
|
|
|
|
<para>
|
|
session-per-request 패턴은 당신이 작업 단위들을 설계하는데 사용할 수 있는 유일한 유용한 개념이 아니다. 많은 비지니스
|
|
프로세스들은 데이터베이스 접근들을 중재하는 사용자 사이의 전체 일련의 상호작용들을 필요로 한다. 웹과 엔터프라이즈
|
|
어플리케이션에서 사용자 상호작용에 걸치는 것은 데이터베이스 트랜잭션에 허용되지 않는다. 다음 예제를 검토하자:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
대화상자의 첫 번째 화면이 열리고, 사용자에게 보여진 데이터는 특정 <literal>Session</literal>과 데이터베이스
|
|
트랜잭션 속에 로드되었다. 사용자가 객체들을 변경시키는 것이 자유롭다.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
|
|
사용자는 5분 후에 "저장"을 클릭하고 그의 변경들이 영속화 되기를 기대한다; 그는 또한 그가 이 정보를 편집하는 유일한
|
|
개인이고 변경 충돌이 발생하지 않기를 기대한다.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
우리는 사용자의 관점에서, 이것을 작업 단위, 장기간 실행되는 <emphasis>어플리케이션 트랜잭션</emphasis>이라고 명명한다.
|
|
당신이 당신의 어플리케이션에서 이것을 어떻게 구현할 수 있는 많은 방법들이 존재한다.
|
|
</para>
|
|
|
|
<para>
|
|
첫 번째 naive 구현은 동시성 변경을 방지하고, 격리와 atomicity(원자 단위성)을 보장하기 위해 데이터베이스에 의해
|
|
소유된 잠금으로 사용자가 생각하는동안 <literal>Session</literal>과 데이터베이스 트랜잭션을 유지할 수도 있다.
|
|
이것은 물론 anti-패턴이다. 왜냐하면 잠금 다툼은 어플리케이션이 동시 사용자들의 가용 숫자를 높이는 것을 허용하지
|
|
않을 것이기 때문이다.
|
|
</para>
|
|
|
|
<para>
|
|
명료하게, 우리는 어플리케이션 트랜잭션을 구현하는데 몇몇 데이터베이스 트랜잭션들을 사용해야 한다. 이 경우에,
|
|
비지니스 프로세스들의 격리를 유지하는 것은 어플리케이션 티어의 부분적인 책임이 된다. 단일 어플리케이션 트랜잭션은
|
|
대개 여러 개의 데이터베이스 트랜잭션들에 걸친다. 그것은 이들 데이터베이스 트랜잭션들 중 오직 한 개(마지막 트랜잭션)가
|
|
업데이트된 데이터를 저장하고, 모든 다른 트랜잭션들이 단순히 데이터를 읽는 (예를 들면, 몇몇 요청/응답 주기에 걸치는
|
|
마법사 스타일의 대화 상자에서) 경우에만 원자단위가 될 것이다. 특히 당신이 Hibernate의 특징들을 사용할 경우에 , 이것은
|
|
들리는 것보다 구현하기가 더 쉽다:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>자동적인 버전화</emphasis> - Hibernate는 당신을 위해 자동적인 optimistic
|
|
동시성 제어를 행할 수 있고, 그것은 사용자가 생각하는 시간 동안 동시적인 변경이 발생했는지를 자동적으로 검출할 수 있다..
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>Detached 객체들</emphasis> - 만일 당신이 이미 논의된 <emphasis>session-per-request</emphasis>
|
|
패턴을 사용하고자 결정하는 경우, 모든 로드된 인스턴스들은 사용자가 생각하는 시간 동안 detached 상태에 있을 것이다.
|
|
Hibernate는 그 객체들을 재첨부시키고 변경들을 영속화 시키는 것을 허용해주며, 그 패턴은
|
|
<emphasis>session-per-request-with-detached-objects(detached-객체들을 가진 요청 당 세션)</emphasis>으로 명명된다.
|
|
자동적인 버전화는 동시성 변경들을 격리시키는데 사용된다.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<emphasis>Long Session</emphasis> - Hibernate <emphasis>Session</emphasis>은 데이터베이스
|
|
트랜잭션이 커밋된 후에 기본 JDBC 커넥션이 연결 해제될 수도 있고, 새로운 클라이언트 요청이 발생할 때 다시 연결될 수
|
|
있다. 이 패턴은 <emphasis>session-per-application-transaction(어플리케이션 트랜잭션 당 세션)</emphasis>으로
|
|
알려져 있고 재첨부를 불필요하게 만든다. 자동적인 버전화는 동시성 변경들을 격리시키는데 사용된다.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
<emphasis>session-per-request-with-detached-objects</emphasis>과
|
|
<emphasis>session-per-application-transaction</emphasis> 양자는 장점들과 단점들을 갖는데, 우리는 이 장의
|
|
뒷 부분에서 optimistic 동시성 제어 단락에서 그것들을 논의한다.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="transactions-basics-identity">
|
|
<title>객체 identity 고려하기</title>
|
|
|
|
<para>
|
|
어플리케이션은 두 개의 다른 <literal>Session</literal>들 내에 있는 동일한 영속 상태에 동시에 접근할 수도 있다.
|
|
하지만 영속 클래스의 인스턴스는 두 개의 <literal>Session</literal> 인스턴스들 사이에 결코 공유되지 않는다.
|
|
그러므로 identity에 대한 두 개의 다른 개념들이 존재한다:
|
|
</para>
|
|
|
|
<variablelist spacing="compact">
|
|
<varlistentry>
|
|
<term>데이터베이스 Identity</term>
|
|
<listitem>
|
|
<para>
|
|
<literal>foo.getId().equals( bar.getId() )</literal>
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry>
|
|
<term>JVM Identity</term>
|
|
<listitem>
|
|
<para>
|
|
<literal>foo==bar</literal>
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
</variablelist>
|
|
|
|
<para>
|
|
그때 (예를 들어 <literal>Session</literal> 영역에서) <emphasis>특정</emphasis> <literal>Session</literal>에
|
|
첨부된 객체들의 경우 두 개의 개념들은 동등한 것이고, 데이터베이스 identity에 대한 JVM identity가 Hibernate에 의해 보장된다.
|
|
하지만, 어플리케이션이 두 개의 다른 세션들에서 "동일한" (영속 identity) 비지니스 객체에 동시에 접근하는 동안, 두 개의 인스턴스들은
|
|
실제로 "다르다"(JVM identity). 충돌들은 flush/커밋 시에 (자동적인 버전화)를 사용하여, optimistic 접근법을 사용하여 해결된다.
|
|
</para>
|
|
|
|
<para>
|
|
이 접근법은 Hibernate와 데이터베이스가 동시성에 대해 걱정하지 않도록 해준다; 그것은 또한 최상의 scalability를 제공한다.
|
|
왜냐하면 단일 쓰레드-작업 단위 내에서 identity 보장은 단지 비용이 드는 잠금이나 다른 동기화 수단들을 필요로 하지 않기 때문이다.
|
|
어플리케이션은 그것이 <literal>Session</literal> 당 단일 쓰레드를 강제하는 한, 어떤 비지니스 객체에 대해 결코 동기화 시킬
|
|
필요가 없다. 하나의 <literal>Session</literal> 내에서 어플리케이션은 객체들을 비교하는데 <literal>==</literal>를
|
|
안전하게 사용할 수가 있다.
|
|
</para>
|
|
|
|
<para>
|
|
하지만, 하나의 <literal>Session</literal> 외부에서 <literal>==</literal>를 사용하는 어플리케이션은 예기치 않은
|
|
결과들을 보게 될 수도 있다. 이것은 어떤 예기치 않은 장소들에서, 예를 들어 당신이 두 개의 detached 인스턴스들을 동일한
|
|
<literal>Set</literal> 내에 집어넣을 경우에 발생할 수도 있다. 둘 다 동일한 데이터베이스 identity를 가질 수 있지만
|
|
(예를 들어 그것들은 동일한 행을 표현한다), JVM identity는 정의 상 detached 상태에 있는 인스턴스들을 보장하지 않는다.
|
|
개발자는 영속 클래스들내에 <literal>equals()</literal> 메소드와 <literal>hashCode()</literal> 메소드를 오버라이드 시켜야 하고
|
|
객체 equality에 대한 그 자신의 개념을 구현해야 한다. 하나의 경고가 존재한다: equality를 구현하는데 데이터베이스 identifier를 결코
|
|
사용하지 말고, 하나의 비지니스 키, 유일한, 대개 불변인 속성들의 조합을 사용하라. 데이터베이스 식별자는 만일 transient 객체가 영속화되는
|
|
경우에 변경될 것이다. 만일 transient 인스턴스가(대개 detached 인스턴스들과 함께) <literal>Set</literal> 내에 보관되는 경우에,
|
|
hashcode 변경은 <literal>Set</literal>의 계약을 파기시킨다. 비지니스 키들에 대한 속성들은 데이터베이스 프라이머리 키들 만큼
|
|
안정적이어서는 안되며, 당신은 오직 객체들이 동일한 <literal>Set</literal> 내에 있는 한에서 안정성을 보장해야만 한다.
|
|
이 쟁점에 관한 논의에 대한 더 많은 것을 Hibernate 웹 사이트를 보라. 또한 이것이 Hibernate 쟁점이 아니며, 단지 자바 객체
|
|
identity와 equality가 구현되어야 하는 방법임을 노트하라.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="transactions-basics-issues">
|
|
<title>공통된 쟁점들</title>
|
|
|
|
<para>
|
|
안티-패턴들 <emphasis>session-per-user-session</emphasis> 또는
|
|
<emphasis>session-per-application</emphasis>을 결코 사용하지 말라(물론 이 규칙에 대한 드문 예외상황들이 존재한다).
|
|
다음 쟁점들 중 몇몇이 또한 권장되는 패턴들로 나타날 수 있음을 노트하고, 당신이 설계 결정을 내리기 전에 내포된 의미들을
|
|
확실히 이해하라:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
<literal>Session</literal>은 쓰레드-안전하지 않다. HTTP 요청들, 세션 빈즈, 또는 Swing worker들처럼
|
|
동시에 작업하는 것으로 가정되는 것들은 하나의 <literal>Session</literal> 인스턴스가 공유될 경우에 경쟁 조건들을
|
|
발생시킬 것이다. 만일 당신이 당신의 <literal>HttpSession</literal> 내에 Hibernate
|
|
<literal>Session</literal>을 유지시키는 경우(나중에 논의됨), 당신은 당신의 Http 세션에 대한 접근을 동기화
|
|
시키는 것을 고려해야 한다. 그 밖의 경우, 충분히 빠르게 reload를 클릭하는 사용자는 두 개의 동시적으로 실행되는
|
|
쓰레드들 내에서 동일한 <literal>Session</literal>을 사용할 수도 있다.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Hibernate에 의해 던져진 예외상황은 당신이 당신의 데이터베이스 트랜잭션을 롤백 시키고 즉시 <literal>Session</literal>을
|
|
닫아야 함을 의미한다(나중에 상세히 논의됨). 만일 당신의 <literal>Session</literal>이 어플리케이션에 바인드 되어 있는 경우,
|
|
당신은 어플리케이션을 중지시켜야 한다. 데이터베이스 트랜잭션 롤백은 당신의 비지니스 객체들을 그것들이 트랜잭션의 시작 시에
|
|
머물렀던 상태로 되돌리지는 않는다. 이것은 데이터베이스 상태와 비지니스 객체들이 동기화를 벗어남을 의미한다. 대개 이것은 문제가 아니다.
|
|
왜냐하면 예외상황들은 회복가능한 것이 아니고 당신이 어떻게든 롤백 후에 시작해야 하기 때문이다.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>Session</literal>은 (Hibernate에 의해 dirty 상태로 관찰되었거나 체크된) 영속 상태에 있는
|
|
모든 객체를 캐시 시킨다. 이것은 당신이 오랜 시간 동안 <literal>Session</literal>을 열어둔 채로 유지하거나
|
|
단순하게 너무 많은 데이터를 로드시킬 경우에, 당신이 OutOfMemoryException을 얻기 전까지, 그것이 끝없이
|
|
성장한다는 점을 의미한다. 이것에 대한 하나의 해결책은 <literal>Session</literal> 캐시를 관리하기 위해
|
|
<literal>clear()</literal>와 <literal>evict()</literal>를 호출하는 것이지만, 당신이 대용량 데이터
|
|
오퍼레이션들을 필요로 하는 경우에 당신은 대개 내장 프로시저를 고려해야 할 것이다. 몇몇 해결책들이
|
|
<xref linkend="batch"/>에 보여져 있다. 사용자 세션 동안에 <literal>Session</literal>을 열려진 채로
|
|
유지하는 것은 또한 실효성이 떨어진 데이터에 대한 높은 확률을 의미한다.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="transactions-demarcation">
|
|
<title>데이터베이스 트랜잭션 경계 설정</title>
|
|
|
|
<para>
|
|
데이터베이스 (또는 시스템) 트랜잭션 경계들은 항상 필수적이다. 데이터베이스와의 통신은 데이터베이스 트랜잭션의 외부에서 발생할 수
|
|
없다(이것은 자동-커밋 모드로 사용되는 많은 개발자들에게는 혼동스러워 보인다). 항상 심지어 읽기 전용 오퍼레이션들에 대해서도 명료한
|
|
트랜잭션 경계들을 사용하라. 당신의 격리 레벨과 데이터베이스 가용성들에 따라, 이것은 필요하지 않을 수 있지만, 만일 당신이 항상
|
|
트랜잭션들을 명시적으로 경계 설정할 경우에는 하강하는 결점들이 존재하지 않는다.
|
|
</para>
|
|
|
|
<para>
|
|
Hibernate 어플리케이션은 관리되지 않는 환경(예를 들면. 스탠드얼론, 간단히 웹 어플리케이션들 또는 Swing 어플리케이션들)과
|
|
관리되는 J2EE 환경에서 실행될 수 있다. 관리되지 않는 환경에서, Hibernate는 대개 그것 자신의 데이터베이스 커넥션 풀에 대한
|
|
책임이 있다. 어플리케이션 개발자는 트랜잭션 경계들을 손수 설정해야 한다. 달리 말해, 개발자 스스로 데이터베이스 트랜잭션들을
|
|
시작하고, 커밋시키거나 롤백시켜야 한다. 관리되는 환경은 대개 예를 들어 EJB 세션 빈즈의 배치 디스크립터 속에 선언적으로 정의된
|
|
트랜잭션 어셈블리를 가진, 컨테이너에 의해-관리되는 트랜잭션들을 제공한다. 그때 프로그램 상의 트랜잭션 경계 설정은 더 이상 필요하지
|
|
않다. 심지어 <literal>Session</literal>을 flush 시키는 것이 자동적으로 행해진다.
|
|
</para>
|
|
|
|
<para>
|
|
하지만, 당신의 영속 계층이 이식성을 유지하게끔 자주 희망된다. Hibernate는 당신의 배치 환경의 고유한 트랜잭션 시스템 속으로
|
|
변환되는 <literal>Transaction</literal>이라 명명되는 wrapper API 를 제공한다. 이 API는 실제로 옵션이지만 우리는
|
|
당신이 CMT session bean 속에 있지 않는 한 그것의 사용을 강력하게 권장한다.
|
|
</para>
|
|
|
|
<para>
|
|
대개 <literal>Session</literal> 종료는 네 개의 구분되는 단계들을 수반한다:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
세션을 flush 시킨다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
트랜잭션을 커밋 시킨다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
세션을 닫는다
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
예외상황들을 처리한다
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
세션을 flush 시키는 것은 앞서 논의되었고, 우리는 이제 관리되는 환경과 관리되지 않는 환경 양자에서 트랜잭션 경계 설정과 예외상황을
|
|
더 자세히 살펴볼 것이다.
|
|
</para>
|
|
|
|
|
|
<sect2 id="transactions-demarcation-nonmanaged">
|
|
<title>관리되지 않는 환경</title>
|
|
|
|
<para>
|
|
만일 Hibernate 영속 계층이 관리되지 않는(non-managed) 환경에서 실행될 경우, 데이터베이스 커넥션들은 대개 Hibernate의
|
|
풀링 메커니즘에 의해 처리된다. session/transaction 처리 관용구는 다음과 같이 보여진다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[//Non-managed environment idiom
|
|
Session sess = factory.openSession();
|
|
Transaction tx = null;
|
|
try {
|
|
tx = sess.beginTransaction();
|
|
|
|
// do some work
|
|
...
|
|
|
|
tx.commit();
|
|
}
|
|
catch (RuntimeException e) {
|
|
if (tx != null) tx.rollback();
|
|
throw e; // or display error message
|
|
}
|
|
finally {
|
|
sess.close();
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
당신은 <literal>Session</literal>을 명시적으로 <literal>flush()</literal>시키지 말아야 한다 -
|
|
<literal>commit()</literal> 호출은 동기화를 자동적으로 트리거 시킨다.
|
|
</para>
|
|
|
|
<para>
|
|
<literal>close()</literal> 호출은 세션의 종료를 마크한다. <literal>close()</literal>의 주된 의미는
|
|
JDBC 커넥션이 그 세션에 의해 포기될 것이라는 점이다.
|
|
</para>
|
|
|
|
<para>
|
|
이 자바 코드는 이식성이 있고 관리되지 않는 환경과 JTA 환경 양자에서 실행된다.
|
|
</para>
|
|
|
|
<para>
|
|
당신은 통상의 어플리케이션에서 비지니스 코드 속에 이 관용구를 결코 보지 않을 것이다; 치명적인(시스템) 예외상황들은 항상
|
|
"상단"에서 잡혀야 한다. 달리 말해, Hibernate를 실행하는 코드가 (영속 계층에서) 호출되고 <literal>RuntimeException</literal>을
|
|
처리하는 (그리고 대개 오직 제거하고 빠져나갈 수 있는) 코드는 다른 계층들 속에 있다. 이것은 당신 자신이 설계하는 도전점일
|
|
수 있고 당신은 J2EE/EJB 컨테이너 서비스들이 이용 가능할 때마다 J2EE/EJB 컨테이너 서비스들을 사용할 것이다. 예외상황
|
|
처리는 이 장의 뒷부분에서 논의된다.
|
|
</para>
|
|
|
|
<para>
|
|
당신은 (디폴트인) <literal>org.hibernate.transaction.JDBCTransactionFactory</literal>를 선택해야 함을 노트하라.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="transactions-demarcation-jta">
|
|
<title>JTA 사용하기</title>
|
|
|
|
<para>
|
|
만일 당신의 영속 계층이 어플리케이션 서버에서(예를 들어, EJB 세션 빈즈 이면에서) 실행될 경우, Hibernate에 의해
|
|
획득된 모든 데이터소스 커넥션은 자동적으로 전역 JTA 트랜잭션의 부분일 것이다. Hibernate는 이 통합을 위한 두 개의
|
|
방도들을 제공한다.
|
|
</para>
|
|
|
|
<para>
|
|
만일 당신이 bean-managed transactions(BMT)를 사용할 경우 Hibernate는 당신이 <literal>Transaction</literal>
|
|
API를 사용할 경우에 BMT 트랜잭션을 시작하고 종료하도록 어플리케이션 서버에게 알려줄 것이다. 따라서 트랜잭션 관리 코드는
|
|
non-managed 환경과 동일하다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[// BMT idiom
|
|
Session sess = factory.openSession();
|
|
Transaction tx = null;
|
|
try {
|
|
tx = sess.beginTransaction();
|
|
|
|
// do some work
|
|
...
|
|
|
|
tx.commit();
|
|
}
|
|
catch (RuntimeException e) {
|
|
if (tx != null) tx.rollback();
|
|
throw e; // or display error message
|
|
}
|
|
finally {
|
|
sess.close();
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
CMT의 경우, 트랜잭션 관할[경계 설정]은 프로그램 상이 아닌, session bean 배치 디스크립터들 속에서 행해진다. 당신이 스스로
|
|
<literal>Session</literal>을 수작업으로 flush 시키고 닫고자 원하지 않을 경우, 단지
|
|
<literal>hibernate.transaction.flush_before_completion</literal>을 <literal>true</literal>로 설정하고,
|
|
<literal>hibernate.connection.release_mode</literal>를 <literal>after_statement</literal> 또는
|
|
<literal>auto</literal>로 설정하고 <literal>hibernate.transaction.auto_close_session</literal>을
|
|
<literal>true</literal>로 설정하라. Hibernate는 그때 자동적으로 flush 되고 당신을 위해 <literal>Session</literal>을
|
|
닫을 것이다. 남아 있는 유일한 작업은 예외상황이 발생할 때 트랜잭션을 롤백시키는 것이다. 다행하게도 CMT bean에서, 이것이 자동적으로
|
|
일어난다. 왜냐하면 session bean 메소드에 의해 던져진 처리되지 않은 <literal>RuntimeException</literal>은 전역 트랜잭션을
|
|
롤백시키도록 컨테이너에게 통보하기 때문이다. <emphasis>이것은 당신이 CMT에서 Hibernate <literal>Transaction</literal> API를
|
|
사용할 필요가 전혀 없음을 의미한다.</emphasis>
|
|
</para>
|
|
|
|
<para>
|
|
당신이 Hibernate의 트랜잭션 팩토리를 구성할 때, 당신이 BMT session bean에서
|
|
<literal>org.hibernate.transaction.JTATransactionFactory</literal>를 선택해야하고, CMT session bean에서
|
|
<literal>org.hibernate.transaction.CMTTransactionFactory</literal>를 선택해야 함을 노트하라. 또한
|
|
<literal>org.hibernate.transaction.manager_lookup_class</literal>를 설정하는 것을 염두에 두라.
|
|
</para>
|
|
|
|
<para>
|
|
만일 당신이 CMT 환경에서 작업하고 있고, 세션을 자동적으로 flushing하고 닫는 것을 사용할 경우, 당신은 또한 당신의 코드의 다른
|
|
부분들에서 동일한 세션을 사용하고자 원할 수도 있다. 일반적으로 관리되지 않는 환경에서 당신은 세션을 소유하는데 하나의
|
|
<literal>ThreadLocal</literal> 변수를 사용할 것이지만, 한 개의 EJB 요청은 다른 쓰레드들(예를 들면 또 다른 세션 빈을
|
|
호출하는 세션 빈) 내에서 실행될 수도 있다. 만일 당신이 당신의 <literal>Session</literal> 인스턴스를 전달하는 것을
|
|
고민하고 싶지 않다면, <literal>SessionFactory</literal>이 JTA 트랜잭션 컨텍스트에 바인드되어 있는 한 개의 세션을
|
|
반환하는, <literal>getCurrentSession()</literal> 메소드를 제공한다. 이것은 Hibernate를 어플리케이션 속으로
|
|
통합시키는 가장 손쉬운 방법이다! "현재" 세션은 (위의 프로퍼티 설정들에 관계없이) auto-flush와 auto-close, 그리고
|
|
auto-connection-release를 항상 이용 가능하게 한다. 우리의 session/transaction 관리 idiom은 다음과 같이 감소된다:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[// CMT idiom
|
|
Session sess = factory.getCurrentSession();
|
|
|
|
// do some work
|
|
...
|
|
|
|
]]></programlisting>
|
|
|
|
<para>
|
|
달리 말해, 당신이 관리 환경에서 행해야 하는 모든 것은 <literal>SessionFactory.getCurrentSession()</literal>을
|
|
호출하고, 당신의 데이터 접근 작업을 행하고, 그리고 나머지를 컨테이너에게 남겨두는 것이다. 트랜잭션 경계들은 당신의
|
|
session bean의 배치 디스크립터들 속에 선언적으로 설정된다. 그 세션의 생명주기는 Hibernate에 의해 완전하게 관리된다.
|
|
</para>
|
|
|
|
<para>
|
|
<literal>after_statement</literal> 커넥션의 사용에는 한 가지 단서가 존재한다. JTA 명세서의 분별없는 제약성으로 인해,
|
|
<literal>scroll()</literal> 또는 <literal>iterate()</literal>에 의해 반환된 어떤 닫혀지지 않은
|
|
<literal>ScrollableResults</literal> 또는 <literal>Iterator</literal> 인스턴스들을 Hibernate가 자동적으로
|
|
제거하는 것이 불가능하다. 당신은 <literal>finally</literal> 블록에서 <literal>ScrollableResults.close()</literal>
|
|
또는 <literal>Hibernate.close(Iterator)</literal>를 명시적으로 호출하여 기본 데이터베이스 커서를
|
|
해제<emphasis>시켜야 한다</emphasis>. (물론 대부분의 어플리케이션들은 CMT 코드에서 <literal>scroll()</literal>
|
|
또는 <literal>iterate()</literal> 사용을 쉽게 피할 수 있다.)
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="transactions-demarcation-exceptions">
|
|
<title>예외상황 처리</title>
|
|
|
|
<para>
|
|
만일<literal>Session</literal>이 (어떤 <literal>SQLException</literal>을 포함하는) 예외상황을 던질 경우,
|
|
당신은 데이터베이스 트랜잭션을 즉시 롤백시키고, <literal>Session.close()</literal>를 호출하고
|
|
<literal>Session</literal> 인스턴스를 폐기시켜야한다. <literal>Session</literal>의 어떤 메소드들은
|
|
그 세션을 일관된 상태로 남겨두지 <emphasis>않을</emphasis> 것이다. Hibernate에 의해 던져진 예외상황은 복구가능한
|
|
것으로 취급될 수 없다. 그 <literal>Session</literal>이 <literal>finally</literal> 블록 내에서
|
|
<literal>close()</literal>를 호출하여 닫혀지도록 확실히 하라.
|
|
</para>
|
|
|
|
<para>
|
|
Hibernate 영속 계층에서 발생할 수 있는 대부분의 오류들을 포장하는, <literal>HibernateException</literal>은
|
|
체크되지 않은 예외상황이다(그것은 Hibernate의 이전 버전에는 없었다). 우리의 의견으로, 우리는 낮은 계층에서 복구불가능한
|
|
예외상황을 붙잡도록 어플리케이션 개발자에게 강제하지 않을 것이다. 대부분의 시스템들에서, 체크되지 않은 치명적인 예외상황들은
|
|
(예를 들어, 더 높은 계층에서) 메소드 호출 스택의 첫 번째 프레임들 중 하나 속에서 처리되고, 한 개의 오류 메시지가 어플리케이션
|
|
사용자에게 표시된다(또는 어떤 다른 적절한 액션이 취해진다). Hibernate는 또한 <literal>HibernateException</literal>이
|
|
아닌, 다른 체크되지 않은 예외상황들을 던질 수도 있음을 노트하라. 다시 이것들은 복구가능하지 않고 적절한 액션이 취해져야 한다.
|
|
</para>
|
|
|
|
<para>
|
|
Hibernate는 데이터베이스와 상호작용하는 동안에 던져진 <literal>SQLException</literal>들을 하나의
|
|
<literal>JDBCException</literal> 속에 포장한다. 사실, Hibernate는 그 예외상황을
|
|
<literal>JDBCException</literal>의 보다 의미있는 서브클래스로 변환하려고 시도할 것이다. 기본
|
|
<literal>SQLException</literal>은 <literal>JDBCException.getCause()</literal>를 통해 항상 이용 가능하다.
|
|
Hibernate는<literal>SessionFactory</literal>에 첨부된 <literal>SQLExceptionConverter</literal>를
|
|
사용하여 <literal>SQLException</literal>을 적당한 하나의 <literal>JDBCException</literal> 서브클래스로
|
|
변환시킨다. 디폴트로 <literal>SQLExceptionConverter</literal>는 구성된 dialect에 의해 정의된다; 하지만
|
|
맞춤 구현 속에 플러그인 시키는 것이 또한 가능하다(상세한 것은 <literal>SQLExceptionConverterFactory</literal>
|
|
클래스에 관한 javadocs를 보라). 표준 <literal>JDBCException</literal> 서브타입은 다음과 같다:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
<literal>JDBCConnectionException</literal> - 기본 JDBC 통신에 대한 오류를 나타낸다.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>SQLGrammarException</literal> - 생겨난 SQL에 대한 문법 또는 구문 문제점을 나타낸다.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>ConstraintViolationException</literal> - 무결성 제약 위반에 관한 어떤 형식을 나타낸다.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>LockAcquisitionException</literal> - 요청된 오퍼레이션을 실행하는데 필수적인 잠금 레벨을
|
|
획득하는 오류를 나타낸다.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>GenericJDBCException</literal> - 다른 카테고리들 중 어떤 것으로 분류되지 않았던 일반적인 예외상황.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="transactions-optimistic">
|
|
<title>Optimistic 동시성 제어</title>
|
|
|
|
<para>
|
|
고도의 동시성과 고도의 가용성을 일치시키는 유일한 접근법은 버전화를 가진 optimistic동시성 제어이다. 버전 체킹은 업데이트 충돌을
|
|
검출하기 위해(그리고 업데이트 손실을 방지하기 위해) 버전 번호들 또는 timestamp들을 사용한다. Hibernate는 optimistic 동시성을
|
|
사용하는 어플리케이션 코드 작성에 세 가지 가능한 접근법들을 제공한다. 우리가 보여주는 쓰임새들은 긴 어플리케이션 트랜잭션들의 상황
|
|
속에 있지만 버전 체킹 또한 단일 데이터베이스 트랜잭션들에서 업데이트 손실을 방지하는 이점을 갖고 있다.
|
|
</para>
|
|
|
|
<sect2 id="transactions-optimistic-manual">
|
|
<title>어플리케이션 버전 체킹</title>
|
|
|
|
<para>
|
|
하나의 구현에서 Hibernate로부터 많은 도움이 없이, 데이터베이스에 대한 각각의 상호작용은 새로운 <literal>Session</literal>
|
|
내에서 일어나고, 개발자는 영속 인스턴스들을 처리하기 전에 데이터베이스로부터 모든 영속 인스턴스들을 다시 로드시킬 책임이 있다.
|
|
이 접근법은 어플리케이션 트랜잭션을 확실히 격리시키기 위해 그것 자신의 버전 체킹을 수행하도록 어플리케이션에게 강제시킨다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[// foo is an instance loaded by a previous Session
|
|
session = factory.openSession();
|
|
Transaction t = session.beginTransaction();
|
|
int oldVersion = foo.getVersion();
|
|
session.load( foo, foo.getKey() ); // load the current state
|
|
if ( oldVersion!=foo.getVersion ) throw new StaleObjectStateException();
|
|
foo.setProperty("bar");
|
|
t.commit();
|
|
session.close();]]></programlisting>
|
|
|
|
<para>
|
|
version 프로퍼티는 <literal><version></literal>을 사용하여 매핑되고, Hibernate는 만일 엔티티가 dirty일 경우
|
|
flush 동안에 그것을 자동적으로 증가시킬 것이다.
|
|
</para>
|
|
|
|
<para>
|
|
물론, 당신이 낮은 데이터 동시성 환경에서 작업하고 있고 버전 체킹을 필요로 하지 않을 경우에, 당신은 이 접근법을 사용할 수 도 있고
|
|
단지 버전 체크를 생략할 수도 있다. 그 경우에, <emphasis>마지막의 커밋 성공</emphasis>은 당신의 긴 어플리케이션 트랜잭션들에
|
|
대한 디폴트 방도가 될 것이다. 이것이 어플리케이션의 사용자들을 혼동시킬 수 있음을 염두에 두라. 왜냐하면 사용자들은 오류 메시지들
|
|
또는 충돌 변경들을 병합시킬 기회 없이 업데이트들 손실을 겪을 수도 있기 때문이다.
|
|
</para>
|
|
|
|
<para>
|
|
명료하게 수작업 버전 체킹은 매우 사소한 환경들에서도 공포적이고 대부분의 어플리케이션들에 대해 실제적이지 않다. 흔히 단일
|
|
인스턴스 뿐만 아니라 변경된 객체들의 전체 그래프들이 체크되어야 한다. Hibernate는 설계 패러다임으로서 긴 <literal>Session</literal>
|
|
또는 detached 인스턴스들에 대해 자동적인 버전 체킹을 제공한다.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="transactions-optimistic-longsession">
|
|
<title>긴 세션과 자동적인 버전화</title>
|
|
|
|
<para>
|
|
하나의 <literal>Session</literal> 인스턴스와 그것의 영속 인스턴스들은 전체 어플리케이션 트랜잭션에 사용된다. Hibernate는
|
|
flush 할 때 인스턴스 버전들을 체크하고 만일 동시성 변경이 검출될 경우에 예외상황을 던진다. 이 예외상황을 잡아내고 처리하는 것을
|
|
개발자의 몫이다(공통된 옵션들은 변경들을 병합시키거나 또는 쓸모가 없지 않은 데이터로 비지니스 프로세스를 다시 시작하는 기회를
|
|
사용자에게 주는 것이다).
|
|
</para>
|
|
|
|
<para>
|
|
<literal>Session</literal>은 사용자 상호작용을 기다릴 때 어떤 기본 JDBC 커넥션으로부터 연결해제된다. 이 접근법은
|
|
데이터베이스 접근의 관점에서 보면 가장 효율적이다. 어플리케이션은 버전 체킹 또는 detached 인스턴스들을 재첨부하는 것에
|
|
그 자체 관계할 필요가 없거나 그것은 모든 데이터베이스 트랜잭션에서 인스턴스들을 다시 로드시킬 필요가 없다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[// foo is an instance loaded earlier by the Session
|
|
session.reconnect(); // Obtain a new JDBC connection
|
|
Transaction t = session.beginTransaction();
|
|
foo.setProperty("bar");
|
|
t.commit(); // End database transaction, flushing the change and checking the version
|
|
session.disconnect(); // Return JDBC connection ]]></programlisting>
|
|
|
|
<para>
|
|
<literal>foo</literal> 객체는 그것이 로드되었던 <literal>Session</literal>이 어느 것인지를 여전히 알고 있다.
|
|
<literal>Session.reconnect()</literal>은 새로운 커넥션을 획득하고(또는 당신이 커넥션을 제공할 수 있다) 그리고
|
|
그 세션을 다시 시작시킨다. <literal>Session.disconnect()</literal> 메소드는 JDBC 커넥션으로부터 세션을
|
|
연결 해제하고 (당신이 커넥션을 제공하지 않는 한) 그 커넥션을 풀(pool)로 반환할 것이다. 재연결 후에, 당신이 업데이트하고
|
|
있는 데이터에 대한 버전 체킹을 강제시키기 위해, 당신은 또 다른 트랜잭션에 의해 업데이트 되었던 어떤 객체들에 대해
|
|
<literal>LockMode.READ</literal>로서 <literal>Session.lock()</literal>을 호출할 수도 있다. 당신은 당신이
|
|
업데이트 <emphasis>중인</emphasis> 어떤 데이터에 대한 잠금을 필요로 하지 않는다.
|
|
</para>
|
|
|
|
<para>
|
|
|
|
만일 <literal>disconnect()</literal>와 <literal>reconnect()</literal>에 대한 명시적인 호출들이 너무 번거러울 경우,
|
|
당신은 대신에 <literal>hibernate.connection.release_mode</literal>를 사용할 수도 있다.
|
|
</para>
|
|
|
|
<para>
|
|
만일 사용자가 생각하는시간 동안 <literal>Session</literal>이 저장되기에는 너무 큰 경우 이 패턴은 문제성이 있다. 예를 들어
|
|
<literal>HttpSession</literal>은 가능한 작은 것으로 유지되어야 한다. 또한 <literal>Session</literal>은 (필수의)
|
|
첫 번째 레벨 캐시이고 모든 로드된 객체들을 포함하기 때문에, 우리는 아마 적은 요청/응답 주기들에 대해서만 이 방도를 사용할 수 있다.
|
|
<literal>Session</literal>이 곧 실효성이 없는 데이터를 갖게 될 것이므로 이것이 진정으로 권장된다.
|
|
</para>
|
|
|
|
<para>
|
|
또한 당신이 연결해제된 <literal>Session</literal>을 영속 계층에 가깝게 유지해야함을 노트하라. 달리말해,
|
|
<literal>Session</literal>을 보관하는데 EJB stateful session bean을 사용하고 <literal>HttpSession</literal>
|
|
내에 그것을 저장하기 위해서 그것을 웹 계층에 전송하지 말라(또는 그것을 별도의 티어에 직렬화 시키지도 말라).
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="transactions-optimistic-detached">
|
|
<title>Detached 객체들과 자동적인 버전화</title>
|
|
|
|
<para>
|
|
영속 저장소에 대한 각각의 상호작용은 새로운 <literal>Session</literal>에서 일어난다. 하지만 동일한 영속 인스턴스들은
|
|
데이터베이스와의 각각의 상호작용에 재사용된다. 어플리케이션은 원래 로드되었던 detached 인스턴스들의 상태를 또 다른
|
|
<literal>Session</literal> 내에서 처리하고 나서 <literal>Session.update()</literal>,
|
|
<literal>Session.saveOrUpdate()</literal>, <literal>Session.merge()</literal>를 사용하여 그것들을
|
|
다시 첨부시킨다.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[// foo is an instance loaded by a previous Session
|
|
foo.setProperty("bar");
|
|
session = factory.openSession();
|
|
Transaction t = session.beginTransaction();
|
|
session.saveOrUpdate(foo); // Use merge() if "foo" might have been loaded already
|
|
t.commit();
|
|
session.close();]]></programlisting>
|
|
|
|
<para>
|
|
다시, Hibernate는 flush 동안에 인스턴스 버전들을 체크할 것이고 업데이트 충돌이 발생할 경우에 예외상황을 던질 것이다.
|
|
</para>
|
|
|
|
<para>
|
|
당신은 또한 <literal>update()</literal>대신에 <literal>lock()</literal>을 호출할 수도 있고 만일 그 객체가
|
|
변경되지 않았음을 당신이 확신하는 경우에 (버전 체킹을 수행하고 모든 캐시들을 무시하는) <literal>LockMode.READ</literal>를
|
|
사용할 수 있다.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="transactions-optimistic-customizing">
|
|
<title>자동적인 버전화를 맞춤화 시키기</title>
|
|
|
|
<para>
|
|
당신은 <literal>optimistic-lock</literal> 매핑 속성을 <literal>false</literal>로 설정함으로써 특정 프로퍼티들과
|
|
콜렉션들에 대한 Hibernate의 자동적인 버전 증가를 불가능하도록 할 수도 있다. 그때 Hibernate는 그 프로퍼티가 dirty 일 경우에
|
|
더 이상 버전을 증가시키지 않을 것이다.
|
|
</para>
|
|
|
|
<para>
|
|
리거시 데이터베이스 스키마들은 자주 static이고 변경될 수 없다. 또는 다른 어플리케이션들은 또한 동일한 데이터베이스에 접근하고
|
|
버전 번호들 또는 심지어 timestamp들을 처리하는 방법을 모를 수도 있다. 두 경우들에서, 버전화는 테이블 내의 특정 컬럼에 의지할 수
|
|
없다. version 또는 timestamp 프로퍼티 매핑 없이 행 내의 모든 필드들에 대한 상태를 비교하여 버전 체크를 강제시키기 위해서,
|
|
<literal><class></literal> 매핑 속에 <literal>optimistic-lock="all"</literal>을 표시하라. 만일
|
|
Hibernate가 이전 상태와 새로운 상태를 비교할 수 있을 경우에, 예를 들면 당신이 하나의 긴 <literal>Session</literal>을
|
|
사용하고 session-per-request-with-detached-objects을 사용하지 않을 경우 이것은 개념적으로만 동작함을 노트하라.
|
|
</para>
|
|
|
|
<para>
|
|
때때로 행해졌던 변경들이 중첩되지 않는 한 동시적인 변경이 허용될 수 있다. 만일 <literal><class></literal>를
|
|
매핑할 때 당신이 <literal>optimistic-lock="dirty"</literal>를 설정하면, Hibernate는 flush 동안에 dirty 필드들을
|
|
비교만 할 것이다.
|
|
</para>
|
|
|
|
<para>
|
|
두 경우들에서, 전용 version/timestamp 컬럼의 경우 또는 full/dirty 필드 비교의 경우, Hibernate는 법전 체크를 실행하고
|
|
정보를 업데이트하는데 엔티티 당 (적절한 <literal>WHERE</literal> 절을 가진) 한 개의<literal>UPDATE</literal>
|
|
문장을 사용한다. 만일 당신이 연관된 엔티티들에 대한 재첨부를 케스케이드 하는데 transitive 영속을 사용할 경우,
|
|
Hibernate는 불필요하게 업데이트들을 실행할 수도 있다. 이것은 대개 문제가 아니지만, 심지어 변경들이 detached 인스턴스들에
|
|
대해 행해지지 않았을 때에도 데이터베이스 내에서 <emphasis>on update</emphasis> 트리거들이 실행될 수도 있다. 그 행을
|
|
업데이트하기 전에 변경들이 실제로 일어났음을 확인하기 위해 인스턴스를 <literal>SELECT</literal>하는 것을 Hibernate에게
|
|
강제시키는, <literal><class></literal> 매핑 속에 <literal>select-before-update="true"</literal>를
|
|
설정함으로써 당신은 이 특징을 맞춤화 시킬 수 있다.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="transactions-locking">
|
|
<title>Pessimistic 잠금</title>
|
|
|
|
<para>
|
|
사용자들은 잠금 방도에 대해 걱정하는데 많은 시간을 할애하하려고 생각하지 않는다. 대개 JDBC 커넥션들에 대한 격리 레벨을 지정하는
|
|
것으로 충분하고 그런 다음 단순히 데이터베이스로 하여금 모든 작업을 행하도록 한다. 하지만 진일보한 사용자들은 때때로 배타적인
|
|
pessimistic 잠금들을 얻거나 또는 새로운 트랜잭션의 시작 시에 잠금들을 다시 얻고자 원할 수도 있다.
|
|
</para>
|
|
|
|
<para>
|
|
Hibernate는 결코 메모리 내에 있는 객체들이 아닌, 데이터베이스의 잠금 메커니즘을 항상 사용할 것이다!
|
|
</para>
|
|
|
|
<para>
|
|
<literal>LockMode</literal> 클래스는 Hibernate에 의해 획득될 수 있는 다른 잠금 레벨들을 정의한다. 잠금은 다음 메커니즘들에
|
|
의해 얻어진다:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
<literal>LockMode.WRITE</literal>는 Hibernate가 한 행을 업데이트 하거나 insert 할 때 자동적으로 획득된다.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>LockMode.UPGRADE</literal>는 <literal>SELECT ... FOR UPDATE</literal> 구문을 지원하는
|
|
데이터베이스 상에서 <literal>SELECT ... FOR UPDATE</literal>를 사용하여 명시적인 사용자 요청 상에서
|
|
얻어질 수 있다.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>LockMode.UPGRADE_NOWAIT</literal>는 오라클에서 <literal>SELECT ... FOR UPDATE NOWAIT</literal>를
|
|
사용하여 명시적인 사용자 요청 상에서 얻어질 수도 있다.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>LockMode.READ</literal>는 Hibernate가 반복 가능한 읽기(Repeatable Read) 또는 Serialization
|
|
격리 레벨에서 데이터를 읽어들일 때 자동적으로 얻어질 수도 있다. 명시적인 사용자 요청에 의해 다시 얻어질 수도 있다.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>LockMode.NONE</literal>은 잠금이 없음을 나타낸다. 모든 객체들은 <literal>Transaction</literal>의 끝에서
|
|
이 잠금 모드로 전환된다. <literal>update()</literal> 또는 <literal>saveOrUpdate()</literal>에 대한 호출을 통해
|
|
세션과 연관된 객체들이 또한 이 잠금 모드로 시작된다.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
"명시적인 사용자 요청"은 다음 방법들 중 하나로 표현된다:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
<literal>LockMode</literal>를 지정한 <literal>Session.load()</literal>에 대한 호출.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>Session.lock()</literal>에 대한 호출.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
<literal>Query.setLockMode()</literal>에 대한 호출.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
만일 <literal>Session.load()</literal>가 <literal>UPGRADE</literal> 또는 <literal>UPGRADE_NOWAIT</literal>
|
|
모드로 호출되고 ,요청된 객체가 아직 이 세션에 의해 로드되지 않았다면, 그 객체는 <literal>SELECT ... FOR UPDATE</literal>를
|
|
사용하여 로드된다. 만일 요청된 것이 아닌 다소 제한적인 잠금으로 이미 로드되어 있는 객체에 대해 <literal>load()</literal>가
|
|
호출될 경우, Hibernate는 그 객체에 대해 <literal>lock()</literal>을 호출한다.
|
|
</para>
|
|
|
|
<para>
|
|
만일 지정된 잠금 모드가 <literal>READ</literal>, <literal>UPGRADE</literal> 또는
|
|
<literal>UPGRADE_NOWAIT</literal> 일 경우에 <literal>Session.lock()</literal>은 버전 번호 체크를 수행한다.
|
|
(<literal>UPGRADE</literal> 또는 <literal>UPGRADE_NOWAIT</literal> 인 경우에,
|
|
<literal>SELECT ... FOR UPDATE</literal>가 사용된다.)
|
|
</para>
|
|
|
|
<para>
|
|
만일 데이터베이스가 요청된 잠금 모드를 지원하지 않을 경우, (예외상황을 던지는 대신에) Hibernate는 적절한 대체 모드를 사용할 것이다.
|
|
이것은 어플리케이션이 이식 가능할 것임을 확실히 해준다.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|
|
|