Chapter 11. CMP 엔진

이번 장에서는 JBoss의 CMP2 엔진의 오퍼레이션에 대한 세부적인 사항들을 다루도록 하겠습니다. 여기서는 EJB 2.0의 관리되는 영속적인 컨테이너(CMP2.0) 모델의 소개는 논의하지 않습니다. CMP2.0을 시작하려면, J2EE 자습서( http://java.sun.com/j2ee/tutorial/index.html) 혹은 엔터프라이즈 자바 빈 - 3차 개정판 (http://www.oreilly.com/catalog/entjbeans3/workbooks/index.html) 에 따라오는 JBoss 워크북을 참조하십시오.

11.1. 시작하기

JBossCMP는 EJB 2.0 어플리케이션을 위한 기본 영속 관리자입니다. JBossCMP는 JBoss의 핵심 기능이기 때문에, CMP 2.0을 사용하기 위해서 기본 JBoss 설치이외의 것은 필요가 없지만, 새로운 EJB 2.0 어플리케이션을 생성하거나 EJB 1.1 어플리케이션을 업그레이드할 때는 주의해야할 몇몇 세부적인 사항이 있습니다.

JBoss에서 EJB JAR 파일을 배치할때는 EJB jar의 버전을 결정하기 위해 ejb-jar.xml 배치 서술자의 DOCTYPE을 사용합니다. 다음은 올바른 EJB 2.0에 대한 DOCTYPE 입니다.

<!DOCTYPE ejb-jar PUBLIC
  "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
            "http://java.sun.com/dtd/ejb-jar_2_0.dtd">

DOCTYPE의 공개된 식별자(public identifier)는 "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN" 입니다. JBossCMP는 standardjboss.xml 파일내에서 Standard CMP 2.x EntityBean 설정을 사용하게 됩니다. 만일 여러분이 커스텀 엔티티 빈 설정을 사용하는 어플리케이션을 갖고 있고, 이것을 EJB 2.0으로 업그레이드 한다면, 반드시 persistence-manager를 변경하고 새로운 인터셉터를 추가해야만 합니다 (보다 자세한 사항은 standardjboss.xml 파일내의 Standard CMP 2.x EntityBean 설정을 참조하십시오). 여러분의 EJB 2.0 어플리케이션을 잘 배치하고 실행시키기 위한 더 이상의 환경설정은 없습니다.

11.1.1. 예제 코드

본 문서내에 표시된 모든 예제에 대한 완전한 소스는 examples/src/main/org/jboss/cmp2 디렉터리에서 찾을 수 있습니다. 코드는 형법기관(criminal organization) 모델의 형법 포탈입니다. 예제 코드에서 사용된 형법 포탈 데이터 모델의 일부분에 대한 다이어그램이 그림 11.1, “메인 CMP2 예제 클래스”에 보여지고 있습니다.

메인 CMP2 예제 클래스

그림 11.1. 메인 CMP2 예제 클래스

예제 코드를 빌드시키려면, 다음과 같이 ant를 실행시키십시오:

[nr@toki examples]$ ant -Dchap=cmp2 config
...
config:
     [copy] Copying 1 file to /tmp/jboss-3.2.6/server/default/deploy
     [echo] Waiting for 5 seconds for deploy...
    [junit] .
    [junit] Time: 3.474

    [junit] OK (1 test)

이 명령을 통해 JBoss 서버쪽에 어플리케이션을 빌드시키고 배치합니다. JBoss 서버를 시작하거나 이미 시작되어 있다면, 다음과 같은 배치 메시지가 보여지게 될 것입니다:

09:54:42,018 INFO  [EjbModule] Deploying OrganizationEJB
09:54:42,399 INFO  [EjbModule] Deploying GangsterEJB
09:54:42,438 INFO  [EjbModule] Deploying JobEJB
09:54:42,468 INFO  [EjbModule] Deploying LocationEJB
09:54:42,507 INFO  [EjbModule] Deploying EJBTestRunnerEJB
09:54:42,587 INFO  [EjbModule] Deploying ReadAheadEJB
09:54:46,300 WARN  [JDBCTypeFactory] Type not mapped: int
09:54:47,223 INFO  [EJBDeployer] Deployed: file:/private/tmp/jboss-3.2.6/server/default/deploy/cmp2-ex1.jar
09:54:48,841 INFO  [OrganizationBean$Proxy] Creating organization Yakuza, Japanese Gangsters
09:54:48,918 INFO  [OrganizationBean$Proxy] Creating organization Mafia, Italian Bad Guys
09:54:48,925 INFO  [OrganizationBean$Proxy] Creating organization Triads, Kung Fu Movie Extras
09:54:48,931 INFO  [GangsterBean$Proxy] Creating Gangster 0 'Bodyguard' Yojimbo
09:54:49,068 INFO  [GangsterBean$Proxy] Creating Gangster 1 'Master' Takeshi
09:54:49,106 INFO  [GangsterBean$Proxy] Creating Gangster 2 'Four finger' Yuriko
09:54:49,117 INFO  [GangsterBean$Proxy] Creating Gangster 3 'Killer' Chow
09:54:49,133 INFO  [GangsterBean$Proxy] Creating Gangster 4 'Lightning' Shogi
09:54:49,143 INFO  [GangsterBean$Proxy] Creating Gangster 5 'Pizza-Face' Valentino
09:54:49,184 INFO  [GangsterBean$Proxy] Creating Gangster 6 'Toohless' Toni
09:54:49,202 INFO  [GangsterBean$Proxy] Creating Gangster 7 'Godfather' Corleone
09:54:49,215 INFO  [JobBean$Proxy] Creating Job 10th Street Jeweler Heist
09:54:49,224 INFO  [JobBean$Proxy] Creating Job The Greate Train Robbery
09:54:49,258 INFO  [JobBean$Proxy] Creating Job Cheap Liquor Snatch and Grab

예제의 테스트를 실행하기전에 JBossCMP의 로그 레벨이 증가되어 있어야만 합니다. CMP 서브시스템에 대한 디버그 로깅을 활성화시키려면, 여러분의 log4j.xml 파일에 다음과 같은 카테고리를 추가합니다:

<category name="org.jboss.ejb.plugins.cmp">
    <priority value="DEBUG"/>
</category>

이와 더불어, 콘솔쪽에 디버그 레벨 메시지가 로깅될 수 있도록 CONSOLE appender상에 경계점(threshold)을 감소시켜줄 필요가 있습니다. 또한 다음과 같은 변경을 log4j.xml 파일에 적용시켜주어야 합니다.

<!-- ============================== -->   
<!-- 콘솔쪽에 메시지를 추가함       -->
<!-- ============================== -->
<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
    <param name="Threshold" value="DEBUG"/>
    <param name="Target" value="System.out"/>
    <layout class="org.apache.log4j.PatternLayout">
        <!-- The default pattern: Date Priority [Category] Message\n -->
        <param name="ConversionPattern" value="%d{ABSOLUTE} %-5p [%c{1}] %m%n"/>
    </layout>
</appender>

CMP 엔진의 완전한 작업을 보기위해서는 다음에 보여지는 것 처럼 org.jboss.ejb.plugins.cmp 카테고리에 대해 커스텀 TRACE 레벨 우선순위를 활성화 시켜야 합니다:

<category name="org.jboss.ejb.plugins.cmp">
    <priority value="TRACE" class="org.jboss.logging.XLevel"/>
</category>

이번 장의 예제를 어떻게 실행시켜 보는지 알아보기전에 마지막으로 참고해야 할 한가지 사항이 남아있습니다. 예제내의 빈들이 배치에서 제거할 때 자신들의 테이블을 제거하도록 설정되어 있기 때문에, JBoss 서버를 시작시키게 되면 항상 여러분은 예제 데이터를 다시 로딩시키기 위해 config 타겟을 재 실행시켜야 합니다. 또한, 예제를 변경시키고 예제 EJB JAR를 재배치시키고자 한다면 이것 또한 예제 데이터를 다시 로딩시키기 위해서 config 타겟을 사용해주어야 합니다.

11.1.2. 테스트

첫 번째 test 타겟에서는 이번 장을 통해 논의되어지는 많은 커스터마이제이션 기능들을 보여주게 됩니다. 이 test를 실행시키기 우해서는 다음과 같은 ant 타겟을 실행시키십시오:

[nr@toki examples]ant -Dchap=cmp2 -Dex=test run-example
14:03:27,920 DEBUG [OrganizationEJB#findByPrimaryKey] Executing SQL: SELECT name FROM ORGA
NIZATION WHERE name=?
14:03:28,011 DEBUG [OrganizationEJB] Executing SQL: SELECT desc, the_boss FROM ORGANIZATIO
N WHERE (name=?)
14:03:28,020 DEBUG [OrganizationEJB] Executing SQL: SELECT id FROM GANGSTER WHERE (organiz
ation=?)
14:03:28,044 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,052 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,070 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,229 DEBUG [GangsterEJB#findBadDudes_ejbql] Executing SQL: SELECT t0_g.id FROM GAN
GSTER t0_g WHERE (t0_g.badness > ?)
14:03:28,256 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,264 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,270 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,276 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,281 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,395 DEBUG [GangsterEJB#findBadDudes_jbossql] Executing SQL: SELECT t0_g.id, t0_g.
badness FROM GANGSTER t0_g WHERE (t0_g.badness > ?) ORDER BY t0_g.badness DESC
14:03:28,417 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,423 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,429 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,439 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,446 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,605 DEBUG [GangsterEJB#findBadDudes_declaredsql] Executing SQL: SELECT id FROM GA
NGSTER WHERE badness > ? ORDER BY badness DESC
14:03:28,613 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,631 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,641 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,647 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,655 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,783 DEBUG [GangsterEJB#ejbSelectBoss_ejbql] Executing SQL: SELECT DISTINCT t0_und
erling_organization_theBos.id FROM GANGSTER t1_underling, ORGANIZATION t4_underling_organi
zation, GANGSTER t0_underling_organization_theBos WHERE ((t1_underling.name = ?) OR (t1_un
derling.nick_name = ?)) AND t1_underling.organization=t4_underling_organization.name AND t
4_underling_organization.the_boss=t0_underling_organization_theBos.id
14:03:28,815 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
14:03:28,822 DEBUG [GangsterEJB#ejbSelectBoss_ejbql] Executing SQL: SELECT DISTINCT t0_und
erling_organization_theBos.id FROM GANGSTER t1_underling, ORGANIZATION t4_underling_organi
zation, GANGSTER t0_underling_organization_theBos WHERE ((t1_underling.name = ?) OR (t1_un
derling.nick_name = ?)) AND t1_underling.organization=t4_underling_organization.name AND 
t4_underling_organization.the_boss=t0_underling_organization_theBos.id
14:03:28,829 DEBUG [GangsterEJB#findByPrimaryKey] Executing SQL: SELECT id FROM GANGSTER W
HERE id=?
...
14:03:29,970 DEBUG [GangsterEJB] Executing SQL: SELECT name, nick_name, badness, hangout, 
organization FROM GANGSTER WHERE (id=?)
14:03:29,980 DEBUG [GangsterEJB] Executing SQL: SELECT cell_area, cell_exch, cell_ext, pa
ge_area, page_exch, page_ext, email FROM GANGSTER WHERE (id=?)
14:03:29,987 DEBUG [GangsterEJB] Executing SQL: UPDATE GANGSTER SET cell_area=?, cell_exc
h=?, cell_ext=?, page_area=?, page_exch=?, page_ext=?, email=? WHERE id=?
14:03:29,995 DEBUG [GangsterEJB] Rows affected = 1

이 test 타겟에서는 다양한 finder, selector 그리고 테이블 매핑을 만들어내는 객체을 실행시킵니다. 우리는 이번 장을 통해 test를 참조하게 될 것입니다.

11.1.3. Read-ahead

다른 메인 타겟은 11.7 섹션, “최적화된 로딩” 에서 보여지는 최적화된 로딩 설정을 보여주기 위한 test의 셋을 실행시킵니다. 이제 로깅이 제대로 셋업되었다면, read-ahead 테스트에서는 수행된 질의들에 대한 유용한 정보를 보여주게 될 것입니다. 여러분은 log4j.xml 파일의 변경을 인식시키도록 하기 위해 JBoss 서버를 다시 시작시킬 필요는 없으나, 변경이 적용되기 위해서는 1분 이상 걸립니다. 다음은 readahead 클라이언트의 실제 실행을 보여주고 있습니다:

[starksm@banshee examples]$ ant -Dchap=cmp2 -Dex=readahead run-example
Buildfile: build.xml
...
run-example:
 
run-examplereadahead:
[junit] .
[junit] Time: 0.561
 
[junit] OK (1 test)

readahead 클라이언트가 실행되면, test동안 실행되는 모든 SQL 질의들은 JBoss 서버 콘솔에 보여집니다. 출력되는 내용을 분석할 때 주의해야 할 중요한 사항들은 실행된 질의의 갯수, 선택된 컬럼과 로딩된 로우의 갯수입니다. 아래에서는 readahead 클라이언트로부터 JBoss 서버 콘솔쪽에 출력되는 내용중 read-ahead none 부분입니다:

########################################################
### read-ahead none
###
08:31:15,892 DEBUG [findAll_none] Executing SQL: SELECT t0_g.id, t0_g.id FROM GANGSTER 
t0_g ORDER BY t0_g.id ASC
08:31:15,902 DEBUG [GangsterEJB] Executing SQL: SELECT name, nick_name, badness, hangout,
organization FROM GANGSTER WHERE (id=?)
08:31:15,912 DEBUG [GangsterEJB] Executing SQL: SELECT name, nick_name, badness, hangout,
organization FROM GANGSTER WHERE (id=?)
08:31:15,912 DEBUG [GangsterEJB] Executing SQL: SELECT name, nick_name, badness, hangout,
organization FROM GANGSTER WHERE (id=?)
08:31:15,912 DEBUG [GangsterEJB] Executing SQL: SELECT name, nick_name, badness, hangout,
organization FROM GANGSTER WHERE (id=?)
08:31:15,922 DEBUG [GangsterEJB] Executing SQL: SELECT name, nick_name, badness, hangout,
organization FROM GANGSTER WHERE (id=?)
08:31:15,922 DEBUG [GangsterEJB] Executing SQL: SELECT name, nick_name, badness, hangout,
organization FROM GANGSTER WHERE (id=?)
08:31:15,932 DEBUG [GangsterEJB] Executing SQL: SELECT name, nick_name, badness, hangout,
organization FROM GANGSTER WHERE (id=?)
08:31:15,932 DEBUG [GangsterEJB] Executing SQL: SELECT name, nick_name, badness, hangout, 
organization FROM GANGSTER WHERE (id=?)
08:31:15,942 INFO [ReadAheadTest]
###
########################################################

우리는 최적화된 로딩을 위한 설정을 다루는 부분에서 다시 이 예제를 살펴보게 될 것입니다.

11.2. jbosscmp-jdbc 구조

jbosscmp-jdbc.xml 서술자는 JBossCMP 엔진의 특성(behavior)을 제어하는데 사용되어집니다. 이 파일은 서버 설정 파일 셋내에서 찾을 수 있는 conf/standardjbosscmp-jdbc.xml 서술자를 통해 전역적으로 작업될 수 있거나 META-INF/jbosscmp-jdbc.xml 서술자를 통해 EJB JAR 배치별로 작업할 수 있습니다. 우리는 JBossCMP 엔진의 기능들(capabilities)을 논의하게 되는 다음 섹션을 통해 살펴보게 되는 요소를 다루도록 하겠습니다. 탑 레벨 요소들이 그림 11.2, “jbosscmp-jdbc 탑 레벨 컨텐츠 모델”에 보여지고 있습니다.

jbosscmp-jdbc 탑 레벨 컨텐츠 모델.

그림 11.2. jbosscmp-jdbc 탑 레벨 컨텐츠 모델.

  • defaults: defaults 섹션은 엔티티 빈을 제어하는 기능을 위한 기본 behavior/setting 을 지정할 수 있도록 합니다. 엔티티 빈 섹션에서 찾을 수 있는 공통 특성(common behavior)들에 필요한 많은 정보들을 단순화하는데 이 섹션을 사용합니다. defaults 컨텐츠의 세부사항은 11.12 섹션, “Defaults” 을 참조하십시오.

  • enterprise-beans: enterprise-beans 요소는 ejb-jar.xml enterprise-beans 서술자에 정의된 엔티티 빈의 커스터마이제이션을 가능하게 합니다. 이에 대한 세부사항은 11.3 섹션, “엔티티 빈”에서 다루고 있습니다.

  • relationships: relationships 요소는 테이블의 커스터마이제이션과 엔티티 관계의 특성을 로딩할 수 있게 합니다. 이에 대한 세부사항은 11.5 섹션, “관계 관리 컨테이너(Container Managed Relationships)”에서 다루고 있습니다.

  • dependent-value-classes: dependent-value-classes 요소는 종속적 값 클래스를 테이블로의 매핑에 대한 커스터마이제이션을 가능케 합니다. 이에 대한 세부사항은 11.4.6 섹션, “종속적 값 클래스(DVCs)”에서 다루고 있습니다.

  • type-mappings: type-mappings 요소에서는 자바에 SQL 템플릿과 함수 매핑과 함께 데이터베이스에 대한 SQL 타입 매핑을 정의합니다. 이에 대한 세부사항은 11.13 섹션, “데이터소스 커스터마이제이션”에서 다루고 있습니다.

  • entity-commands: entity-commands 요소는 영속적 저장소내에서 어떻게 하나의 엔티티 인스턴스를 생성하는지 알고 있는 엔티티 생성 커맨드 인스턴스의 정의를 할 수 있게 합니다. 이에 대한 세부사항은 11.11 섹션, “엔티티 커맨드와 프라이머리 키 생성”에서 다루고 있습니다.

  • user-type-mappings: user-type-mappings 요소에서는 사용자 타입을 mapper 클래스를 사용하여 컬럼으로 매핑시키는 정의를 합니다. mapper는 중재자(mediator)와 유사합니다: 저장할 때, 사용자 타입의 인스턴스를 받아 컬럼 값으로 번역합니다. 로딩될 때, 컬럼 값을 받아 사용자 타입의 인스턴스로 번역합니다. 사용자 타입 매핑의 세부사항들은 11.13.3 섹션, “사용자 타입 매핑”에서 다루고 있습니다.

  • reserved-words: reserved-words 요소에서는 테이블을 생성시킬 때 이스케이프되어져야하는 하나 이상의 예약어를 정의합니다. 각 예약된 단어는 word 요소의 컨텐츠로써 지정되어집니다.

예제 11.1. jbosscmp-jdbc.xml에 대한 DTD

jbosscmp-jdbc.xml 서술자에 대한 DTD는 JBOSS_DIST/docs/dtd/jbosscmp-jdbc_3_2.dtd에서 찾을 수 있습니다. 이 DTD의 퍼블릭 DOCTYPE은 다음과 같습니다:

<!DOCTYPE jbosscmp-jdbc PUBLIC    
          "-//JBoss//DTD JBOSSCMP-JDBC 3.2//EN"
          "http://www.jboss.org/j2ee/dtd/jbosscmp-jdbc_3_2.dtd">

11.3. 엔티티 빈

비록 cmp 분야와 finder 쪽에 새롭게 추가된 기능들과 주요 변경사항이 있기는 하나, CMP 2.0에서 기본적인 엔티티 빈의 구조의 변화는 없습니다. EJB 2.0의 새로운 기능은 로컬 인터페이스에 대한 추가입니다. 로컬 인터페이스는 두 개의 인터페이스 조합으로써 로컬 인터페이스와 로컬 홈 인터페이스[3] 입니다. 이 인터페이스들은 개념적으로는 리모트 인터페이스와 홈 인터페이스(때때로 리모트 홈으로 불려지기도 함)과 동일합니다만, 로컬 인터페이스들은 단지 동일한 자바 VM내에서만 액세스가 가능하다는 것이 다릅니다. 이것은 로컬 인터페이스를 참조에 의한 넘김(pass-by-reference) 문법(semantic), 모든 메쏘드 매개변수[4]의 시리얼라이징과 디-시리얼라이징에 관련된 오버헤드를 제거하는 데 사용될 수 있도록 합니다. 로컬 인터페이스들은 CMP에 대해 고유하지 않으며 이번 문서에서 다루지 않습니다. Gangster 엔티티에 대한 단순화한 코드는 다음과 같습니다:

예제 11.2. 엔티티 로컬 홈 인터페이스

// Gangster 로컬 홈 인터페이스
public interface GangsterHome extends EJBLocalHome {   
    Gangster create(Integer id, String name, String nickName)
        throws CreateException;
    Gangster findByPrimaryKey(Integer id) 
        throws FinderException; 
}

예제 11.3. 엔티티 로컬 인터페이스

// Gangster 로컬 인터페이스
public interface Gangster extends EJBLocalObject {    
    Integer getGangsterId();    
    String getName();
    String getNickName();
    void setNickName(String nickName); 
} 

예제 11.4. 엔티티 구현(Implementation) 클래스

// Gangster 구현 클래스
public abstract class GangsterBean 
    implements EntityBean 
{
     private EntityContext ctx; 
     private Category log = Category.getInstance(getClass());
     public Integer ejbCreate(Integer id, String name, String nickName)
         throws CreateException 
     {
         log.info("Creating Gangster " + id + " '" + nickName + "' "+ name);
         setGangsterId(id);
         setName(name);
         setNickName(nickName);
         return null;
     }
     
     public void ejbPostCreate(Integer id, String name, String nickName) {
     }
     
     // CMP field accessors ---------------------------------------------
     public abstract Integer getGangsterId();
     public abstract void setGangsterId(Integer gangsterId); 
     public abstract String getName();
     public abstract void setName(String name);
     public abstract String getNickName();
     public abstract void setNickName(String nickName);
     public abstract int getBadness();
     public abstract void setBadness(int badness);
     public abstract ContactInfo getContactInfo();
     public abstract void setContactInfo(ContactInfo contactInfo);  
     //... 
     
     // EJB callbacks ---------------------------------------------------
     public void setEntityContext(EntityContext context) { ctx = context; }
     public void unsetEntityContext() { ctx = null; }
     public void ejbActivate() { }    
     public void ejbPassivate() { }   
     public void ejbRemove() { log.info("Removing " + getName()); }
     public void ejbStore() { }
     public void ejbLoad() { }
}

ejb-jar.xml 파일내의 엔티티에 대한 기본 선언은 CMP 2.0에서 크게 변하지 않았습니다. GangsterEJB 인터페이스들의 선언과 cmp 필드들은 아래에 보여집니다.

예제 11.5. ejb-jar.xml 엔티티 선언

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-jar PUBLIC
                
     "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
     "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
<ejb-jar>
    <display-name>CMP 2.0 Lab Jar</display-name>
    <enterprise-beans>
        <entity>
            <display-name>Gangster Entity Bean</display-name>
            <ejb-name>GangsterEJB</ejb-name>
            <local-home>org.jboss.cmp2.crimeportal.GangsterHome</local-home>
            <local>org.jboss.cmp2.crimeportal.Gangster</local>
            <ejb-class>org.jboss.cmp2.crimeportal.GangsterBean</ejb-class>
            <persistence-type>Container</persistence-type>
            <prim-key-class>java.lang.Integer</prim-key-class>
            <reentrant>False</reentrant>
            <cmp-version>2.x</cmp-version>
            <abstract-schema-name>gangster</abstract-schema-name>
            <cmp-field>
                <field-name>gangsterId</field-name>
            </cmp-field>
            <cmp-field>
                <field-name>name</field-name>
            </cmp-field>
            <cmp-field>
                <field-name>nickName</field-name>
            </cmp-field>
            <cmp-field>
                <field-name>badness</field-name>
            </cmp-field>
            <cmp-field>
                <field-name>contactInfo</field-name>
            </cmp-field>
            <primkey-field>gangsterId</primkey-field>
            <!-- ... -->
        </entity>
    </enterprise-beans>
</ejb-jar>

새로운 local-homelocal 요소들은 homeremote 요소들과 동등합니다. cmp-version 요소는 새로운 것이며 1.x 또는 기본 2.x 둘 중에 하나가 될 수 있습니다. 이 요소는 추가되어졌기 때문에 1.x와 2.x 엔티티는 동일한 어플리케이션내에서 혼합되어질 수 있습니다. abstract-schema-name 요소 역시 새로운 것으로써 EJB-QL 질의문내에서 이 엔티티 타입을 식별하는데 사용되며, EJB-QL은 11.6 섹션, “질의” 에서 논의됩니다.

11.3.1. 엔티티 매핑

엔티티에 대한 JBossCMP 설정은 jbosscmp-jdbc.xml 파일내에서 entity 요소와 함께 선언합니다. 이 파일은 EJB JAR의 META-INF 디렉터리에 위치되며 JBossCMP를 위한 옵션 설정 정보의 모든 것을 포함하고 있습니다. entity 요소들은 jbosscmp-jdbc 탑 레벨 요소아래의 enterprise-beans 요소내에 함께 그룹화되어집니다. entity 설정의 예가 아래에 제시되어 있습니다.

예제 11.6. 샘플 jbosscmp-jdbc.xml 엔티티 매핑

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jbosscmp-jdbc PUBLIC
     "-//JBoss//DTD JBOSSCMP-JDBC 3.2//EN"
     "http://www.jboss.org/j2ee/dtd/jbosscmp-jdbc_3_2.dtd">
<jbosscmp-jdbc>
    <!-- ... -->
    <enterprise-beans>
        <entity>
            <ejb-name>GangsterEJB</ejb-name>
            <table-name>gangster</table-name>
            <!-- CMP 필드 (CMP-필드 참조) -->
            <!-- 로드 그룹 (로드 그룹 참조)-->
            <!-- 질의 (질의 참조) -->
        </entity>
    </enterprise-beans>
</jbosscmp-jdbc>

이 경우, DOCTYPE 선언은 선택적이지만, 사용하면 설정 오류를 줄여주게 됩니다. 또한, 설정을 ejb-jar.xml 파일내에 선언된 entity에 일치시키는데 사용되는 ejb-name을 제외한 모든 요소들은 선택적으로 사용됩니다. 다른 경우라는 표시를 하지 않는 이상, 기본 값은 jbosscmp-jdbc.xml 파일이나 현재 서버 설정 파일 셋에 대한 conf/standardjbosscmp-jdbc.xml 파일중 하나의 기본 섹션으로부터 나옵니다. defaults 섹션은 11.12 섹션, “Defaults”에서 논의됩니다.

각각의 entity 요소에 대한 상세한 설명은 다음과 같습니다:

  • ejb-name: 이 설정이 적용되는 EJB의 이름으로 필요한 요소입니다. 이 요소는 반드시 ejb-jar.xml 파일내에의 엔티티에 대한 ejb-name과 일치해야만 합니다.

  • datasource: 이것은 선택적 사용되는 요소로써 데이터소스를 룩업(찾아보기)하는데 사용되는 jndi-name 입니다. 모든 데이터베이스 커넥션은 엔티티 혹은 데이터소스에서 얻어진 관계-테이블에서 사용됩니다. 엔티티에 대해 서로 다른 데이터소스를 갖는 것은 권장되지 않습니다. 이는 finders와 ejbSelects가 질의할 수 있는 도메인의 제약이 넓어지기 때문입니다. 기본값은 defaults 섹션이 덮어씌여지지 않는 경우 java:/DefaultDS 입니다.

  • datasource-mapping: 선택 사용되는 요소로써 자바 타입이 어떻게 SQL 타입에 매핑되는지와 EJB-SQ 함수가 데이터베이스에 특화된 함수로 어떻게 매핑되는지를 결정하는 type-mapping의 이름을 지정합니다. 타입 매핑은 11.13.2 섹션, “타입 매핑”에서 논의됩니다. 기본값은 defaults 섹션이 덮어씌여지지 않는다면, Hypersonic SQL 입니다.

  • create-table: 선택 사용되는 요소로써 true인 경우, JBossCMP는 엔티티에 대한 테이블을 생성토록 시도하도록 지정합니다. 어플리케이션이 배치되면, JBossCMP는 테이블을 만들기 전에 먼저 테이블의 존재를 확인합니다. 테이블이 발견되면, 테이블은 생성되지 않습니다. 이 옵션은 개발 초기 단계에서 테이블 구조가 자주 변경되는 경우 매우 유용합니다. 기본값은 defaults 섹션을 덮어쓰지 않는다면 false 입니다.

  • alter-table: create-table이 자동으로 스키마를 생성하는데 사용되는 반면, alter-table은 엔티티 빈의 변경과 함께 현재의 스키마를 유지하는데 사용될 수 있습니다. 테이블 변경(Alter table)은 다음과 같은 특정한 업무를 따라 수행되게 됩니다:

    • 새로운 필드가 생성되어집니다.
    • 더이상 사용되지 않는 필드는 제거되어집니다.
    • 선언된 길이보다 짧은 문자열 필드는 자신의 길이를 선언된 길이로 늘어나게 됩니다(모든 데이터베이스에서 지원되지는 않습니다).
  • remove-table: 선택 사용되는 요소로써 true로 설정되면 JBossCMP는 각각의 엔티티와 관계와 매핑된 각각의 관계 테이블에 대해 테이블을 드롭하도록 시도합니다. 어플리케이션이 배치제거(undeployed)되면, JBossCMP는 테이블을 드롭하게 됩니다. 이 옵션은 개발 초기 단계에서 테이블 구조가 자주 변경될때 사용하면 매우 유용합니다. 기본값은 defaults 섹션을 덮어쓰지 않는 한 false 입니다.

  • post-table-create: 선택 사용되는 요소로써 데이터베이스 테이블이 생성된 후 곧바로 실행되어야만 하는 임의의 SQL 문장을 지정합니다. 이 명령문은 create-table이 true이며 테이블이 존재하지 않았을 경우에만 실행됩니다.

  • read-only: 선택 사용되는 요소로써 true일 경우, 빈 프로바이더가 어떠한 필드의 값도 변경되도록 허용되지 않음을 지시합니다. 읽기-전용인 필드는 데이터베이스에 저장 또는 삽입이 되지 않습니다. 프라이머리 키 필드가 읽기-전용인 경우, create 메쏘드는 CreateException이 발생됩니다. 읽기-전용 필드에 대해 set accessor가 호출되면, EJBException이 발생됩니다. 읽기-전용 필드는 last update와 같은 데이터베이스 트리거에 의해 채워지는 필드들에 대해 유용합니다. read-only 옵션은 cmp-field basis 별로 덮어쓸 수 있으며 11.4.4 섹션, “읽기-전용 필드”에서 논의토록 하겠습니다. 기본값은 defaults 섹션을 덮어쓰지 않을 경우, false 입니다.

  • read-time-out: 선택 사용되는 요소로써 읽기-전용 필드가 올바른지에 대한 읽기에 소요되는 시간(밀리세컨즈로 단위)입니다. 0으로설정하면, 값은 트랜잭션의 시작시에 항상 다시 로딩되고 -1인 경우는 절대로 타임아웃이 되지 않습니다. 이 옵션 또한 cmp-field basis 별로 덮어쓸수 있습니다. read-only가 false 이면, 이 값은 무시됩니다. 기본값은 defaults 섹션을 덮어쓰지 않으면 -1 입니다.

  • row-locking: 선택 사용되는 요소로써 true인 경우, JBossCMP는 트랜잭션내에서 로딩되는 모든 로우들에 락을 겁니다. 대부분의 데이터베이스는 엔티티를 로딩할 때 SELECT FOR UPDATE를 사용하여 이를 구현합니다만, 실제 문법은 이 엔티티를 사용한 datasource-mapping내의 row-locking-template에 의해 결정됩니다. 기본값은 defaults 섹션을 덮어쓰지 않으면 false 입니다.

  • pk-constraint: 선택 사용되는 요소로써 true로 설정하면 JBossCMP는 테이블을 생성할 때 프라이머리 키 제약사항을 추가하게 됩니다. 기본값은 defaults 섹션을 덮어쓰지 않으면 true 입니다.

  • read-ahead: 선택 사용되는 요소로써 엔티티에 대한 질의 결과와 cmr-fields의 캐슁을 제어합니다. 이 옵션은 11.7.3 섹션, “Read-ahead”에서 논의됩니다.

  • list-cache-max: 선택 사용되는 요소로써 이 엔티티에 의해 추적할 수 있는 read-lists의 갯수를 지정합니다. 이 옵션은 on-load에서 논의됩니다. 기본값은 defaults 섹션을 덮어쓰지 않으면 1000 입니다.

  • fetch-size: 선택 사용되는 요소로써 사용하는 데이터저장소에 대해 한번의 라운드-트립내에서 읽어들이는 엔티티의 갯수를 지정합니다. 기본값은 defaults 섹션을 덮어쓰지 않으면 0 입니다.

  • clean-read-ahead-on-load: 엔티티가 read-ahead 캐쉬로부터 로딩되어졌을 경우, JBoss는 read-ahead 캐쉬로부터 사용된 데이터를 제거할 수 있습니다. 기본값은 false 입니다.
  • table-name: 선택 사용되는 요소로써 이 엔티티에 대한 데이터를 갖게됮는 테이블의 이름입니다. 각 엔티티 인스턴스는 이 테이블의 한 로우에 저장되어 집니다. 기본값은 ejb-name 입니다.

  • cmp-field: 선택 사용되는 요소로써 ejb-jar.xml cmp-field가 영속적인 저장소에 매핑되는 방법을 정의할 수 있도록 합니다. 11.4 섹션, “CMP-필드”에서 논의됩니다.

  • load-groups: 선택 사용되는 요소로써 필드의 로드 그룹핑을 선언하기 위해 하나 이상의 CMP 필드의 그룹핑을 지정합니다. 이것은 11.7.2 섹션, “로드 그룹”에서 다루도록 하겠습니다.

  • eager-load-groups: 선택 사용되는 요소로써 로드 그룹에 참여시킬 하나 이상의 로드 그룹핑을 정의합니다. 이것은 11.8.2 섹션, “Eager-loading 프로세스”에서 논의됩니다.

  • lazy-load-groups: 선택 사용되는 요소로써 lazy-load 그룹으로써 하나 이상의 로드 그룹핑을 정의합니다. 이것은 11.8.3 섹션, “Lazy 로딩 프로세스”에서 논의됩니다.

  • query: 선택 사용되는 요소로써 finder와 selector 정의를 지정합니다. 이것은 11.6 섹션, “질의”에서 논의됩니다.

  • unknown-pk: 선택 사용되는 요소로써 java.lang.Object의 알려지지 않은 프라이머리 키 타입을 영속적인 저장소에 매핑하는 방법을 정의할 수 있도록 합니다.

  • entity-command: 선택 사용되는 요소로써 엔티티 생성 명령 인스턴스를 정의할 수 있도록 합니다. 일반적으로 이것은 프라이머리 키 생성을 위해 허용되는 커스텀 커맨드 인스턴스 정의에 사용됩니다. 자세한 것은 11.11 섹션, “엔티티 커맨드와 프라이머리 키 생성”에서 다루어집니다.

  • optimistic-locking: 선택 사용되는 요소로써 낙관적(optimistic) 락킹에 대한 정책을 정의합니다. 자세한 내용은 11.10 섹션, “낙관적 락킹”에서 논의됩니다.

  • audit: 선택 사용되는 요소로써 감사(audit)받게 되는 CMP 필드들을 정의합니다. 세부내용은 11.4.5 섹션, “엔티티 액세스 감사”에서 다루어집니다.

11.4. CMP-필드

11.4.1. CMP-필드 Abstract Accessors

CMP 필드는 비록 기능적인 측면에서 본다면 CMP 2.0에서 변경된 것은 없지만, 더이상 빈 구현 클래스내에서 필드를 사용하여 선언하지 않습니다. CMP 2.0에서 CMP 필드들은 직접 액세스하는 것보다는 각각의 CMP 필드를 한 세트의 추상 액세서(abstract accessor) 메쏘드와 함께 엔티티의 빈 구현 클래스내에 선업됩니다. 추상 액세서는 어떠한 구현도 주어지지 않는다는 점만 제외하면 자바빈의 속성 액세서와 유사합니다. 예를 들어, 다음에 오는 리스트에서는 gangster 엔티티내에 gangsterId, name, nickName, 그리고 badness CMP 필드 액세서를 선언하고 있습니다:

예제 11.7. cmp-field 추상 액세서 선언 샘플

public abstract class GangsterBean implements EntityBean {
    public abstract Integer getGangsterId();
    public abstract void setGangsterId(Integer gangsterId);
    public abstract String getName();
    public abstract void setName(String param);
    public abstract String getNickName();
    public abstract void setNickName(String param);
    public abstract int getBadness();
    public abstract void setBadness(int param);
}

각 CMP 필드는 getter와 setter 메쏘드 모두를 필요로 하며, 각 accessor 메쏘드는 반드시 public abstract로 선언되어져야 합니다.

11.4.2. CMP-필드 선언

ejb-jar.xml 파일내에 cmp-field를 선언하는 것은 EJB 2.0에서도 변한게 없습니다. 예를 들어, 예제 11.7, “cmp-필드 abstract accessor 선언 샘플”에서 정의된 gangsterId, name, nickNamebadness 필드들을 정의하려면, 다음과 같이 ejb-jar.xml 파일에 추가해주어야 합니다:

예제 11.8. ejb-jar.xml cmp-필드 선언

<ejb-jar>
  <enterprise-beans>
    <entity>
        <ejb-name>GangsterEJB</ejb-name>
        <cmp-field><field-name>gangsterId</field-name></cmp-field>
        <cmp-field><field-name>name</field-name></cmp-field>
        <cmp-field><field-name>nickName</field-name></cmp-field>
        <cmp-field><field-name>badness</field-name></cmp-field>
    </entity>
  </enterprise-beans>
</ejb-jar>

11.4.3. CMP-필드 컬럼 매핑

ejb-jar.xml cmp-field의 매핑은 jbosscmp-jdbc.xml 파일의 entity 안쪽에서 cmp-field 요소내에 선언되어집니다. jbosscmp-jdbc.xmlcmp-field 요소의 컨텐츠 모델은 아래쪽에 보여집니다.

다음에 오는 예제는 cmp-field 매핑의 사용예입니다.

<jbosscmp-jdbc>
  <enterprise-beans>
    <entity>
      <ejb-name>GangsterEJB</ejb-name>
      <table-name>gangster</table-name>
                 
      <cmp-field>
        <field-name>gangsterId</field-name>
        <column-name>id</column-name>
      </cmp-field>
      <cmp-field>
        <field-name>name</field-name>
        <column-name>name</column-name>
        <not-null/>
      </cmp-field>
      <cmp-field>
        <field-name>nickName</field-name>
        <column-name>nick_name</column-name>
        <jdbc-type>VARCHAR</jdbc-type>
        <sql-type>VARCHAR(64)</sql-type>
      </cmp-field>
      <cmp-field>
        <field-name>badness</field-name>
        <column-name>badness</column-name>
      </cmp-field>
    </entity>
  </enterprise-beans>
</jbosscmp-jdbc>

cmp-field 요소에서 컬럼의 이름과 데이터타입을 제어할 수 있습니다. 각 요소에 대한 상세 설명은 다음과 같습니다:

  • field-name: 필요한 요소로써 설정되어진 cmp-필드의 이름입니다. 이것은 ejb-jar.xml 파일내에서 이 entity를 위해 선언된 cmp-fieldfield-name 요소에 일치해야 합니다.

  • column-name: 선택 사용되는 요소로써 cmp-field가 매핑된 컬럼의 이름입니다. 기본은 field-name 값을 사용합니다.

  • not-null: 선택 사용되는 요소로써 JBossCMP가 이 엔티티에 대해 자동적으로 테이블을 생성시킬 때 컬럼 선언의 끝부분에 NOT NULL을 추가해줘야 하는지를 지시합니다. 프라이머리 키 필드에 대한 디폴트와 기본형은 NOT NULL 입니다.

  • jdbc-type: 이것은 JDBC PreparedStatement 혹은 JDBC 결과셋으로부터 로딩되는 데이터에서 매개변수를 설정할 때 사용되는 JDBC 타입입니다. 올바른 타입은 java.sql.Types 에 정의되어 있습니다. sql-type이 지정된 경우에만 필요하며, 기본값은 datasourcemapping에 기초를 두게 됩니다.

  • sql-type: 이 필드에 대한 테이블 문장을 생성하는데 사용되는 SQL 타입입니다. 올바른 sql-types은 데이터베이스 벤더에 의해서만 제한됩니다. jdbc-type이 지정된 경우에만 필요하며, 기본값은 datasourcemapping에 기초를 두게 됩니다.

  • property: 선택 사용되는 요소로써 종속 값 클래스 cmp-필드의 속성을 영속적인 저장소에 어떻게 매핑시킬지를 정의할 수 있도록 합니다. 이것은 종속 값 클래스(DVC)에서 다루어집니다.

  • auto-increment: 선택 사용되는 필드 지시자로써 이 옵션을 사용하면 데이터베이스 계층에서 자동-증가되게 됩니다. 필드를 생성된 컬럼에 매핑시키는 것 뿐만 아니라 외부적으로 컬럼을 조작할 수 있게 하는데 사용됩니다.

  • dbindex: 선택 사용되는 필드 지시자로써 이 옵션을 사용하면 서버는 데이터베이스내에 대응되는 컬럼에 대한 인덱스를 생성해야 하고 인덱스의 이름은 fieldname_index 이 됩니다.

  • check-dirty-after-get: 이 값은 기본 타입과 기본 java.lang 불변(immutable) 랩퍼(wrapper-Integer,String 등)에 대해서는 기본적으로 false가 됩니다. 잠재적으로 변경될 수 있는 객체에 대해서 JBoss는 이들 필드가 get 오퍼레이션 이후에 dirty될 수 있음을 표시하게 됩니다. 객체에 대해 dirty 체크하는 것에 따른 비용이 크다면, check-dirty-after-get을 false로 설정하여 최적화시킬 수도 있습니다.

  • state-factory: 이 필드에 대한 dirty 체킹을 수행하는 상태 팩토리 객체(state factory object)의 클래스 이름을 지정합니다. 상태 팩토리 클래스는 반드시 CMPFieldStateFactory 인터페이스를 구현해야 합니다.

11.4.4. 읽기-전용 필드

cmp-필드에 대한 추상 액세서 사용에 따른 또다른 잇점으로는 읽기-전용 필드를 사용할 수 있다는 점입니다. 1.x CMP 엔진인 JAWS에서는 엔티티에 대해 read-time-out과 함께 읽기-전용을 지원했습니다. 하지만, CMP 1.x에서는 빈 프로바이더가 읽기-전용 엔티티에 대한 필드의 값을 항상 변경해줘야 하는 것과 컨테이너에서 해줄 일이 없다는 것이 문제가 됩니다. CMP 2.x에서 컨테이너는 액세서를 위한 구현을 지원하기 때문에 빈 프로바이더가 읽기-전용 빈의 값을 설정하려고 하면 예외를 발생시킬 수 있습니다.

JBossCMP에서 이 기능은 cmp-field 요소에 read-onlyread-time-out 요소를 추가하는 필드 레벨의 확장이 이루어졌습니다. 이러한 요소들은 엔티티 레벨에서 해왔던 것과 동일한 방식으로 작동합니다. 필드가 읽기-전용일 때, INSERT 혹은 UPDATE 문장에서 사용되지 않습니다. 프라이머리 키 필드가 read-only인 경우, create 메쏘드는 CreateException을 발생시킵니다. set 액세서가 read-only 필드에 대해 호출되면, EJBException을 발생시킵니다. 읽기-전용 필드는 last update와 같은 데이터베이스 트리거에 의해 채워지는 필드에 유용합니다. 읽기-전용 cmp-field 선언의 예는 다음과 같습니다:

<jbosscmp-jdbc>
  <enterprise-beans>
    <entity>
      <ejb-name>GangsterEJB</ejb-name>
      <cmp-field>
        <field-name>lastUpdated</field-name>
        <read-only>true</read-only>
        <read-time-out>1000</read-time-out>
      </cmp-field>
    </entity>
  </enterprise-beans>
</jbosscmp-jdbc>

11.4.5. 엔티티 액세스 감사

entity 섹션의 audit 요소는 어떻게 액세스할지와 엔티티 빈의 감사 방법을 지정할 수 있도록 합니다. 엔티티 빈이 보안 도메인 아랫쪽으로 액세스되어질 때 유일하게 허용되기 때문에 이것은 성립된 호출자(caller) 신분입니다. audit 요소의 컨텐츠 모델은 그림 11.3, “jbosscmp-jdbc.xml audit 요소의 컨텐츠 모델”.

jbosscmp-jdbc.xml audit 요소의 컨텐츠 모델

그림 11.3. jbosscmp-jdbc.xml audit 요소의 컨텐츠 모델

  • created-by: 선택 사용되는 요소로써 지정된 column-name 이나 CMP field-name 중에 하나로 저장되어져야 하는 엔티티를 생성한 호출자(caller)를 가리킵니다.

  • created-time: 선택 사용되는 요소로써 지정된 column-name 이나 CMP field-name 중에 하나로 저장되어져야 하는 엔티티 생성의 시간을 가리킵니다.

  • updated-by: 선택 사용되는 요소로써 지정된 column-name 이나 CMP field-name 중에 하나로 저장되어져야 하는 최근에 수정된 호출자를 가리킵니다.

  • updated-time: 선택 사용되는 요소로써 지정된 column-name 이나 CMP field-name 중에 하나로 저장되어져야 하는 엔티티 수정의 최근 시간을 가리킵니다.

  • */field-name: 이 요소는 액세스되어진 엔티티 빈의 지시된 cmp-field내에 저장되어져야 하는 대응되는 감사 정보를 가리킵니다. entyty내의 실제 CMP 필드에 매칭될 필요는 없다는 것에 주의하십시오. 일치하는 필드 이름이 있는 경우, 여러분은 대응되는 CMP 필드의 추상 getters와 setters를 사용하여 어플리케이션 내에서 audit 필드에 액세스할 수 있습니다. 다른 경우에는 audit 필드가 만들어지고 내부 엔티티에 추가되며 여러분은 audit 필드의 이름을 사용하여 EJB-QL 질의내에서 감사 정보에 액세스할 수 있게 되나, 엔티티 엑세서를 통해 직접 액세스할 수는 없습니다.

  • */column-name: 이 요소에서는 엔티티 테이블의 지정된 컬럼내에 저장되어야 하는 대응되는 audit 정보를 가리킵니다. JBossCMP가 테이블을 생성하는 경우, jdbc-typesql-type 요소는 저장소 타입을 정의하는데 사용될 수 있습니다.

예제 11.9. audit 요소의 정의 샘플

<jbosscmp-jdbc>
  <enterprise-beans>
    <entity>
      <ejb-name>AuditChangedNamesEJB</ejb-name>
      <table-name>cmp2_audit_changednames</table-name>
      <audit>
        <created-by>
          <column-name>createdby</column-name>
        </created-by>
        <created-time>
          <column-name>createdtime</column-name>
        </created-time>
        <updated-by>
          <column-name>updatedby</column-name></updated-by>
        <updated-time>
          <column-name>updatedtime</column-name>
        </updated-time>
      </audit>
    </entity>
  </enterprise-beans>
</jbosscmp-jdbc>

11.4.6. 종속 값 클래스(Dependent Value Classes-DVCs)

종속 값 클래스(DVC)는 자동적으로 인지되는 타입보다는 cmp-field의 타입인 자바 클래스를 식별하는데 사용되는 멋진 용어입니다. 보다 상세한 사항은 엔터프라이즈 자바빈 사양서를 참고하십시오. 기본적으로는 DVC가 직렬화(serialized)되어 있고 직렬화된 폼은 단일 데이터베이스 컬럼에 저장되어집니다. 비록 여기서 논의되지는 않지만, 직렬화된 폼에서 클래스의 오랜 기간 저장되는 경우에 발생된다고 알려진 몇몇 문제들이 있습니다. JBossCMP에서는 DVC의 내부 데이터에 대한 저장소를 하나 이상의 컬럼이 되도록 지원합니다. 이것은 레거시 자바빈과 데이터베이스 구조 지원에 유용합니다. 매우 평탄한 구조(highly flattened structure)를 갖는 데이터베이스를 찾아보기란 매우 힘듭니다 (즉, SHIP_LINE1, SHIP_LINE2, SHIP_CITY등과 같은 필드를 갖고 있는 PURCHASE_ORDER라는 테이블과 청구주소를 위한 필드에 필요한 추가적인 세트). 또다른 일반적인 데이터베이스 구조에는 지역코드, 교환 및 내부회선으로 구분되는 필드를 갖는 전화번호나 여러 필드에 걸쳐 사람의 이름을 분리해 담는 경우가 포함됩니다. DVC에서 여러 컬럼들은 하나의 논리적인 자바빈에 매핑될 수 있습니다.

JBossCMP는 매핑되는 DVC가 간단한 속성을 위한 자바빈 네이밍 사양을 따라야만 하며, 데이터베이스내에 저장되는 각각의 속성들이 getter와 setter 메쏘드[5] 무도를 갖아야만 합니다. 더 나아가서, 빈은 반드시 직렬화되어야만 하고 인수가 없는 컨스트럭쳐를 갖아야만 합니다. 속성은 어떠한 간단한 타입이나, 매핑되지 않은 DVC 혹은 매핑된 DVC가 될 수 있지만, EJB[6]가 될 수는 없습니다. DVC 매핑은 dependent-value-classes 요소내에서 지정됩니다.

jbosscmp-jdbc dependent-value-classes 요소 모델.

그림 11.4. jbosscmp-jdbc dependent-value-classes 요소 모델.

간단한 ContactInfo DVC 클래스의 예제가 다음에 보여집니다.

public class ContactInfo 
    implements Serializable 
{
    /** 휴대전화 번호. */
    private PhoneNumber cell;
    
    /** 페이저 번호. */
    private PhoneNumber pager;
    
    /** 이메일 주소 */
    private String email;

    // ...
}

연락 정보에는 또 다른 DVC 클래스로 표현되는 전화 번호가 포함됩니다.

public class PhoneNumber
    implements Serializable 
{
    /** 전화 번호 앞의 3자리. */
    private short areaCode;

    /** 전화 번호 중간 3자리. */
	private short exchange;

    /** 전화 번호의 끝 4자리. */
	private short extension;

    // ...
} 

두 클래스에 대한 dependent-value-classes 매핑은 상대적으로 알아보기 쉽습니다.

<jbosscmp-jdbc>
  <dependent-value-classes>
    <dependent-value-class>
      <description>A phone number</description>
      <class>org.jboss.cmp2.crimeportal.PhoneNumber</class>
      <property>
        <property-name>areaCode</property-name>
        <column-name>area_code</column-name>
      </property>
      <property>
        <property-name>exchange</property-name>
        <column-name>exchange</column-name>
      </property>
      <property>
        <property-name>extension</property-name>
        <column-name>extension</column-name>
      </property>
    </dependent-value-class>
                 
    <dependent-value-class>
      <description>General contact info</description>
      <class>org.jboss.cmp2.crimeportal.ContactInfo</class>
      <property>
        <property-name>cell</property-name>
        <column-name>cell</column-name>
      </property>
      <property>
        <property-name>pager</property-name>
        <column-name>pager</column-name>
      </property>
      <property>
        <property-name>email</property-name>
        <column-name>email</column-name>
        <jdbc-type>VARCHAR</jdbc-type>
        <sql-type>VARCHAR(128)</sql-type>
      </property>
    </dependent-value-class>
  </dependent-value-classes>
</jbosscmp-jdbc>

각각의 DVC는 dependent-value-class 요소로 선언됩니다. DVC는 class 요소내에 선언된 자바 클래스 타입에 의해 식별됩니다. 영속적이게되는 각 속성은 property 요소로 정의합니다. 이 사양은 cmp-field 요소에 기반을 두기 때문에 자기-설명적(self-explanatory)이어야 합니다. 이러한 제약사항은 향후 릴리즈에서는 제거될 것 입니다. 현재 제안에는 로컬 엔티티의 경우 프라이머리 키 필드를 저장하고 리모트 엔티티의 경우 엔티티 처리를 담고 있습니다.

dependent-value-classes 섹션에서는 클래스의 내부 구조와 기본 매핑을 정의합니다. JBossCMP가 필드를 알수없는(unknown) 타입으로 인지하는 경우, 등록된 DVC 리스트를 검색하게 되며, DVC를 발견하게 되면 컬럼 셋쪽에 필드를 저장합니다. 찾지 못하는 경우에는 단일 컬럼내에 직렬화된 폼으로 필드를 저장합니다. JBossCMP는 DVC의 상속을 지원하지 않습니다. 따라서 이 검색은 필드의 선언된 타입에 대해서만 이루어집니다. DVC는 다른 DVC로부터 구성될 수 있기 때문에 JBossCMP가 DVC쪽으로 동작할 경우, DVC 트리 구조를 커럼 셋으로 평탄화(flattens)합니다. JBossCMP가 구동(startup)되는 동안 DVC 서킷을 찾는 다면, EJBException을 발생시킵니다. 프로퍼티의 기본 컬럼명은 cmp-field에 기반을 둔 컬럼명에 언드스코어가 붙고 이어서 프로퍼티 컬럼명이 붙습니다. 프로퍼티가 DVC인 경우, 프로세스는 반복되어집니다. 즉, ContactInfo DVC를 사용하는 info라는 이름의 cmp-field는 다음과 같은 컬럼을 갖게됩니다:

info_cell_area_code
info_cell_exchange
info_cell_extension
info_pager_area_code
info_pager_exchange
info_pager_extension
info_email

자동으로 생성된 컬럼 이름은 과도하게 길거나 어색할 수 있습니다. 컬럼의 기본 매핑은 다음과 같이 entity 요소내에서 덮어쓸 수 있습니다:

<jbosscmp-jdbc>
  <enterprise-beans>
    <entity>
      <ejb-name>GangsterEJB</ejb-name>
      <cmp-field>
        <field-name>contactInfo</field-name>
        <property>
          <property-name>cell.areaCode</property-name>
          <column-name>cell_area</column-name>
        </property>
        <property>
          <property-name>cell.exchange</property-name>
          <column-name>cell_exch</column-name>
        </property>
        <property>
          <property-name>cell.extension</property-name>
          <column-name>cell_ext</column-name>
        </property>
                
        <property>
          <property-name>pager.areaCode</property-name>
          <column-name>page_area</column-name>
        </property>
        <property>
          <property-name>pager.exchange</property-name>
          <column-name>page_exch</column-name>
        </property>
        <property>
          <property-name>pager.extension</property-name>
          <column-name>page_ext</column-name>
        </property>
                 
        <property>
          <property-name>email</property-name>
          <column-name>email</column-name>
          <jdbc-type>VARCHAR</jdbc-type>
          <sql-type>VARCHAR(128)</sql-type>
        </property>
      </cmp-field>
    </entity>
  </enterprise-beans>
</jbosscmp-jdbc>

엔티티에 대한 프로퍼티 정보를 덮어쓰는 경우, 여러분은 cell.areaCode에서 처럼 균일한 관점(flat perspective)으로 속성을 참조할 필요가 있습니다.

11.5. 관리되는 관계 컨테이너(Container Managed Relationships)

관리되는 관계 컨테이너(CMR)는 CMP2.0의 강력한 새로운 기능입니다. 프로그래머들은 엔티티 객체 사이의 관계 생성을 EJB1.0에서 소개된 이후 계속 해왔지만, CMP2.0이전에는 엔티티의 프라이머리 키를 정확히 해주고 이를 모조(pseudo) 외래 키 필드에 저장해주는 각각의 관계에 대한 코드 작성을 많이 해주어야만 했습니다. 간단한 관계 조차도 지루한 코드 작업을 수반해야 했고 참조적 보전성(referential integrity)을 필요로 하는 복잡한 관계의 경우는 코딩에 많은 시간을 필요로 했습니다. CMP2.0부터는 수작업으로 관계에 대한 코딩을 할 필요가 없어졌습니다. 컨테이너는 참조적 보전성을 갖는 일대일, 일대다 그리고 다대다 관계를 관리할 수 있습니다. CMR 사용에 있어 한가지 제약사항은 이것이 오직 로컬 인터페이스 사이에서만 정의된다는 것입니다. 즉, 관계가 서로 다른 가상 머신에 위치한 두 엔티티사이에서 생설될 수 없다는 것을 의미합니다[7].

관리되는 관계 컨테이너를 생성하기 위해서는 두 기본 단계를 밟습니다: cmr-field 추상 액세서를 생성하고, ejb-jar.xml 파일에 관계를 정의합니다. 다음 두 섹션에서 이 단계들을 설명하도록 하겠습니다.

11.5.1. CMR-필드 추상 액세서(Accessors)

<

CMR-필드 추상 액세서는 단일-값을 갖는 관계는 관련된 엔티티의 로컬 인터페이스를 리턴해야 한다는 점과 멀티-값을 갖는 관계에서는 오직 java.util.Collection (또는 java.util.Set) 객체를 리턴한다는 것만 제외하면 cmp-fields와 동일한 사용법을 갖습니다. cmp-fields를 사용함에 있어서 관계상의 두 엔티티중 최소한 한개는 반드시 cmr-field 추상 액세서를 갖아야만 합니다. 예를 들면, OrganizationGangster 사이의 일대다 관계를 선언하기 위해서는 먼저 다음에 오는 것을 OrganizationBean 클래스에 추가합니다:

public abstract class OrganizationBean
    implements EntityBean 
{
    public abstract Set getMemberGangsters();
    public abstract void setMemberGangsters(Set gangsters);
} 

두 번째로 다음에 오는 것을 GangsterBean 클래스에 추가합니다:

public abstract class GangsterBean
    implements EntityBean 
{
    public abstract Organization getOrganization();
    public abstract void setOrganization(Organization org);
}

비록 각각의 빈에 하나의 cmr-field를 선언하였으나, 관계내의 두 빈중 오직 하나만이 액세서의 셋을 갖아야 합니다. cmp-fields를 다루는 경우, 하나의 cmr-field는 getter와 setter 메쏘드 모두를 필요로 합니다.

11.5.2. 관계 선언

ejb-jar.xml 파일내에서 관계의 선언은 복잡하고 오류가 일어날 가능성이 높습니다. 관계 선언에 사용되는 XML은 비쥬얼 베이직 문법처럼 모순이 많습니다. 관계 설정을 위한 최선의 방법은 XDoclet과 같은 도구를 사용하거나 잘 동작하고 있는 관계를 복사해서 붙여넣는 것입니다. Organization-Gangster 관계의 선언은 다음과 같습니다:

예제 11.10. ejb-jar.xml에서의 관계 선언

<ejb-jar>
    <relationships>
        <ejb-relation>
            <ejb-relation-name>Organization-Gangster</ejb-relation-name>
            <ejb-relationship-role>
                <ejb-relationship-role-name>org-has-gangsters </ejb-relationship-role-name>
                <multiplicity>One</multiplicity>
                <relationship-role-source>
                    <ejb-name>OrganizationEJB</ejb-name>
                </relationship-role-source>
                <cmr-field>
                    <cmr-field-name>memberGangsters</cmr-field-name>
                    <cmr-field-type>java.util.Set</cmr-field-type>
                </cmr-field>
            </ejb-relationship-role>
            <ejb-relationship-role>
                <ejb-relationship-role-name>gangster-belongs-to-org </ejb-relationship-role-name>
                <multiplicity>Many</multiplicity>
                <cascade-delete/>
                <relationship-role-source>
                    <ejb-name>GangsterEJB</ejb-name>
                </relationship-role-source>
                <cmr-field>
                    <cmr-field-name>organization</cmr-field-name>
                </cmr-field>
            </ejb-relationship-role>
        </ejb-relation>
    </relationships>
</ejb-jar>

보시는 바와 같이 각 관계는 탑 레벨의 relationships[8] 요소내에서 하나의 ejb-relation 요소로 선언되고, 각각의 ejb-relation에는 두 개의 ejb-relationship-role 요소(관계내에서 각각의 엔티티를 위해 하나씩)를 포함합니다. ejb-relationship-role 태그는 다음과 같습니다:

  • ejb-relationshiprole-name: 선택 사용되는 요소로써 역할(role)을 식별하고 데이터베이스 매핑을 jbosscmp-jdbc.xml 파일에 일치하는데 사용됩니다. 이름은 관련된 역할과 동일해서는 않됩니다.

  • multiplicity: 필요한 요소로써 One 혹은 Many가 와야 합니다. 모든 XML 요소들에서 처럼, 이 요소도 대소문자를 구별합니다(case sensitive).

  • cascade-delete: 선택 사용되는 요소로써, JBossCMP가 부모 엔티티가 삭제된 경우 자식 엔티티도 삭제하도록 합니다. 계단식 삭제(Cascade deletion)는 관계의 다른 쪽이 일대다인 경우에 대해 역할에 대해서만 허용됩니다. 기본값은 계단식 삭제가 되지 않습니다.

  • relationship-role-source/ejb-name: 필요한 요소로써 역할을 갖는 엔티티의 이름을 부여합니다.

  • cmr-field/cmr-field-name: 엔티티가 cmr-필드 추상 액세서를 갖는 경우 엔티티가 갖는 하나의 cmr-필드의 이름입니다.

  • cmr-field/cmr-field-type: 이것은 cmr-필드의 타입입니다. java.util.Collection 또는 java.util.Set 이어야 합니다. cmr-필드 추상 액세서가 컬렉션 값을 경우에 한해 필요합니다.

cmr-field 추상 액세서 추가와 관계 선언이후, 관계는 동작해야만 합니다. 관계에 대한 보다 상세한 정보는 EJB2.0 사양서의 10.3 섹션을 참조하십시오. 다음 섹션에서 우리는 관계에 대한 데이터베이스 매핑을 살펴보겠습니다.

11.5.3. 관계 매핑

관계는 외래키(foreign key)나 별도의 relation-table을 사용하여 매핑될 수 있습니다. 일대일과 일대다 관계는 기본적으로 외래키 매핑 스타일을 사용하고, 다대다 관계는 relation-table 매핑 스타일만을 사용합니다. 관계의 매핑은 jbosscmp-jdbc.xml 서술자에서 ejb-relation 요소를 통해 relationships 섹션내에 선언됩니다. 관계는 ejb-jar.xml 파일에서의 ejb-relation-name에 의해 식별됩니다. jbosscmp-jdbc.xmlejb-relation 요소 컨텐츠 모델이 그림 11.5, “jbosscmp-jdbc.xml의 ejb-relation 요소 컨텐츠 모델”에 보여지고 있습니다.

jbosscmp-jdbc.xml의 ejb-relation 요소 컨텐츠 모델

그림 11.5. jbosscmp-jdbc.xml의 ejb-relation 요소 컨텐츠 모델

Organization-Gangster에 대한 관계 매핑 선언의 기본 템플릿은 다음과 같습니다:

<jbosscmp-jdbc>
    <relationships>
        <ejb-relation>
            <ejb-relation-name>Organization-Gangster</ejb-relation-name>
            <foreign-key-mapping/>
            <ejb-relationship-role>
                <ejb-relationship-role-name>org-has-gangsters</ejb-relationship-role-name>
                <key-fields>
                    <key-field>
                        <field-name>name</field-name>
                        <column-name>organization</column-name>
                    </key-field>
                </key-fields>
            </ejb-relationship-role>
            <ejb-relationship-role>
                <ejb-relationship-role-name>gangster-belongs-to-org</ejb-relationship-role-name>
                <key-fields/>
            </ejb-relationship-role>
        </ejb-relation>
    </relationships>
</jbosscmp-jdbc>

매핑되어진 관계에 대한 ejb-relation-name이 선언된 이후, 관계는 read-onlyread-time-out 요소를 사용하여 읽기-전용으로 선언될 수도 있습니다. 이 요소들은 entity 요소내에서 자신의 상대짝으로써 동일한 사용법을 갖습니다.

ejb-relation 요소는 foreign-key-mapping 요소나 외래키 매핑과 relation-table 매핑 섹션에서 각각 논의되는 relation-table-mapping 요소 중에 하나를 포함해야만 합니다. 이 요소는 또한 다음 섹션에서 기술되는 ejb-relationship-role 요소의 한 쌍도 포함할 수 있습니다.

11.5.3.1. 관계 역할 매핑

두 개의 ejb-relationship-role 요소 각각은 관계내에서 엔티티에 특정한 매핑 정보를 포함하며, ejb-relationship-role 요소의 컨텐츠 모델은 그림 11.6, “jbosscmp-jdbc ejb-relationship-role 요소 컨텐츠 모델”에 보여지고 있습니다.

jbosscmp-jdbc ejb-relationship-role 요소 컨텐츠 모델

그림 11.6. jbosscmp-jdbc ejb-relationship-role 요소 컨텐츠 모델

중요 요소의 세부 설명은 다음과 같습니다:

  • ejb-relationship-role-name: 필수 요소로써 이 설정이 적용되는 역할의 이름을 지정합니다. 이 요소는 ejb-jar.xml 파일내에서 이 질의를 위해 선언된 역할중에 하나의 이름과 일치해야만 합니다.

  • fk-constraint: 선택 사용되는 요소이며, true로 설정하면 JBossCMP는 테이블에 외래키 제약을 추가한다는 것을 가르키게 됩니다. JBossCMP는 프라이머리 테이블과 관련된 테이블 모두가 배치되는 동안 JBossCMP에 의해 셩성되는 경우에만 제약을 추가하게 됩니다.

  • key-fields: 선택 사용되는 요소로써 현재 엔티티의 프라이머리 키 필드의 매핑을 지정합니다. 이 요소는 정확한 필드 매핑이 요구될 때만 필요합니다. 그 외의 경우, key-fields 요소는 반드시[9] 현재 엔티티의 프라이머리 키 각각에 대해 하나의 key-fields 요소를 포함해야만 합니다. 이 요소의 세부사항은 아래에 기술되어 있습니다.

  • read-ahead: 선택 사용되는 요소로써 관계의 캐슁을 제어합니다. 이 옵션은 11.8.3.1 섹션, “관계”에서 논의됩니다.

  • batch-cascade-delete: 관계가 ejb-jar.xml내에서 batch-delete로 표시되면, 대응되는 관계는 batch-cascade-delete로 표시될 수 있습니다. 이 경우, 계단식 삭제가 단일 SQL 문장으로 수행되게 됩니다.

위에서 언급되었던 것 처럼, key-fields 요소에는 현재의 엔티티에 대한 각각의 프라이머리 키 필드를 위한 하나의 key-field가 포함됩니다. key-field 요소는 not-null 옵션을 지원하지 않는다는 것을 제외하면 엔티티의 cmp-field 요소와 동일한 문법을 사용합니다. relation-tablekey-field는 자동적으로 널이 않되는데, 그것은 이것이 테이블의 프라이머리 키이기 때문입니다. 한편, 외래키 필드는 기본적으로 널값을 허용해야 합니다. 이것은 현재의 JBossCMP 구현에서 ejbCreateejbPostCreate 사이의 새로운 엔티티를 위해 데이터베이스쪽에 한개의 열을 삽입하기 때문입니다. EJB 사양서에서 ejbPostCreate가 일어나기 전까지는 관계가 수정되도록 허용하지 않기 때문에, 외래키는 널값으로 초기화됩니다. 이것은 제거할때도 유사한 문제를 안고 있습니다. 여러분은 jboss.xmlinsert-after-ejb-post-create 컨테이너 설정 플래그를 사용하여 이러한 삽입 특성(insert behavior)을 바꿀 수 있습니다. 다음 예제에서는 insert-after-ejb-post-create 사용법을 보여주고 있습니다.

<jboss>
   <!-- ... -->
   <container-configurations>
     <container-configuration extends="Standard CMP 2.x EntityBean">
       <container-name>INSERT after ejbPostCreate Container</container-name>
       <insert-after-ejb-post-create>true</insert-after-ejb-post-create>
     </container-configuration>
   </container-configurations>
</jboss>

널값이 아닌 외래 키 문제를 풀수 있는 또 다른 방법은 foreign key 요소를 널값이 아닌 CMP 필드에 매핑시키는 것입니다. 이 경우, 여러분은 관련된 CMP 필드의 setters를 사용하여 ejbCreate내에 간단하게 외래키 필드를 생성합니다.

key-fields 요소 컨텐츠 모델이 그림 11.7, “jbosscmp-jdbc key-fields 요소 컨텐츠 모델”에 표시되어 있습니다.

jbosscmp-jdbc key-fields 요소 컨텐츠 모델

그림 11.7. jbosscmp-jdbc key-fields 요소 컨텐츠 모델

key-field 요소에 포함된 요소들의 세부 설명은 다음과 같습니다:

  • field-name: 필요한 요소로써 이 매핑이 적용되는 필드를 식별합니다. 이 이름은 반드시 현재의 엔티티에 대한 프라이머리 키 필드와 일치해야 합니다.

  • column-name: 프라이머리 키 필드가 저장되어질 컬럼명을 지정하기 위해 이 요소를 사용합니다. 관계가 foreign-key-mapping을 사용할 경우, 이 컬럼은 관계된 엔티티를 위한 테이블에 추가되게 됩니다. 관계가 relation-table-mapping을 사용할 경우, 이 컬럼은 relation-table에 추가됩니다. 이 요소는 매핑된 종속 값 클래스에서 사용할 수 없습니다. 대신 property 요소를 사용합니다.

  • jdbc-type: 이것은 JDBC PreparedStatement나 JDBC 결과셋으로부터 데이터를 로딩하는 경우에 사용되는 JDBC 타입입니다. 올바른 타입은 java.sql.Types에 정의되어 있습니다.

  • sql-type: 이 필드를 위해 테이블 생성 문장에서 사용된 SQL 타입입니다. 올바른 타입은 여러분이 사용하는 데이터베이스 벤더에 의해서만 제한됩니다.

  • property: 종속 값 클래스인 프라이머리 키 필드의 매핑을 지정하는데 사용되는 요소입니다.

  • dbindex: 선택 사용가능한 이 필드 지시자를 사용하게 되면, 서버는 데이터베이스내에 대응되는 컬럼쪽에 인덱스를 생성하도록 하고, 생성되는 인덱스의 이름은 fieldname_index이 됩니다.

11.5.3.2. 외래 키(Foreign Key) 매핑

외래키 매핑은 일대일과 일대다 관계에서 가장 흔한 매핑 스타일입니다만, 다대다 관계에서는 허용되지 않습니다. 외래키 매핑 요소는 빈 foreign-key-mapping 요소를 ejb-relation 요소쪽에 추가하여 간단하게 선언됩니다.

앞의 섹션에서 언급되었듯이, 외래키 매핑에서 ejb-relationship-role내에 선언된 key-fields는 관련된 엔티티의 테이블쪽에 추가되어집니다. key-fields 요소가 비어있을 경우, 외래키는 엔티티를 위해 생성되지 않습니다. 일대다 관계에서, 다(多)측(이 에제에서는 Gangster가 됨)은 반드시 빈 key-fields 요소를 갖아야만 하고, 일(一)쪽(이 예제에서는 Organization이 됨)은 하나의 key-fields 매핑을 갖아야만 합니다. 일대일 관계에서는 한쪽 또는 양쪽의 역할에서 외래 키를 갖을 수 있습니다.

외래 키 매핑은 관계의 방향과는 무관합니다. 즉, 일대일 단방향 관계(오직 한쪽만 액세서를 갖는)에서의 한쪽 또는 양쪽 역할에서 외래키를 갖을 수 있다는 것을 의미합니다. Organization-Gangster 관계에 대한 완전한 외래키 매핑이 아래에 보여지며, 외래키 요소는 굵은체로 표시하였습니다:

<jbosscmp-jdbc>
    <relationships>
        <ejb-relation>
            <ejb-relation-name>Organization-Gangster</ejb-relation-name>
            <foreign-key-mapping/>
            <ejb-relationship-role>
                <ejb-relationship-role-name>org-has-gangsters</ejb-relationship-role-name>
                <key-fields>
                    <key-field>
                        <field-name>name</field-name>
                        <column-name>organization</column-name>
                    </key-field>
                </key-fields>
            </ejb-relationship-role>
            <ejb-relationship-role>
                <ejb-relationship-role-name>gangster-belongs-to-org</ejb-relationship-role-name>
                <key-fields/>
            </ejb-relationship-role>
        </ejb-relation>
    </relationships>
</jbosscmp-jdbc>

11.5.3.3. Relation-table 매핑

Relation-table 매핑은 일대일과 일대다 관계에서 덜 보편적으로 사용되지만, 다대다 관계에서 허용되는 유일한 매핑 스타일입니다. Relation-table 매핑은 아래 그림에서 보여지는 컨텐츠 모델을 갖는 relation-table-mapping 요소를 사용하여 정의됩니다.

jbosscmp-jdbc relation-table-mapping 요소 컨텐츠 모델

그림 11.8. jbosscmp-jdbc relation-table-mapping 요소 컨텐츠 모델

Gangster-Job 관계에 대한 relation-table-mapping이 다음 예제에 보여지며, 테이블 매핑 요소는 굵은체로 표시하였습니다:

예제 11.11. jbosscmp-jdbc.xml Relation-table 매핑

<jbosscmp-jdbc>
    <relationships>
        <ejb-relation>
            <ejb-relation-name>Gangster-Jobs</ejb-relation-name>
            <relation-table-mapping>
                <table-name>gangster_job</table-name>
            </relation-table-mapping>
            <ejb-relationship-role>
                <ejb-relationship-role-name>gangster-has-jobs</ejb-relationship-role-name>
                <key-fields>
                    <key-field>
                        <field-name>gangsterId</field-name>
                        <column-name>gangster</column-name>
                    </key-field>
                </key-fields>
            </ejb-relationship-role>   
            <ejb-relationship-role>
                <ejb-relationship-role-name>job-has-gangsters</ejb-relationship-role-name>
                <key-fields>
                    <key-field>
                        <field-name>name</field-name>
                        <column-name>job</column-name>
                    </key-field>
                </key-fields>
            </ejb-relationship-role>
        </ejb-relation>
    </relationships>
</jbosscmp-jdbc>

relation-table-mapping 요소는 entity 요소에서 사용가능한 옵션의 서브셋을 포함합니다. 여러분의 편의를 위해 이 요소들의 세부 설명을 여기서 다시 보여주고 있습니다:

  • table-name: 선택 사용되는 요소로써 이 관계에 대한 데이터를 갖게되는 테이블의 이름이 주어집니다. 기본값은 엔티티에 기반을 두고 cmr-field 이름입니다.

  • datasource: 선택 사용되는 요소로써 데이터소스를 룩업하는데 사용하는 jndi-name이 주어집니다. 모든 데이터베이스 커넥션들이 데이터소스로부터 얻어집니다. 엔티티들에 대해 서로 다른 데이터소스를 갖는 것은 finders와 ejbSelect들이 질의를 날릴 수 있는 도메인 제약이 광범위해지기 때문에 권장되지 않습니다.

  • datasourcemapping: 선택 사용되는 요소로써 사용할 type-mapping의 이름을 지정할 수 있도록 합니다.

  • create-table: 선택 사용되는 요소로써 true로 설정하면 JBossCMP는 관계에 대한 테이블을 생성하는 시도를 하게됩니다. 어플리케이션이 배치되면, JBossCMP는 테이블을 생성하기 전에 먼저 테이블이 존재하는지를 점검합니다. 만약 테이블이 있다면, 테이블은 생성되지 않습니다. 이 옵션은 개발 초기 단계에서 테이블의 구조가 자주 변경되는 상황에 유용합니다.

  • post-table-create: 선택 사용되는 요소로써 데이터베이스 테이블이 생성된 직후 실행되어져야만 하는 임의의 SQL 문장을 지정합니다. 이 명령은 create-table이 true이고 테이블이 먼저 존재하지 않는 경우에만 실행됩니다.

  • remove-table: 선택 사용되는 요소로써 true로 설정되면 JBossCMP는 어플리케이션을 배치제거할 때 relation-table을 드롭하는 시도를 하도록 지시합니다. 이 옵션은 개발 초기 단계에서 테이블 구조가 자주 변경되는 상황에 유용합니다.

  • row-locking: 선택 사용되는 요소로써 true로 설정되면 JBossCMP가 트랜잭션내의 모든 로딩된 로우에 락을 걸도록 지시합니다. 대부분의 데이터베이스는 엔티티를 로딩할 때 SELECT FOR UPDATE 문법을 사용하여 이를 구현하지만, 실제 문법은 이 엔티티에서 사용되는 datasource-mapping내의 row-locking-template에 의해 결정됩니다.

  • pk-constraint: 선택 사용되는 요소로써 true로 설정되면 JBossCMP는 테이블을 생성할 때 프라이머리 키 제약조건을 추가하도록 지시합니다.

11.6. 질의(Queries)

CMP2.0의 또다른 강력한 새로운 기능은 EJB 질의 언어(EJB-QL)와 ejbSelect 메소드의 도입입니다. CMP1.1에서는, 모든 EJB 컨테이너가 finders를 지정하기 위해 다른 방법을 갖고 있었으며, 이는 J2EE 이식성에 심각한 위협이었습니다. CMP2.0에서는 플랫폼에 비종속적인 방법으로 finders 지정과 ejbSelect 메쏘드를 사용할 수 있도록 EJB-QL이 만들어졌습니다. ejbSelect 메쏘드는 엔티티 구현에 사설 질의 문장(private query statements)을 제공토록 설계되었습니다. 자신이 정의되어 있는 홈 인터페이스와 동일한 타입의 엔티티들만을 반환하도록 제한되어 있는 finders와는 다르게 ejbSelect 메쏘드에서는 어떠한 엔티티 타입이나 엔티티의 필드 한개만도 리턴할 수 있습니다.

EJB-QL은 본 문서에서 다룰 영역을 벗어나기 때문에 여기서는 기본 메쏘드 코딩과 질의 정의에 대해서만 다루도록 하겠습니다. 보다 자세한 정보는 엔터프라이즈 자바빈 사양서를 참조하십시오.

11.6.1. Finder 와 ejbSelect 선언

finders의 정의는 CMP2.0에서도 변한것이 없습니다. Finders는 여전히 엔티티의 홈 인터페이스(로컬 혹은 리모트)에서 선언됩니다. 로컬 홈 인터페이스쪽에 선언된 Finders는 RemoteException을 발생시키지 않습니다. 다음에 오는 코드에서는 GangsterHome 인터페이스쪽에 findBadDudes_ejbql[10] finder를 선언합니다:

예제 11.12. Finder 선언

public interface GangsterHome 
    extends EJBLocalHome 
{
    Collection findBadDudes_ejbql(int badness) throws FinderException;
}

ejbSelect 메쏘드는 엔티티 구현 클래스내에 선언되며, cmp-fieldcmr-field 추상 액세서와 같은 public abstract이어야만 합니다. Select 메쏘드는 FinderException을 발생시키도록 선언되어야만 하지만, RemoteException을 발생시키지는 않습니다. 다음에 오는 코드에서는 한개의 ejbSelect 메쏘드가 선언되어 있습니다:

예제 11.13. ejbSelect 선언

public abstract class GangsterBean 
    implements EntityBean 
{
    public abstract Set ejbSelectBoss_ejbql(String name)
        throws FinderException;
}

11.6.2. EJB-QL 선언

EJB 2.0 사양서에서는 모든 ejbSelect 또는 finder 메쏘드(findByPrimaryKey 제외)가 ejb-jar.xml 파일내에 선언된 EJB-QL 질의를 갖아야만 하도록 요구하고 있습니다. EJB-QL 질의는 entity요소내에 포함된 query 요소내에서 선언되어집니다. 다음은 findBadDudes_ejbqlejbSelectBoss_ejbql 질의에 대한 선언입니다.

<ejb-jar>
    <enterprise-beans>
        <entity>
            <ejb-name>GangsterEJB</ejb-name> 
            <!-- ... -->
            <query>
                <query-method>
                    <method-name>findBadDudes_ejbql</method-name>
                    <method-params>
                        <method-param>int</method-param>
                    </method-params>
                </query-method>
                <ejb-ql><![CDATA[
                 SELECT OBJECT(g)
                 FROM gangster g
                 WHERE g.badness > ?1
                 ]]></ejb-ql>
            </query>
            <query>
                <query-method>
                    <method-name>ejbSelectBoss_ejbql</method-name>
                    <method-params>
                        <method-param>java.lang.String</method-param>
                    </method-params>
                </query-method>
                <ejb-ql><![CDATA[
                 SELECT DISTINCT underling.organization.theBoss
                 FROM gangster underling
                 WHERE underling.name = ?1 OR underling.nickName = ?1
                 ]]></ejb-ql>
            </query>
        </entity>
    </enterprise-beans>
</ejb-jar>

EJB-QL 은 SQL과 유사하지만 큰 차이점을 갖습니다. EJB-QL에서 주의해야할 중요한 사항들을 다음에 나열하였습니다:

  • EJB-QL 은 typed 랭귀지입니다. 즉, like 타입의 비교만을 허용합니다(문자열은 문자열과만 비교됩니다).

  • equals 비교의 경우에는 변수(단일 값을 갖는 경로)는 반드시 왼쪽편에 와야만 합니다. 아래에 몇몇 예제를 보여주고 있습니다[11]:

g.hangout.state = 'CA' 올바름
'CA' = g.shippingAddress.state 틀림
'CA' = 'CA' 틀림
(r.amountPaid * .01) > 300 틀림
r.amountPaid > (300 / .01) 올바름
  • 매개변수는 java.sql.PreparedStatement 처럼 인덱스 1이 기본입니다.

  • 매개변수는 비교의 오른쪽 편에만 가능합니다. 즉:

gangster.hangout.state = ?1 올바름
?1 = gangster.hangout.state 틀림

11.6.3. EJB-QL을 SQL 매핑으로 덮어쓰기

EJB-QL을 SQL 매핑으로 하는 것은 jbosscmp-jdbc.xml 파일내에서 덮어쓰기로 할 수 있습니다. finder 나 ejbSelect는 여전히 ejb-jar.xml 파일내에서 EJB-QL 선언을 가져야만 하지만, ejb-ql 요소는 비어있을 수 있습니다. 현재로서는 SQL은 JBossQL, DynamicQL, DeclaredSQL 이나 BMP 스타일의 커스텀 ejbFind 메쏘드로 덮어쓰일 수 있습니다. 모든 EJB-QL 오버라이드는 EJB 2.0 사양에 대해 비-표준 확장이기 때문에 이러한 표현의 사용은 여러분의 어플리케이션 이식성에 제한을 가져옵니다. BMP 커스텀 finders를 제외한 모든 EJB-QL 오버라이드는 entity/query 요소를 사용하여 선언되며, 그 컨텐츠 모델은 그림 11.9, “jbosscmp-jdbc query 요소 컨텐츠 모델”에 표시되어 있습니다.

jbosscmp-jdbc query 요소 컨텐츠 모델

그림 11.9. jbosscmp-jdbc query 요소 컨텐츠 모델

  • description: 선택적 사용되는 질의에 대한 설명.

  • query-method: 필요한 요소로써 설정된 query 메쏘드를 지정합니다. 이것은 ejb-jar.xml 파일내에서 이 엔티티에 대해 선언된 query-method와 일치해야만 합니다.

  • jboss-ql, dynamic-ql, declared-sql: 이 요소들은 query 메소드 지정을 위한 다른 수단들이며, 각각은 해당 섹션내에서 다루어집니다.

  • read-ahead: 선택 사용되는 요소로써 질의에서 참조되는 엔티티들과 함께 사용하기 위한 추가적인 필드들의 로딩을 최적화할 수 있도록 합니다. 이것의 세부적인 논의는 11.7 섹션, “최적화된 로딩”에서 다루겠습니다.

11.6.4. JBossQL

JBossQL은 EJB-QL의 미비한 사항들을 돕기위해 설계되었습니다. 보다 유연한 문법의 추가와 더불어 새로운 기능, 키워드 및 절(clauses)이 JBossQL에 추가되어졌습니다. 이 책이 쓰여질 당시, JBossQL에는 ORDER BY, OFFSET 그리고 LIMIT절, INLIKE 오퍼레이터, COUNT, MAX, MIN, AVG, SUM, UCASE 그리고 LCASE 함수가 포함되어 있으며, 또한 ejbSelect 메쏘드를 위한 SELECT 절내에 함수들을 포함할 수 있습니다.

JBossQL은 jbosscmp-jdbc.xml파일내에서 JBossQL 질의를 포함하는 query/jboss-ql 요소를 사용하여 선언됩니다. 다음에 오는 예제에서는 JBossQL 선언 예제가 제공됩니다.

<jbosscmp-jdbc>
    <enterprise-beans>
        <entity>
            <ejb-name>GangsterEJB</ejb-name>
            <query>
                <query-method>
                    <method-name>findBadDudes_jbossql</method-name>
                    <method-params>
                        <method-param>int</method-param>
                    </method-params>
                </query-method>
                <jboss-ql><![CDATA[
                 SELECT OBJECT(g)
                 FROM gangster g
                 WHERE g.badness > ?1
                 ORDER BY g.badness DESC
                 ]]></jboss-ql>
            </query>
        </entity>
    </enterprise-beans>
</jbosscmp-jdbc>

대응되는 생성된 SQL은 직관적입니다.

SELECT t0_g.id
    FROM gangster t0_g
    WHERE t0_g.badness > ?
    ORDER BY t0_g.badness DESC

JBossQL의 또다른 기능은 LIMIT와 OFFSET 함수를 사용하여 블럭내에 finder 결과를 가져오는 것입니다. 가령 수행된 작업의 큰 숫자를 통해 반복시켜야 한다면, 다음과 같은 findManyJobs_jbossql finder가 선언될 수 있습니다.

<jbosscmp-jdbc>
    <enterprise-beans>
        <entity>
            <ejb-name>GangsterEJB</ejb-name>
            <query>
                <query-method>
                    <method-name>findManyJobs_jbossql</method-name>
                    <method-params>
                        <method-param>int</method-param>
                    </method-params>
                    <method-params>
                        <method-param>int</method-param>
                    </method-params>
                </query-method>
                <jboss-ql><![CDATA[
                 SELECT OBJECT(j)
                 FROM jobs j
                 OFFSET ?1 LIMIT ?2
                 ]]></jboss-ql>
            </query>
        </entity>
    </enterprise-beans>
</jbosscmp-jdbc>

11.6.5. DynamicQL

DynamicQL은 JBossQL 질의를 런타임 생성과 실행이 가능하도록 합니다. DynamicQL의 query 메쏘드는 JBossQL 질의를 가져와 매개변수로써 인수를 질의하는 추상적인 메쏘드입니다. JBossCMP는 JBossQL을 컴파일하고 생성된 SQL을 실행시킵니다. 다음 코드는 주(state) 셋내의 어떠한 주에서라도 사형당한 모든 폭력배(gangsters)를 선택(select)하는 JBossQL 질의를 생성시킵니다:

public abstract class GangsterBean 
    implements EntityBean 
{
    public Set ejbHomeSelectInStates(Set states)
	throws FinderException
    {
	// JBossQL 질의 생성
	StringBuffer jbossQl = new StringBuffer();
	jbossQl.append("SELECT OBJECT(g) ");
	jbossQl.append("FROM gangster g ");
	jbossQl.append("WHERE g.hangout.state IN (");
	for(int i = 0; i < states.size(); i++) {
	    if(i > 0) {
		jbossQl.append(", ");
	    }

	    jbossQl.append("?").append(i+1);
	}

	jbossQl.append(") ORDER BY g.name");

	// Object[]에 인수를 팩킹
	Object[] args = states.toArray(new Object[states.size()]);

	// dynamic-ql 질의 호출
	return ejbSelectGeneric(jbossQl.toString(), args);
    }
}

DynamicQL ejbSelect 메쏘드는 올바른 ejbSelect 메쏘드 이름을 갖을 수는 있지만, 그 메쏘드는 매개변수로써 반드시 항상 문자열과 Object 배열을 가져야만 합니다. DynamicQL은 jbosscmp-jdbc.xml 파일에서 빈 query/dynamic-ql 요소를 사용하여 선언됩니다. 다음은 ejbSelectGeneric의 선언입니다.

<jbosscmp-jdbc>
    <enterprise-beans>
        <entity>
            <ejb-name>GangsterEJB</ejb-name>
            <query>
                <query-method>
                    <method-name>ejbSelectGeneric</method-name>
                    <method-params>
                        <method-param>java.lang.String</method-param>
                        <method-param>java.lang.Object[]</method-param>
                    </method-params>
                </query-method>
                <dynamic-ql/>
            </query>
        </entity>
    </enterprise-beans>
</jbosscmp-jdbc>

11.6.6. DeclaredSQL

DeclaredSQL은 레가시 JAWS CMP 1.1 엔진 finder 선언에 기반을 두고 있지만, CMP2.0을 위해 갱신되어졌습니다. 일반적으로 이 선언은 EJB-QL 이나 JBossQL에서 표현할 수 없는 WHERE 절을 갖는 질의를 제한하는데 사용됩니다. declared-sql 요소에 대한 컨텐츠 모델을 그림 11.10, “jbosscmp-jdbc declared-sql 요소 컨텐츠 모델.>”에 표시하였습니다.

jbosscmp-jdbc declared-sql 요소 컨텐츠 모델.>

그림 11.10. jbosscmp-jdbc declared-sql 요소 컨텐츠 모델.>

  • select: 무엇을 선택해야할지를 지정하며, 다음 요소들로 구성됩니다:

    • distinct: 이 빈 요소가 존재하게 되면, JBossCMP는 생성시키는 SELECT 절에 DISTINCT 키워드를 추가합니다. 기본값은 메쏘드가 java.util.Set을 리턴하면 DISTINCT를 사용하는 것입니다.

    • ejb-name: 이것은 선택되어지는 엔티티의 ejb-name 입니다. 질의가 ejbSelect 메쏘드용일 경우에만 필요합니다.

    • field-name: 지정된 엔티티로부터 선택되어지는 cmp-field의 이름입니다. 기본값은 전체 엔티티를 선택하는 것입니다.

    • alias: 이것은 메인 셀렉트 테이블을 위해 사용되는 별칭(alias)입니다. 기본값은 ejb-name을 사용합니다.

    • additional-columns: finders와 함께 임의의 컬럼으로 정렬시키는 것을 충적시키거나 select에서 집계 함수(aggregate functions)를 사용하기 위해 선택되어지는 다른 컬럼들을 선언합니다.

  • from: 생성된 from 절에 추가시킬 추가적인 SQL을 선언합니다.

  • where: 질의에 대한 where 절을 선언합니다.

  • order: 질의에 대한 order 절을 선언합니다.

  • other: 질의 끝부분에 추가시키는 추가적인 SQL을 선언합니다.

다음은 DeclaredSQL 선언의 예제입니다.

<jbosscmp-jdbc>
    <enterprise-beans>
        <entity>
            <ejb-name>GangsterEJB</ejb-name>
            <query>
                <query-method>
                    <method-name>findBadDudes_declaredsql</method-name>
                    <method-params>
                        <method-param>int</method-param>
                    </method-params>
                </query-method>
                <declared-sql>
                    <where><![CDATA[ badness > {0} ]]></where>
                    <order><![CDATA[ badness DESC ]]></order>
                </declared-sql>
            </query>
        </entity>
    </enterprise-beans>
</jbosscmp-jdbc>

생성되는 SQL은 다음과 같습니다:

SELECT id
FROM gangster
WHERE badness > ?
ORDER BY badness DESC

여기서 알수 있듯이, JBossCMP는 해당 엔티티에 대한 프라이머리 키를 선택하는데 필요한 SELECT 와 FROM 절을 생성합니다. 자동적으로 생성된 FROM 절의 끝부분에 추가될 수 있는 추가적인 FROM 절을 필요로 할 경우, 다음의 DeclaredSQL 선언 예제에서 추가적인 FROM 절을 사용한 예를 참고하십시오.

<jbosscmp-jdbc>
    <enterprise-beans>
        <entity>
            <ejb-name>GangsterEJB</ejb-name>
            <query>
                <query-method>
                    <method-name>ejbSelectBoss_declaredsql</method-name>
                    <method-params>
                        <method-param>java.lang.String</method-param>
                    </method-params>
                </query-method>
                <declared-sql>
                    <select>
                        <distinct/>
                        <ejb-name>GangsterEJB</ejb-name>
                        <alias>boss</alias>
                    </select>
                    <from><![CDATA[, gangster g, organization o]]></from>
                    <where><![CDATA[
                     (LCASE(g.name) = {0} OR LCASE(g.nick_name) = {0}) AND
                     g.organization = o.name AND o.the_boss = boss.id
                     ]]></where>
                </declared-sql>
            </query>
        </entity>
    </enterprise-beans>
</jbosscmp-jdbc>

생성되는 SQL은 다음과 같습니다:

SELECT DISTINCT boss.id
    FROM gangster boss, gangster g, organization o
    WHERE (LCASE(g.name) = ? OR LCASE(g.nick_name) = ?) AND
          g.organization = o.name AND o.the_boss = boss.id

FROM 절이 컴파로 시작된다는 것에 주의하십시오. 이것은 컨테이너가 생성된 FROM 절의 끝부분에 선언된 FROM 절을 추가시키기 때문입니다. 또한 FROM 절을 SQL JOIN 문장으로 시작할 수도 있습니다. 이것은 ejbSelect 메쏘드이기 때문에, 선택되어지는 엔티티를 선언하는 select 요소를 가져야만 합니다. 질의에 대한 별칭 또한 선언되어지는 것에 주의하십시오. 별칭을 선언하지 않으면, tt class="literal">table_name.field_name 스타일 컬럼 선언을 갖는 SELECT 절의 결과인 table-name이 별칭으로 사용됩니다. 모든 데이터베이스 벤더들이 이러한 문법을 지원하지는 않기 때문에, 별칭에 대한 선언이 선호되어집니다. 선택적으로 사용하는 빈 distinct 요소는 SELECT 절이 SELECT DISTINCT 선언을 사용하기 위해서 입니다. 또한 DeclaredSQL 선언에서는 cmp-field를 선택하기 위해 ejbSelect 메쏘드에서 사용되어집니다.

이제 우리는 Organization 오퍼레이션내의 모든 우편 코드를 선택하기 위한 ejbSelect를 오버라이드하는 예제를 살펴보도록 하겠습니다.

<jbosscmp-jdbc>
    <enterprise-beans>
        <entity>
            <ejb-name>OrganizationEJB</ejb-name>
            <query>
                <query-method>
                    <method-name>ejbSelectOperatingZipCodes_declaredsql</method-name>
                    <method-params>
                        <method-param>java.lang.String</method-param>
                    </method-params>
                </query-method>
                <declared-sql>
                    <select>
                        <distinct/>
                        <ejb-name>LocationEJB</ejb-name>
                        <field-name>zipCode</field-name>
                        <alias>hangout</alias>
                    </select>
                    <from><![CDATA[ , organization o, gangster g ]]></from>
                    <where><![CDATA[
                     LCASE(o.name) = {0} AND o.name = g.organization AND
                     g.hangout = hangout.id
                     ]]></where>
                    <order><![CDATA[ hangout.zip ]]></order>
                </declared-sql>
            </query>
        </entity>
    </enterprise-beans>
</jbosscmp-jdbc>

대응되는 SQL은 다음과 같습니다:

SELECT DISTINCT hangout.zip
    FROM location hangout, organization o, gangster g
    WHERE LCASE(o.name) = ? AND o.name = g.organization AND g.hangout = hangout.id
                ORDER BY hangout.zip

11.6.6.1. 매개변수(Parameters)

JBossCMP DeclaredSQL은 엔티티와 DVC 매개변수를 지원하는 완전히 새로운 매개변수 처리 시스템을 사용합니다. 매개변수는 꼬부라진 괄호(curly brackets-{})으로 감싸여지며, 0 인덱스로 시작하는데, 이것은 EJB-QL 매개변수의 기저(base)와 다릅니다. 매개변수에는 세 개의 카테고리가 있습니다: simple, DVC, 그리고 entity:

  • simple: simple 매개변수는 known(매핑된) DVC나 엔티티를 제외한 어떠한 타입도 될 수 있습니다. simple 매개변수는 오직 {0}과 같은 인수 번호만을 포함합니다. simple 매개변수가 설정되면, 매개변수를 설정하는데 사용된 JDBC 타입은 엔티티에 대한 데이터소스매핑에 의해 결정됩니다. unknown DVC는 직렬화된 후 매개변수로 설정됩니다. 대부분의 데이터베이스가 WHERE절내에 BLOB 값의 사용을 지원하지 않는다는 것에 주의하십시오.

  • DVC: DVC 매개변수는 known(매핑된) DVC라면 어느것이든 올수 있습니다. DVC 매개변수는 simple 속성으로 참조제거(dereferenced down)되어야만 합니다 (다른 DVC가 아닌). 예를 들면, ContactInfo 타입의 CVS 속성 하나를 갖는다면, 올바른 매개변수 선언으로 {0.email}{0.cell.areaCode} 은 되지만, {0.cell}은 아닙니다. 매개변수 설정에 사용된 JDBC 타입은 속성의 클래스 타입에 기반을 두며 엔티티의 datasourcemapping 입니다. 매개변수 설정에 사용된 JDBC 타입은 dependent-value-class 요소내의 속성을 위해 선언되어집니다.

  • entity: entity 매개변수는 어플리케이션내의 어떠한 엔티티라도 올 수 있습니다. entity 매개변수는 simple 프라이머리 키 필드나 DVC 프라이머리 키 필드의 simple 속성으로 참조제거(dereferenced down)되어야만 합니다. 예를 들면, Gangster 타입의 하나의 매개변수를 갖고 있다면, 올바른 매개변수 선언으로는 {0.gangsterId}가 됩니다. ContactInfo 타입의 info라는 이름을 갖는 프라이머리 키 필드를 갖는 몇몇 엔티티를 갖고 있는 경우에는 valid parameter 선언으로 {0.info.cell.areaCode}가 될 것입니다. 엔티티의 프라이머리 키에 대한 멤버인 필드만이 참조제거될 수 있습니다(이러한 제약사항은 나중에 나올 버전에서는 제거될 것입니다). 매개변수를 설정하는데 사용한 JDBC 타입은 엔티티 선언내의 필드를 선언한 JDBC 타입입니다.

11.6.7. EJBQL 2.1 과 SQL92 질의

기본 질의 컴파일러에서는 EJB-QL 2.1 이나 SQL92 표준을 완전하게 지원하지 않습니다. 여러분이 이것들중에 속한 함수가 필요하다면, 질의 컴파일러를 대체해 줄 수 있습니다. 기본 컴파일러는 standardjbosscmp-jdbc.xml내에 지정되어 있습니다.

<defaults>
    ...
    <ql-compiler>org.jboss.ejb.plugins.cmp.jdbc.JDBCEJBQLCompiler</ql-compiler>
    ...
</defaults>

SQL92 컴파일러를 사용하고자 한다면, ql-compiler 요소안에 간단하게 SQL92 컴파일러를 지정하면 됩니다.

<defaults>
    ...
    <ql-compiler>org.jboss.ejb.plugins.cmp.jdbc.EJBQLToSQL92Compiler</ql-compiler>
    ...
</defaults>

이러한 변경은 전체 시스템내의 모든 빈의 질의 컴파일러를 바꾸게 됩니다. 또한 여러분은 jbosscmp-jdbc.xml내에서 각각의 요소에 대한 ql-compiler를 지정해줄 수도 있습니다. 다음은 앞쪽에서 사용했던 질의중에 하나를 사용한 예제입니다.

<query>
    <query-method>
        <method-name>findBadDudes_ejbql</method-name>
        <method-params>
            lt;method-param>int</method-param>
        </method-params>
    </query-method>
    <ejb-ql><![CDATA[
        SELECT OBJECT(g)
        FROM gangster g
        WHERE g.badness > ?1]]>
    </ejb-ql>
    <ql-compiler>org.jboss.ejb.plugins.cmp.jdbc.EJBQLToSQL92Compiler</ql-compiler>
</query>

SQL92 질의 컴파일러의 한가지 중요한 제약사항은 사용되는 read-ahead 전략에 상관없이 엔티티의 모든 필드들을 항상 선택한다는 것입니다. 즉, on-load read-ahead 전략을 갖도록 설정된 질의가 있을 때 첫 번째 질의에서는 ResultSet으로부터 읽혀들여지는 프라이머리 키 필드에 대해서만 단지 프라이머리 키 필드뿐만 아니라 모든 필드들을 포함하게 되며, 로드될 때 다른 필드들이 read-ahead 캐쉬쪽으로 실제로 로딩되게 됩니다. 기본 로드 그룹 *에서 on-find read-ahead는 기대한대로 동작합니다.

11.6.8. BMP 커스텀 Finders

JBossCMP는 빈의 영속성을 관리하는 커스텀 finders를 지원하기 위해 JAWS를 지속적으로 변경해오고 있습니다. 커스텀 finder가 홈 또는 로컬 홈 인터페이스내에 선언된 finder와 일치한다면, JBossCMP는 ejb-jar.xml 또는 jbosscmp-jdbc.xml 파일내에 선언된 어떠한 다른 구현에 대한 커스텀 finder를 항상 호출하게 됩니다. 다음에 오는 간단한 예제에서 프라이머리 키[12]의 컬렉션에 의한 엔티티들을 찾을 수 있습니다:

예제 11.14. 커스텀 Finder 예제 코드

public abstract class GangsterBean
    implements EntityBean 
{
    public Collection ejbFindByPrimaryKeys(Collection keys)
    {
        return keys;
    }
}

11.7. 최적화된 로딩(Optimized Loading)

최적화된 로딩의 목표는 최소 갯수의 질의를 갖고 트랜잭션을 완료할 때 필요한 최소의 데이터량을 로딩하는 것입니다. JBossCMP의 튜닝은 로딩 프로세스를 세부적으로 이해하는 정도에 의존적입니다. 이번 섹션에서는 JBossCMP의 로딩 프로세스의 내부동작과 이것의 설정을 기술하도록 하겠습니다. 로딩 프로세스의 튜닝은 로딩 시스템의 전체적인 이해를 실제적으로 필요로 하기 때문에 이번 장을 한번 이상 읽어주셔야 할 것입니다.

11.7.1. 로딩 시나리오

로딩 프로세스를 살펴보는 가장 쉬운 방법은 사용법 시나리오를 살펴보는 것입니다. 가장 흔한 시나리오는 엔티티들의 컬렉션을 위치시키고 결과에 대해 반복적으로 몇몇 오퍼레이션을 수행하는 것입니다. 다음에 오는 예제는 모든 범죄자를 포함하는 html 테이블을 생성시킵니다:

예제 11.15. 로딩 시나리오 예제 코드

public String createGangsterHtmlTable_none() 
    throws FinderException 
{
    StringBuffer table = new StringBuffer();
    table.append("<table>");

    Collection gangsters = gangsterHome.findAll_none();
    for(Iterator iter = gangsters.iterator(); iter.hasNext(); ) {
	Gangster gangster = (Gangster)iter.next();
	table.append("<tr>");
	table.append("<td>").append(gangster.getName());
	table.append("</td>");
	table.append("<td>").append(gangster.getNickName());
	table.append("</td>");
	table.append("<td>").append(gangster.getBadness());
	table.append("</td>");
	table.append("</tr>");
    }

    return table.toString();
}

이 코드가 단일 트랜잭션내에서 호출되며 모든 최적화된 로딩이 켜져있지 않는다고 가정해보도록 하겠습니다. 5번째 줄에서 JBossCMP는 다음과 같은 질의를 실행합니다:

예제 11.16. 최적화되지 않은 findAll 질의

SELECT t0_g.id
    FROM gangster t0_g
    ORDER BY t0_g.id ASC

8번째 줄에서 예제 데이터베이스 내의 여덟명의 범죄자 데이터를 로딩하기 위해 JBossCMP는 다음의 8개의 질의를 실행합니다:

예제 11.17. 최적화되지 않은 로드 질의

SELECT name, nick_name, badness, hangout, organization
  FROM gangster WHERE (id=0)
SELECT name, nick_name, badness, hangout, organization
  FROM gangster WHERE (id=1)
SELECT name, nick_name, badness, hangout, organization
  FROM gangster WHERE (id=2)
SELECT name, nick_name, badness, hangout, organization
  FROM gangster WHERE (id=3)
SELECT name, nick_name, badness, hangout, organization
  FROM gangster WHERE (id=4)
SELECT name, nick_name, badness, hangout, organization
  FROM gangster WHERE (id=5)
SELECT name, nick_name, badness, hangout, organization
  FROM gangster WHERE (id=6)
SELECT name, nick_name, badness, hangout, organization
  FROM gangster WHERE (id=7)

이 시나리오에는 두가지 문제점이 있습니다. 첫 번째는 JBossCMP가 findAll에 대해 하나의 질의를 실행시키고, 발견한 각각의 요소마다 하나의 질의를 실행하기 때문에 과도한 질의를 날린다는 것입니다. 이것은 "n+1" 문제[13]로 잘 알려져있으며, 다음 섹션에서 논의되는 read-ahead 전략으로 해결할 수 있습니다. 두 번째는 JBossCMP가 한 번도 사용되지 않는 hangout과 organization 필드[14]까지도 로딩하기 때문에 사용하지 않는 필드의 값이 로딩되어진다는 문제입니다. 기대하는 로딩(eager loading)의 설정은 11.8.2 섹션, “Eager-loading 프로세스”에서 논의됩니다. 다음에 오는 테이블에서는 질의의 실행 결과를 보여주고 있습니다:

표 11.1. 최적화되지 않은 질의 실행

idnamenick_namebadnesshangoutorganization
0YojimboBodyguard70Yakuza
1TakeshiMaster101Yakuza
2YurikoFour finger42Yakuza
3ChowKiller93Triads
4ShogiLightning84Triads
5ValentinoPizza-Face45Mafia
6ToniToothless26Mafia
7CorleoneGodfather67Mafia

11.7.2. 로드 그룹

로딩 시스템의 설정과 최적화는 entity 요소내에 이름을 갖는 로드 그룹을 선언하는 것에서부터 시작합니다. 하나의 로드 그룹에는 단일 오퍼레이션내에서 로딩되어지는 cmp-fields의 이름과 외래키를 갖는 cmr-fields이 포함됩니다 (즉, Organization-Gangster 예제에서의 Gangster). 다음에는 설정의 예제를 보여주고 있습니다:

<jbosscmp-jdbc>
    <enterprise-beans>
        <entity>
            <ejb-name>GangsterEJB</ejb-name> 
            <!-- ... -->
            <load-groups>
                <load-group>
                    <load-group-name>basic</load-group-name>
                    <field-name>name</field-name>
                    <field-name>nickName</field-name>
                    <field-name>badness</field-name>
                </load-group>
                <load-group>
                    <load-group-name>contact info</load-group-name>
                    <field-name>nickName</field-name>
                    <field-name>contactInfo</field-name>
                    <field-name>hangout</field-name>
                </load-group>
            </load-groups>
        </entity>
    </enterprise-beans>
</jbosscmp-jdbc>

이 예제에서는 두 개의 로드 그룹이 선언되어 있습니다: basic 그리고 contact info. 로드 그룹이 상호 배타적(mutually exclusive)일 필요는 없다는 것에 주의하십시오. 가령, 로드 그룹 모두 nickName 필드를 포함합니다. 선언된 로드 그룹에 추가하여, JBossCMP는 자동으로 *(별 그룹)으로 명명된 하나의 그룹을 추가합니다. 이 그룹에는 모든 cmp-field와 entity내의 외래키를 갖는 cmr-field를 포함합니다.

11.7.3. Read-ahead

JBossCMP에서 최적화된 로딩은 read-ahead라고 불립니다. 이 용어는 JAWS로부터 상속된 것으로, 로딩된 엔티티에 대한 열 뿐만 아니라 다음에 오는 일부 열들까지도 읽어들이는 기술을 지칭합니다; 그래서 read-ahead 용어가 되었습니다. JBossCMP는 on-findon-load 라는 두 개의 메인 전략을 구현하여 앞 섹션에서 이야기되었던 로딩 문제를 최적화시키게 됩니다. read-ahead 과정에서 로딩된 추가적인 데이터는 엔티티들이 실제 액세스되기 전까지는 JBoss내에서 구체화(materialized)되지 않음으로써 메모리내의 엔티티 객체에 즉시 관련되지는 않습니다. 그 대신, 이것은 엔티티쪽에 로딩되거나 트랜잭션이 끝나게되기 전까지는 미리 로딩된 캐쉬내에 저장됩니다. 다음 섹션에서 read-ahead 전략을 논의하도록 하겠습니다.

11.7.3.1. on-find

on-find 전략은 질의가 호출될 때 추가적인 컬럼을 읽습니다. 세부적인 시나리오인 예제 11.15, “로딩 시나리오 예제 코드”에서 질의가 최적화되었다면, JBossCMP는 5번째 줄에 있는 다음 질의를 실행시키게 됩니다:

SELECT t0_g.id, t0_g.name, t0_g.nick_name, t0_g.badness 
    FROM gangster t0_g
    ORDER BY t0_g.id ASC

그런 다음 8번째 줄에서 필요한 모든 데이터들이 미리 로딩된 캐쉬에 담기게 됨으로써 추가적인 질의가 실행되지 않습니다. 이 전략은 소량의 데이터를 반환하는 질의에서 효과적이지만, 메모리내[15]에 큰 결과셋을 로딩시키는 경우에는 매우 비효율적이 됩니다. 다음 표에서는 이 질의에 대한 결과를 보여주고 있습니다:

표 11.2. on-find 최적화 질의 실행

idnamenick_namebadnesshangoutorganization
0 Yojimbo Bodyguard 7 0 Yakuza
1 Takeshi Master 10 1 Yakuza
2 Yuriko Four finger 4 2 Yakuza
3 Chow Killer 9 3 Triads
4 Shogi Lightning 8 4 Triads
5 Valentino Pizza-Face 4 5 Mafia
6 Toni Toothless 2 6 Mafia
7 Corleone Godfather 6 7 Mafia

read-ahead 전략과 질의에 대한 load-groupquery 요소내에 정의됩니다. read-ahead 전략이 query 요소내에 선언되어 있지 않다면, entity 요소나 defaults 요소내에 선언된 전략이 사용됩니다. on-find 설정은 다음과 같습니다:

<jbosscmp-jdbc>
    <enterprise-beans>
        <entity>
            <ejb-name>GangsterEJB</ejb-name>
            <!--...-->
            <query>
                <query-method>
                    <method-name>findAll_onfind</method-name>
                    <method-params/>
                </query-method>
                <jboss-ql><![CDATA[
                 SELECT OBJECT(g)
                 FROM gangster g
                 ORDER BY g.gangsterId
                 ]]></jboss-ql>
                <read-ahead>
                    <strategy>on-find</strategy>
                    <page-size>4</page-size>
                    <eager-load-group>basic</eager-load-group>
                </read-ahead>
            </query>
        </entity>
    </enterprise-beans>
</jbosscmp-jdbc> 

on-find 전략을 사용할 때의 한가지 문제는 선택된 모든 엔티티에 대해 반드시 추가적인 데이터를 로딩시킨다는 것입니다. 보통 웹 어플리케이션에서는 페이지상에 고정된 갯수의 결과만을 렌더링시키게 됩니다. 미리 로딩된 데이터는 트랜잭션에 대한 길이내에서만 유효하고 트랜잭션은 단일 웹 HTTP 히트(hit)로 제한되므로, 미리 로딩된 데이터 대부분은 사용되지 않습니다. 다음 섹션에서 논의하게 되는 on-load 전략은 이러한 문제가 일어나지 않습니다.

11.7.3.1.1. Left join read ahead

Left join read ahead는 향상된 on-find read-ahead 전략입니다. 기저(base) 인스턴스로부터 필드 뿐만 아니라 CMR 네비게이션에 의해 기저 인스턴스로부터 도달되어지는 관련된 인스턴스까지도 하나의 SQL 질의내에 미리 로딩시킬 수 있도록 허용합니다. CMP 네비게이션의 깊이(depth)에 대한 제한은 없습니다. 또한 네비게이션과 관계 타입 매핑에서 사용되는 CMR 필드들의 기본에 제한이 없습니다. 즉, 외래키와 관계-테이블 매핑 스타일 모두를 지원합니다. 몇가지 예제를 살펴보도록 하겠습니다. 엔티티와 관계 선언은 아래에서 찾을 수 있습니다.

11.7.3.1.2. D#findByPrimaryKey

D라는 엔티티를 갖고 있다고 가정해보도록 하겠습니다. findByPrimaryKey에 대해 생성된 전형적인 SQL 질의는 다음과 같게 됩니다:

SELECT t0_D.id, t0_D.name FROM D t0_D WHERE t0_D.id=?

findByPrimaryKey를 실행시키는 동안 우리가 bscs라는 두 개의 collection-valued CMR 필드를 미리 로드시키기 원한다고 가정해보도록 하겠습니다.

<query>
    <query-method>
        <method-name>findByPrimaryKey</method-name>
        <method-params>
            <method-param>java.lang.Long</method-param>
        </method-params>
    </query-method>
    <jboss-ql><![CDATA[SELECT OBJECT(o) FROM D AS o WHERE o.id = ?1]]></jboss-ql>
    <read-ahead>
        <strategy>on-find</strategy>
        <page-size>4</page-size>
        <eager-load-group>basic</eager-load-group>
        <left-join cmr-field="bs" eager-load-group="basic"/>
        <left-join cmr-field="cs" eager-load-group="basic"/>
    </read-ahead>
</query>

left-join은 eager-loaded되는 관계를 선언합니다. 생성된 SQL은 다음과 같습니다:

SELECT t0_D.id, t0_D.name,
       t1_D_bs.id, t1_D_bs.name,
       t2_D_cs.id, t2_D_cs.name
  FROM D t0_D
       LEFT OUTER JOIN B t1_D_bs ON t0_D.id=t1_D_bs.D_FK
       LEFT OUTER JOIN C t2_D_cs ON t0_D.id=t2_D_cs.D_FK
 WHERE t0_D.id=?

특정한 id를 갖는 D에 대해, 우리는 이것에 관련된 모든 BC를 미리 로드시키며 이것들을 로딩시킨 인스턴스를 데이터베이스가 아닌 read-ahead 캐쉬에서 액세스할 수 있습니다.

11.7.3.1.3. D#findAll

똑같은 방식으로, 우리는 모든 D를 선택하는 D에 대한 findAll 메쏘드도 최적화시킬 수 있습니다. 평범한 findAll 질의는 다음과 같습니다:

SELECT DISTINCT t0_o.id, t0_o.name FROM D t0_o ORDER BY t0_o.id DESC

관계를 미리 로딩시키기 위해, 우리는 간단히 left-join 요소를 질의에 추가시킬 필요가 있습니다.

 <query>
    <query-method>
        <method-name>findAll</method-name>
    </query-method>
    <jboss-ql><![CDATA[SELECT DISTINCT OBJECT(o) FROM D AS o ORDER BY o.id DESC]]></jboss-ql>
    <read-ahead>
        <strategy>on-find</strategy>
        <page-size>4</page-size>
        <eager-load-group>basic</eager-load-group>
        <left-join cmr-field="bs" eager-load-group="basic"/>
        <left-join cmr-field="cs" eager-load-group="basic"/>
    </read-ahead>
</query>

그렇게 해서 생성된 SQL은 다음과 같습니다:

SELECT DISTINCT t0_o.id, t0_o.name,
                t1_o_bs.id, t1_o_bs.name,
                t2_o_cs.id, t2_o_cs.name
  FROM D t0_o
       LEFT OUTER JOIN B t1_o_bs ON t0_o.id=t1_o_bs.D_FK
       LEFT OUTER JOIN C t2_o_cs ON t0_o.id=t2_o_cs.D_FK
 ORDER BY t0_o.id DESC
 

이제 간단한 findAll 질의는 각각의 D 객체에 대한 관련된 BC 객체들 미리 로딩시킵니다.

11.7.3.1.4. A#findAll

그러면 이제 좀 더 복잡한 설정을 살펴보도록 하겠습니다. 몇개의 관계를 갖는 미리 로딩된 인스턴스 A를 생각해보도록 하겠습니다.

  • CMR 필드 parent를 갖는 A에서 도달되는 자신의 parent(자기-관계,self-relation).

  • CMR 필드 b를 갖는 A로부터 도달되는 B와 CMR 필드 c를 갖는 B로부터 도달되는 관련된 C.

  • CMR 필드 b2를 갖는 A로부터 도달되는 B 와 CMR 필드 c를 갖는 B로부터 도달되는 관련된 C.

레퍼런스를 위해, 표준 질의는 다음과 같습니다:

SELECT t0_o.id, t0_o.name FROM A t0_o ORDER BY t0_o.id DESC FOR UPDATE

다음의 메터데이터에서 우리의 미리 로딩될 계획을 기술하고 있습니다.

<query>
    <query-method>
        <method-name>findAll</method-name>
    </query-method>
    <jboss-ql><![CDATA[SELECT OBJECT(o) FROM A AS o ORDER BY o.id DESC]]></jboss-ql>
    <read-ahead>
        <strategy>on-find</strategy>
        <page-size>4</page-size>
        <eager-load-group>basic</eager-load-group>
        <left-join cmr-field="parent" eager-load-group="basic"/>
        <left-join cmr-field="b" eager-load-group="basic">
            <left-join cmr-field="c" eager-load-group="basic"/>
        </left-join>
        <left-join cmr-field="b2" eager-load-group="basic">
            <left-join cmr-field="c" eager-load-group="basic"/>
        </left-join>
    </read-ahead>
</query>

생성되는 SQL 질의는 다음과 같습니다:

SELECT t0_o.id, t0_o.name,
       t1_o_parent.id, t1_o_parent.name,
       t2_o_b.id, t2_o_b.name,
       t3_o_b_c.id, t3_o_b_c.name,
       t4_o_b2.id, t4_o_b2.name,
       t5_o_b2_c.id, t5_o_b2_c.name
  FROM A t0_o
       LEFT OUTER JOIN A t1_o_parent ON t0_o.PARENT=t1_o_parent.id
       LEFT OUTER JOIN B t2_o_b ON t0_o.B_FK=t2_o_b.id
       LEFT OUTER JOIN C t3_o_b_c ON t2_o_b.C_FK=t3_o_b_c.id
       LEFT OUTER JOIN B t4_o_b2 ON t0_o.B2_FK=t4_o_b2.id
       LEFT OUTER JOIN C t5_o_b2_c ON t4_o_b2.C_FK=t5_o_b2_c.id
 ORDER BY t0_o.id DESC FOR UPDATE
 

이런 설정으로 여러분은 추가적인 데이터베이스의 로딩없이 A에서 찾게 되는 어떠한 인스턴스로부터도 CMR을 네비게이션할 수 있습니다.

11.7.3.1.5. A#findMeParentGrandParent

여기서는 자기-관계(self-relation)에 대한 예제를 좀더 제공하고 있습니다. 우리가 하나의 인스턴스와 자신의 parent, grand-parent 그리고 grand-grand-parent의 사전 로딩(preload)을 하나의 질의에서 할 수 있는 메쏘드를 작성하길 원한다고 가정해보도록 하겠습니다. 이렇게 하기 위해서는 중첨된 left-join 선언을 사용해야 할 것입니다.

<query>
    <query-method>
        <method-name>findMeParentGrandParent</method-name>
        <method-params>
            <method-param>java.lang.Long</method-param>
        </method-params>
    </query-method>
    <jboss-ql><![CDATA[SELECT OBJECT(o) FROM A AS o WHERE o.id = ?1]]></jboss-ql>
    <read-ahead>
        <strategy>on-find</strategy>
        <page-size>4</page-size>
        <eager-load-group>*</eager-load-group>
        <left-join cmr-field="parent" eager-load-group="basic">
            <left-join cmr-field="parent" eager-load-group="basic">
                <left-join cmr-field="parent" eager-load-group="basic"/>
            </left-join>
        </left-join>
    </read-ahead>
</query>

생성된 SQL은 다음과 같습니다:

SELECT t0_o.id, t0_o.name, t0_o.secondName, t0_o.B_FK, t0_o.B2_FK, t0_o.PARENT,
       t1_o_parent.id, t1_o_parent.name,
       t2_o_parent_parent.id, t2_o_parent_parent.name,
       t3_o_parent_parent_parent.id, t3_o_parent_parent_parent.name
  FROM A t0_o
       LEFT OUTER JOIN A t1_o_parent ON t0_o.PARENT=t1_o_parent.id
       LEFT OUTER JOIN A t2_o_parent_parent ON t1_o_parent.PARENT=t2_o_parent_parent.id
       LEFT OUTER JOIN A t3_o_parent_parent_parent ON t2_o_parent_parent.PARENT=t3_o_parent_parent_parent.id
 WHERE (t0_o.id = ?) FOR UPDATE

여기서 left-join을 제거하면, 다음과 같은 메터데이터만을 얻게 된다는 것에 주의하십시오.

SELECT t0_o.id, t0_o.name, t0_o.secondName, t0_o.B2_FK, t0_o.PARENT FOR UPDATE

11.7.3.2. on-load

on-load 전략은 엔티티가 로딩되어진 경우, 요청된 엔티티와 이것들이 선택되어진 순서에 따라 다음에 오는 몇몇 엔티티들로 시작하는 몇몇 엔티티들에 대한 추가적인 데이터의 로딩 블럭입니다(block-loads). 이 전략은 find 혹은 select의 결과가 전진 방향으로 액세스된다는 이론에 기반을 두고 있습니다. 질의가 실행되면, JBossCMP는 리스트 캐쉬내에서 찾은 엔티티들의 순서를 저장합니다. 나중에 엔티티들중에 하나가 로딩될 때, JBossCMP는 이 리스트를 사용하여 로딩시킬 엔티티들의 블럭을 결정합니다. 또한 이 전략은 on-find 전략에서 데이터를 로딩시키지 못하는 오류가 발생할 때도 사용할 수 있습니다. 이 전략에서는, 예제 11.15, “로딩 시나리오 예제 코드”의 5번째 줄에서 실행된 질의는 변하지 않은채 남습니다.

예제 11.18. on-load (최적화되지 않은) findAll 질의

SELECT t0_g.id
    FROM gangster t0_g
    ORDER BY t0_g.id ASC 

예를 들어, on-load/page-size가 4로 설정되어 있다면, JBossCMP는 엔티티들에 대한 name, nickName 그리고 badness 필드를 로딩하기 위해 다음에 오는 두 개의 질의를 실행시키게 됩니다:

예제 11.19. on-load 최적화된 로드 질의

SELECT id, name, nick_name, badness
    FROM gangster
    WHERE (id=0) OR (id=1) OR (id=2) OR (id=3)
SELECT id, name, nick_name, badness
    FROM gangster
    WHERE (id=4) OR (id=5) OR (id=6) OR (id=7)

다음에 오는 표에서는 이 질의에 실행결과를 보여주고 있습니다:

표 11.3. on-load 최적화된 질의 실행

idnamenick_namebadnesshangoutorganization
0 Yojimbo Bodyguard 7 0 Yakuza
1 Takeshi Master 10 1 Yakuza
2 Yuriko Four finger 4 2 Yakuza
3 Chow Killer 9 3 Triads
4 Shogi Lightning 8 4 Triads
5 Valentino Pizza-Face 4 5 Mafia
6 Toni Toothless 2 6 Mafia
7 Corleone Godfather 6 7 Mafia

on-find 전략에서 처럼, on-loadread-ahead 요소내에 선언되어집니다. 이번 예제를 위한 on-load 설정이 아래쪽에 보여집니다.

예제 11.20. jbosscmp-jdbc.xml on-load 선언

<jbosscmp-jdbc>
  <enterprise-beans>
    <entity>
      <ejb-name>GangsterEJB</ejb-name>
      <!-- ... -->
      <query>
        <query-method>
          <method-name>findAll_onload</method-name>
          <method-params/>
        </query-method>
        <jboss-ql><![CDATA[
             SELECT OBJECT(g)
             FROM gangster g
             ORDER BY g.gangsterId
             ]]></jboss-ql>
        <read-ahead>
          <strategy>on-load</strategy>
          <page-size>4</page-size>
          <eager-load-group>basic</eager-load-group>
       </read-ahead>
      </query>
    </entity>
  </enterprise-beans>
</jbosscmp-jdbc>

11.7.3.3. none

none 전략은 진정한 반-전략(anti-strategy)입니다. 이 전략은 시스템의 기본 lazy-load 코드에 대비한 대체이며 어떠한 데이터에 대해서도 read-ahead를 구사하지 않았거나 찾은 엔티티들에 대한 순서를 기억하지 않습니다. 이로부터 질의와 퍼포먼스에 일어나는 결과는 이번 장의 앞쪽에서 언급하였습니다. none 전략은 read-ahead 요소와 함께 선언됩니다. read-ahead 요소가 page-size 요소나 eager-load-group을 포함하고 있다면, 무시되어집니다. none 전략은 다음에 오는 에제에 선언되어 있습니다.

예제 11.21. jbosscmp-jdbc.xml none 선언

<jbosscmp-jdbc>
    <enterprise-beans>
        <entity>
            <ejb-name>GangsterEJB</ejb-name>
            <!-- ... -->
            <query>
                <query-method>
                    <method-name>findAll_none</method-name>
                    <method-params/>
                </query-method>
                <jboss-ql><![CDATA[
                 SELECT OBJECT(g)
                 FROM gangster g
                 ORDER BY g.gangsterId
                 ]]></jboss-ql>
                 <read-ahead>
                    <strategy>none</strategy> 
              </read-ahead>
            </query>
        </entity>
    </enterprise-beans>
</jbosscmp-jdbc>

11.8. 로딩 프로세스

앞 섹션에서는 "엔티티가 로딩될 때"라는 구분을 몇몇 단계에서 사용하였습니다. 이것은 의도적으로 논쟁거리를 남겨놓은 것으로써 엔티티에 대해 commit 옵션을 지정하고 트랜잭션의 현재 상태를 엔티티가 로딩된 경우 결정하기 때문입니다. 다음에 오는 섹션에서는 commit 옵션과 로딩 절차에 대해 논의하겠습니다.

11.8.1. Commit 옵션

로딩 프로세스의 핵심은 엔티티에 대한 데이터가 만료될 때를 제어하는 commit 옵션입니다. JBoss는 4개의 commit 옵션 A, B, C 그리고 D를 지원합니다. 처음 세개는 엔터프라이즈 자바빈 사양서에 기술되어 있지만, 마지막 네 번째것은 JBoss에 특화된 것입니다. 각각의 commit 옵션에 대한 세부 설명은 다음과 같습니다:

  • A: JBossCMP는 단일 사용자가 데이터베이스를 사용한다고 가정합니다; 따라서 JBossCMP는 트랜잭션사이에서 엔티티에 대한 현재 값을 캐쉬할 수 있게 되어 실제적인 성능 이득을 가져올 수 있게 됩니다. 이러한 가정때문에, JBossCMP에 의해 관리되는 어떠한 데이터라도 JBossCMP의 외부에서 변경시킬 수 없습니다. 가령, 다른 프로그램에서 데이터를 변경하거나 JDBC의 직접적인 사용(비록 JBoss내에서라도)은 데이터베이스 상태의 모순(inconsistent)을 일이키게 됩니다.

  • B: JBossCMP가 데이터베이스를 사용하는 사용자를 한명 이상으로 가정하지만, 트랜잭션사이의 엔티티에 관한 컨텍스트 정보를 유지합니다. 이 컨텍스트 정보는 엔티티의 최적화된 로딩을 위해 사용합니다. 이것이 기본 commit 옵션입니다.

  • C: JBossCMP가 트랜잭션이 끝에서 모든 엔티티 컨텍스트 정보를 버립니다.

  • D: JBoss에 특화된 commit 옵션입니다. 이 옵션은 지정된 일정 시간동안에만 데이터의 유효성을 지속한다는 것만 제외하면 A 옵션과 유사합니다.

commit 옵션은 jboss.xml 파일내에서 선언됩니다. 이 파일에 대한 상세한 설명은 5 장, JBoss에서의 EJB를 참조하십시오. 다음에 오는 예제는 어플리케이션내의 모든 엔티티 빈에 대해 commit 옵션을 A로 변경시킵니다:

예제 11.22. jboss.xml Commit 옵션 선언

<jboss>
    <container-configurations>
        <container-configuration>
            <container-name>Standard CMP 2.x EntityBean</container-name>
            <commit-option>A</commit-option>
        </container-configuration>
    </container-configurations>
</jboss>

11.8.2. Eager-loading 프로세스

CMP 2.0에서의 가장 중요한 변경사항중 하나는 추상 액세스 메쏘드에 대한 CMP 필드용 클래스 필드 사용입니다. CMP 1.x에서는 컨테이너가 트랜잭션내에서 어떤 필드가 필요한지 몰랐기 때문에 컨테이너가 빈[16]을 로딩시킬 때 모든 필드를 eager load 시켜야만 했습니다. CMP 2.x에서는 컨테이너가 추상 액세서를 위한 구현을 생성하기 때문에 컨테이너는 필요한 필드에 대한 데이터를 알 수 있습니다. JBossCMP는 엔티티를 로딩하는 시점에서 필드에 대한 일부만을 eager load 시키도록 설정할 수 있으며, 후에 필요에 따라 남은 필드를 lazy load 합니다.

엔티티가 로딩되면, JBossCMP는 반드시 로딩될 필요가 있는 필드들을 결정해야 합니다. 기본적으로 JBossCMP는 선택한 이 엔티티에 마지막으로 질의한 eager-load-group을 사용하게 됩니다. 엔티티가 질의내에서 선택되지 않았거나 마지막 질의에서 none read-ahead 전략을 사용했다면, JBossCMP는 이 엔티티에 대해 선언된 기본 eager-load-group을 사용하게 됩니다. 다음에 오는 설정 예제에서는 basic 로드 그룹이 GangsterEJB 엔티티에 대한 기본 eager-load-group으로 설정됩니다:

<jbosscmp-jdbc>
  <enterprise-beans>
    <entity>
      <ejb-name>GangsterEJB</ejb-name>
      <!-- ... -->
      <load-groups>
        <load-group>
          <load-group-name>most</load-group-name>
          <field-name>name</field-name>
          <field-name>nickName</field-name>
          <field-name>badness</field-name>
          <field-name>hangout</field-name>
          <field-name>organization</field-name>
        </load-group>
      </load-groups>
      <eager-load-group>most</eager-load-group>
    </entity>
  </enterprise-beans>
</jbosscmp-jdbc>

eager loading 프로세스는 트랜잭션내에서 엔티티에 대한 메쏘드가 최초로 호출될 때 초기화됩니다. 로드 프로세스의 상세한 설명은 다음과 같습니다:

  • 엔티티 컨텍스트가 여전히 유효하다고 한다면, 어떠한 로딩도 필요하지는 않으며 결국 로딩 프로세스는 완료됩니다. commit 옵션 AD를 사용할 때 엔티티 컨텍스트가 유효하다면, 데이터는 타임아웃되지 않습니다.

  • 엔티티 컨텍스트내에 존재하는 데이터가 쏟아져 나옵니다(flush). 이것은 오래된 데이터가 새로 로딩된 데이터쪽으로 번지지(bleed) 않도록 보장합니다.

  • 프라이머리 키 값은 프라이머리 키 필드쪽으로 다시 주입(inject)됩니다. 프라이머리 키 객체는 실제로는 필드에 비종속적이며 2 단계에서 분출(flush)된 이후에 다시 로딩될 필요가 있습니다.

  • 이 엔티티에 대해 사전에 로딩된 캐쉬내의 모든 데이터는 필드쪽으로 로드됩니다.

  • JBossCMP는 여전히 로딩될 필요가 있는 추가적인 필드들을 결정합니다. 일반적으로 로딩될 필드들은 엔티티의 eager-load-group에 의해 결정되지만, 엔티티가 질의나 on-find 또는 on-load read-ahead 전략을 갖는 CMR 필드를 사용하여 위치된다면 덮어씌여질 수 있습니다. 모든 필드들이 이미 로딩되어졌다면, 로드 프로세스는 7단계를 그냥 넘어갑니다.

  • 필요한 컬럼을 선택하기 위해 하나의 질의가 실행되어집니다. 만일 이 엔티티가 on-load 전략을 사용한다면, 한 페이지내의 데이터는 11.7.3.2 섹션, “on-load”에서 논의되는 것처럼 로딩됩니다. 현재 엔티티를 위한 데이터는 컨텍스트내에 저장되며 다른 엔티티들을 위한 데이터는 미리 로딩된 캐쉬내에 저장됩니다.

  • 엔티티의 ejbLoad 메쏘드가 호출됩니다.

11.8.3. Lazy loading 프로세스

느린 로딩(Lazy loading)은 선호 로딩(eager loading)의 또 다른 반쪽입니다. 필드가 선호되는 로딩(eager loaded)이 아니라면, 느린 로딩(lazy loaded)이어야 합니다. 빈이 로드되지 않은 필드를 액세스하는 경우, JBossCMP는 필드를 로딩하고 로드되지 않은필드의 lazy-load-group내의 필드가 멤버입니다. JBossCMP는 set join을 수행한 후 이미 로딩된 필드들을 제거합니다. 설정 예제가 다음에 보여집니다.

<jbosscmp-jdbc>
    <enterprise-beans>
        <entity>
            <ejb-name>GangsterEJB</ejb-name>
            <!-- ... -->
            <load-groups>
                <load-group>
                    <load-group-name>basic</load-group-name>
                    <field-name>name</field-name>
                    <field-name>nickName</field-name>
                    <field-name>badness</field-name>
                </load-group>
                <load-group>
                    <load-group-name>contact info</load-group-name>
                    <field-name>nickName</field-name>
                    <field-name>contactInfo</field-name>
                    <field-name>hangout</field-name>
                </load-group>
            </load-groups>
            <!-- ... -->
            <lazy-load-groups>
                <load-group-name>basic</load-group-name>
                <load-group-name>contact info</load-group-name>
            </lazy-load-groups>
        </entity>
    </enterprise-beans>
</jbosscmp-jdbc>

빈 프로바이더가 자신의 환경과 함께 getName()을 호출하면, JBossCMP는 name, nickName 그리고 badness를 로딩합니다 (이것들이 이미 로딩되지 않았다고 가정함으로써). 빈 프로바이더가 getNickName()을 호출하면, name, nickName, badness, contactInfo, 그리고 hangout이 로딩됩니다. 느린 로딩 프로세스의 자세한 설명은 다음과 같습니다:

  • 이 엔티티에 대한 미리 로딩된 캐쉬내의 모든 데이터는 필드쪽에 로딩됩니다.

  • 미리 로딩된 캐쉬에 의해 필드 값이 로드되어진다면, 느린 로딩 프로세스가 완료됩니다.

  • JBossCMP는 이 필드를 포함하는 모든 lazy-load-groups을 찾아 그룹에 대한 set join을 수행하고 이미 로딩된 필드들을 제거합니다.

  • 필요한 컬럼을 선택하기 위해 질의가 실행됩니다. 기본 로딩 프로세스에서 처럼, JBossCMP는 엔티티들의 블럭을 로딩할 수도 있습니다. 현재의 엔티티에 대한 데이터가 컨텍스트내에 저장되며 다른 엔티티들을 위한 데이터는 사전에 로딩된 캐쉬내에 저장됩니다.

11.8.3.1. 관계

관계(Relationships)는 CMR 필드가 필드와 질의 모두이기 때문에 느린 로딩의 특별한 경우입니다. 필드가 on-load 블럭으로 로딩된다면, 다음에 오는 몇몇 엔티티들을 위한 엔티티와 CMR 필드의 값을 찾기위한 현재의 값이 로딩되었다는 것을 의미합니다. 질의에 대해서는 관련된 엔티티의 필드 값들이 on-find를 사용하여 미리 로딩되어질 수 있습니다.

다시 로딩이 되었는지를 조사하는 가장 쉬운 방법은 사용법 시나리오를 살펴보는 것이라는 것을 다시 언급하겠습니다. 이번 예제에서 생성된 HTML 테이블에는 각각의 gangster와 이들의 hangout이 포함됩니다. 다음은 예제 코드입니다:

예제 11.23. 관계 느린 로딩 예제 코드

public String createGangsterHangoutHtmlTable() 
    throws FinderException
{
    StringBuffer table = new StringBuffer();
    table.append("<table>");
    Collection gangsters = gangsterHome.findAll_onfind();
    for (Iterator iter = gangsters.iterator(); iter.hasNext(); ) {
        Gangster gangster = (Gangster)iter.next();

        Location hangout = gangster.getHangout();
        table.append("<tr>");
        table.append("<td>").append(gangster.getName());
        table.append("</td>");
        table.append("<td>").append(gangster.getNickName());
        table.append("</td>");
        table.append("<td>").append(gangster.getBadness());
        table.append("</td>");
        table.append("<td>").append(hangout.getCity());
        table.append("</td>");
        table.append("<td>").append(hangout.getState());
        table.append("</td>");
        table.append("<td>").append(hangout.getZipCode());
        table.append("</td>");
        table.append("</tr>");
    }

    table.append("</table>");return table.toString();
}

이 예제를 위한 Gangster findAll_onfind 질의에 대한 설정은 on-find 섹션에서 변경된 것이 없습니다. Location 엔티티와 Gangster-Hangout 관계에 대한 설정은 다음과 같습니다:

예제 11.24. jbosscmp-jdbc.xml 관계 느린 로딩 설정

<jbosscmp-jdbc>
    <enterprise-beans>
        <entity>
            <ejb-name>LocationEJB</ejb-name>
            <load-groups>
                <load-group>
                    <load-group-name>quick info</load-group-name>
                    <field-name>city</field-name>
                    <field-name>state</field-name>
                    <field-name>zipCode</field-name>
                </load-group>
            </load-groups>
            <eager-load-group/>
        </entity>
    </enterprise-beans>
    <relationships>
        <ejb-relation>
            <ejb-relation-name>Gangster-Hangout</ejb-relation-name>
            <foreign-key-mapping/>
            <ejb-relationship-role>
                <ejb-relationship-role-name>
                    gangster-has-a-hangout
                </ejb-relationship-role-name>
                <key-fields/>
                <read-ahead>
                    <strategy>on-find</strategy>
                    <page-size>4</page-size>
                    <eager-load-group>quick info</eager-load-group>
                </read-ahead>
            </ejb-relationship-role>
            <ejb-relationship-role>
                <ejb-relationship-role-name>
                    hangout-for-a-gangster
                </ejb-relationship-role-name>
                <key-fields>
                    <key-field>
                        <field-name>locationID</field-name>
                        <column-name>hangout</column-name>
                    </key-field>
                </key-filaelds>
            </ejb-relationship-role>
        </ejb-relation>
    </relationships>
</jbosscmp-jdbc>

25번째 줄에서 JBossCMP는 다음과 같은 질의를 실행시키게 됩니다:

SELECT t0_g.id, t0_g.name, t0_g.nick_name, t0_g.badness
    FROM gangster t0_g
    ORDER BY t0_g.id ASC

그런 다음 29번째 줄에서 JBossCMP는 숨겨진 city, state 그리고 zip 필드를 로딩하기 위한 다음에 오는 두 개의 질의를 실행합니다:

SELECT gangster.id, gangster.hangout,
       location.city, location.st, location.zip
    FROM gangster, location
    WHERE (gangster.hangout=location.id) AND
          ((gangster.id=0) OR (gangster.id=1) OR
          (gangster.id=2) OR (gangster.id=3))
SELECT gangster.id, gangster.hangout,
       location.city, location.st, location.zip
    FROM gangster, location
    WHERE (gangster.hangout=location.id) AND
          ((gangster.id=4) OR (gangster.id=5) OR
          (gangster.id=6) OR (gangster.id=7))

다음에 보여지는 표에서 이 질의 실행결과를 보여주고 있습니다:

idnamenick_namebadnesshangoutidcitystzip
0YojimboBodyguard700San FranCA94108
1TakeshiMaster1011San FranCA94133
2YurikoFour finger422San FranCA94133
3ChowKiller933San FranCA94133
4ShogiLightning844San FranCA94133
5ValentinoPizza-Face455New YorkNY10017
6ToniToothless266ChicagoIL60661
7CorleoneGodfather677Las VegasNV89109

11.8.4. 느린 로딩 결과셋

기본적으로 multiobject finder 나 ejbSelect 메쏘드가 실행되면, ResultSet이 끝나는 즉시 읽혀집니다. 클라이언트는 EJBLocalObject의 컬렉션이나 이 컬렉션을 통해 반복되어질 수 있는 CMP 필드 값을 수신합니다. 결과셋이 큰 경우, 이러한 접근 방식은 효율적이지 않습니다. 일부의 경우, 클라이언트가 컬렉션으로부터 대응되는 값을 읽는 시도를 하기전까지는 결과셋내에서 다음 번 열을 읽기까지 지연시키는 것이 낫습니다. 여러분은 lazy-resultset-loading 요소를 사용하여 질의에 대해 이러한 특성을 얻을 수 있습니다.

<query>
    <query-method>
        <method-name>findAll</method-name>
    </query-method>
    <jboss-ql><![CDATA[select object(o) from A o]]></jboss-ql>
    <lazy-resultset-loading>true</lazy-resultset-loading>
</query>

느린 결과 셋의 로딩을 사용하는 경우 여러분이 염두해주어야 하는 몇가지 문제가 존재합니다. 느리게 로딩되는 결과셋과 관련된 Collection과 함께 작업할 때 특별한 주의를 해야 합니다. iterator()에 대한 첫 번째 호출은 ResultSet으로부터 읽어오는 특별한 Iterator를 반환합니다. 이 Iterator가 소모(exhausted)되기전까지, iterator()에 대한 일련의 호출이나 add() 메쏘드에 대한 호출은 예외를 일으키게 됩니다. remove()size() 메쏘드는 기대된대로 동작합니다.

11.9. 트랜잭션

이번 장에서 보여주고 있는 모든 예제들에서는 하나의 트랜잭션내에서 동작되도록 정의되어 있습니다. 트랜잭션의 원자성(granularity)은 미리 로딩된 데이터의 생명주기를 정의하기 때문에 최적화된 로딩에서 주요한 사항입니다. 트랜잭션이 완료, commits 또는 취소(roll back)되면, 미리 로딩된 캐쉬내의 데이터는 사라집니다. 이것은 심각한 성능 저하를 미치게 됩니다.

트랜잭션 없이 실행시킬 때의 성능적인 측면은 예제 11.15, “로딩 시나리오 예제 코드”와 유사함을 보여주게 됩니다. 이 예제는 먼저 네 개의 gangsters를 선택(결과셋을 조그맣게 유지하기 위해)하는 on-find 최적화된 질의를 사용하며 트랜잭션으로 감쌓지 않고 실행시킵니다. 예제 코드는 다음과 같습니다:

예제 11.25. 트랜잭션 없는 로딩 예제 코드

public String createGangsterHtmlTable_no_tx() throws FinderException
{
    StringBuffer table = new StringBuffer();
    table.append("<table>");

    Collection gangsters = gangsterHome.findFour();
    for(Iterator iter = gangsters.iterator(); iter.hasNext(); ) {
        Gangster gangster = (Gangster)iter.next();
        table.append("<tr>");
        table.append("<td>").append(gangster.getName());
        table.append("</td>");
        table.append("<td>").append(gangster.getNickName());
        table.append("</td>");
        table.append("<td>").append(gangster.getBadness());
        table.append("</td>");
        table.append("</tr>");
    }
    
    table.append("</table>");
    return table.toString();
}

53번째 줄에서 실행되는 질의는 다음과 같습니다:

예제 11.26. 트랜잭션 없는 on-find 최적화된 findAll 질의

SELECT t0_g.id, t0_g.name, t0_g.nick_name, t0_g.badness
  FROM gangster t0_g
  WHERE t0_g.id < 4followi
  ORDER BY t0_g.id ASC 

보통 이것은 질의만을 실행시키지만, 이 코드가 트랜잭션내에서 실행되지 않기 때문에 미리 로딩된 모든 데이터가 findAll을 반환하자마자 사라집니다. 그런 다음 56번째 줄에서 JBossCMP는 다음에 오는 네 개의 질의[17](각각의 루프에 대해 하나씩)를 실행합니다:

예제 11.27. 트랜잭션 없는 on-load 최적화된 로드 질의

SELECT id, name, nick_name, badness
  FROM gangster
  WHERE (id=0) OR (id=1) OR (id=2) OR (id=3)
SELECT id, name, nick_name, badness
  FROM gangster
  WHERE (id=1) OR (id=2) OR (id=3)
SELECT id, name, nick_name, badness
  FROM gangster
  WHERE (id=2) OR (id=3)
SELECT name, nick_name, badness
  FROM gangster
  WHERE (id=3)

다음 그림에서는 질의의 실행을 보여줍니다:

트랜잭션 없는 on-find 최적화된 질의 실행

그림 11.11. 트랜잭션 없는 on-find 최적화된 질의 실행

이것의 성능은 데이터베이스로부터 로딩되는 데이터의 양때문에 read ahead none 보다 훨씬 떨어집니다. 로딩된 열의 갯수는 다음 식에 의해 결정됩니다:

예제에서의 트랜잭션이 엔티티에 대한 단일 호출에 연결되어 있기 때문에 이 모든것이 일어나게 됩니다. 때문에 "트랜잭션내에서 어떻게 코드를 실행시켜야 하는가?"라는 중요한 질문이 나오게 됩니다. 그 해답은 코드가 어디서 동작되는지에 따라 달라집니다. 코드가 EJB(session, entity, 혹은 message driven)내에서 동작한다면, 메쏘드는 Required 혹은 assembly-descriptor내에서 RequiresNew trans-attribute로 표시되어져야만 합니다. 다음에 오는 코드는 호출을 사용자 트랜잭션과 함께 선언한 메쏘드로 감쌉니다:

예제 11.28. 사용자 트랜잭션 예제 코드

public String createGangsterHtmlTable_with_tx()
    throws FinderException
{
    UserTransaction tx = null;
    try {
        InitialContext ctx = new InitialContext();
        tx = (UserTransaction) ctx.lookup("UserTransaction");
        tx.begin();

        String table = createGangsterHtmlTable_no_tx();
	
        if (tx.getStatus() == Status.STATUS_ACTIVE) {
	        tx.commit();
        }
	    return table;
    } catch (Exception e) {
        try {
            if (tx != null) tx.rollback();
        } catch (SystemException unused) {
            // eat the exception we are exceptioning out anyway
        }
        if (e instanceof FinderException) {
	        throw (FinderException) e;
        }
        if (e instanceof RuntimeException) {
	        throw (RuntimeException) e;
        }

        throw new EJBException(e);
    }
}

11.10. 낙관론적 락킹(Optimistic Locking)

JBoss는 엔티티 빈의 낙관론적인 락킹을 지원합니다 낙관론적 락킹은 동일한 엔티티 빈의 여러 인스턴스들을 동시에 활성화시킬 수 있도록 합니다. 일관성(Consistency)은 낙관론적인 락킹 정책 선택에 따라 강제(enforced)됩니다. 낙관론적 락킹 정책 선택은 데이터베이스에 데이터 수정에 소요되는 커밋 시간에서 사용되는 필드의 셋을 정의합니다. 낙관론적 일관성 점검은 선택한 필드의 셋에 대한 값들이 현재 트랜잭션이 시작되었던 때에 존재했던 데이터베이스내의 값과 동일하다는 것을 가정합니다. 이것은 가정된 값을 포함하고 있는 select for UPDATE WHERE ... 문을 사용하여 이루어집니다.

jbosscmp-jdbc.xml 서술자에서 entity/optimistic-locking 요소를 사용하여 난관론적 락킹 정책 선택을 지정합니다. optimistic-locking 요소의 컨텐츠 모델을 아래 그림에 표시하였으며, 이어 이 요소의 설명을 하였습니다.

jbosscmp-jdbc optimistic-locking 요소 컨텐츠 모델

그림 11.12. jbosscmp-jdbc optimistic-locking 요소 컨텐츠 모델

  • group-name: 이 요소는 load-group의 필드에 기반한 낙관론적 락킹이라는 것을 지정합니다. 이 요소의 값은 엔티티의 load-group-name중에 하나와 일치해야만 합니다. 이 그룹내의 필드는 낙관론적 락킹을 위해 사용되게 됩니다.

  • modified-strategy: 이 요소는 수정된 필드에 기반한 낙관론적 락킹이라는 것을 지정합니다. 이 전략에서는 트랜잭션동안 수정되어진 필드가 낙관론적 락킹에 사용되어진 필드라는 것을 내포하고 있습니다.

  • read-strategy: 이 요소는 읽혀진 필드에 기반을 둔 낙관론적 락킹이라는 것을 지정합니다. 이 전략에서는 트랜잭션내에서 읽기/변경된 필드가 낙관론적 락킹에 사용되었다는 것을 내포하고 있습니다.

  • version-column: 이 요소는 버전 컬럼 전략에 기반한 낙관론적 락킹이라는 것을 지정합니다. 이 요소룰 지정하게 되면 낙관론적 락킹을 위해 엔티티 빈쪽에 java.lang.Long 타입의 추가적인 버전 필드를 추가하게 됩니다. 각각의 엔티티 업데이트가 이 필드의 값을 증가시키게 됩니다. field-name 요소가 CMP 필드의 이름에 대한 지정이 가능하게 하는 반면, column-name 요소는 대응되는 테이블 컬럼을 지정할 수 있게 합니다.

  • timestamp-column: 이 요소는 timestamp 컬럼 전략에 기반한 낙관론적 락킹이라는 것을 지정합니다. 이 요소를 지정하게 되면 낙관론적 락킹을 위해 엔티티 빈쪽에 java.util.Date 타입의 추가적인 필드가 더해집니다. 각각의 엔티티 갱신은 이 필드에 현재 시간을 설정하게 됩니다. field-name 요소에서 CMP 필드의 이름에 대한 지정이 가능하게 하는 반면, column-name 요소는 대응되는 테이블의 컬럼 지정을 가능하게 합니다.

  • key-generator-factory: 이 요소는 키 생성에 기반한 낙관론적 락킹이라는 것을 지정합니다. 요소의 값은 org.jboss.ejb.plugins.keygenerator.KeyGeneratorFactory 구현의 JNDI 이름입니다. 이 요소를 지정하게 되면 낙관론적 락킹을 위해 엔티티 빈에 추가적인 필드가 더해집니다. 필드의 타입은 field-type 요소를 통해 지정되어야만 합니다. 각각의 엔티티 갱신은 키 생성기를 통해 얻어진 새로운 값으로 key 필드를 갱신시킵니다. field-name 요소는 CMP 필드의 이름에 대한 지정을 가능하게 하는 반면, column-name 요소는 대응되는 테이블의 컬럼을 지정할 수 있도록 합니다.

모든 낙관론적 락킹 전략을 보여주는 샘플 jbosscmp-jdbc.xml 서술자가 아래에 보여집니다.

예제 11.29. 모든 낙관론적 락킹 전략을 보여주는 샘플 jbosscmp-jdbc.xml 서술자

<!DOCTYPE jbosscmp-jdbc PUBLIC 
    "-//JBoss//DTD JBOSSCMP-JDBC 3.2//EN"
    "http://www.jboss.org/j2ee/dtd/jbosscmp-jdbc_3_2.dtd">
<jbosscmp-jdbc>
    <defaults>
        <datasource>java:/DefaultDS</datasource>
        <datasource-mapping>Hypersonic SQL</datasource-mapping>
    </defaults>
    <enterprise-beans>
        <entity>
            <ejb-name>EntityGroupLocking</ejb-name>
            <create-table>true</create-table>
            <remove-table>true</remove-table>
            <table-name>entitygrouplocking</table-name>
            <cmp-field>
                <field-name>dateField</field-name>
            </cmp-field>
            <cmp-field>
                <field-name>integerField</field-name>
            </cmp-field>
            <cmp-field>
                <field-name>stringField</field-name>
            </cmp-field>
            <load-groups>
                <load-group>
                    <load-group-name>string</load-group-name>
                    <field-name>stringField</field-name>
                </load-group>
                <load-group>
                    <load-group-name>all</load-group-name>
                    <field-name>stringField</field-name>
                    <field-name>dateField</field-name>
                </load-group>
            </load-groups>
            <optimistic-locking>
                <group-name>string</group-name>
            </optimistic-locking>
        </entity>
        <entity>
            <ejb-name>EntityModifiedLocking</ejb-name>
            <create-table>true</create-table>
            <remove-table>true</remove-table>
            <table-name>entitymodifiedlocking</table-name>
            <cmp-field>
                <field-name>dateField</field-name>
            </cmp-field>
            <cmp-field>
                <field-name>integerField</field-name>
            </cmp-field>
            <cmp-field>
                <field-name>stringField</field-name>
            </cmp-field>
            <optimistic-locking>
                <modified-strategy/>
            </optimistic-locking>
        </entity>
        <entity>
            <ejb-name>EntityReadLocking</ejb-name>
            <create-table>true</create-table>
            <remove-table>true</remove-table>
            <table-name>entityreadlocking</table-name>
            <cmp-field>
                <field-name>dateField</field-name>
            </cmp-field>
            <cmp-field>
                <field-name>integerField</field-name>
            </cmp-field>
            <cmp-field>
                <field-name>stringField</field-name>
            </cmp-field>
            <optimistic-locking>
                <read-strategy/>
            </optimistic-locking>
        </entity>
        <entity>
            <ejb-name>EntityVersionLocking</ejb-name>
            <create-table>true</create-table>
            <remove-table>true</remove-table>
            <table-name>entityversionlocking</table-name>
            <cmp-field>
                <field-name>dateField</field-name>
            </cmp-field>
            <cmp-field>
                <field-name>integerField</field-name>
            </cmp-field>
            <cmp-field>
                <field-name>stringField</field-name>
            </cmp-field>
            <optimistic-locking>
                <version-column/>
                <field-name>versionField</field-name>
                <column-name>ol_version</column-name>
                <jdbc-type>INTEGER</jdbc-type>
                <sql-type>INTEGER(5)</sql-type>
            </optimistic-locking>
        </entity>
        <entity>
            <ejb-name>EntityTimestampLocking</ejb-name>
            <create-table>true</create-table>
            <remove-table>true</remove-table>
            <table-name>entitytimestamplocking</table-name>
            <cmp-field>
                <field-name>dateField</field-name>
            </cmp-field>
            <cmp-field>
                <field-name>integerField</field-name>
            </cmp-field>
            <cmp-field>
                <field-name>stringField</field-name>
            </cmp-field>
            <optimistic-locking>
                <timestamp-column/>
                <field-name>versionField</field-name>
                <column-name>ol_timestamp</column-name>
                <jdbc-type>TIMESTAMP</jdbc-type>
                <sql-type>DATETIME</sql-type>
            </optimistic-locking>
        </entity>
        <entity>
            <ejb-name>EntityKeyGeneratorLocking</ejb-name>
            <create-table>true</create-table>
            <remove-table>true</remove-table>
            <table-name>entitykeygenlocking</table-name>
            <cmp-field>
                <field-name>dateField</field-name>
            </cmp-field>
            <cmp-field>
                <field-name>integerField</field-name>
            </cmp-field>
            <cmp-field>
                <field-name>stringField</field-name>
            </cmp-field>
            <optimistic-locking>
                <key-generator-factory>UUIDKeyGeneratorFactory</key-generator-factory>
                <field-type>java.lang.String</field-type>
                <field-name>uuidField</field-name>
                <column-name>ol_uuid</column-name>
                <jdbc-type>VARCHAR</jdbc-type>
                <sql-type>VARCHAR(32)</sql-type>
            </optimistic-locking>
        </entity>
    </enterprise-beans>
</jbosscmp-jdbc>

11.11. 엔티티 명령과 프라이머리 키 생성

엔티티 빈 클래스의 바깥쪽에서 프라이머리 키 생성을 지원하는 것은 3.2부터 추가된 것입니다. 이것은 영속적인 저장소쪽으로 엔티티들을 삽입하는데 사용하는 엔티티 생성 명령어 객체의 커스텀 구현을 통해 사용가능합니다. 사용가능한 명령어 리스트는 jbosscmp-jdbc.xml 서술자의 entity-command 요소내에 지정됩니다. 기본 entity-commandjbosscmp-jdbc.xml파일의 defaults요소내에 지정될 수 있습니다. 각각의 entity 요소는 자신이 갖는 entity-command에 의해 기본적으로 entity-command를 덮어쓸 수 있습니다. entity-command의 컨텐츠 모델과 자식 요소들이 다음에 보여지고 있습니다.

jbosscmp-jdbc.xml entity-command 요소 컨텐츠 모델

그림 11.13. jbosscmp-jdbc.xml entity-command 요소 컨텐츠 모델

  • entity-command: 각 entity-command 요소는 엔티티 생성 구현을 지정합니다.

  • entity-command/name: name 속성에는 defaultsentity 요소내의 참조되는 entity-commands 섹션내에 정의된 명령어를 가능하게 하는 이름을 지정합니다.

  • entity-command/class: class 속성에는 org.jboss.ejb.plugins.cmp.jdbc의 구현을 지정합니다. JDBCCreateEntityCommand는 키 생성을 지원합니다. 데이터베이스 벤더에 특정한 명령어들은 일반적으로 org.jboss.ejb.plugins.cmp.jdbc를 서브클래스로 합니다. 데이터베이스가 삽입(insert)의 사이드 효과로서 프라이머리 키를 생성하는 경우 JDBCIdentityColumnCreateCommand 이거나 명령어가 생성된 키를 삽입시켜야 한다면 org.jboss.ejb.plugins.cmp.jdbc.JDBCInsertPKCreateCommand 입니다.

  • entity-command/attribute: 선택 사용되는 attribute 요소는 엔티티 명령어 구현 클래스에서 사용가능하게 되는 임의의 name/value 속성 쌍의 지정을 가능하게 합니다. attribute 요소는 name 속성을 지정하는 name 속성을 필요로 하며, attribute 요소 컨텐츠는 속성의 값입니다. 속성 값은 org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCEntityCommandMetaData.getAttribute(String) 메쏘드를 통해 액세스가 가능합니다.

11.11.1. 기존의 엔티티 명령어

다음은 현재 standardjbosscmp-jdbc.xml 서술자내에서 찾을 수 있는 entity-command 정의입니다:

  • default (org.jboss.ejb.plugins.cmp.jdbc.JDBCCreateEntityCommand) :
    이것이 standardjbosscmp-jdbc.xmldefaults 요소내에서 참조되는 entity-command 처럼 JDBCCreateEntityCommand는 기본 엔티티 생성입니다. 이 entity-command는 할당된 프라이머리 키 값을 사용하여 INSERT INTO 질의를 실행합니다.

  • no-select-before-insert(org.jboss.ejb.plugins.cmp.jdbc.JDBCCreateEntityCommand):
    이것은 jboss.jdbc:service=SQLExceptionProcessor 서비스를 가리키는 name="SQLExceptionProcessor" 속성을 지정하여 insert 전에 select 하는 것을 건너뛰게하는 default에 대한 변형입니다. SQLExceptionProcessor 서비스는 고유한 제약사항 위배에 대한 결정이 가능하도록 하는 boolean isDuplicateKey(SQLException e) 오퍼레이션을 제공합니다.

  • pk-sql(org.jboss.ejb.plugins.cmp.jdbc.JDBCPkSqlCreateCommand):
    JDBCPkSqlCreateCommand는 다음 프라이머리 키 값을 획득하기 위해 pk-sql 속성에서 제공되는 INSERT INTO 질의 문을 실행합니다. 이것의 주요 타겟 사용처는 시퀀스(sequence)를 갖는 데이터베이스의 지원입니다.

  • mysql-get-generated-keys (org.jboss.ejb.plugins.cmp.jdbc.mysql.JDBCMySQLCreateCommand):
    JDBCMySQLCreateCommand는 생성된 키를 가져오기 위한 MySQL의 네이티브(native) java.sql.Statement 인터페이스 구현에서의 getGeneratedKeys 메쏘드를 사용하는 INSERT INTO 질의를 실행합니다.

  • oracle-sequence (org.jboss.ejb.plugins.cmp.jdbc.keygen.JDBCOracleCreateCommand):
    JDBCOracleCreateCommand는 단일 문장내에서 키를 생성하기 위해 RETURNING 절과 합쳐진 시퀀스를 사용하는 오라클을 사용하는 경우의 create 명령어입니다. 시퀀스 컬럼의 이름을 지정하는 꼭 사용해야만 하는 sequence 요소를 갖습니다.

  • hsqldb-fetch-key (org.jboss.ejb.plugins.cmp.jdbc.hsqldb.JDBCHsqldbCreateCommand):
    JDBCHsqldbCreateCommand는 생성된 키를 가져오기 위해 CALL IDENTITY() 문을 실행한 후 INSERT INTO 질의를 실행합니다.

  • sybase-fetch-key (org.jboss.ejb.plugins.cmp.jdbc.sybase.JDBCSybaseCreateCommand):
    JDBCSybaseCreateCommand는 생성된 키를 가져오기 위해 SELECT @@IDENTITY 문을 실행한 후 INSERT INTO 질의를 실행합니다.

  • mssql-fetch-key (org.jboss.ejb.plugins.cmp.jdbc.keygen.JDBCSQLServerCreateCommand):
    IDENTITY 컬럼으로부터 가져오는 값을 사용하는 마이크로소프트사의 SQL 서버용 JDBCSQLServerCreateCommand. 기본적으로 트리거의 영향을 감소시키기 위해 SELECT SCOPE_IDENTITY()를 사용합니다; pk-sql 속성으로 덮어쓸 수도 있습니다. V7의 경우.

  • informix-serial (org.jboss.ejb.plugins.cmp.jdbc.informix.JDBCInformixCreateCommand):
    JDBCInformixCreateCommand는 생성된 키를 가져오기 위해 인포믹스 네이티브 java.sql.Statement 인터페이스 구현에서의 getSerial 메쏘드 사용후 INSERT INTO 질의를 실행합니다.

  • postgresql-fetch-seq (org.jboss.ejb.plugins.cmp.jdbc.keygen.JDBCPostgreSQLCreateCommand):
    시퀀스의 currval를 가져오는 PostgreSQL용 JDBCPostgreSQLCreateCommand. 선택 사용되는 sequence 속성은 기본 table_pkColumn_seq와 함께 시퀀스의 이름을 변경하기 위해 사용될 수도 있습니다.

  • key-generator (org.jboss.ejb.plugins.cmp.jdbc.JDBCKeyGeneratorCreateCommand):
    JDBCKeyGeneratorCreateCommandkey-generator-factory에 의해 참조되는 키 생성기(generator)로부터 프라이머리 키에 대한 값을 얻은 후 INSERT INTO 질의를 실행합니다. key-generator-factory 속성은 반드시 org.jboss.ejb.plugins.keygenerator.KeyGeneratorFactory 구현의 바인딩된 JNDI 이름을 제공해야 합니다.

  • get-generated-keys (org.jboss.ejb.plugins.cmp.jdbc.jdbc3.JDBCGetGeneratedKeysCreateCommand):
    JDBCGetGeneratedKeysCreateCommand는 자동-생성된 키를 가져오기 위한 능력을 갖는 JDBC3 prepareStatement(String, Statement.RETURN_GENERATED_KEYS)를 사용하여 만들어진 문장을 사용하여 INSERT INTO 질의를 실행합니다. 생성된 키는 PreparedStatement.getGeneratedKeys 메쏘드를 호출함으로써 획득됩니다. 이것이 JDBC3 지원을 필요로 하기 때문에, JDBC 드라이버를 지원하는 JDK1.4.1+ 에서만 사용할 수 있습니다.

알려진 프라이머리 키 cmp-field에 매핑된 생성된 키를 갖는 hsqldb-fetch-key entity-command를 사용하는 설정 예가 아래에 보여집니다.

예제 11.30. 알려진 pk cmp-filed에 대한 자동생성된 키 설정 샘플

<jbosscmp-jdbc>
  <enterprise-beans>
    <entity>
      <ejb-name>LocationEJB</ejb-name>
      <pk-constraint>false</pk-constraint>
      <table-name>location</table-name>
                 
      <cmp-field>
        <field-name>locationID</field-name>
        <column-name>id</column-name>
        <auto-increment/>
      </cmp-field>
      <!-- ... -->
      <entity-command name="hsqldb-fetch-key"/>
                 
    </entity>
  </enterprise-beans>
</jbosscmp-jdbc>

명백한 cmp-field 없이 알려지지 않은 프라이머리 키를 사용하는 대체가능한 예제가 아래에 보여지고 있습니다.

<jbosscmp-jdbc>
    <enterprise-beans>
        <entity>
            <ejb-name>LocationEJB</ejb-name>
            <pk-constraint>false</pk-constraint>
            <table-name>location</table-name>
            <unknown-pk>
                <unknown-pk-class>java.lang.Integer</unknown-pk-class>
                <field-name>locationID</field-name>
                <column-name>id</column-name>
                <jdbc-type>INTEGER</jdbc-type>
                <sql-type>INTEGER</sql-type>
                <auto-increment/>
            </unknown-pk>
            <!--...-->
            <entity-command name="hsqldb-fetch-key"/>
        </entity>
    </enterprise-beans>
</jbosscmp-jdbc>

11.12. Defaults

JBossCMP의 전역(global) defaultsserver/<server-name>/conf/ 디렉터리의 standardjbosscmp-jdbc.xml 파일에 정의됩니다. 각각의 어플리케이션은 jbosscmp-jdbc.xml 파일내에서 전역 defaults를 덮어쓸 수 있습니다. defaults 옵션은 설정 파일의 defaults 요소내에 포함되어 있으며, 그 컨텐츠 모델은 다음과 같습니다.

jbosscmp-jdbc/defaults 컨텐츠 모델

그림 11.14. jbosscmp-jdbc/defaults 컨텐츠 모델

defaults 섹션의 예를 아래에 표시하였습니다:

<jbosscmp-jdbc>
    <defaults>
        <datasource>java:/DefaultDS</datasource>
        <datasource-mapping>Hypersonic SQL</datasource-mapping>
        <create-table>true</create-table>
        <remove-table>false</remove-table>
        <read-only>false</read-only>
        <read-time-out>300000</read-time-out>
        <pk-constraint>true</pk-constraint>
        <fk-constraint>false</fk-constraint>
        <row-locking>false</row-locking>
        <preferred-relation-mapping>foreign-key</preferred-relation-mapping>
        <read-ahead>
            <strategy>on-load</strategy>
            <page-size>1000</page-size>
            <eager-load-group>*</eager-load-group>
        </read-ahead>
        <list-cache-max>1000</list-cache-max>
    </defaults>
</jbosscmp-jdbc>

11.12.1. jbosscmp-jdbc.xml defaults 선언 샘플

각각의 옵션은 엔티티와 관계 혹은 모두에 적용될 수 있으며 특정한 엔티티나 관계내에 덮어쓸 수 있습니다. 각 옵션에 대한 세부설명은 다음과 같습니다:

  • datasource: 선택 사용되는 요소로써 데이터소스를 룩업하는데 사용하는 jndi-name 입니다. 엔티티나 relation-table에 의해 사용되는 모든 데이터베이스 커넥션들은 데이터소스로부터 얻어집니다. 엔티티에 대해 서로다른 데이터소스를 갖는 경우, findersejbSelects에서 질의할 수 있는 도메인의 제약범위가 넓어지게 됩니다.

  • datasource-mapping: 선택 사용되는 이 요소에서는 자바 타입을 어떻게 SQL 타입으로 매핑시킬지와 EJB-QL 함수를 데이터베이스에 특화된 함수로 어떻게 매핑시킬지를 결정하는 type-mapping의 이름을 지정합니다. 타입 매핑은 11.13.2 섹션, “타입 매핑”에서 논의합니다.

  • create-table: 선택 사용하는 요소로써 true인 경우 JBossCMP는 엔티티에 대한 테이블을 생성하기 위한 시도록 하도록 지시됩니다. 어플리케이션이 배치되면, JBossCMP는 테이블을 생성하기전에 이미 존재하는지를 점검합니다. 테이블을 찾으면, 테이블은 만들어지지 않습니다. 이 옵션은 개발 초기 단계에서 테이블 구조가 자주 변경되는 경우 유용합니다. 기본값은 false 입니다.

  • alter-table: create-table이 자동적으로 스키마 생성을 하기위해 사용된다면, alter-table은 엔티티 빈에 변경된 사항들과 함께 현재 스키마를 유지하는데 사용됩니다. 테이블 치환(Alter)은 다음에 오는 특정한 작업을 수행하게 됩니다:

    • 새로운 필드가 생성되게 됩니다.

    • 더이상 사용되지 않는 필드는 제거되게 됩니다.

    • 선언된 길이보다 짧은 문자열 필드는 선언된 길이만큼 늘어납니다(모든 데이터베이스에서 지원되지 않음).

  • remove-table: 선택 사용되는 요소로써 true인 경우, JBossCMP는 각각의 엔티티에 대해 테이블을 드롭하려는 시도를 하며 각 관계 테이블은 관계로 매핑됩니다. 어플리케이션이 배치제거되는 경우, JBossCMP는 테이블을 드롭시키려는 시도를 하게 됩니다. 이 옵션은 개발 초기 단계에서 테이블 구조를 자주 변경하는 경우 매우 유용합니다. 기본값은 false입니다.

  • read-only: 선택 사용되는 요소로써 true인 경우 빈 프로바이더는 어떠한 필드의 값도 변경시킬 수 없다는 것을 지시합니다. 읽기만 가능한 필드는 데이터베이스에 저장되거나 삽입되지 않습니다. 프라이머리 키 필드가 읽기전용이면, create 메쏘드는 CreateException을 발생시키게 됩니다. read-only 필드에 대해 set 액세서가 호출된다면, EJBException을 발생시킵니다. 읽기전용 필드는 last update와 같은 데이터베이스 트리거에 의해 채워지는 필드에 유용합니다. read-only 옵션은 필드 기저당 덮어쓰기가 가능합니다. 기본값은 false 입니다.

  • read-time-out: 선택 사용되는 요소로써 읽기전용 필드가 올바른 것에 대해 읽어들여지는 시간의 정도를 밀리세컨즈로 지정합니다. 0 값은 값이 트랜잭션 시작시 항상 다시 로딩되는 것을 의미하며, -1인 경우에는 값은 절대로 타임아웃되지 않습니다. 이 옵션 또한 CMP 필드 기저(basis)당 덮어씌여질 수 있습니다. read-only가 false라면, 이 값은 무시됩니다. 기본값은 -1 입니다.

  • row-locking: 선택 사용되는 요소로써 true인 경우, JBossCMP는 트랜잭션내의 모든 로딩된 열에 락을 걸도록 지시합니다. 대부분의 데이터베이스는 이것을 엔티티가 로딩될 때 SELECT FOR UPDATE 문법을 사용하여 구현하지만, 실제 문법은 이 엔티티에서 사용되는 datasource-mapping내의 row-locking-template에 의해 결정되어집니다.

  • pk-constraint: 선택 사용되는 요소로써 true이면, JBossCMP는 테이블을 생성할 때 프라이머리 키 제약조건을 추가하도록 지시됩니다. 기본값은 true 입니다.

  • preferred-relation-mapping: 선택 사용되는 요소로써 관계에 대한 선호되는 매핑 스타일을 지시합니다. preferred-relation-mapping 요소는 반드시 foreign-key 혹은 relation-table중에 하나 이어야 합니다.

  • read-ahead: 선택 사용되는 요소로써 질의 결과와 엔티티에 대한 CMR 필드의 캐슁을 제어합니다. 이 옵션은 11.7.3 섹션, “Read-ahead”에서 논의되었습니다.

  • list-cache-max: 선택 사용되는 요소로써 이 엔티티에 의해 추적될 수 있는 read-lists의 갯수를 지정합니다. 이 옵션은 11.7.3.2 섹션, “on-load”에서 논의되었습니다. 기본값은 1000 입니다.

  • clean-read-ahead-on-load: 엔티티가 read ahead 캐쉬로부터 로딩될 때, JBoss에서는 read ahead 캐쉬로부터 사용된 데이터를 제거합니다. 기본값은 false 입니다.
  • fetch-size: 선택 사용되는 요소로써 사용되는 데이터저장소쪽으로의 한번의 라운드-트립에서 읽혀지는 엔티티의 갯수를 지정합니다. 기본값은 0 입니다.

  • unknown-pk: 선택적으로 사용하는 요소로써 java.lang.Object의 알려지지 않은 프라이머리 키 타입에 대한 기본 매핑을 영속적인 저장소에 매핑시키는 정의를 허용합니다.

  • entity-command: 선택적으로 사용하는 요소로써 엔티티 생성을 위한 기본 명령어를 정의할 수 있도록 합니다. 이 옵션은 11.11 섹션, “엔티티 명령어와 프라이머리 키 생성”에서 상세히 논의되었습니다.

11.13. 데이터소스 커스터마이제이션

JBossCMP는 많은 데이터베이스에 대한 미린 정의된 타입-매핑을 포함하고 있습니다: Cloudscape, DB2, DB2/400, Hypersonic SQL, InformixDB, InterBase, MS SQLSERVER, MS SQLSERVER2000, mySQL, Oracle7, Oracle8, Oracle9i, PointBase, PostgreSQL, PostgreSQL 7.2, SapDB, SOLID, 그리고 Sybase. 제공되는 매핑이 마음에 들지 않거나 여러분이 사용하는 데이터베이스를 지원하지 않는다면, 새로운 매핑을 정의하시면 됩니다. 제공되는 매핑중내에 오류가 있다거나 새로운 데이터베이스에 대한 매핑을 새로 만들었다면, 소스포지에 위치한 JBoss 프로젝트 페이지에 패치를 올려주시기 바랍니다.

데이터베이스의 커스터마이제이션은 jbosscmp-jdbc.xml 서술자의 type-mapping 섹션을 통해 작업하게 됩니다. type-mapping 요소에 대한 컨텐츠 모델이 그림 11.15, “jbosscmp-jdbc type-mapping 요소 컨텐츠 모델.”에 표시되어 있습니다. 해당 요소들은 다음과 같습니다:

  • name: 필요한 요소로써 데이터베이스 커스터마이제이션을 식별하는 이름을 제공합니다. defaultsentity안의 datasource-mapping 요소에 의한 매핑을 참조하는데 사용됩니다.

  • row-locking-template: 필요한 요소로써 선택된 열에 대한 열의 락킹을 생성하는데 사용된 PreparedStatement 템플릿을 지정합니다. 템플릿은 반드시 세 개의 인수를 지원해야만 합니다:

    1. select 절
    2. from 절. 테이블의 순서는 현재 보장되지 않습니다.
    3. where 절

    select 문장내에서 열의 락킹을 지원하지 않을 경우, 이 요소는 비어있어야만 합니다. 열의 락킹에 대한 가장 일반적인 형태는 다음과 같은 select for update 입니다: SELECT ?1 FROM ?2 WHERE ?3 FOR UPDATE.

  • pk-constraint-template: 필요한 요소로써 테이블 생성 문장내에서 프라이머리 키 제약조건을 생성시키는데 사용되는 PreparedStatement 템플릿을 지정합니다. 템플릿은 반드시 두 개의 인수를 지원해야만 합니다:

    1. 프라이머리 키 제약조건의 이름; 여기서 이름은 항상 pk_{table-name}이 됩니다.
    2. 프라이머리 키 컬럼명의 콤마로 분리된 리스트.

    테이블 생성 문장내에서 프라이머리 키 제약조건절을 지원하지 않는다면, 이 요소는 반드시 비어있어야만 합니다. 프라이머리 키 제약조건에 대한 가장 일반적인 형태는 CONSTRAINT ?1 PRIMARY KEY (?2) 입니다.

  • fk-constraint-template: 별도의 문장내에 외래 키 제약조건을 생성시키는데 사용되는 템플릿입니다. 템플릿은 반드시 다섯 개의 인수를 지원해야만 합니다:

    1. 테이블명
    2. 외래 키 제약조건 이름; 이름은 항상 fk_{table-name}_{cmr-field-name} 입니다.
    3. 외래 키 컬럼명의 컴마로 분리된 리스트.
    4. 참조 테이블명.
    5. 참조되는 프라이머리 키 컬럼명의 컴마로 분리된 리스트.

    데이터소스가 외래 키 제약조건을 지원하지 않는다면, 이 요소는 반드시 비어야만 합니다. 외래 키 제약조건의 가장 일반적인 형태는 ALTER TABLE ?1 ADD CONSTRAINT ?2 FOREIGN KEY (?3) REFERENCES ?4 (?5) 입니다.

  • auto-increment-template: 자동 증가 컬럼을 지정하기 위한 SQL 템플릿을 선언합니다.

  • add-column-template: alter-table이 true인 경우, 이 SQL 템플릿에는 존재하고 있는 테이블에 컬럼을 추가시키기 위한 문법을 지정합니다. 기본값은 ALTER TABLE ?1 ADD ?2 ?3 입니다. 매개변수는 다음과 같습니다:

    1. 테이블명
    2. 컬럼명
    3. 컬럼 타입
  • drop-column-template: alter-table이 true인 경우, 이 SQL 템플릿에는 존재하고 있는 테이블로부터 컬럼을 드롭시키기 위한 문법을 지정합니다. 기본값은 ALTER TABLE ?1 DROP ?2 입니다. 매개변수는 다음과 같습니다:

    1. 테이블명
    2. 컬럼명
  • alter-column-template: alter-table이 true인 경우, 이 SQL 템플릿에는 존재하는 테이블로부터 컬럼의 타입을 바꿔주기 위한 문법을 지정합니다. 기본값은 ALTER TABLE ?1 ALTER ?2 TYPE ?3 입니다. 매개변수는 다음과 같습니다:

    1. 테이블명
    2. 컬럼명
    3. 컬럼 타입
  • alias-header-prefix: 필요한 요소로써 별칭(alias) 헤더를 생성할 때 사용되는 접두사를 지정합니다. 별칭 헤더는 이름 중복을 방지하기 위해 EJB-QL 컴파일러에 의해 생성된 테이블 별칭의 앞쪽에 붙습니다. 별칭 헤더는 다음과 같이 구성됩니다: alias-header-prefix + int_counter + alias-header-suffix. "t"라는 alias-header-prefix와 "_"라는 alias-header-suffix가 지정되면 별칭 헤더는 t0_가 됩니다.

  • alias-header-suffix: 필요한 요소로써 생성된 별칭 헤더의 접미사 부분을 지정합니다.

  • alias-max-length: 필요한 요소로써 생성된 별칭 헤더에 대한 허용되는 최대 길이를 지정합니다.

  • subquery-supported: 필요한 요소로써 type-mapping 서브쿼리가 true 혹은 false 인지를 지정합니다. 몇몇 EJB-QL 오퍼레이터는 기존의 서브쿼리에 매핑되어있습니다. subquery-supported가 false이면, EJB-QL 컴파일러는 left join을 사용하게 되며 널(null)입니다.

  • true-mapping: 필요한 요소로써 EJB-QL 질의내에서 true 식별을 정의합니다. 예제에는 TRUE, 1, 그리고 (1=1)이 포함되어 있습니다.

  • false-mapping: 필요한 요소로써 EJB-QL 질의내에서 false 식별을 정의합니다. 예제에는 FALSE, 0, 그리고 (1=0)이 포함되어 있습니다.

  • function-mapping: 선택 사용되는 요소로써 EJB-QL 함수를 SQL 구현으로의 매핑을 하나 이상 지정합니다. 자세한 사항은 11.13.1 섹션, “함수 매핑”을 참조하십시오.

  • mapping: 필요한 요소로써 자바 타입을 대응되는 JDBC와 SQL 타입으로의 매핑을 지정합니다. 자세한 사항은 11.13.2 섹션, “타입 매핑”을 참조하십시오.

jbosscmp-jdbc type-mapping 요소 컨텐츠 모델.

그림 11.15. jbosscmp-jdbc type-mapping 요소 컨텐츠 모델.

11.13.1. 함수 매핑

  • function-name: 필요한 요소로써 EJB-QL 함수 이름이 주어집니다. 즉, concat, substring.

  • function-sql: 필요한 요소로써 적용되고 있는 데이터베이스에 알맞은 함수를 위한 SQL을 지정합니다. concat 함수에 포함된 예는 다음과 같습니다 :
    (?1 || ?2), concat(?1, ?2), (?1 + ?2).

11.13.2. 타입 매핑

type-mapping은 자바 클래스 타입과 데이터베이스 타입 사이의 매핑에 대한 간단한 셋입니다. 타입 매핑에 대한 셋은 mapping 요소의 셋으로 정의되며, 그 컨텐츠 모델은 다음과 같습니다.

jbosscmp-jdbc mapping 요소 컨텐츠 모델.

그림 11.16. jbosscmp-jdbc mapping 요소 컨텐츠 모델.

JBossCMP가 타입에 대한 매핑을 찾지 못하는 경우에는 객체를 직렬화하고 java.lang.Object 매핑을 사용합니다. 아랫쪽에서 mapping 요소의 세 자식 요소들에 대해 설명하였습니다:

  • java-type: 필요한 요소로써 매핑된 자바 클래스의 완전한 형태를 갖는 이름이 주어집니다. 클래스가 java.lang.Short와 같은 기본 래퍼 클래스인 경우, 매핑 또한 기본형 타입에 적용됩니다.

  • jdbc-type: 필요한 요소로써 JDBC의 PreparedStatement에서 매개변수를 설정하거나 JDBC ResultSet으로부터 데이터를 로딩시킬 때 사용되는 JDBC 타입을 지정합니다. 올바른 타입들이 java.sql.Types내에 정의되어 있습니다.

  • sql-type: 필요한 요소로써 테이블 생성 문장내에서 사용되는 SQL 타입을 지정합니다. 올바른 타입은 여러분이 사용하는 데이터베이스 벤더에 의해서만 제한됩니다.

오라클9i의 short에 대한 mapping 요소 예제가 아래에 보여집니다.

예제 11.31. 오라클9i에 대한 short 매핑 샘플

<jbosscmp-jdbc>
    <type-mappings>
        <type-mapping>
            <name>Oracle9i</name>
            <!--...-->
            <mapping>
                <java-type>java.lang.Short</java-type>
                <jdbc-type>NUMERIC</jdbc-type>
                <sql-type>NUMBER(5)</sql-type>
            </mapping>
        </type-mapping>
    </type-mappings>
</jbosscmp-jdbc>

11.13.3. User 타입 매핑

org.jboss.ejb.plugins.cmp.jdbc.Mapper 인터페이스의 인스턴스를 지정함으로써 User 타입 매핑을 통해 JDBC 컬럼 타입을 커스텀 CMP 필드 타입으로 매핑시킬 수 있습니다. 그 정의는 아래에 보여지고 있습니다.

예제 11.32. org.jboss.ejb.plugins.cmp.jdbc.Mapper 인터페이스

public interface Mapper
{
    /**
     * 이 메쏘드는 CMP 필드가 저장될 때 호출됩니다.
     * @param fieldValue - CMP 필드 값
     * @return column value.
     */
    Object toColumnValue(Object fieldValue);    

    /** 
     * 이 메쏘드는 CMP 필드가 로딩될 때 호출됩니다.
     * @param columnValue - 로딩된 컬럼 값.
     * @return CMP field value.
     */
    Object toFieldValue(Object columnValue);
}

견본적인 유스케이스는 정수 타입을 자신의 타입-보호 자바 enum 인스턴스로 매핑하는 것입니다. 하나 이상의 user-type-mapping 요소로 구성된 user-type-mappings 요소의 컨텐츠 모델이 아래에 보여지고 있습니다.

user-type-mapping 컨텐츠 모델

그림 11.17. user-type-mapping 컨텐츠 모델

  • java-type: 매핑내에서의 CMP 필드 타입에 대한 완전한 형태를 갖는 이름.

  • mapped-type: 매핑내에서의 데이터베이스 타입에 대한 완전한 형태를 갖는 이름.

  • mapper: java-typemapped-type 사이의 변환을 처리하는 Mapper 인터페이스 구현의 완전한 형태를 갖는 이름.

  • check-dirty-after-get: 이 값은 기본형(primitive) 타입과 기본 java.lang 불변의 랩퍼(wrappers)에 대해서는 기본적으로 false가 됩니다 (Integer, String, 등등). 잠재적으로 바뀔 수 있는 객체에 대해서 JBoss는 이들 필드를 get 오퍼레이션 이후에 잠재적으로 dirty 해지는 것으로 표시하게 됩니다. 하나의 객체에 대해 dirty 체크 하는 것이 과도한 부하를 일으킨다면, check-dirty-after-get을 false로 설정하여 최적화시킬 수 있습니다.

  • state-factory: 필드에 대한 dirty 확인을 수행할 수 있는 상태 팩토리 객체(state factory object)의 클래스 이름을 지정합니다. 상태 팩토리 클래스는 반드시 CMPFieldStateFactory 인터페이스를 구현해야만 합니다.



[3] 로컬 인터페이스라는 용어는 EJBLocalObject 단독으로 참조하는데 사용될 뿐만 아니라, EJBLocalObject/EJBLocalHome 조합도 참조합니다. 비록 이것때문에 혼동되기는 하나, EJB 커뮤니티내에서 이 용어의 현재 사용법입니다.

[4] JBoss를 포함하여 대부분의 J2EE 서버에서는 pass-by-reference 시만틱을 사용하여 원격 인터페이스를 통해 in-VM 호출을 최적화시킬 수 있습니다.

[5] DVC가 자바빈의 네이밍 컨벤션의 사용에 대한 요구사항은 향후 릴리즈될 JBossCMP에서 사라지게 될 것입니다.

[6] 이러한 제약사항은 향후 릴리즈에서 역시 사라지게 될 것입니다. 현재 계획에는 인수를 갖지 않는 메쏘드로부터 가져올 수 있는 값을 허용하고 단일 인수 메쏘드나 컨스트럭쳐와 함께 설정될 수 있습니다.

[7] EJB 사양서는 동일한 VM내의 서로다른 어플리케이션들의 엔티티 사이에서의 관계 조차 허용하지 않습니다.

[8] 사양서가 일치하지 않는 첫 번째 장소입니다. 사양서가 다음과 같은 태그를 사용할 경우 보다 쉬울 수 있습니다: relationships, relationship, 그리고 relationship-name.

[9] 외래 키 매핑과 함께 이 요소는 비어있을 수 있다는 것에 주의하십시오;이것은 현재 엔티티를 위한 외래 키가 될 수 없다는 것을 의미합니다. 이것은 일대다 관계에서 다측을 위해 필요합니다. 즉, Organization-Gangster 예제에서 Gangster쪽입니다.

[10] ejbql 접미사를 무시합니다; 이것은 필수는 아닙니다. 다음에 이 질의는 JBossQL과 declaredSQL을 사용하여 구현되어지며, 접미사는 jbosscmp-jdbc.xml 파일내에서 서로다른 질의 스펙을 분리하는데 사용됩니다.

[11] 리차드 몬손-해펠이 쓴 "엔터프라이즈 자바빈 3차 개정판"의 244 페이지에서 제공하는 "(r.amountPaid * .01) > 300" 예제에서는 WHERE 절내에서 수식 오퍼레이터의 사용방법을 보여주고 있으며, EJB-QL 문법이 올바른지 아닌지를 표시하였습니다.

[12] 데이터베이스와 접촉없이 프라이머리 키를 실제 엔티티 객체로 빠르게 변환시키기 때문에 매우 유용한 finder입니다. 한가지 약점은 데이터베이스내에 존재하지 않는 프라이머리 키를 갖는 엔티티 객체를 생성할 수 있다는 것입니다. 안좋은 엔티티에 대한 메쏘드의 호출은 NoSuchEntityException을 발생시키게 됩니다. 또 다른 약점은 finder의 구현에서 EJB 스펙을 위배하는 엔티티 빈이 생기며, JBoss EJB 검증자(verifier)가 StrictVerifier 속성을 false로 설정하지 않는다면 이러한 엔티티의 배치에 실패한다는 것입니다.

[13] 이러한 특성에 대한 이유는 JBoss 컨테이너내부의 질의 결과 처리와 관계되어 있습니다. 비록 선택된 실제 엔티티 빈이 질의가 실행되면 리턴되는 것처럼 보일지라도, JBoss는 매칭되는 엔티티들의 프라이머리 키만을 실제로 리턴하며 이것에 대해 메소드가 호출되기 전까지 엔티티는 로드되지 않습니다.

[14] 일반적으로 JBossCMP는 contactInfo 필드도 로딩하지만, 가독성을 위해 contact 정보를 일곱개의 컬럼으로 매핑하기 때문에 이 예제에서는 disabled 되어 있습니다. contactInfo 필드의 기본 로딩을 disable 시키는데 사용된 실제 설정이 목록 6-12에 제공됩니다.

[15] JBossCMP는 read-ahead 캐쉬 구현내에서 소프트 참조를 사용하기 때문에 데이터는 캐쉬되어진 후 즉시 릴리즈됩니다.

[16] 앞으로 나올 버전에서 JBossCMP는 트랜잭션사이에서 커밋 옵션 B 엔티티에 대한 현재 데이터를 유지할 수 있게 되고 last-update 낙관적 락킹을 사용하여 데이터가 여전히 현재와 동일한지를 검증할 수 있게 될 것입니다. 많은 량의 데이터를 포함하는 엔티티의 경우, 이것은 현저한 성능 향상을 기대할 수 있게 합니다.

[17] 이것보다 오히려 더 않좋습니다. JBossCMP는 이 질의 각각을 실행하는 것보다 세 배나 더 실행합니다; 액세스되는 CMP 필드 각각에 대해 한번씩. 이것은 사전에 로딩된 값이 CMP 필드 액세서 호출 사이에서 버려지기 때문입니다.