서두… - devkk.tistory.comdevkk.tistory.com/attachment/hk0.pdf · ejb 스펙이 나온 지...

141
cafe.daum.net/weblogic - 1 - 서진권 [email protected] 서두… 이 글은 제가 EJB프로젝트를 해오면서 느낀 점들을 정리해서 다시 한번 EJB개념을 올바르게 하고 싶어서 입니다. 강의는 반말로 하겠습니다. ㅋㅋㅋ 왜냐구요 ? 배우는 데는 왕도고 나발이고 없습니다. 그 대신 욕은 안 합니다.^^ 암 튼 이해를 바라며, 무단으로 필자의 허락 없이 daum 웹로직 카페(cafe.daum.net/weblogic)를 제외하고는 게시 하지 않았으면 좋겠습니다. 또, 이 문서가 상업적으로 이용되어서도 안되겠습니다. 끝으로 잘 이해가 가지 않거나 EJB프로젝트 도중 문제가 생겼을 경우 언제든지 메일 주시면 감사하겠습니다.

Transcript of 서두… - devkk.tistory.comdevkk.tistory.com/attachment/hk0.pdf · ejb 스펙이 나온 지...

cafe.daum.net/weblogic

- 1 - 서진권 [email protected]

서두…

이 글은 제가 EJB프로젝트를 해오면서 느낀 점들을 정리해서 다시 한번 EJB개념을 올바르게 하고 싶어서 입니다.

강의는 반말로 하겠습니다. ㅋㅋㅋ 왜냐구요 ? 배우는 데는 왕도고 나발이고 없습니다. 그 대신 욕은 안 합니다.^^

암 튼 이해를 바라며, 무단으로 필자의 허락 없이 daum 웹로직 카페(cafe.daum.net/weblogic)를 제외하고는 게시

하지 않았으면 좋겠습니다. 또, 이 문서가 상업적으로 이용되어서도 안되겠습니다. 끝으로 잘 이해가 가지 않거나

EJB프로젝트 도중 문제가 생겼을 경우 언제든지 메일 주시면 감사하겠습니다.

cafe.daum.net/weblogic

- 2 - 서진권 [email protected]

INDEX

제 1장 프롤로그·························································································3

1.1 Tier ·······························································································4

1.2 EJB(Enterprise Java Bean)가 도대체 뭐길래…············································5

1.3 EJB의 종류······················································································6

1.4 EJB클래스 구성·················································································9

제 2장 준비운동······················································································· 11

2.1 클래스 패스 설정 ············································································ 11

2.2 오라클 8i JDBC XA thin 드라이버 ························································· 12

2.3 웹로직 설치 ··················································································· 13

2.4 웹로직 설정 ··················································································· 23

2.5 환경변수 설정 ················································································ 37

2.6 웹로직 기동 ··················································································· 38

2.7 웹로직 중지 ··················································································· 41

2.8 웹로직 Admin 콘솔 ·········································································· 43

2.9 웹로직 Web Application 위치(HTML, JSP)··············································· 45

2.10 웹로직 JDBC 컨넥션 풀 설정 ···························································· 45

2.11 Directory 정리··············································································· 48

제 3장 Stateless Session Bean ···································································· 50

3.1 컴파일, 배치(Deploy), 실행(run)··························································· 63

제 4장 Stateful Session Bean ······································································ 73

제 5장 CMP (Container Managed Persistent) Entity Bean ····································· 89

5.1 Finder와 EJB QL(Query Language) ······················································111

5.2 PK클래스 셋트와 이벤트성 메소드의 관계···············································115

제 6장 BMP(Bean Managed Persistent) Entity Bean···········································122

cafe.daum.net/weblogic

- 3 - 서진권 [email protected]

제 1장 프롤로그

EJB 스펙이 나온 지 어느덧 벌써 4-5년 정도가 흘 다. 이제 국내 자바 기반으로 된 프로젝트에서

는 어느 정도 EJB를 사용한다. 4-5년 밖에 안된 최신의 외국기술이 국내에 굉장히 많이 사용하게 된

것은 EJB외에는 몇 개가 안 된다고 생각한다.

필자는 이제까지 많은 EJB 프로젝트를 해왔고, 또 여러 기업을 돌아다니며 프로젝트를 했던 이유로

서로 업무가 다른 EJB 프로젝트를 비교할 수 있는 기회가 많았고, 또 여러 EJB설계자, 개발자를 많

이 만나 EJB를 사용하는 사람들 또한 비교할 수 있는 기회가 많았다. 그러한 점으로 인해 개인적으로

이 프로젝트는 뭐가 잘못되었고, 이 엔지니어(EJB설계자, 개발자, 사용자 등을 포함)의 EJB개념과 생

각이 뭐가 잘못되었는지를 알 수 가 있었다.

필자가 이제까지 여러 업체를 돌아다니면서 느낀 거지만 우리나라의 EJB를 사용하는 사람 중 십중

팔구는 EJB를 잘못 사용하고 있다. 실례로 나의 경험상 EJB로 작성된 프로젝트가 단 한 개만 정확히

EJB를 준수했고 나머지 수없이 많은 프로젝트는 모두다 잘못된 EJB개념 및 실제 코딩이 되있었다.

이 글을 쓰는 목적은 Sun에서 발표한 EJB를 처음 접하게 되어 정확한 개념과 실제 프로젝트에서의

성공적인 프로젝트를 구축하고 싶은 엔지니어에게 기회를 제공하려고 하며, 이미 EJB의 개념과 프로

젝트를 했던 독자들도 이 글을 한번쯤 다시 읽어보기를 권하고 싶다. 또 EJB를 사용했던 프로젝트가

실패로 돌아갔던 독자에게도 읽어 보기를 권한다.

그리고 처음 EJB 미들웨어를 개발하려는 독자들도 읽어보기 바란다. 첫 단추를 잘못 맞추면 잘못 맞

춘 버릇으로 계속해서 잘못된 개념과 사용법으로 프로젝트를 할 수밖에 없다.

내 조그마한 개인적인 바람은 이 글을 모두 읽었던 엔지니어가 EJB프로젝트를 성공적으로 구축해서,

수없이 많은 국내의 제대로 된 서버 컴포넌트 중 하나였으면 하는 바램이다.

그럼 이쯤에서 서두는 이만 접고 이 강의에서 사용할 환경들을 설명하겠다.

* 환경

운영체제 : 유닉스, 리눅스, Window 환경 프로페셔널 이상(2000, XP, NT)

어플리케이션 서버 : 웹로직 7.0(SP4)

데이터베이스 서버 : 오라클 8i 이상

Java 2 Standard Edition SDK : 1.3이상

Java 2 Enterprise Edition SDK : 1.3이상

JDBC클라이언트 드라이버 : 오라클 8i JDBC드라이버 이상

* 이글을 읽어봐야할 독자들(다음 사항에 한가지 경우라도 해당되면 꼭…)

EJB 프로젝트를 한번도 해보지 않았으며, EJB라는 단어만 알고 있다.(단, Servlet, JSP를 사용하여

DB와 연동했다.)

EJB를 정말 완벽하게 구현하고 싶다.

cafe.daum.net/weblogic

- 4 - 서진권 [email protected]

EJB 프로젝트를 했는데 실패로 돌아갔다.

EJB의 Entity Bean 과 Session Bean을 구별을 못한다.

EJB프로젝트를 이제까지 Session Bean만 사용했다.

EJB를 이해는 했는데 완벽한 예제는 해보지 않았다.

그리고 이 글을 읽는 독자들은 적어도 JAVA에 대해서는 어느 정도 이해를 하고 있다고 생각하며, 또

Servlet, JSP(결과적으론 둘 다 같은 거지만…), Applet등을 사용하여 DB와 연동을 해봤다는 전제하에

서 진행을 하겠다. 만약 Java를 전혀 모른다든지, Servlet, JSP를 모르는 독자는 다른 서적이나 정보를

참고하여 이해한 다음 이 글을 보기 바란다. 그럼 본격적인 강의를 시작하겠다.

1.1 Tier 모든 어플리케이션 및 시스템은 3가지의 로직으로 구성된다. 데이터베이스 로직, 비즈니스 로직, 프

리젠테이션 로직 이다.

데이터 베이스 로직(Database Logic, DL) : 데이터 베이스 자체를 의미한다.

비지니스 로직(Business Logic, BL) : 데이터 베이스 로직을 절적히 가공하며, 가공된 데

이터는 프로젠테이션 로직으로 보내져서 I/O장치 에 표현하거나, 출력된다.

프리젠테이션 로직(Presentation Logic, PL) : 프리젠테이션 로직은 말 그대로 비즈니스

로직으로부터 받은 데이터를 사용자에게 보여준다(Presentation)

이 3가지 로직이 어플리케이션이나 시스템에 어떻게 배치가 되었냐 에 따라서 n-Tier 시스템이라고

부른다.

1 Tier : 가장 전통적이고 원시적이고 심플한 구조이다. DL, BL, PL 의 3가지 로직이 모

두 한 개의 물리적인 시스템 및 논리적인 어플리케이션에 내장되어있다. 예를 들면, 엑

셀, 디베이스, 로터스, 개인이 사용하는 일기장 등이 여기에 속한다. 쉽게 말해 PC에서

사용하는 데이터베이스 이다. 장점으로는 단순하고 심플해서 관리하기 쉽다. 단점으로는

속도증가를 할려면 반드시 하드웨어의 성능개선이 필요하며, 보안도 문제가 있다.

2 Tier : 데이터 베이스 로직이 물리적이거나 논리적으로 따로 존재한다. C/S(클라이언트

/서버)환경이라고 하기도 한다. 예를 들면 직접적으로 오라클 서버에 접속해서 데이터를

가져온후 화면에 뿌리는 시스템, 오라클의 Sqlplus 등이 있다. 장점으로는 이때부터 본

격적으로 여러사용자가 접속하여 DB를 공유했다. 완벽한 보안은 아니지만 데이터가 따

로 존재하기 때문에 어느 정도 보안은 해결되었다. 단점, 클라이언트가 늘어날수록 비용

이 치솟아 오른다. 클라이언트용 어플리케이션이 BL, PL이 같이 존재하므로 무겁다.

cafe.daum.net/weblogic

- 5 - 서진권 [email protected]

3 Tier : DL, BL, PL 3가지 모든 로직이 서로 물리적, 논리적으로 분리 되어있다. 현재 가

장 이상적인 시스템으로 선택되고 있다. 예를 들면, EJB(Sun), COM(Microsoft),

Tuxedo(Bea사), Corba,(OMG), MIDAS(Borland) 이러한 미들웨어를 기반으로된 시스템

들이다. 장점으로는 시스템 구조상 모든 로직이 분리되어서 데이터 베이스까지 접근하

는데는 3단계의 과정을 거치기 때문에 2Tier보다는 훨씬 안정적이다. 또한 클라이언트

쪽에 BL이 없어서 훨씬 안정적이며 가볍기 때문에 유지보수 비용도 적게 든다. 한번작

성된 비즈니스 로직은 차후 다시 개발을 하지 않고 호출하여 사용할 수 있다. 단점으로

는 초보적인 개발자가 개발하기가 드럽다(?). 또 시스템이 분리된 만큼 복잡하다.

1.2 EJB(Enterprise Java Bean)가 도대체 뭐길래…

EJB(Enterprise Java Bean)라는 말을 직역하면 다음과 같다.

“기업 자바 콩들” Bean은 “커피 콩” 이라는 뜻도 있음.

정말 뭐 같은 소리다. 너무 개념적이고, 또 중요한 Beans(콩들)라는 것도 모르겠다. 다시 한번 필자가

알기 쉽게 한글로 정리해서 써보겠다.

“기업환경에서의 자바 컴포넌트”

하나씩 설명해서 말해보자면, 기업은 분명 개인이 아니다. 그 말은 여러 사람이 같이 사용한다는 의

미이다. 만약 개인이라면 어떠한 자료를 공유하거나, 동시다발적은 일은 하지 않을것이다. Bean 이라

는 것은 일종의 컴포넌트 또는 어플리케이션(이하 Bean)이며, 그게 개인만 사용하면 그냥 자바 Bean

이다(Java Standard Edition). 자바를 개발한 Sun사는 개인사용자가 자바 Bean을 사용하고 있을 때,

기업환경에서 Bean을 작성하는 방법이라던가, 비즈니스 로직을 정의 하며, 다른 Bean들 서로가 자기

의 기능을 공유할 수 있도록 특별한 규약이 필요했다. 또 시기적으로 Microsoft의 COM을 견제할만한

어떠한 기술이 필요했으며, 그 당시 COM은 실제 업무에서는 그 다지 효용성이 없었다. 또한 개발자

들도 COM은 서로 다른 언어의 중간 매개체라고 생각할 뿐, COM이 추구하는 진정한 독립된 컴포넌

트 개발이라고 생각하는 개발자는 그 다지 없었다. 이러한 이유로 나온게 바로 EJB이다. 즉 현실세계

에서 말한다면 기업의 한 서버에 자바로 구현된 Bean(비즈니스 로직)을 여러 사용자 또는 다른 Bean

들이 사용할 수 있게 해놓은 규약이라고 생각하면 된다. COM과의 상대적인 적수인 자바의

RMI(Remote Mothod Invocation : 원거리 함수 호출)라는 기술이 있는데, 쉽게 말하자면 내 컴퓨터에

서 다른 컴퓨터의 기능을 호출해서 쓴다는 얘기이다. 중요한 점은 EJB는 바로 이 RMI의 기반으로 되

었다는 점이다.

그러면 기업에서 사용하는 빈들이 뭘까 ? 조그마한 의미에서는 개인이 사용하는 유틸리티부터 크게

는 인사관리 시스템에 접속해서 인사정보를 가져오는 Bean이라고 볼 수 있다. EJB 관점에서 바라봤

을 때 기업환경에서 사용하는 Bean들의 종류는 크게 두 가지이다.

cafe.daum.net/weblogic

- 6 - 서진권 [email protected]

첫째, 데이터 베이스 관련 Beans

둘째, 서비스 관련 Beans

기업환경에서 데이터 베이스 없이는 요즘 세상에서는 존재할 수 가 없다. 뭐 솔직히 요즘 세상이 아

니더라도 몇 백 년 전에도 공공기관 이라던지, 하다못해 상인들도 모두 종이로 작성된 데이터 베이스

를 가지고 있었으니, 요즘 기업환경에서 데이터 베이스는 당연히 없어서는 안될 존재이다. Sun은 위

의 두 가지 의미로 기업에서의 Bean를 설명하고 있다. 여기서 데이터 베이스 관련 Beans는 뭐 하는

Beans인지 이 글을 읽는 독자들은 분명히 알 것이다(DB에 대해서 어느정도 알기 때문에). 그러나 서

비스 관련 Beans는 무엇일까 ? 쉽게 생각해서 서버에서 시간을 가져오는 어떠한 Bean이 있다고 하

자. 그럼 이 Bean을 보고 서비스 관련 Beans라고 한다. 당연히 서버에서 시간만 가져오면 되므로 데

이터 베이스를 뒤지거나 접속할 필요가 없으니깐 서비스 관련 Beans라고 할 수 있겠다. 만약 잘 모

르겠다면 첨부터 다시 조목조목 읽어보기 바란다.

1.3 EJB의 종류

EJB에서는 크게 두 가지 빈이 있다.(주의!!! EJB 2.0 스펙 에서는 한가지가 추가 되었음. 그러나 아

직도 크게 두 가지로 일반적으로 생각한다.) 첫 번째 Entity Bean이며, 두 번째 종류는 Session Bean

이다. 눈치를 챈 독자라면 이 두 가지 Entity Bean과 Session Bean을 어느 정도 감을 잡았으리라고

생각한다.

Entity Bean

엔티티 빈은 위에서 말한 기업환경에서의 두 가지 Beans중 첫 번째 경우에 해당한다. 바로 데이터

베이스 관련 Bean이다. 기업환경에서 데이터 베이스가 존재하면 분명히 2차원적인 어떠한

자료구조가 있다. 이런 것들을 컴퓨팅 환경에서는 DB 테이블이라고 부르는데 이 DB테이블을 객체화,

컴포넌트화, 어플리케이션화 해놓은게 엔티티 빈이라고 생각하면 될 것이다. 이 엔티티 빈은 또 두

가지로 나누어 진다.

바로 CMP(Container Managed Persistence), BMP(Bean Managed Persistence)이다. CMP와 BMP를

알기 전에 일단 Container 라는 것을 알아야 하는데 간략하게 컨테이너라는 것은 EJB서비스를

해주고 EJB가 배치된 곳 이라고 생각하면 된다.

예를 들어 일반적으로 웹로직 서버를 사용하여 EJB를 배치했다면 웹로직 컨테이너를

사용하는것이고, 특별히 컨테이너가 없다면 바로 Sun사의 컨테이너를 사용하는 것이다. 이 강좌의

모든 예제는 모두 웹로직 컨테이너를 사용한다. 그럼 이제 CMP와 BMP를 설명해보자. CMP는

데이터베이스에서 일어나는 스키마, 이벤트(추가, 삭제등)를 모두 EJB를 배치한 컨테이너에서

관리한다는 말이다.

이렇게 되었을 경우 EJB개발자는 DB테이블이 어떠한 구조라는 것만 컨테이너에게 알려주면 그 후에

일어나는 이벤트, 트랜잭션등 은 모두 컨테이너가 알아서 한다. 한마디로 말해서 개발자가 DB때문에

cafe.daum.net/weblogic

- 7 - 서진권 [email protected]

노가다는 하지 않는다.

BMP는 이벤트, 트랜젹션등을 모두 컨테이너가 아닌 Bean에서 관리한다. 그 말은 Bean에서 모두

정의 한다는 의미이다. 또 그러한 이유 때문에 이벤트, 트랜잭션등은 모두 EJB개발자가 정의 해야

한다.

둘 다 장단점이 있다. CMP는 개발하기 쉽지만, 상세하게 개발자가 제어를 못한다. 왜 ?

컨테이너에서 관리하기 때문에… BMP는 굉장히 디테일 하게 제어를 할 수 있다. 하지만 노가다가

굉장히 심하다.

Session Bean

세션빈은 서비스 관련 빈을 정의 할 때 사용한다.(단, DB를 액색스 하는 경우도 있는데, 이경우는

주로 합계, 여러 레코드에 대한 연산등이다. 꼭 기억해 둘 것…) 주로 서비스적인 측면이 강하다.

예를 들어서 현재 서버의 IP주소를 구한다던가, EJB 클라이언트에서 서버에게 어떠한 배치처리를

요구한다던가 하는 것들을 바로 세션빈에서 정의하게 된다. 세션빈도 두 가지 종류로 나누어 진다.

바로 Stateless Session Bean과 Stateful Session Bean이다.

직역하자면… “상태를 가지고 있지 않는…”, “상태를 가지고 있는” ㅋㅋㅋ 여기서도 개소리가 나온다.

국내에 있는 모든 서적들은 저렇게 서술 되 있다. 영어 실력이 없어서 그런지 아니면 정확한 개념을

몰라서 그런지는 몰라도 “상태를 가지고 있지 않는” 이라는 말을 첨 듣는 사람이 어떻게 이해를

하겠는가 ? 저 말들만 가지고 지금 이 글을 읽고 있는 독자들은 이해가 가는가 ?

쉽게 정리해서 다시한번 말하자면 Stateless는 클라이언트가 어떠한 기능을 요구하면 바로 그

기능을 사용하고 끝나버린다(메모리에서 해지). 그이상 그 이하도 아니고 오로지 서버에 있는 함수

하나 호출하고 끝난다. 하지만 Stateful은 클라이언트가 어떠한 기능을 요구하고 나서도 계속

컨테이너에 살아있다.(메모리를 가지고 있음) 그럼 Stateless와 Stateful은 어떻게 사용할까 ?

Stateless : 서버의 시간을 가져오는 기능, IP Address를 가져오는 기능 또는 서버의 배치(Batch)

작업 등등 일반적인 서비스 개념

Stateful : 로그인 로그아웃, 장바구니, 인증 등등 서비스를 계속 유지 시켜 할 경우.

이제 다시 세션빈의 상태, 무상태를 설명할 수 있을 것이다. 상태는 바로 Stateful을 가리키는

말이며, 무상태는 Stateless를 가리키는 말이다.

참고로, 애석하게도 Stateful은 웹환경에서는 그다지 많이 사용하질 못한다…왜 ? jsp나 서블릿의

특성때문이다. 어떠한 특성이냐면, 어떠한 jsp가 실행되면, 그 jsp에서 썼던 자원들은 모두 반납을

해야된다. 반납을 하지 않으면, 메모리누수 때문에 언제 서버가 다운될지 모른다. Stateful을 사용하고

반납을 하면, 클라이언트에서 기입한 정보가 Stateful에서는 모두 삭제 되기 때문이다. 그럼 Stateful을

반납 하지 않으면 되지 않냐 ? 라는 질문은 하지 말기 바란다. 몇초전에 이미 설명했다.

Message Driven Bean

이 빈은 EJB 2.0에서 새로 추가된 빈이다. 간단하게 어떤 빈이냐면 클라이언트에서 컨테이너에게

어떠한 메시지(객체일수도 있고, 텍스트 일수도 잇다.)를 주면, 그것을 받은 컨테이너는 작성한

cafe.daum.net/weblogic

- 8 - 서진권 [email protected]

메시지 빈을 수행한다. 한가지 특이한 점은 Entity Bean, Session Bean모두 클라이언트에서 직접

호출할 수 있는데 반해 메시지 드리븐 빈 은 클라이언트에서 직접 호출할 수 없다. 메시지를

던져줘야지만 서버에서 동작을 일으킨다. MQS(Message Que Server)라고 생각하면 된다. 이걸 어디다

사용하느냐… 대량을 데이터를 처리하는 프로세스가 있다고 하자. 클라이언트(jsp, applet…)가 어떠한

작업을 요청하면 그에 대에 응답하는 시점은 대량의 데이터가 다 처리되고 나서 일 것이다. 그렇게

되면 클라이언트는 그 데이터가 다 처리될 때 까지 대기 해야 될 것 아닌가 ? 웹환경에서는 정말

우스운 일이다. 아무 응답도 없는 브라우져를 몇분이상 바라봐야 한다니… 이때 메시지 드리븐 빈을

사용하여, 클라이언트가 처리할 데이터를 무조건 서버에 등록하는 것이다. 그리고 그 데이터를 받는

메시지 드리븐 빈이 호출하고 그 데이터를 하나씩 처리한다. 단, 클라이언트에서는 그 데이터가

성공적으로 진행되었는지는 나중에 알수 있다.

자 ~ 그럼 EJB의 종류를 알아보았으며, 다시 한번 간략하게 정의 해보자

Entity Bean : 데이터 베이스 관련 빈

CMP : 컨테이너에서 이벤트, 트랜잭션등을 관리

BMP : 빈에서 이벤트, 트랜잭션등을 관리

Session Bean : 서비스 관련 빈

Stateless : 서비스(기능) 만 해주고 끝나는 빈

Stateful : 서비스를 해주고 나서도 클라이언트 정보를 가지고 있는 빈

Message Driven Bean : 메시지를 통하여 서버의 빈을 동작시킬수 있는 빈

cafe.daum.net/weblogic

- 9 - 서진권 [email protected]

1.4 EJB클래스 구성

모든 EJB(Message Driven Bean은 제외)는 다음 세가지 클래스가 기본적으로 필요하다.

Remote Interface : 이 자바 인터페이스는 클라이언트에서 어떠한 메소드를 사용하는지를

선언한다. 말 그대로 인터페이스라서 사용하고자 하는 함수 또는 프로시져만 선언한다. 왜

Remote Interface라고 이름이 붙여 졌냐면, 원격에서 마치, 클라이언트에 서버의 로직이 있는

것처럼 행동한다. 실제로는 모든 정의는 다음에 설명할 Bean 클래스에 정의 되어있다.

Home Interface : 클라이언트가 실제 EJB서버 컴포넌트에 접속할 때 가장 먼저 하는일이 이

Home Inteface 객체의 인스턴스를 받는다. 일단 인스턴스를 받는데 성공하면 그 다음에는 다시

Remote Interface를 구한다.

Bean Class : 이 클래스는 Remote Interface에서 추상적으로 정의된 함수들(껍데기 함수들)을

다시 재정의 한다. 이 클래스가 바로 비즈니스 로직을 담당하는 곳이다. 그리고 실제

EJB개발자들이 가장 작업을 많이 하는 클래스 이다.

그외 부수적인 클래스 또는 필요한 파일이 있다.

PK클래스 : 엔티티 빈을 사용 할 경우 테이블의 프라이머리 키 컬럼을 선언한 클래스 이다. 이

클래스로 레코드를 찾아서 적잘한 데이터 베이스 로직을 행한다.

ejb-jar.xml : EJB가 어떠한 구조인지를 정의하는 xml파일이다. Remote Interface클래스 명은

무엇있지, Home Interface 클래스는 무엇인지, Bean 클래스는 무엇인지 EJB이름은 무엇인지

등등을 기술한다. 배포 또는 배치 될 때 컨테이너는 이 xml파일을 참조하여 기본적인 위의

3가지 클래스 및 EJB이름등의 정보를 참조한다. 이 xml파일은 Sun에서 규약한 파일이다.

weblogic-ejb-jar.xml : EJB의 기본적인 것 외에 웹로직 컨테이너에서 제공되는 기능들을 정의

하는 곳이다. ejb-jar.xml 이미 Sun에서 규약했으며, 기본적인 EJB정보를 가지고 있다.

weblogic-ejb-jar.xml은 EJB가 웹로직의 어떤 DB 컨넥션 풀을 사용하는지, 빈이 캐쉬를 어떻게

설정할것인지 또, 가장 중요한 클라이언트에서 EJB에 바인딩 할 때 어떠한 JNDI이름으로

바인딩할 것인지를 정의한다.

weblogic-cmp-rdbms-jar.xml : 엔티티빈의 CMP를 사용할 경우 이 xml 파일에 테이블의

구조를 정의한다. 테이블명, 컬럼, 테이블을 물리적으로 새로만들것 인지 등등 테이블 스키마에

관련된 사항을 기입하면, 컨테이너에서는 이 파일을 참조하여 이벤트, 트랜잭션을 관리한다.

application.xml : Sun에서 정의한 java 어플리케이션 기본적인 정보를 가지고 있다. 통상 EJB는

jar로 묶여서 컨테이너에서 실행되는데, application.xml에 그 jar을 모듈로 정의하고, 여러 개의

jar파일의 EJB를 하나의 ear파일로 묶어서 관리할 수도 있다.

이상 EJB를 작성할 때 필요한 기본적이고 부수적인 파일들에 설명했다. 그럼 클라이언트에서 어떻게

EJB를 호출하는것일까 ? 웹로직에 우리가 작성한 어떤 EJB가 이미 배치(이하 디폴로이)되었다는

가장하에서 순서별로 적어보았다.

cafe.daum.net/weblogic

- 10 - 서진권 [email protected]

1. 클라이언트가 JNDI 이름으로 EJB컨테이너에 접속한다.(바인딩)

2. EJB컨테이너는 클라이언트에서 요청한 JNDI이름에 해당하는 EJB홈인터페이스의 인스턴스를

클라이언트에게 넘겨준다.

3. 클라이언트가 구해온 홈인터페이스를 통해 리모트 인터페이스를 구한다.

4. 리모트 인터페이스를 통하여 클라이언트는 서버에 이미 작성해놓은 Bean클래스의

함수(비즈니스 로직)을 수행한다.

5. 필요할 경우 가공된 데이터를 화면이나 기타 다른 곳으로 출력한다.

간략 하게 나마 Tier, 미들웨어, EJB개념에 대해서 설명했다. 이제 여기 까지 읽은 독자들은 EJB에

대해서는 간략하게 설명할 줄 알것이다.

서두에 국내의 EJB시스템이 분명 십중팔구는 잘못 되있다고 말햇는데, 왜 이러한 사태가 일어났는

지 개인적으로 생각해보면, 교육기관과 국내 서적이 문제가 많다고 볼 수 있다. 몇 년 전 내가 윈도우

환경에서 C/S플랫폼이 주류를 이루던 시절, 모 기업에서 웹로직 3.0 이라는 어플리케이션 서버교육을

하니 한번 교육을 받아 보라는 것이었다. 물론 그때 EJB 스펙이 발표 된지 체 1년도 안된 시점이었다.

또 나는 EJB가 대충 뭐 하는 솔루션인지도 알고 있었고, 더 중요한 것은 내가 윈도우 플랫폼에서 작

업을 많이 했던 터라 EJB의 적수인 윈도우의 COM에 대해서도 어느 정도는 알고 있었다. 첫 2,3일

정도는 교육에 별 무리가 없었다. 그리고 3일째 되는 날부터 EJB교육을 하기 시작했다. COM을 이미

알고 있었던 필자가 교육 3일째 되던 날 도망 나왔다. 내가 알고 있는 개념에 비해서 EJB개념이 너무

어렵고, 개발도 너무 복잡하고, 교육내용도 정말 뭐 같았다.

그 교육이 끝나고 2-3개월 지나서 EJB 스펙 원문을 봐가면서 공부를 하기 시작했다. EJB에 대한

지식이 어느 정도 쌓이니깐 EJB를 알 수 있었고, 몇 달 전에 교육을 받은 EJB교육이 분명 잘 못되었

다는 것도 알 수가 있었다. 그 이유는 교육을 진행했던 사람이 Session Bean을 위주로 했기 때문이다.

또 실제 프로젝트에서 Session Bean만 했었던걸로 기억난다. 굳이 저런 교육기관 뿐만 아니라 국내의

EJB관련 서적도 문제가 많았다.

그럼 도대체 Entity Bean은 뭐 하러 있는 걸까 ? 그냥 멋으로 Sun에서 만들 일은 절대 없다. 이 물

음에 해답은 이미 설명했다.

cafe.daum.net/weblogic

- 11 - 서진권 [email protected]

제 2장 준비운동

저번 장 에서는 간단하게 EJB개념을 알아보았다. 이장은 웹로직에서 EJB를 사용하기 전에 환경설정

이라고 생각하면 되겠다. 설치작업과 환경설정이라서 딱딱한 내용은 없다고 본다. 그리고 초보자도 쉽

게 따라서 할 수 있으니 읽는 것 보다는 직접 설치해보는게 백번은 낮다고 생각한다. 그리고 웹로직

에서 EJB를 사용하기 위한 설정을 이미 알고 있는 독자들은 이장을 넘어가도 좋다. 단, 3장부터 실제

EJB코딩을 하는데 환경설정이 잘 못 되어서 컴파일을 못 시킬경우 필자는 죽어도 책임을 못 진

다.(^^)

필요한 소프트웨어는 다음과 같다. 모두 공개용이고 옆에 다운받는 장소를 링크 해놓았으니 돈들어

갈 생각은 안해도 좋다.

Java Standard Edition(J2SE) SDK 1.4 이상 http://java.sun.com

Java Enterprise Edition(J2EE) SDK 1.3 이상 http://java.sun.com

오라클 8i JDBC XA thin 드라이버 http://www.oracle.com

웹로직 7.0(SP4)

http://commerce.bea.com/showallversions.jsp?family=WLS

자신의 OS에 맞는 웹로직을 다운받자.

Text Editor(VI, 울트라 에디터도 좋고, 메모장이라도 좋다.)

일단 위의 모든 소프트웨어를 다운 받았으면 J2SE, J2EE만 설치 하기 바란다. J2SE, J2EE 설치방법

은 이 강좌에서는 설명 안하겠다.

2.1 클래스 패스 설정

J2SE(Java Standrad Edtion) SDK 설정

J2SE 1.4 을 받아서 설치 했으면 다음과 같이 클래스 패스를 설정해준다.

* 유닉스의 경우

export CLASSPATH=.:

쉘의 프로파일에 기입한다.

* 윈도우의 경우

set CLASSPATH=.;

부팅시 자동으로 설정할때는 제어판의 환경변수 설정에서 기입한다.

[설정] -> [제어판] -> [시스템] -> [고급] -> [환경변수] -> [시스템변수] -> [CLASSPATH]

J2EE(Java Enterprise Edition) SDK 설정

유닉스의 경우

cafe.daum.net/weblogic

- 12 - 서진권 [email protected]

export CLASSPATH=$CLASSPATH: (j2ee설치한곳/lib/j2ee.jar)

만약 j2ee를 /usr/java/j2ee1.3.1 으로 설치했다면.

export CLASSPATH=$CLASSPATH:/usr/java/j2ee1.3.1/lib/j2ee.jar

윈도우의 경우

set CLASSPATH=%CLASSPATH%;(j2ee설치한곳₩lib₩j2ee.jar)

만약 j2ee를 D:₩j2sdkee1.3.1에 설치했다면,

set CLASSPATH=%CLASSPATH%;D:₩j2sdkee1.3.1₩lib₩j2ee.jar

이러한 CLASSPATH를 잡아 주는 이유는 “.” 같은 경우는 현재 위치의 java파일을 컴파일하고 클래

스를 참조하기 위해서이고(이미 알거라고 생각한다.), j2ee.jar 를 잡아 주는 이유는 j2ee.jar안에 EJB

를 컴파일하고 참조하는 클래스가 많기 때문이다.

2.2 오라클 8i JDBC XA thin 드라이버

일반적으로 미들웨어 없이 jsp나 servlet 으로 개발할 경우 Oracle thin 드라이버를 사용한다. XA 지

원 thin 드라이버란 Two phase 트랜잭션을 지원 해주는데, EJB 엔티티빈을 사용하는데는 필수 이다.

굳이 EJB가 아니더라도 웹로직의 Jolt(턱시도 서비스)를 사용하는데도 필 수 이다. 이 XA 드라이버를

설정 하지 않아서 엔티티빈에서 트랜잭션 관리가 되지 않아 실제 프로젝트를 모조리 세션빈으로 작성

한 개발자도 여럿봤다. 요즘 www.oracle.com 에서 다운로드 하는 classes12.zip드라이버는 모두 XA

를 지원한다. 아무튼, 오라클 8i면 8i용 최신 드라이버를, 9i이면 9i용 최신 드라이버를 받자.

적당한 위치에 classes12.zip 파일을 복사한다. 이 강좌에서는 /usr/java/jdbc 에 위치시켰고. 윈도우

에서는 D:₩j2sdk1.4.0₩jdbc 에 위치 시켰다. 이 JDBC 드라이버를 클래스 패스에 설정한다.

J2SDK 설정하는 것처럼

유닉스는 export CLASSPATH=$CLASSPATH: /usr/java/jdbc/classes12.zip

윈도우는 set CLASSPATH=%CLASSPATH%;D:₩j2sdk1.4.0₩jdbc₩classes12.zip

이 될것이다.

여기까지 J2SDK, JDBC 클래스 패스를 설정했는데, 최종적으로 한번 정리해서 클래스 패스를 설정해

보자.

유닉스의 경우

export CLASSPATH=$CLASSPATH:.:/usr/java/j2ee1.3.1/lib/j2ee.jar:/usr/java/jdbc/classes12.zip

쉘 프로파일에 기입 한다.

윈도우의 경우

[설정] -> [제어판] -> [시스템] -> [고급] -> [환경변수] -> [시스템변수] -> [CLASSPATH]를 더블

cafe.daum.net/weblogic

- 13 - 서진권 [email protected]

클릭한후(없으면 [새로 생성] CLASSPATH를 만든다음 다음 값을 기입한다.)

.;D:₩j2sdkee1.3.1₩lib₩j2ee.jar; D:₩j2sdk1.4.0₩jdbc₩classes12.zip

그런 다음 [적용] -> [확인] 버튼을 반드시 클릭하기 바란다.

2.3 웹로직 설치

bea.com에서 웹로직 7.0 SP4를 다운 받았으면 윈도우 같은 경우 다운 받은 파일을 실행시키기만

하면 되고, 유닉스 계열도 마찬가지로 압축파일을 실행시켜 설치하면 된다. 유닉스 계열 같은 경우에

는 웹로직 전용 유저를 하나 만들자. 본 예제에서는 wl70 이라는 유저를 만들겠으며, 유저 그룹명은

weblogic이다. 그리고 웹로직 설치할 장소는 /weblogic 이다. root사용자 권한으로 그룹, 유저, 디렉

토리를 만들고, 만든 디렉토리를 weblogic에게 읽기/쓰기 권한을 준다.

유닉스 웹로직 유저 설정

# groupadd weblogic

# adduser -g weblogic wl70

# mkdir /weblogic

# chown wl70.weblogic /weblogic

윈도우의 경우는 특별히 사용자를 신경쓰지 않아도 되니, 그냥 administrator로 설치해도 강좌를 진

행하는데는 무방하다.

유닉스에서 설치

유닉스에서는 웹로직을 설치 하는 두 가지 방법을 제공한다. 그래픽설치와 콘솔모드 설치이다. 일반

적으로 복잡하지 않은 콘솔모드로 설치하고, 또 텔넷으로 원거리 설치도 가능하니 그냥 콘솔모드로

진행하겠다. Wl70유저로 텔넷으로 로그인 한 다음 다운로드 받은 bin 파일을 적당한 위치에 두고, 바

로 실행한다. 만약 실행모드가 아니면 chmod 명령을 사용해 실행 모드로 바꾸기 바란다.

$ ./platform704_jrockit70sp4_linux.bin

Extracting 0%........

cafe.daum.net/weblogic

- 14 - 서진권 [email protected]

* 콘솔 모드 환영 메시지

* 엔터키나 Next를 입력하고, 동의서의 동의한다…Yes입력

cafe.daum.net/weblogic

- 15 - 서진권 [email protected]

* 설치할 홈 디렉토리 선택. 1번 선택…

* 설치할 곳의 위치를 입력한다. 여기서는 /weblogic/wl70으로 했다. 각자 원하는대로 설정…

cafe.daum.net/weblogic

- 16 - 서진권 [email protected]

* 입력한 위치로 홈디렉토리를 사용할 것인지를 묻는다. 1번 선택…

* 전체 인스톨과, 사용자 선택 인스톨이다. 여기서는 전체 인스톨을 선택한다..1번

cafe.daum.net/weblogic

- 17 - 서진권 [email protected]

* 실제 인스톨 할 위치이다. 다른곳에 설치하고자 할경우 그 위치를 입력한다. 여기서는 그냥 엔터…

* 이제 인스톨할 위치를 완전히 정했으면, Yes를 선택하여 다음으로 간다. 1번선택…

cafe.daum.net/weblogic

- 18 - 서진권 [email protected]

* 설치 진행중인 화면…

* 설치 완료. 그냥 아무키나 누름.

cafe.daum.net/weblogic

- 19 - 서진권 [email protected]

윈도우에서 설치

* 설치 시작화면. Next 버튼 클릭.

cafe.daum.net/weblogic

- 20 - 서진권 [email protected]

* 라이센스 동의화면, Yes와 Next 버튼 클릭.

* 설치할 홈 디렉토리 설정

cafe.daum.net/weblogic

- 21 - 서진권 [email protected]

* 설치 유형 선택. Typical 설치 선택 후 Next 버튼 클릭

* 제품이 실제 설치될 장소 선택. Next 버튼 클릭

cafe.daum.net/weblogic

- 22 - 서진권 [email protected]

* 설치 진행 화면

* 설치 완료 화면

cafe.daum.net/weblogic

- 23 - 서진권 [email protected]

2.4 웹로직 설정

유닉스와 윈도우에서 설치하는 방법을 알아 봤는데 뭐 특별히 어려운 건 없다고 생각한다. 이제 설

치를 했으니, 웹로직 서버를 기동하기 위한 서버 설정을 해보자. 서버 설정을 하기전에 웹로직서버의

구성은 다음과 같은 몇가지 것들이 있다.

Domain : 모든 웹로직 서버의 가장 기본적인 관리 단위이다. 한 Domain에는 여러 개의

웹로직 서버가 속해 있으며, 복잡한 기업에서는 여러 개의 도매인이 존재한다.

Managed Server : Admin Server에 의해 관리되며, 실제 어플리케이션이나 컴포넌트들은

이곳에 배치된다. Admin Server의 환경설정을 읽어와 Admin Server와 똑 같은 환경으로

돌아가는 인스턴스 서버이다.

Cluster Server : Clustering 을 할 수 있는 서버며, 한 개의 어플리케이션을 여러 개의

서버가 동시에 공유하며, 서비스 해준다.

Admin Server : Managed와 Cluster 서버를 관리한다. 한 개의 Admin Server는 여러 개

의 Managed Server및 Cluster Server를 관리한다.

위에서 설명한 것 보다 더 많은 용어가 있는데, 더 깊숙히 들어갔다가는 EJB강좌는 해보지도 못하고

웹로직만 소개하는 강좌가 될 것 같으므로 더 이상은 생략하겠다. 더 상세한 정보는 WebLogic Admin

Guide를 참고 하기 바란다.

Domain은 웹로직의 가장 규모가 큰 기본적인 단위이다. Domain안에는 여러 개의 웹로직 서버가 존

재한다. 보통의 경우 한 개의 Admin Server와 여러 개의 Managed Server를 두어서 관리한다. 하지만

이 강좌에서는 그렇게 까지 할 필요가 없으므로, Admin Server와 Managed Server가 혼합된

Standalone Server로 강좌를 진행하려고 한다.

설치된 곳의 디렉토리 중 /설치한 곳/common/bin으로 가보면, 유닉스 경우 dmwiz.sh 파일이, 윈도

우 경우 dmwiz.cmd 파일이 있을것이다. 이 파일은 도메인 설정 마법사 파일이다. 이 파일을 실행시

키면, 보통의 유닉스의 경우 콘솔모드로, 윈도우 경우 GUI환경으로 실행이 된다. Domain을 설정하게

되면 Domain이 위치할 경로를 설정하는데, 이곳에 서버가 기동하는 갖가지 파일과 어플리케이션이

앞으로 존재하게 될 것이다. 이 강좌에서는 /project 라는 곳에 만들다. / 에 접근할 수 있는 권한으로

다음과 같은 디렉토리를 만들어 wl70 사용자에게 권한을 주자.

# mkdir /project

# chown wl70.weblogic /project

위에 같이 디렉토리를 만들었고, 도메인 설정 마법사를 사용하여 도메인을 설정할 경우, /project 하

위에 Domain 명에 해당하는 디렉토리가 하나더 생길것이다.

그럼 다음은 유닉스와 윈도우에서 도메인 설정 마법사를 설정하는 방법이다.

cafe.daum.net/weblogic

- 24 - 서진권 [email protected]

* 유닉스 에서 도메인 설정 마법사 (여러가지 Domain이 설정이 있지만, 3번을 선택…)

* Single Server(Standalone Server)를 선택한다.

cafe.daum.net/weblogic

- 25 - 서진권 [email protected]

* Domain 명 수정. 1번 선택.

* Domain 명을 입력. 여기선 ejbdomain 입력.

cafe.daum.net/weblogic

- 26 - 서진권 [email protected]

* 싱글 서버 설정. 1번을 선택.

* 서버명 입력. 여기선 ejbserver라고 했다.

cafe.daum.net/weblogic

- 27 - 서진권 [email protected]

* 서버 주소를 설정한다. 2번 선택

* 서버 주소 입력…

cafe.daum.net/weblogic

- 28 - 서진권 [email protected]

* 실행할 포트와 SSL포트는 7001, 7002를 그대로 사용하자 그냥 Next또는 엔터.

* Domain의 위치를 설정한다. 1번 선택

cafe.daum.net/weblogic

- 29 - 서진권 [email protected]

* 위에서 작성했던 /project로 설정한다.

* Domain의 서버를 관리한 사용자를 입력한다. 잊지말것!!! 여기서는 admin 으로 했다.

cafe.daum.net/weblogic

- 30 - 서진권 [email protected]

* Domain 서버를 관리할 사용자의 암호를 입력한다. 절대 잊지말 것!!! 여기서는 admin123으로 했다.

* 최종적으로 설정한 설정 값 들을 보여 준다. Create를 입력하면 실제 Domain이 작성된다.

cafe.daum.net/weblogic

- 31 - 서진권 [email protected]

* 작성 완료 화면. Next 또는 엔터키 입력.

* 마법사를 다시 실행할 것인지를 묻는다. 이제는 종료…2번선택.

여기 까지가 유닉스에서 도메인과 서버 설정하는 마법사였다. 나중에는 Managed서버만 따로 추가

할수 도 있을것이다.

cafe.daum.net/weblogic

- 32 - 서진권 [email protected]

* 윈도우 에서 도메인 설정 마법사

dmwiz.cmd를 실행하면 GUI환경에서 실행된다. 마법사 처음 시작화면에서 Template는 WLS Domain

을 선택하고, Name 란에는 ejbdomain을 입력한다. Next를 클릭하면 다음 화면으로 진행된다.

cafe.daum.net/weblogic

- 33 - 서진권 [email protected]

* 싱글 서버 선택. Next 버튼 클릭.

* Domain 위치설정 여기서는 C:₩bea₩user_projects₩ 로 했다. 각자 설정하길.

cafe.daum.net/weblogic

- 34 - 서진권 [email protected]

* 서버명(ejbserver), 주소(IP), 포트(7001), SSL(7002) 설정.

* 사용자명과 암호 설정 (admin/admin123)

cafe.daum.net/weblogic

- 35 - 서진권 [email protected]

* 윈도우 부팅시 서비스로 등록 할 것인지 여부…하지 말자. 별로다…^^

* 시작 메뉴에 단축아이콘으로 만들 것인지 여부… 이것도 하지 말자…

cafe.daum.net/weblogic

- 36 - 서진권 [email protected]

* 최종 설정값 요약 화면. Create 버튼을 클릭하면 생성한다.

* 마법사를 다시 시작할 것인지를 묻는다.

cafe.daum.net/weblogic

- 37 - 서진권 [email protected]

2.5 환경변수 설정

웹로직에서 기동/중지, EJB컴파일 하기 위한 환경변수 설정을 해줘야 하는데, 웹로직 홈디렉토리

정보라든가 JAVA의 위치, CLASSPATH같은 것들이다. 하지만 첨부터 작성할 필요는 없고, 웹로직

디렉토리에 가보면 setEnv.sh(Window는 setEnv.cmd)파일이 있다. 그것을 한번만 실행만 해주면

되는것이다. 특별히 수정할 곳은 없지만, 혹시나 웹로직디렉토리나, 자바의 위치가 다르게 기입이

되었다면, 수정하기 바란다. 그리고 소스파일에서 특별한 클래스를 임포트 했다면 그것도 넣어주기

바란다. 아마 이 강좌를 첨부터 그대로 했던 사람이라면 특별히 수정할 것이 없다고 본다. 그리고

환경설정 파일을 매번 호출해기 귀찮으면 사용자의 프로파일에서 호출하게끔 하던지, 윈도우 경우

단축아이콘으로 호출하게 하면 된다. 그리고 더 이상 이 강좌에서는 setEnv관련 내용은 나오지

않으니, 나중에 setEnv.sh를 호출하지 않아서 컴파일이 안되는 사태는 책임을 못진다.

다음과 같이 실행한다.

$ setEnv.sh

또는 쉘이 다를경우

$ source setEnv.sh

이렇게 실행했을경우 대충 다음과 같은 메시지가 나올것이다.

LD_LIBRARY_PATH=/weblogic/wl70/weblogic700/server/lib/linux/i686:/weblogic/wl70/weblogic700/

server/lib/linux/i686/oci817_8

CLASSPATH=/weblogic/wl70/jrockit70sp4_131_08/lib/tools.jar:/weblogic/wl70/weblogic700/server:/

weblogic/wl70/weblogic700/server/lib/weblogic_sp.jar:/weblogic/wl70/weblogic700/server/lib/weblo

gic.jar:.:/usr/java/jdbc/classes12.zip

PATH=/weblogic/wl70/weblogic700/server/bin:/weblogic/wl70/jrockit70sp4_131_08/jre/bin:/weblogi

c/wl70/jrockit70sp4_131_08/bin:/weblogic/wl70/weblogic700/server/lib/linux/i686:/usr/local/bin:/bin

:/usr/bin:/usr/X11R6/bin:/home/wl70/bin:/usr/java/j2sdk1.4.2_02/bin:/usr/java/j2sdkee1.3.1/bin

Your environment has been set.

그리고 set명령을 사용해 정확히 CLASSPATH가 셋팅이 되었는지도 확인해본다. 이렇게 되면

기동/중지 컴파일 하기 위한 모든 환경설정은 끝이났다.

cafe.daum.net/weblogic

- 38 - 서진권 [email protected]

2.6 웹로직 기동

유닉스 경우

$ cd /project/ejbdomain

$ ./startWebLogic.sh

웹로직을 기동할 수 있는 사용자명과 암호를 입력받게 된다. 도메인 설정 마법사에서 입력했던

admin/admin123 을 입력한다. 만약 이게 귀찮다면 startWebLogic.sh 파일을 편집하면 된다. 파일 중

간쯤 보면 WLS_USER= 와 WLS_PWD= 문장이 보일 것이다. 이곳에 기동할 사용자명과 암호를 입력

하면 기동시 매번 입력하지 않아도 된다.

WLS_USER=admin

WLS_PW=admin123

cafe.daum.net/weblogic

- 39 - 서진권 [email protected]

성공적으로 기동이 되면 위와 같은 화면이 나온다. 반드시 <Server started in RUNNING mode>가 나

와야 한다. 그런데 문제가 하나 생겼다. 원거리에서 기동을 시킬경우 텔넷을 닫으면 웹로직 서버가 정

지 되버릴 것 아닌가? 기동된 웹로직 서버 텔넷 화면에서 CTRL + C 를 눌러 웹로직을 정지 시킨후,

유닉스 nohup 명령어로 백그라운드로 돌리자. nohup 명령어를 적용시키기전에 startWebLogic.sh파일

을 열어보면 마지막쯤 다음 문장을 발견할 수 있다.

. "/weblogic/wl70/weblogic700/server/bin/startWLS.sh"

설치한 위치 마다 다르겠지만. startWebLogic.sh 에서 웹로직이 설치된 곳의 startWLS.sh 쉘파일을

다시 호출하는 것을 알수있다. startWebLogic.sh 파일은 도메인서버와 관련된 정보만 설정하고 실제

웹로직을 기동하는 파일은 startWLS.sh 쉘파일이다. nohup 명령어로 백그라운드 처리를 원한다면

startWLS.sh파일을 고치는 것이 좋다.

/weblogic/wl70/weblogic700/server/bin/startWLS.sh 파일을 다시 에디터로 열어서 쉘파일의 마지막

부분 정도에 가면 다음과 같은 라인들을 볼 수 있다.

if [ "$ADMIN_URL" != "" ]

then

set –x

……(수정할 부분)

cafe.daum.net/weblogic

- 40 - 서진권 [email protected]

else

set –x

……(수정할 부분)

fi

set +x

fi

위의 ……(수정할 부분)을 보면 두부분이 "${JAVA_HOME}/bin/java" 이렇게 시작할 것이다. 이곳 앞

에다 nohup을 붙여주고 마지막 줄에는 &를 붙여주자. 다음과 같이 수정 될 것이다.

if [ "$ADMIN_URL" != "" ]

then

set -x

nohup "${JAVA_HOME}/bin/java" ………… weblogic.Server &

else

set -x

nohup "${JAVA_HOME}/bin/java" ………… weblogic.Server &

fi

위의 문장중 빨간색이 우리가 수정한 것이다.

다시 웹로직을 기동해보면 백그라운드로 돌아가는 것을 확인할 수 있다. nohup이 아닌 그냥 기동할

경우 항상 관리자ID와 암호를 묻는 것을 보았다. 그러나 nohup돌릴경우 커맨드 라인에서 입력을 못

받게 되므로 그냥 지나처 버린다. 반드시 nohup으로 기동할 경우 startWebLogic.sh파일에

WLS_USER와 WLS_PW를 설정하기 바란다.

cafe.daum.net/weblogic

- 41 - 서진권 [email protected]

그리고 웹로직에서 출력되는 모든 문자는 앞으로 nohup.out 파일에 뿌려지게 된다. 이파일의 이름과

위치가 잘 못됏으면 nohup 에서 리다이렉션을 써서 적당히 고치기 바란다. >> wl70.log 이런식으로

말이다. 이제 기동은 끝났다.

윈도우의 경우

윈도우도 유닉스와 마찬가지로 C:₩bea₩user_projects₩ejbdomain 가보면 startWebLogic.cmd 파일

이 존재한다. 이걸 실행하면 사용자명과 암호를 넣고 실행되는 모습을 볼 수 있다. 윈도우도 마찬가지

로 항상 입력하기 귀찮으면 커맨드 파일에 WLS_USER와 WLS_PW를 설정하기 바란다.

2.7 웹로직 중지

유닉스의 경우

다음에 설명하게 될 Admin 콘솔에서 서버를 브라우져상 에서 종료시킬 수 있다. 또 kill 명령어로 프

로세스 번호를 직접 처서 웹로직을 중지 시켜도 크게 상관은 없으나, 이 방법은 정말 위급한 상황에

서만 사용하고, 정식적인 절차를 밟아서 웹로직을 중지 시키자. Admin 콘솔에서 중지시키는 방법은

그냥 마우스 클릭만 하면 되기 때문에 따로 설명하진 않고 여기서는 커맨드 라인 명령어로 중지시키

는 방법만 소개한다.

일단 중지시키는 쉘 파일을 만들기 전에 /weblogic/wl70/weblogic700/server/bin/startWLS.sh 파일

cafe.daum.net/weblogic

- 42 - 서진권 [email protected]

을 복사해서 /project/ejbdomain/stopWebLogic.sh 파일로 만든다.

$ cd /weblogic/wl70/weblogic700/server/bin

$ cp startWLS.sh /project/ejbdomain/stopWebLogic.sh

nohup 명령어를 써서 수정했던 곳과 마찬가지로 다음 부분을 찾는다.

if [ "$ADMIN_URL" != "" ]

then

set -x

nohup "${JAVA_HOME}/bin/java" ………… weblogic.Server &

else

set -x

nohup "${JAVA_HOME}/bin/java" ………… weblogic.Server &

fi

위 문장들을 모두 지우고 다음과 같이 작성한다.

"${JAVA_HOME}/bin/java" weblogic.Admin -url t3://설정한주소:7001 SHUTDOWN -username admin

-password admin123

수정후 저장한 다음 stopWLS.sh 를 실행하면 기동된 웹로직이 중지된다.

* 웹로직 중지 화면(참고, 바로 중지가 되지는 않는다. 중지하면서 여러가지 일을 해야되기 때문)

cafe.daum.net/weblogic

- 43 - 서진권 [email protected]

윈도우 경우

윈도우 경우도 유닉스와 마찬가지로 CTRL+C를 눌러서 윈도우 창을 중지 하던지 아니면 cmd파일을

만들어서 유닉스처럼 하면 된다.

2.8 웹로직 Admin 콘솔

웹로직 Admin 콘솔은 네트워크상에서 비주얼한 환경설정과 서버 관리를 제공해 준다. 서버의 기동

부터 웹로직의 모든 설정과 관리를 이곳 Admin 콘솔에서 할 수 있다. Admin 콘솔은 Admin Server에

서 돌아가게 되며, Managed Server는 이곳 Admin 콘솔에서 목록 형태로 보여지고, 이들을 관리한다.

Admin 콘솔을 접속하고자 할때는 Admin Server의 주소에 console을 붙인다. 예를들면 URL은 다음

과 같다.

http:/ /192.168.1.150:7001/console

http:/ /192.168.1.150:7001 은 Admin Server의 주소이고, console은 Admin console의 서블릿이다.

일단 우리가 작성한 도메인의 Single Server를 기동한 다음 위의 주소 형식대로 접속해 보기 바란다.

cafe.daum.net/weblogic

- 44 - 서진권 [email protected]

Admin 콘솔에 접속하면 사용자 암호를 넣게 되어있다. 여기서도 우리가 작성한 admin/admin123을

입력하고 Sign in 버튼을 클릭한다.

cafe.daum.net/weblogic

- 45 - 서진권 [email protected]

위 화면은 Admin 콘솔의 주화면이다. 이곳에서 웹로직 서버의 모든 서비스를 관리하게 된다.

2.9 웹로직 Web Application 위치(HTML, JSP)

Domain디렉토리로 설정한곳(유닉스:/project/ejbserver, 윈도우: C:₩bea₩user_projects₩ejbdomain)

에 가보면 applications이라는 디렉토리가 존재하는데, 이곳에 우리들이 작성할 EJB나 JSP가 위치하

게 된다. 그리고 applications의 하위에 DefaultWebApplication 이라는 디렉토리가 있는데 이곳에 jsp

나 html이 존재 하게 된다. 브라우져로 Single Server의 주소인 http://설치한곳:7001/ 으로 접속해 보

면 웹로직 서버를 소개하는 페이지가 보일 것이다. 이 페이지는 바로 DefaultWebApplicaton의

index.html파일이다. 이곳에 우리가 사용할 jsp나 html을 놓게된다.

2.10 웹로직 JDBC 컨넥션 풀 설정

웹로직 서버에서 서비스 해주는 Sun의 프로덕트는 굉장히 많다. 하다 못해 메일을 발송해주는 서비

스까지 있으니 말이다. 필자 개인적으로도 아직까지 말로만 듣고 써보지 못한 서비스가 많다. 수 많은

서비스중 JDBC 컨넥션풀의 기능은 다른 웹 어플리케이션 서버들의 타의 추종을 불허한다. 내가 이제

cafe.daum.net/weblogic

- 46 - 서진권 [email protected]

까지 써본 컨넥션 풀 중 가장 안정적이고 가장 속도가 좋은 서비스인거 같다.(물론 가장 기본적인

http서비스는 맘에 안든다. 단순히 컨넥션 풀만 가지고 좋다고 할 순 없지만…)

웹로직에서 컨넥션 풀을 설정하는 방법은 3가지가 있다. 첫 번째는 웹로직 어드민 콘솔에서 웹 상에

서 설정하는 방법, 두 번째는 config.xml을 수정하는 방법, 세 번째는 웹로직 API를 사용하여 프로그

램 짜는 방법이다. 두번째야 뭐 그렇게 할 수도 있지만, 세 번째 같은 경우는 웬만한 고급 시스템, 개

발자가 아니고서는 하지 않을 것이다. 여기서는 첫 번째 예제를 사용한다.

웹로직에서 JDBC를 쓸려면 Sun의 J2SDK 환경에 클래스 패스를 설정(우리는 이미 했다.)해야 한다.

웹로직을 기동하는 사용자가 CLASSPATH에 이미 JDBC클래스를 가지고 있다면 따로 웹로직에서 설

정할 필요가 없다. 우리는 XA thin 드라이버 설정에서 이미 했으므로 설정할 필요없다.

웹로직을 기동한다.

웹로직 콘솔 url로 접속한다. http://192.168.1.150:7001/console

관리자ID와 암호를 넣는다. admin/admin123

콘솔 화면의 왼쪽 트리메뉴에서 Service 항목 아래에 있는 JDBC 항목에 Connection

Pools를 선택한다.

화면 오른쪽 메인 화면에 Configure a new JDBC Connection Pool…를 클릭한다.

다음과 같은 항목으로 값을 입력한다.

Name : EJBDBPool

URL : jdbc:oracle:thin:@오라클IP주소:1521:오라클SID

Driver : oracle.jdbc.xa.client.OracleXADataSource

Properties : user=scott

password=tiger

[Create]버튼을 클릭한다.

[Connections] 탭으로 이동한다음

Initial Capacity : 2

Max Capacity : 3

Support Local Transaction : Check

[Apply] 버튼을 클릭한다.

[Targets] 탭으로 이동후 [Server] 탭으로 이동한다.

[Available]리스트 박스에서 ejbserver를 선택한후 -> 버튼을 클릭한다.

최종적으로 [Apply] 버튼을 클릭한다.

성공적으로 컨넥션 풀이 설정되면 nohup.out에는 아무런 메시지가 출력되지 않고, 또 [Monitoring]

탭에 Montitor All Active Pools …클릭 한다음 우리가 작성했던 컨넥션 풀이 나와야 한다. 만약

nohup.out 파일에 Exception이 발생했거나 Montitor All Active Pools에 작성했던 컨넥션 풀이 나오지

않으면 분명 설정에 문제가 있는 것이다. 만약 실패 했다면 작성 했던 컨넥션 풀을 삭제하고, 다시 처

음부터 차근차근 해보기 바란다.

cafe.daum.net/weblogic

- 47 - 서진권 [email protected]

* 컨넥션 풀 모니터링 화면

TX 데이터 소스 설정

우리가 설치했던 JDBC 드라이버는 정확히 오라클 XA Thin 드라이버다. XA 드라이버는 이기종, 또는

클러스터링의 다른 리소스(컨넥션풀, 턱시도)를 사용할 경우도 완벽한 트랜잭션을 재공해준다. 이때

직접적으로 자바 Connection 객체를 핸들링해서는 트랜잭션을 관리할 수 없다. 지금 그 이유를 설명

하기에는 너무 설명할게 많다. 아마 엔티티빈을 생성해보면 필자가 왜 직접적으로 Connection객체를

가지고는 트랜잭션을 관리를 못 하는지 알 것이다. 이 문제는 엔티티빈에서 자세하게 알아보고,

Connection 객체 대신에 중간에 다시 DataSource라는 것을 써서 트랜잭션 관리 한다는 것만 일단

알아두자. 만약 이기종간, 클러스터링이 아니라 하나의 웹로직 서버에 EJB를 배치했다면 DataSource

만 가지고도 얼마든지 트랜잭션을 관리할 수 있다. 하지만 이 강좌에서는 완벽한 트랜잭션을 제공하

기 위해 TX DataSource를 사용한다.

웹로직 어드민 콘솔 왼쪽 메뉴에 [JDBC] 항목 아래에 [TX DataSource]를 선택한다.

화면 오른쪽 메인화면에 Configure new JDBC Tx Data Source…를 클릭한다.

cafe.daum.net/weblogic

- 48 - 서진권 [email protected]

다음과 같은 값으로 설정한다.

Name : EJBDBPoolTxDataSource

JNDI : EJBDBPoolTxDataSource

Pool Name : EJBDBPool

[Create] 버튼을 클릭한다.

[Targets]탭을 클릭한후에 [Server]탭의 [Available] 리스트박스의 ejbserver를 선택후

-> 버튼을 클릭한다.

최종적으로 [Apply] 버튼을 클릭한다.

TxDataSource설정도 JDBC와 마찬가지로 nohup.out에 아무 문자도 출력되지 않아야지 정상적으로

생성된 것이다. 만약 Exception이 발생했다면 어딘가 설정에 문제가 있다는 얘기다.

2.11 Directory 정리

실제 강좌를 진행하기전에 다시한번 디렉토리를 정리 해보자. 첨 접해보는 독자도 많기 때문에 디렉

토리를 했갈리수 있어서 이 강좌에서 사용하는 디렉토리 설정을 정리한다.

유닉스 경우

/weblogic/wl70 BEA홈디렉토리

/weblogic/wl70/weblogic700 웹로직 7.0 설치한 디렉토리

/weblogic/wl70/weblogic700/server/bin 웹로직 기동/중지 등 중요한 실행 디렉토리

/project 강좌를 진행할 메인 디렉토리

/project/ejbdomain 강좌를 진행할 도메인 디렉토리

/project/ejbdomain/ejbserver 강좌를 진행할 Single 서버 디렉토리

/project/ejbdomain/ejbserver/clientclasses client 에서 필요한 클래스(없으면 만들 것,

그리고 CLASSPATH 환경변수에 추가한다.)

/project/ejbdomain/ejbserver/serverclasses server 에서 필요한 클래스(없으면 만들 것,

그리고 CLASSPATH 환경변수에 추가한다.)

/project/ejbdomain/applications 작성된 EJB(ear파일)가 위치하는 곳.

/project/ejbdomain/applications/DefaultWebApp jsp, html등의 파일이 위치하는 곳.

/project/ejbsource 모든 예제 소스 디렉토리(없으면 만들 것)

윈도우 경우

C:₩bea BEA홈디렉토리

C:₩bea₩weblogic700 웹로직 7.0 설치한 디렉토리

C:₩bea₩weblogic700₩server₩bin 웹로직 기동/중지 등 메인 실행 디렉토리

C:₩bea₩user_projects 강좌를 진행할 메인 디렉토리

cafe.daum.net/weblogic

- 49 - 서진권 [email protected]

C:₩bea₩user_projects₩ejbdomain 강좌를 진행할 도메인 디렉토리

C:₩bea₩user_projects₩ejbdomain₩ejbserver 강좌를 진행할 Single 서버 디렉토리

C:₩bea₩user_projects₩ejbdomain₩ejbserver₩clientclasses client 에서 필요한 클래

스(없으면 만들 것, 그리고 CLASSPATH 환경변수에 추가한다.)

C:₩bea₩user_projects₩ejbdomain₩ejbserver₩serverclasses server 에서 필요한 클래

스(없으면 만들 것, 그리고 CLASSPATH 환경변수에 추가한다.)

C:₩bea₩user_projects₩ejbdomain₩applications 작성된 EJB(ear파일)가 위치하는 곳.

C:₩bea₩user_projects₩ejbdomain₩applications₩DefaultWebApp jsp, html등의 파일이 위

치하는 곳.

C:₩bea₩user_projects₩ejbsource 모든 예제 소스 디렉토리(없으면 만들 것)

사소한 것이지만, 했갈리지 말기 바란다. 이렇게 해서 EJB를 사용하기 위해 J2SE, J2EE, WebLogic

을 설치하고 설정했다. 다음장 부터는 본격적으로 상세히 실제 예제를 바탕으로 EJB를 설명하겠다.

cafe.daum.net/weblogic

- 50 - 서진권 [email protected]

제 3장 Stateless Session Bean

제 1, 2 장에서 길다면 길 수 있고, 짧다면 짧은 EJB 개념과 준비에 대해서 이야기 했다.

이장에서는 세션빈을 실제로 사용하는 방법에 대해서 설명한다. 간단한 동작방식과 개발, 배포, 배치,

클라이언트에서 서비스를 받아보는 식으로 진행하겠다. 첫 예제이니 만큼 설명할게 많은데 앞으로

이강좌에서 나오는 모든 예제는 실제 필자가 작성하면서 컴파일, 디플로이(배치), 클라이언트에서

접속 하기 때문에 잘 안되는 예제는 없으리라고 믿는다. 그럼 서두는 이만 접고...

세션빈의 두가지 종류중 먼저 stateless session bean 부터 알아보자. 가장 쉽게 생각해서 stateless

session bean은 서버에 있는 함수만 호출하고 끝난다고 생각하면 된다. 마치 클래스에 있는 함수를

호출하는 것과 똑같다. 차이점은 서버에서 실행되고, 리턴값이 있을경우 클라이언트에게 전달되며,

종료시 어떠한 메모리정보(무상태)도 없이 끝나고 만다. 실제 사용하는 예제는 서버의 어떤 정보를

구해온다던지, 배치작업호출, 어떠한 DB테이블에서 통계를 구하는 예 등등이 있다. 그냥 일반적으로

서버에서 실행되는 함수라고 생각하면 무방하다. 다음 그림은 stateless session bean의 흐름과

라이프사이클, 서버의 상태를 설명한다.

그림을 설명하자면 다음과 같은 절차가 될 것이다.

1. 웹로직 기동시 미리 EJB에 등록된 개수만큼 EJB를 POOL에 넣어둔다.

2. 클라이언트가 요청시 POOL에서 EJB를 꺼내어 해당 함수를 실행한다.

3. 함수 완료시 EJB를 다시 웹로직 콘테이너에게 반환한다.(이때 EJB는 비활성 상태가 된다.)

4. 클라이언트가 재요청시 2.를 다시 반복한다.

cafe.daum.net/weblogic

- 51 - 서진권 [email protected]

stateless session bean은 단지 서버에 있는 함수만 실행해주고 끝나는 것일뿐이다. POOL이라는

컨테이너의 메커니즘(성능, 관리를 위한)만 있을뿐 함수만 실행해주고 종료한다. 세션빈을 작성하기

위해서는 몇가지 필요한 클래스와 xml파일이 있다. 제 1장에서 어느정도 설명을 했지만 다시한번

그때의 기억을 떠올리며 정리해보자. 이미 설명했기 때문에 상세한 설명은 피하겠다.

Home Interface

Remote Interface

Bean Class

ejb-jar.xml

weblogic-jar.xml

그럼 실제로 작성해 보자. 지금 작성할려고 하는 세션빈은 웹로직 서버의 메모리 정보를 구한다음

클라이언트에게 리턴해준다. 전체 메모리, 사용중인 메모리, 사용가능한 메모리를 구한다.

MemoryInfoHome.java(Home Inteface)

package ejb.stateless.MemoryInfo;

import java.rmi.RemoteException;

import javax.ejb.CreateException;

import javax.ejb.EJBHome;

public interface MemoryInfoHome extends EJBHome

{

MemoryInfo create() throws CreateException, RemoteException;

}

Home Interface가 하는일은 주로 EJB생성과 관련된 것이다. 일단 소스를 보자면.

MemoryInfoHome인터페이스는 EJBHome으로부터 상속을 받는다. 나중에 배울 엔티티빈이건

메시지빈이건 무조건 Home Interface는 EJBHome으로부터 시작된다. create()라는 메소드를

정의했는데, 이 create메소드가 바로 다음소스인 Remote Interface의 인스턴스를 구하게 된다.

다시말하자면 클라이언트가 처음으로 EJB에 접속할 때 제일 처음 하는 행동이 이 Home인터페이스를

구하는 과정(Binding)이며, 클라이언트의 Remote Interface를 통해서 서버의 함수를 실행하게 된다.

create메소드는 반드시 Home Interface에 선언이 되어야 하며, 리턴되는 객체는 Remote

Interface여야 한다. 다음은 Remote Interface의 소스이다.

cafe.daum.net/weblogic

- 52 - 서진권 [email protected]

MemoryInfo.java(Remote Interface)

package ejb.stateless.MemoryInfo;

import java.rmi.RemoteException;

import javax.ejb.EJBObject;

public interface MemoryInfo extends EJBObject

{

public long getTotalMemory() throws RemoteException;

public long getUsedMemory() throws RemoteException;

public long getFreeMemory() throws RemoteException;

public void runGC() throws RemoteException;

}

Remote Interface는 실제 실행되는 함수의 껍데기라고 생각하면 된다. 모든 미들웨어가 그러하듯이

클라이언트쪽에는 반드시 서버에서 실행될 기능의 구조체 내지는 형태가 있기 마련이다. 위에서

작성한 4개의 함수들에 대한 정의는 여기서 하지 않는다. 이 인터페이스에서만 선언하고 정의는

다음에 소개할 Bean Class에서 이 함수들의 행동을 정의한다. 즉 다시말하자면 Remote Interface도

엄연히 Java의 인터페이스 이다. 인터페이스는 추상적으로만 선언을 하고, 나중에 이걸 다시

정의한다. 위에서도 언급 했지만 미들웨어는 클라이언트쪽에 반드시 구조체를 가지고 있다. 이

Remote Interface가 바로 구조체라고 생각하면 되는데, 만약 인터페이스가 아니라 일반 클래스

였다면 EJB는 구현하기 힘들어졌을것이고, 추상적인 어떠한 구조체가 아니라서 클라이언트의

구조체와 실제 서버의 행동에 반드시 동기화를 맞춰줘야 하기때문이다. 특이한점은 없고,

MemoryInfo인터페이스는 Home Interface와 다른점은 EJBObject에서 상속을 받는것만 차이점이 있고

나머지는 설명할게 없다. 그리고 우리가 작성할 함수에 대해서 설명하자면…

getTotalMemory() : 현재 웹로직 서버의 힙메모리 크기를 구한다.

getUsedMemory() : 현재 웹로직 서버의 힙메모리에서 사용된 메모리를 구한다.

getFreeMemory() : 현재 웹로직 서버의 남은 사용가능한 힙메모리를 구한다.

runGC() : 힙메모리를 명시적으로 garbage collecting(메모리정리) 한다.

이제 가장중요한 Bean Class에 대해서 살펴보자.

MemoryInfoBean.java(Bean Class)

cafe.daum.net/weblogic

- 53 - 서진권 [email protected]

package ejb.stateless.MemoryInfo;

import javax.ejb.CreateException;

import javax.ejb.SessionBean;

import javax.ejb.SessionContext;

import javax.naming.InitialContext;

import javax.naming.NamingException;

public class MemoryInfoBean implements SessionBean

{

private SessionContext ctx;

public void ejbActivate()

{

}

public void ejbRemove()

{

}

public void ejbPassivate()

{

}

public void setSessionContext(SessionContext ctx)

{

this.ctx = ctx;

}

public void ejbCreate () throws CreateException

{

}

public long getTotalMemory()

{

return Runtime.getRuntime().totalMemory();

cafe.daum.net/weblogic

- 54 - 서진권 [email protected]

}

public long getUsedMemory()

{

return getTotalMemory() - getFreeMemory();

}

public long getFreeMemory()

{

return Runtime.getRuntime().freeMemory();

}

public void runGC()

{

System.gc();

}

}

Bean Class는 RemoteInterface에서 추상적으로 선언했던 함수들에 대해서 실질적인 비즈니스 코드가

정의된다. 우리가 작성할 EJB가 세션빈이기 때문에 MemoryBean 클래스는 SessionBean으로부터

상속을 받는다. 일단 멤버객체로 ctx라는 객체가 있는데, Context형이다. Context는 자바의 JNDI를

사용하여 어떠한 서버의 서비스 또는 자원을 활용할 때, 연결정보를 갖는 객체이다. JDBC로 따지면

Connection객체정도 된다. 일단 Context는 자바의 객체이므로 상세한 설명은 피한다. ejbActive(),

ejbRemove(), ejbPassivate(), setSessionContext(), ejbCreate()모두 Session Bean에 추상적으로

선언될걸 다시 정의한것인데, ejbActive()함수는 사용자가 호출하는게 아니라 EJB가 활성화가 되었을

때 이벤트로 이 ejbActive()를 Session Bean객체에서 호출하게 된다. 마찬가지로 EJB삭제, 비활성,

생성 되었을경우 적당한 코딩을 해주면 Session Bean에서 자동으로 호출해주기 때문에 따로 신경쓸

필요는 없다.

한가지 특이한 함수가 setSessionContext()인데 인수로 Context()객체가 넘어온다. 이 Context는

클라이언트가 어떤식으로 접속했는지의 정보가 있기 때문에 EJB를 작성하면 통상적으로, Context멤버

변수를 선언하고, setSessionContext()에서 그 멤버 변수를 클라이언트가 붙은 Context로 초기화

한것이다.

그 다음 4개의 함수(getTotalMemory, getUsedMemory, getFreeMemory, runGC)는 우리가 Remote

Inerface에서 작성한 함수들이다. 여기서부터 실질적은 코드가 들어간다. 메모리를 구하는

3개의함수는 모두 자바의 Runtime객체에서의 함수를 써서 단순히 리턴해준다. 그리고 runGC함수는

System.gc()함수를 써서 구현했다. 이렇게 되면 명시적으로 힙메모리의 쓸데없는 메모리를 정리한다.

cafe.daum.net/weblogic

- 55 - 서진권 [email protected]

근데, 자바의 인터페이스를 잘 아는 사람이라면 한가지 의아하게 생각할 것이다. 무엇이냐면 Bean

Class(MemoryInfoBean.java)와 Remote Interface(MemoryInfo.java)가 아무런 연관관계도 없는데

어떻게 Remote Interface에서 추상적으로 선언한 함수들이 Bean Class에서 정의가 된다는 것일까 ?

이 비 은 다음 두개의 xml파일에 숨겨져 있다.

ejb-jar.xml(Sun에서 규약한 일반적인 EJB의 정보가 담겨있는 xml파일)

<?xml version="1.0"?>

<!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>

<enterprise-beans>

<session>

<ejb-name>MemoryInfo</ejb-name>

<home>ejb.stateless.MemoryInfo.MemoryInfoHome</home>

<remote>ejb.stateless.MemoryInfo.MemoryInfo</remote>

<ejb-class>ejb.stateless.MemoryInfo.MemoryInfoBean</ejb-class>

<session-type>Stateless</session-type>

<transaction-type>Container</transaction-type>

</session>

</enterprise-beans>

<assembly-descriptor>

<container-transaction>

<method>

<ejb-name>MemoryInfo</ejb-name>

<method-name>*</method-name>

</method>

<trans-attribute>Required</trans-attribute>

</container-transaction>

</assembly-descriptor>

<ejb-client-jar>MemoryInfo_Client.jar</ejb-client-jar>

</ejb-jar>

ejb-jar.xml 파일이 하는일은 EJB구조가 어떻게 생겨 먹엇냐를 정의하는 곳이다. <ejb-name>이라는

cafe.daum.net/weblogic

- 56 - 서진권 [email protected]

속성을 보면 MemoryInfo라고 되어있다. 이 말은 이 EJB의 이름은 앞으로 MemoryInfo라는 문자로

쓰겠단 의미이다. 그 다음 부터가 중요한데, <home>속성은 Home Interface클래스를 풀 패스로

적어놓은 곳이다. 마찬가지로 각각에 <remote>는 Remote Interface를, <ejb-class>는 Bean Class를

풀 패스로 기입한다. 자..이제 위에서 언급한 Bean Class에서 의아한 문제의 힌트가 바로 여기에

있다. 이 xml에서 Home은뭐고, Remote는 뭐고, Bean은 어떤거니깐 Remote Interface에서 우리가

작성한 4개의 함수가 Bean Class에서 추상화(?)정의가 되는 것이다. 엄 히 말하면 추상화도 아니다.

왜라는 반문에 대답은 이미 했다. 이 통합된 xml이 있으니깐 컴파일(java컴파일이 아님, 웹로직

컴파일러가 따로 있음)하는 과정에서 파싱정도라고 생각하면 된다. EJB를 배치 시키기전 두번

컴파일을 해야 해는데, 한번은 자바 컴파일(home, remote, bean을 javac로 컴파일), 또한번은

웹로직에서 컴파일한다. 웹로직에서 컴파일 할 경우, 이때 ejb-jar.xml파일을 참조하여 Remote

Interface에서 선언된 함수가 Bean클래스에서 정의가 되있냐를 파싱하는 것이다. 만약 public 함수가

Remote Interface에서는 선언이 되있고, Bean Class에서 선언이 되어 있지 않다면, 자바 컴파일

도중에는 문제가 발생하지 않지만, 웹로직 컴파일 할경우 문제가 생긴다. 그러니 거짓말로 Remote

Interface에 선언하지 말길 바란다.

<session-type>속성은 우리가 작성할 EJB가 stateless session bean이기 때문에 Stateless라고

기입한거고, 만약 다음에 작성할 stateful 일경우는 Stateful이 된다.

<transaction-type>은 이 EJB가 어떠한 방식으로 트랜잭션(DB 트랜잭션, 프로세스 트랜잭션)을

어떻게 관리할 것인가를 정의하는 곳인데, Container라는 값은 웹로직 컨테이너에서 디폴트로 관리를

하라는 의미이다. 만약 사용자가 세 히 트랜잭션을 관리할 필요성이 있다면 Bean값을 넣어주면

EJB에서 트랜잭션을 관리한다는 의미이다. 이 말은 개발자가 모든 트랜잭션을 관리하기 때문에

일일이 프로그램을 짜줘야 한다. 통상의 경우 복잡하거나 시간이 오래걸리는 배치처리 작업을

호출하지 않는 이상 세션빈에서는 십중팔구 Container에서 관리한다.

나머지 속성들 또한 트랜잭션에 관련된 내용인데, 이후 트랜잭션 장에서 상세히 알아보기로 하고

일단 여기서는 통상적으로 그렇게 쓰려니 생각하고 넘어가길 바란다.

<ejb-client-jar> 태그는 클라이언트 쪽 jar파일을 만들것인지를 결정하는 태그이다. 통상 컨테이너에

있는 EJB를 호출할 경우 클라이어언트에는 Remote, Home인터페이스가 필요하게된다. 물론,

CLASSPATH에 그 클래스가 존재해도 무방하지만, <ejb-client-jar>에서 정의 하면 컴파일시 Remote,

Home과 그외 클라이언트에서 호출시 필요한 클래스를 자동으로 생성해 준다.

weblogic-ejb-jar.xml (ejb-jar.xml이외에 웹로직에서 정의하는 확장된 xml이다.)

<?xml version="1.0"?>

<!DOCTYPE weblogic-ejb-jar PUBLIC

'-//BEA Systems, Inc.//DTD WebLogic 7.0.0 EJB//EN'

'http://www.bea.com/servers/wls700/dtd/weblogic-ejb-jar.dtd'>

cafe.daum.net/weblogic

- 57 - 서진권 [email protected]

<weblogic-ejb-jar>

<weblogic-enterprise-bean>

<ejb-name>MemoryInfo</ejb-name>

<jndi-name>ejb-stateless-MemoryInfo</jndi-name>

</weblogic-enterprise-bean>

</weblogic-ejb-jar>

아무리 웹로직에서 EJB를 사용한다 하더라도 이전에 설명했던 ejb-jar.xml을 무시할 순 없다.

Sun에서 규약한 xml파일이기 때문이다. 하지만 웹로직 ejb를 사용한다면 추가된 xml파일이 몇 개가

더 있는데 그중 weblogic으로 시작하는 xml파일이다. 이 xml파일에다가 어떤정보를 기입하냐면,

웹로직서비스(Connection Pool, Tuxedo Connetor, JNDI…등등 웹로직은 수많은 서비스를

제공한다.)를 어떻게 받을 것인가를 정의한다. 우리가 사용했던 예제는 DB를 액세스 하지 않기

때문에 JNDI서비스만 받으면 된다. <jndi-name>속성은 바로 jndi를 어떤 문자로 바인딩 할것인가를

정의한다. 어떻게 보면 클라이언트에서 EJB서비스를 받을경우 가장 중요한 정보가 될수 있다.

여기서는 ejb-stateless-MemoryInfo 문자들로 지정했는데 실제 클라이언트에서 이 문자들로

바인딩한다.

application.xml (ejb모듈을 정의 하는 파일. 컴파일시 ear파일을 생성하는 정보가 된다.)

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE application PUBLIC '-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN'

'http://java.sun.com/dtd/application_1_3.dtd'>

<application>

<display-name>MemoryInfo stateless EJB</display-name>

<description>MemoryInfo stateless EJB</description>

<module>

<ejb>MemoryInfo.jar</ejb>

</module>

</application>

applicaton.xml에 EJB의 이름과 설명을 기입한다. <display-name>과 <description>태그에서는

사용하게될 applications 의 이름과 설명을 기입하며, 가장 중요한 <module>태그는 EJB컴파일후

생성되는 최종적인 jar파일인데, jar파일 하나만 가지고도 EJB를 실행 시킬수 있지만, 또다시 관련된

jar파일을 여러 개 모아서 하나의 ear파일로 한꺼번에 배치 시킬수도 있다.

자… 이제 내친김에 클라이언트쪽 프로그램까지 짜보자. 이 클라이언트 프로그램은 간단하다. 우리가

작성했던 EJB에 바인딩후, Home Interface를 통해 Remote Interface를 구한후 함수를 차례로

실행하면서 , 전체메모리, 사용한메모리, 사용가능한 메모리를 구하고 서버의 가비지 콜렉팅을 한후

cafe.daum.net/weblogic

- 58 - 서진권 [email protected]

EJB를 닫고, 종료한다.

Client.java

package ejb.stateless.MemoryInfo;

import java.rmi.RemoteException;

import java.util.Properties;

import javax.ejb.CreateException;

import javax.ejb.RemoveException;

import javax.naming.Context;

import javax.naming.InitialContext;

import javax.naming.NamingException;

import javax.rmi.PortableRemoteObject;

public class Client

{

private static final String JNDI_NAME = "ejb-stateless-MemoryInfo";

private static final String url = "t3://192.168.1.150:7001";

public static void main(String args[])

{

Context ctx = null;

MemoryInfoHome home = null;

MemoryInfo mi = null;

try

{

Properties h = new Properties();

h.put(Context.INITIAL_CONTEXT_FACTORY,

"weblogic.jndi.WLInitialContextFactory");

h.put(Context.PROVIDER_URL, url);

System.out.println("Connecting weblogic...");

ctx = new InitialContext(h);

cafe.daum.net/weblogic

- 59 - 서진권 [email protected]

}

catch (NamingException ne)

{

System.out.println("We were unable to get a connection to the

WebLogic server at "+url);

System.out.println("Please make sure that the server is running.");

System.out.println(ne);

return;

}

try

{

home = (MemoryInfoHome)

PortableRemoteObject.narrow(ctx.lookup(JNDI_NAME), MemoryInfoHome.class);

mi = (MemoryInfo) PortableRemoteObject.narrow(home.create(),

MemoryInfo.class);

System.out.println("Total memory : " + mi.getTotalMemory());

System.out.println("Used memory : " + mi.getUsedMemory());

System.out.println("Free memory : " + mi.getFreeMemory());

System.out.println("Now do force garbage collecting... ");

mi.runGC();

}

catch (Exception e)

{

System.out.println("The client was unable to lookup the EJBHome.

Please make sure ");

System.out.println("that you have deployed the ejb with the JNDI name

"+JNDI_NAME+" on the WebLogic server at "+url);

System.out.println(e);

return;

}

finally

{

System.out.println("Release EJB to EJBPools.");

try

cafe.daum.net/weblogic

- 60 - 서진권 [email protected]

{

if (mi != null)

mi.remove();

}catch(Exception e){}

}

}

}

소스설명

import java.rmi.RemoteException;

import java.util.Properties;

import javax.ejb.CreateException;

import javax.ejb.RemoveException;

import javax.naming.Context;

import javax.naming.InitialContext;

import javax.naming.NamingException;

import javax.rmi.PortableRemoteObject;

EJB를 사용하면 임포트를 많이도 사용하는데, 그중 대부분이 Exception 이 일어날경우 처리하는

Exception Class이다. 또, 통상의 모든 EJB는 RMI로 통신하고, 자바의 표준 바인딩 방식은 JNDI를

사용하기 때문에 java.rmi 패키지와 java.naming 패키지를 많이 이용한다.

private static final String JNDI_NAME = "ejb-stateless-MemoryInfo";

private static final String url = "t3://192.168.1.150:7001";

위 두개의 내부 멤버 변수들은 어떠한 이름으로 JNDI를 바인딩 할 것인가와 웹로직 서버에

접속하기 위한 주소를 정적 변수로 가지고 있다. JNDI_NAME은 우리가 weblogic-ejb-jar.xml에서

<jndi-name> 속성에서 정의한 문자들이다. url변수가 가지고 있는 값중 한가지 특이한 것은

프로토콜이 t3라는 프로토콜인데, 아마 처음 보는 사람은 http에 익숙해져서 신기하게 여길것이다.

t3라는 프로토콜의 유래는 BEA사에서 웹로직을 만든게 아니다. Tengah라는 어플리케이션 서버가

있었는데, Tengah서버의 내부 네트웍 프로토콜이 t3라는 프로토콜이다. 아마 Tengar 3.0 이후에

BEA에서 Tengah서버를 인수하여 Weblogic으로 바꾸진것이다. 분위기가 이상한데로 흘러 갔는데…

아묻튼, t3프로토콜을 못 믿는 사람들은 http로 바꿔도 좋다. 하지만 개인적으로 t3를 써서 피본적인

한번도 없다…

cafe.daum.net/weblogic

- 61 - 서진권 [email protected]

Context ctx = null;

MemoryInfoHome home = null;

MemoryInfo mi = null;

메인함수 내부의 변수들인데, EJB에 연결할 수 있는 Context와 실제 EJB의 Home Interface,

Remote Interface의 인스턴스를 선언한후 null로 초기화 했다.

try

{

Properties h = new Properties();

h.put(Context.INITIAL_CONTEXT_FACTORY,

"weblogic.jndi.WLInitialContextFactory");

h.put(Context.PROVIDER_URL, url);

System.out.println("Connecting weblogic...");

ctx = new InitialContext(h);

}

catch (NamingException ne)

{

System.out.println("We were unable to get a connection to the

WebLogic server at "+url);

System.out.println("Please make sure that the server is running.");

System.out.println(ne);

return;

}

try-catch 절까지 모두 EJB를 연결하는 부분이다. Properties 인스턴스 h는 EJB를 어떻게 연결할

것인가를 정의한다. Context.INITIAL_CONTEXT_FACTORY 상수는 Context뿐만 아니라 자바의 모든

이기종 연결, 통신은 모두 Factory라는 개념의 초기화 하는 클래스를 통해서 이루어 진다.

Context.INITIAL_CONTEXT_FACTORY 바로 Context를 초기화할 팩토리 클래스를 지정하는 곳인데,

문자열로 패키지의 풀 패스를 적어준다. 우리는 웹로직 컨테이너를 사용하므로

"weblogic.jndi.WLInitialContextFactory" 라고 기입했다. PROVIDER_URL 항목은 EJB컨테이너 서버의

주소를 말하는 것이다. 초기화한 url을 저곳에 넣은 것이다. 참고로 지금 작성하고 있는 Client.java는

일반 자바 어플리케이션 이므로 웹로직과 상관없이 다른 머신에서 실행될수도 있다. 하지만 jsp 나

servlet같은 경우는 웹로직 컨테이너 안에서 EJB와 같이 실행된다. 이때 위처럼 콘텍스트 준비를

다음과 같이 간단하게도 할 수 있다.

cafe.daum.net/weblogic

- 62 - 서진권 [email protected]

try

{

ctx = new InitialContext();

}

catch(Exception …)

하지만, EJB와 jsp가 같은 서로 다른 컨테이너에서 실행된다면 저렇게 사용하면 안된다.

최종적으로 h 인스턴스를 Context를 초기화 해주는 InitialContext() 에 인수로 전달해서 연결정보와

함께 새로운 Context를 얻었다. 만약 연결도중 문제가 생겼다면 catch()절에 적당한 말을 써준다.

두번째 try-catch 절은 초기화한 context로 바인딩 하는 과정과 우리가 작성햇던 stateless session

bean의 4개의 함수를 차례대로 호출한다.

home = (MemoryInfoHome) PortableRemoteObject.narrow (ctx.lookup(JNDI_NAME),

MemoryInfoHome.class);

mi = (MemoryInfo) PortableRemoteObject.narrow (home.create(), MemoryInfo.class);

home 인터페이스를 구하는 과정을 바인딩 과정이라고 하는데 위의 소스를 보면

ctx.lookup(JNDI_NAME)이라는 문장이 바로 바인딩 하는 과정이다. 초기화한 ctx에 연결정보가 있고,

JNDI_NAME에 바로 바인딩할 JNDI이름이 있기 때문에 자바의 Context.lookup함수를 사용하여 바인딩

한 것이다. 여기서 중요한 부분이 있는데, Java API문서를 보면 알겠지만, ctx.lookup이 리턴하는 것은

분명 Object이다. 그렇기 때문에 형변환이 필요하고, RMI를 사용하는 EJB는

PortableRemoteObject.narrow함수를 사용하여 home인터페이스를 구하게 되는것이다. 보다 깊숙한

설명은 Java API의 PortableRemoteObject 클래스를 설펴보기 바란다. 일단, EJB를 실제로 사용하기

전 바인딩 과정까지 모두 마쳤다.

이제 실제로 EJB서버의 함수를 호출할 클라이언트쪽 껍데기가 필요한데, 이때 사용하는게 바로

Remote Interface를 구하는 것이다. Remote Interface는 Home Interface가 미리 선행적으로

구해져야지 Remote Interface를 구할 수 있다. 바로 Home Interface의 create함수만 사용하면

되는것이다. 우리가 Home Interface에서 작성한 create() 함수도 기억을 더듬어 create시 리턴되는

형이 바로 MemoryInfo다. 여기서도 마찬가지로 Remote Interface를 구할경우

PortableRemoteObject.narrow() 함수를 사용한다.

System.out.println("Total memory : " + mi.getTotalMemory());

System.out.println("Used memory : " + mi.getUsedMemory());

System.out.println("Free memory : " + mi.getFreeMemory());

System.out.println("Now do force garbage collecting... ");

cafe.daum.net/weblogic

- 63 - 서진권 [email protected]

mi.runGC();

위의 소스는 최종적으로 우리가 작성했던 함수를 호출하며 구해온 데이터를 화면에 출력한다. 모두

MemoryInfo의 mi 인스턴스에서 함수를 호출하는 것이다.

finally

{

System.out.println("Release EJB to EJBPools.");

try

{

if (mi != null)

mi.remove();

}catch(Exception e){}

}

어떻게 보면 finally 절은 웹로직 서버의 안전성을 생각한 소스다. EJB객체를 사용후 바로 종료를

해줘야 한다. 그래야지 EJB Pool에 객체가 다시 반환이 되는 것이다. 만약 try-finally로 remove를

하지 않았다면, 아마 웹로직 서버가 사흘이 멀다하고 숏다운 될 것이다. 이 문제로 고생하는 개발자

여럿봤다. 그러니 Session Bean(Entyti Bean에서 remove 했다간 큰일남.)을 사용후 반드시 try-

finally로 remove해주기 바란다.

참고적으로 클라이언트 소스에서 바인딩 하는 과정이 매우 소스가 길다는 것을 알수 있다. 만약

따로 바인딩하는 클래스와 Remote인터페이스를 구하는 클래스를 만든다면 소스는 10-15라인 정도로

모두 끝나기 때문에 보다 더 간단하질 것이다. 두말하면 잔소리 겠지만 EJB를 사용하는 모든

클라이언트에서 사용하기 때문에 소스를 엄청줄일 수 있을것이다.

이제 여기까지 세션빈을 작성하기 위한 Home, Remote, Bean, ejb-jar.xml, weblogic-

ejb.jar.xml파일 까지 모두 완료되었다. 컴파일 하기전 java 소스 파일을

/project/ejbsource/MemoryInfo 으로 복사하자. 그리고 앞으로 계속 작성될 소스는

/project/ejbsource하위에 작성하게 될것이다. 윈도우 사용자도 적당히 디렉토리를 생성하기 바라며,

아무래도 처음 해보는 것이기 때문에 뭐하는 디렉토리인지는 했갈리지 않는게 중요하겠다.

3.1 컴파일, 배치(Deploy), 실행(run)

EJB를 컴파일하는 과정은 의외로 복잡하다. 그러한 과정을 일단 정리해보면 다음과 같다.

cafe.daum.net/weblogic

- 64 - 서진권 [email protected]

컴파일 하기 위한 환경설정

모든 Java(Remote, Home, Bean… 등등) 소스 컴파일

컴파일된 클래스와 xml파일을 jar파일로 압축

압축된 jar파일을 웹로직 컴파일러로 다시한번 컴파일

jar파일 모듈별로 ear파일로 묶음.

클라이언트가 필요시 Home Interface, Remote Interface 컴파일 또는 컴파일된 class를

클라이언트 전용의 jar파일로 묶음.

웹로직에서 ejb를 컴파일 하는 방법은 크게 3가지가 있는데,

Batch 파일을 작성하여 컴파일 하는 방법

Apache 의 ant 유틸리티를 이용하여 컴파일(위의 방법과 거의 동일)

웹로직 빌더를 이용하여 컴파일

이러한 방법들이다. 흔히 batch파일을 이용하여 컴파일을 많이 하는데 sh파일이나 윈도우의

cmd파일로 과정을 배치화 하여 컴파일 한다. 웹로직 빌더는 Applet으로 된 GUI환경의

컴파일/디플로이 하는 소프트웨어 인데, 서버환경에서는 거의 사용하지 못하고 클라이언트서만

사용하기 때문에 텔넷환경에서는 사용을 못하는 단점도 있고 컴파일후 바로 웹로직 서버에 배치 할수

있는 장점도 있다. batch파일이나 ant유틸리티를 사용하면 서버에서도 컴파일 한후 바로 배치 시킬수

있으므로 이 방법을 추천한다. 한가지 흠이라면 처음 작성시에는 노가다가 좀 있다는 것이다. 하지만

한번 작성되고 나면 ejb이름만 바꿔준다음 복사하여 또 사용할 수 있다는 장점도 있다. 그리고 ant

컴파일 하는 과정은 xml에 정보를 기입한후 컴파일 하는 것이데 batch컴파일과는 별반 다를게 없다.

이 강좌에서는 시대가 시대이니 만큼 ant, xml을 이용한 컴파일 방법을 사용한다. 처음에는 매우

복잡하게 보이는데 일단 한 개가 작성되면 조금씩 조금씩 수정하여 다음에 다시 사용할 수도 있다.

하지만 개인적으로 예전보다는 많이 좋아졌다는 생각이 든다. 초기 EJB는 정말 복잡했다. 해본

사람은 알겠지만 정말 무시못하는 작업이었다.

컴파일

강좌중 처음 컴파일 하는 과정인데, 전장에서 설명했던 setEnv.sh파일이나 setEnv.cmd파일을 꼭

호출한 상태에서 컴파일 하기 바란다.

ant 유틸리티를 사용하기전에 두개의 파일이 필요한데, 어느곳에 Compile, Deploy 할것인가등의

정보가 기입된 프로퍼티 파일과 어떠한 과정으로 컴파일을 진행할것인가의 정보가 담긴 xml파일이다.

ejbcomp.properties

#choose classic, modern, jikes, or jvc

cafe.daum.net/weblogic

- 65 - 서진권 [email protected]

JAVAC=modern

BEA_HOME=/weblogic/wl70

WL_HOME=/weblogic/wl70/weblogic700

CLIENT_CLASSES=/project/ejbdomain/ejbserver/clientclasses

SERVER_CLASSES=/project/ejbdomain/ejbserver/serverclasses

EX_WEBAPP_CLASSES=/project/ejbdomain/applications/DefaultWebApp/WEB-INF/classes

EX_WEBAPP=/project/ejbdomain/applications/DefaultWebApp

APPLICATIONS=/project/ejbdomain/applications

CLASSPATH=${java.class.path}

PORT=7001

WINDOWS="Windows XP,Windows 2000,Windows NT,Windows 98,Windows 95"

UNIX="HP-UX,Solaris,SunOS,AIX,Linux"

# Database properties

# These user defined properties are available to automate

# table setup for those examples that use an Oracle database.

DBSERVER=

DBPORT=

SID=

USER=

PASSWORD=

위 프로퍼티 파일은 javac의 위치는 어떻게 되며, EJB를 컴파일한 장소는 어떻게 되며, 클라이언트가

필요하는 클래스의 위치는 어떤곳인가등을 정의한 파일이다. 다음에 설명할 build.xml에 이 프로퍼티

파일을 지정하면 ant에서 이정보를 인식하게 된다. 만약 위치가 다를 경우 적절히 수정해 주기

바라며, 위의 프로퍼티 파일은 웹로직 예제에서 따가지고 온 것이기 때문에 DBSERVER, DBPORT

등의 항목등은 신경 쓰지 말기 바란다. 이 강좌에서는 상관 없는 항목들이다.

build.xml

<project name="ejb-stateless-MemoryInfo" default="all" basedir=".">

<!-- set global properties for this build -->

<property environment="env"/>

<property file="../ejbcomp.properties"/>

<property name="build.compiler" value="${JAVAC}"/>

<property name="source" value="."/>

cafe.daum.net/weblogic

- 66 - 서진권 [email protected]

<property name="build" value="${source}/build"/>

<property name="dist" value="${source}/dist"/>

<target name="all" depends="clean, init, compile_ejb, jar_ejb, ejbc, ear_app,

compile_client"/>

<target name="init">

<!-- Create the time stamp -->

<tstamp/>

<!-- Create the build directory structure used by compile

and copy the deployment descriptors into it-->

<mkdir dir="${build}"/>

<mkdir dir="${build}/META-INF"/>

<mkdir dir="${dist}"/>

<copy todir="${build}/META-INF">

<fileset dir="${source}">

<include name="ejb-jar.xml"/>

<include name="weblogic-ejb-jar.xml"/>

</fileset>

</copy>

</target>

<!-- Compile ejb classes into the build directory (jar preparation) -->

<target name="compile_ejb">

<javac srcdir="${source}" destdir="${build}"

includes="MemoryInfo.java, MemoryInfoHome.java, MemoryInfoBean.java"/>

</target>

<!-- Update ejb jar file or create it if it doesn't exist, including XML

deployment descriptors -->

<target name="jar_ejb" depends="compile_ejb">

<jar jarfile="${dist}/MemoryInfo.jar"

basedir="${build}"

update="yes">

</jar>

</target>

cafe.daum.net/weblogic

- 67 - 서진권 [email protected]

<!-- Run ejbc to create the deployable jar file -->

<target name="ejbc" depends="jar_ejb">

<java classname="weblogic.ejbc" fork="yes" failonerror="yes">

<sysproperty key="weblogic.home" value="${WL_HOME}/server"/>

<arg line="-verbose -compiler javac ${dist}/MemoryInfo.jar"/>

<classpath>

<pathelement path="${CLASSPATH}"/>

</classpath>

</java>

</target>

<!-- Put the ejb into an ear, to be deployed from the ${APPLICATIONS} dir -->

<target name="ear_app" depends="jar_ejb">

<ear earfile="${APPLICATIONS}/MemoryInfo.ear" appxml="${source}/application.xml">

<fileset dir="${dist}" includes="MemoryInfo.jar"/>

</ear>

</target>

<!-- Compile client app into the clientclasses directory, and move the client jar file (created by

ejbc) there as well -->

<target name="compile_client">

<move file="${source}/MemoryInfo_Client.jar"

tofile="${CLIENT_CLASSES}/MemoryInfo_Client.jar"/>

<javac srcdir="${source}"

destdir="${CLIENT_CLASSES}"

includes="Client.java"

classpath="${CLASSPATH};${CLIENT_CLASSES}/MemoryInfo_Client.jar"

/>

</target>

<target name="clean">

<delete dir="${build}"/>

</target>

<!-- Run the example -->

cafe.daum.net/weblogic

- 68 - 서진권 [email protected]

<target name="run">

<java classname="ejb.stateless.MemoryInfo.Client" fork="yes" failonerror="true">

<classpath>

<pathelement path="${CLASSPATH};${CLIENT_CLASSES}/MemoryInfo_Client.jar"/>

</classpath>

</java>

</target>

</project>

위의 파일이 바로 build.xml파일인데, 매우 복잡하게 보인다. 하지만 복잡할 거 하나도 없다. 위에서

말한 컴파일한 과정이 바로 이곳에 모두 나열된 것이다. EJB외적인 내용으므로 깊숙히 설명을

피하는데, 특별히 알아 둘 것은 <property file="../ejbcomp.properties"/> 이 항목을 보면 아까

설명했던 프로퍼티가 이곳에서 참조 된다는 것을 알수 있다. 참고로 ejbcomp.properties파일은 현재

의 상위 디렉토리에 존재해야 한다. 이렇게 하는 이유는 앞으로 작성될 모든 ejb컴파일은

ejbcomp.properties를 참조하기 때문이다. build.xml 파일이야 그렇다 치더라도 매번 프로퍼티 파일을

만들순 없지 않은가 ? 잠시후에도 설명하겠지만 ant를 실행하면 ant는 현재 디렉토리에서 build.xml을

기본값으로 찾는다. build.xml에서 환경설정값인 ejbcomp.properties파일도 찾아서 해당하는 곳에

컴파일 하는 것이다. 그리고 그외 build.xml파일을 유심히 봐보면 위에서 말한 컴파일과정을

정리한것과 유사함을 알 수 있을 것이다.

윗부분 정도에 보면 다음과 같은 태그를 볼수 있는데,

<target name="all" depends=" clean, init, compile_ejb, jar_ejb, ejbc, ear_app, compile_client "/>

clean, init, compile_ejb, jar_ejb, ejbc, ear_app, compile_client 속성 값이 뜻하는 의미가 바로 컴파일

과정을 나타내는 식별자다.

build.xml파일을 보면 중간 중간에 <target name="XXXXX">의 태그가 나오는데 XXXXX값이 과정을

나타내는 식별자다.

자… 이제 컴파일 하는 모든 과정이 끝났다. 소스가 있는 위치에서 다음과 같이 실행한다.

$ ant

실행을 하면 아마 다음과 상이한 컴파일 과정이 화면이 출력된다.

$ ant

Buildfile: build.xml

clean:

cafe.daum.net/weblogic

- 69 - 서진권 [email protected]

init:

[mkdir] Created dir: /project/ejbsource/MemoryInfo/build

[mkdir] Created dir: /project/ejbsource/MemoryInfo/build/META-INF

[mkdir] Created dir: /project/ejbsource/MemoryInfo/dist

[copy] Copying 2 files to /project/ejbsource/MemoryInfo/build/META-INF

compile_ejb:

[javac] Compiling 3 source files to /project/ejbsource/MemoryInfo/build

:

:

:

만약 컴파일 도중 문제가 생겼다면 분명히 문법에러던지 아니면 setEnv.sh 파일의 환경설정과

프로퍼티 파일이 어떠한 잘못이 있었기 때문이니, 천천히 다시한번 해보기 바란다. 성공적으로 컴파일

될경우 다음과 같이 에러나 경고가 없이 화면에 출력되어야 한다.

:

:

:

[java] [total 3441ms]

[java] [EJBCompiler] : Recompilation completed

[java] [EJBCompiler] : Rmic completed

[java] <2003-12-06 오전 12시36분09초> <Info> <EJB> <010076> <Client-jar

"MemoryInfo_Client.jar" created.>

[java] ejbc successful.

ear_app:

[ear] Building ear: /project/ejbdomain/applications/MemoryInfo.ear

compile_client:

[move] Moving 1 files to /project/ejbdomain/ejbserver/clientclasses

[javac] Compiling 1 source file to /project/ejbdomain/ejbserver/clientclasses

all:

BUILD SUCCESSFUL

Total time: 30 seconds

cafe.daum.net/weblogic

- 70 - 서진권 [email protected]

BUILD SUCCESSFUL이라는 문자가 나오더라도 중간 과정에 만약 에러가 있다면 꼭 해결하고

반드시 경고라든가 에러가 없어야 한다. 그리고 dist라는 디렉토리가 생성되있는데, 이곳이 바로

2번의 컴파일 과정을 거친 최종적인 배포 jar파일이 잇는 곳이다. 이곳에 MemoryInfo.jar파일이

있는데, 이 파일이 가지고 배치(Deploy)를 하게되며. 또 EJB 2.0 이후로는 ear파일로 다시 묶어서

Deploy도 할수 있다.

배치(Deploy)

웹로직에서는 크게 4가지 방법의 디플로이를 제공한다.

웹로직의 weblogic.Deployer클래스를 이용하여 디플로이(Hot Deploy)

웹로직 Admin 콘솔에서 디플로이

웹로직 빌더에서 디플로이

자동 디플로이(Development 모드에서만 가능)

보통의 경우 weblogic.Deployer 유틸리티를 이용하여 디플로이 하는데, 이를 Hot Deploy라고 하며,

언제든지, 동적으로 디플로이/언디플로이가 가능하다.

콘솔에서 디플로이 하는 방법은 가장 쉬운 방법인데, 컴파일된 ear또는 jar파일을 콘솔에서 업로드

하는 방법이다.

웹로직 빌더도 콘솔 디플로이와 마찬가지로 디플로이를 원하는 ear또는 jar파일을 선택후 원하는

서버에 디플로이 한다.

Admin 콘솔과 웹로직 빌더 디플로이의 장점은 GUI환경에서의 디플로이 하기 때문에 매우 쉽고

편하지만, 대량의 batch성 디플로이는 좀 까다롭다.

자동 디플로이란 웹로직 서버(Single Server)가 기동된 상태에서 ear파일을 서버 디렉토리하위의

applications에 복사만 해도 자동으로 디플로이가 된다. 어떻게 보면 젤 편한 디플로이지만, 개발

모드에서만 가능하므로 Single Server에만 가능하며, 실제 서버에서는 Managed Server가 여러대

존재할 가능성이 많기 때문에 불가능하다.

이 강좌에서는 Single Server에서 모든 예제가 돌아간다. 당연한 얘기 겠지만, 자동 디플로이가 가장

편한 방법이라고 할 수 있겠다. 그리고 build.xml파일의 중간쯤 다음과 같은 태그가 있는데,

<target name="ear_app" depends="jar_ejb">

이 절에서 ear파일을 생성한 다음 서버디렉토리 하위의 applications에 복사하는 것을 볼 수 있다.

고로 특별히 디플로이를 하지 않아도 이미 ant도중에 applications 디렉토리로 ear파일이 복사 되어

자동 디플로이가 된것이다. 의심이 가는 독자라면, 직접 /project/ejbdomain/applications 위치로

가보면 될 것이다. 그리고 웹로직 서버의 로그파일이나 nohup.out파일에 보면 다음과 같은 문장이

출력 되어있을것이다.

<2003-12-06 오전 01시00분44초> <Notice> <WebLogicServer> <000360> <Server started in

cafe.daum.net/weblogic

- 71 - 서진권 [email protected]

RUNNING mode>

<2003-12-06 오전 01시01분36초> <Notice> <Application Poller> <149400> <Activating application:

_appsdir_MemoryInfo_ear>

<2003-12-06 오전 01시01분36초> <Notice> <Application Poller> <149404> <Activate application

_appsdir_MemoryInfo_ear on ejbserver - Running>

<2003-12-06 오전 01시01분38초> <Notice> <Application Poller> <149404> <Activate application

_appsdir_MemoryInfo_ear on ejbserver - Completed>

마지막에 Completed라는 메시지가 나오면, 성공적으로 디플로이 되었다는 얘기이고, Admin 콘솔로

접속해서 왼쪽 트리메뉴중 [ejbdomain]->[Deployments]->[EJB] 항목을 펼쳐보면,

MemoryInfo.jar라는 항목이 있을것이다.

그리고 화면 오른쪽 메인화면을 보면 이름은 MemoryInfo.jar로 되어있고, Path는 ear파일의 full

패스와 /MemoryInfo.jar로 되어있는데, ear파일안에 MemoryInfo.jar라는 얘기이다. 그리고 이러한

정보들은 config.xml파일에 저장이 되게 되며, 웹로직이 다시 기동시에는 config.xml파일 설정으로

ear파일을 읽어 들여서 디플로이 한다. 여기까지 자동 디플로이를 사용하여, 디플로이 한 방법이다.

cafe.daum.net/weblogic

- 72 - 서진권 [email protected]

자, 이제 우리가 작성했던 Client.java를 실행해보자.

$ ant run

만약 성공적으로 실행이 된 독자는 아마 다음과 상이한 화면을 볼 수 있을것이다.

Buildfile: build.xml

run:

[java] Connecting weblogic...

[java] Total memory : 84066304

[java] Used memory : 65767272

[java] Free memory : 18272160

[java] Now do force garbage collecting...

[java] Release EJB to EJBPools.

BUILD SUCCESSFUL

Total time: 16 seconds

서버의 전체 메모리, 사용한메모리, 사용가능한메모리를 화면에 출력한후 서버를 가비지 콜렉팅후

EJB를 닫고 종료한다. stateless session bean은 어떠한 클라이언트 정보도 없이 그냥 서버에 있는

함수만 한번 실행해주고, 끝나버린다. 그 이상 그 이하도 아니다.

근데 왜 ant run 하면 클라이언트가 실행되냐면 build.xml파일의 마지막쯤 보면 아래와 같은 태그

절이 있다.

<target name="run">

ant run을 하게되면 다른 태그는 실행을 하지 않고, 위의 태그 절만 실행하게된다, 그 태그절을 보면

java로 실행하는 것을 알 수 있을 것이다. 만약 java 커맨드 라인으로 우리가 작성한 Client를 실행

한다면 다음과 같이 실행하면 된다.

$ java -cp $CLASSPATH: $CLIENT_CLASSES /MemoryInfo_Client.jar ejb.stateless.MemoryInfo.Client

실행을 하면 ant run과 마찬가지로 비슷한 화면이 출력된다.

Connecting weblogic...

Total memory : 84066304

Used memory : 57245512

Free memory : 26818744

Now do force garbage collecting...

Release EJB to EJBPools.

cafe.daum.net/weblogic

- 73 - 서진권 [email protected]

제 4장 Stateful Session Bean

이번장은 세션빈의 두번째인 stateful session bean에 대해서 설명하겠다. stateful session bean은

stateless session bean에 비하여 서버에 클라이언트 정보를 가질수 있다. 상세히 말하자면

EJB클라이언트가 서버의 Home인터페이스를 create할 때부터, 클라이언트가 EJB를 remove()할 때

까지 모든 정보를 가지고 있다는 뜻이다. stateless session bean은 함수가 종료되면, 어떠한 정보도

서버쪽에 남지 않게 된다. 하지만 stateful session bean은 EJB 인스턴스 자체가 서버에 존재 하기

때문에 멤버변수, 함수들은 모두 함수를 호출하고 나서도 기억되고 있다. 다음은 stateful session

bean의 라이프 사이클이다.

그림을 설명하자면 다음과 같은 절차가 될 것이다.

1. 웹로직 기동시 어떠한 stateful세션빈도 서버에 존재하지 않는다.

2. 최초 클라이언트가 요청시 서버 EJB를 활성화 하고 서버 EJB는 활성화된 인스턴스를

메모리에 직렬화(Serialize)한다.

3. weblogic-ejb-jar.xml 의 속성중 <max-bean-in-cache> 만큼 EJB 인스턴스가 쌓일 때

까지 계속 클라이언트 요청마다 활성화 한다.

4. 클라이언트에서 remove()메소드를 호출시 직렬화된 데이터는 모두 제거되고, 활성화되었던

EJB를 캐쉬에 반환한다.

stateful session bean 이 stateless session bean과 가장 큰 차이점은 remove하기전까지는 모든

EJB정보를 가지고 있다는 점이다. 정보를 저장하는 매체가 바로 캐쉬인데, 클라이언트에서 <max-

bean-in-cache>의 숫자 보다 많으면 CacheFullException 을 발생하게 된다.

다음 예제는 장바구니를 구현한 예제인데, 물건을 저장하는 매체가 stateful session bean이고, 그

안에 간단한 계산(business logic)이 담겨 있다.

cafe.daum.net/weblogic

- 74 - 서진권 [email protected]

CartProduct.java(장바구니에 담겨 있는 품목 Class)

package ejb.stateful.ShopingCart;

import java.io.Serializable;

public final class CartProduct implements Serializable

{

private String productCode = "";

private String productName = "";

private int qty = 0;

private int price = 0;

public CartProduct()

{

}

public CartProduct(String productCode, String productName, int qty, int price)

{

this.productCode = productCode;

this.productName = productName;

this.qty = qty;

this.price = price;

}

public String getProductCode()

{

return this.productCode;

}

public String getProductName()

{

return this.productName;

}

public int getQty()

cafe.daum.net/weblogic

- 75 - 서진권 [email protected]

{

return this.qty;

}

public int getPrice()

{

return this.price;

}

public double getAmount()

{

return this.qty * this.price;

}

}

이 클래스가 하는 일은 제품코드, 제품명, 수량, 단가, 금액을 나타내는 클래스이다. 이 클래스가

여러 개 모이면 장바구니 리스트가 되는 것이다. 다른 것은 그렇다 치더라도 유심히 보아야 할 것이

한가지 있다. 클래스가 Serializable 인터페이스로부터 상속을 받는데, 대개 자바에서 Serializable하는

이유는 작게는 어떠한 Object를 파일또는 메모리에 저장하여, 지속적인 정보를 가지는 것이고, 크게는

Client와 Server간에 어떠한 통신이 일어났을경우, 서버에 있는 Object가 클라이언트에 전달될 경우도

마찬가지로 Object가 가지고 있는 정보를 Client에 그대로 지속시켜 주는 것이다. 보통의 EJB는

RMI기반으로 통신을 하게 되는데, 이때 서버쪽의 어떠한 Object를 클라이언트가 요구할 수가 있게

된다. 더 구체적으로 말하면 클라이언트에서 현재 서버에 담겨 있는 어떠한 제품을 보고 싶은경우

분명히 서버 메소드를 호출하여, 리턴된 값을 가지고 적절한 가공 처리를 할 것이다.

RMI를 사용할 경우 바로 Client와 Server간에 정보를 주고 받는 것이 클래스 이면 그것은 반드시

직렬화(Serializable)가 되어야 하므로, 위 클래스는 Serializable 인터페이스로부터 상속을 받는다.

ShopingCartHome.java(Home 인터페이스)

package ejb.stateful.ShopingCart;

import java.rmi.RemoteException;

import javax.ejb.CreateException;

import javax.ejb.EJBHome;

public interface ShopingCartHome extends EJBHome

cafe.daum.net/weblogic

- 76 - 서진권 [email protected]

{

ShopingCart create() throws CreateException, RemoteException;

}

stateless session bean과 마찬가지로 Home Interface에서의 할일이 여기서도 create()시 Remote

Interface를 리턴해주는 일 밖에 없다.

ShopingCart(Remote 인터페이스)

package ejb.stateful.ShopingCart;

import javax.ejb.*;

import java.rmi.RemoteException;

public interface ShopingCart extends EJBObject

{

public void addProduct(String productCode, String productName,

int qty, int price) throws RemoteException;

public void removeProduct(String productCode) throws RemoteException;

public Object[] cartArray() throws RemoteException;

public int getCountProduct() throws RemoteException;

public double getTotAmount() throws RemoteException;

}

함수를 설명하자면 다음과 같다.

addProduct : stateful session bean에 제품을 하나 추가한다. 제품코드, 제품명, 수량,

단가를 인수로 주게 된다.

removeProduct : 제품을 하나 삭제한다. 인수는 제품코드이다.

cartArray : 현재 stateful session bean이 가지고 있는 모든 제품을 가져온다. 리턴되는 형이

Object로 되어있는데, 소스설명중 가장 처음에 설명한 CartProduct 클래스가 한 개이상

배열에 담겨서 리턴된다.

getCountProduct : 현재 장바구니에 몇 개의 제품이 있는지 구한다.

getTotAmount : 장바구니의 모든 합계 금액을 구한다.

ShopingCartBean(Bean class)

cafe.daum.net/weblogic

- 77 - 서진권 [email protected]

package ejb.stateful.ShopingCart;

import java.util.ArrayList;

import javax.ejb.CreateException;

import javax.ejb.SessionBean;

import javax.ejb.SessionContext;

import javax.naming.Context;

import javax.naming.InitialContext;

import javax.naming.NamingException;

public class ShopingCartBean implements SessionBean

{

private ArrayList cartList = null;

private SessionContext ctx;

public void setSessionContext(SessionContext ctx)

{

this.ctx = ctx;

}

public void ejbActivate()

{

}

public void ejbPassivate()

{

}

public void ejbCreate() throws CreateException

{

cartList = new ArrayList();

}

public void ejbRemove()

{

cartList.clear();

cafe.daum.net/weblogic

- 78 - 서진권 [email protected]

cartList = null;

}

public void addProduct(String productCode, String productName, int qty, int price)

{

CartProduct cp = new CartProduct(productCode, productName, qty, price);

cartList.add(cp);

}

public void removeProduct(String productCode)

{

String loopProductCode = "";

for(int i = 0; i < cartList.size(); ++i)

{

loopProductCode = ((CartProduct)cartList.get(i)).getProductCode();

if (loopProductCode.equals(productCode))

{

cartList.remove(i);

break;

}

}

}

public Object[] cartArray()

{

return cartList.toArray();

}

public int getCountProduct()

{

return cartList.size();

}

public double getTotAmount()

{

double addVal = 0.0;

cafe.daum.net/weblogic

- 79 - 서진권 [email protected]

for(int i = 0; i < cartList.size(); ++i)

addVal += ((CartProduct)cartList.get(i)).getAmount();

return addVal;

}

}

steteful session bean도 stateless session bean과 마찬가지로 SessionBean 으로부터 상속을 받는다.

멤버 인스턴스로 cartList가 있는데, 이 인스턴스가 하는 일이 바로 클라이언트에서 요구하는

기능(제품추가, 삭제, 조회)의 제품들을 가지고 있는 Object라고 생각하면 될것이다. 눈치를 챘을

수도 있겠지만, stateful session bean이 이 인스턴스를 바로 직렬화 해서 클라이언트가

remove()메소드를 호출 하기 전까지 계속 정보를 유지하고 있는 것이다.

Bean 클래스가 복잡하게 보일수 있는데 이전장에서 했던 Bean과 구조상 크게 다를게 없다.

다른점은 ejbCreate()시 cartArray를 초기화 해줬고, ejbRemove()시 cartArray 내용을 지운 것 밖에는

없고, 나머지는 제품추가, 제품삭제, 조회 등이다. 상세하게 자바 소스를 설명하는 것 보단 이글을

읽는 사람들이 어느 정도 자바를 할 줄 아는 사람들이므로 구체적인 설명은 피하겠다.

ejb-jar.xml

<?xml version="1.0"?>

<!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>

<enterprise-beans>

<session>

<ejb-name>ShopingCart</ejb-name>

<home>ejb.stateful.ShopingCart.ShopingCartHome</home>

<remote>ejb.stateful.ShopingCart.ShopingCart</remote>

<ejb-class>ejb.stateful.ShopingCart.ShopingCartBean</ejb-class>

<session-type>Stateful</session-type>

<transaction-type>Container</transaction-type>

</session>

</enterprise-beans>

<assembly-descriptor>

cafe.daum.net/weblogic

- 80 - 서진권 [email protected]

<container-transaction>

<method>

<ejb-name>ShopingCart</ejb-name>

<method-name>*</method-name>

</method>

<trans-attribute>Required</trans-attribute>

</container-transaction>

</assembly-descriptor>

<ejb-client-jar>ShopingCart_Client.jar</ejb-client-jar>

</ejb-jar>

ejb-jar.xml파일도 stateless session bean과 크게 다를바가 없는데, 한가지 중요한점은 stateless

session bean과 구별하는 방법은 ejb-jar.xml파일에서 구분하는 것이다. 중간쯤 보면 <session-

type>Stateful</session-type> 항목이 있는데 이곳이, Stateless면 stateless session bean으로 컴파일

된다는 말이고, Stateful이면 stateful session bean으로 컴파일 된다.

weblogic-ejb-jar.xml

<?xml version="1.0"?>

<!DOCTYPE weblogic-ejb-jar PUBLIC

'-//BEA Systems, Inc.//DTD WebLogic 7.0.0 EJB//EN'

'http://www.bea.com/servers/wls700/dtd/weblogic-ejb-jar.dtd'>

<weblogic-ejb-jar>

<weblogic-enterprise-bean>

<ejb-name>ShopingCart</ejb-name>

<jndi-name>ejb-stateful-ShopingCart</jndi-name>

</weblogic-enterprise-bean>

</weblogic-ejb-jar>

weblogic-ejb-jar.xml파일도 마찬가지로 여기서 해주는 일은 JNDI이름만 잡아 준 것이 전부이다.

Client.java

package ejb.stateful.ShopingCart;

import java.rmi.RemoteException;

cafe.daum.net/weblogic

- 81 - 서진권 [email protected]

import java.util.Properties;

import javax.ejb.CreateException;

import javax.ejb.RemoveException;

import javax.naming.Context;

import javax.naming.InitialContext;

import javax.naming.NamingException;

import javax.rmi.PortableRemoteObject;

public class Client

{

private static final String JNDI_NAME = "ejb-stateful-ShopingCart";

private static final String url = "t3://192.168.1.150:7001";

public static void main(String args[])

{

Context ctx = null;

ShopingCartHome home = null;

ShopingCart sc = null;

try

{

Properties h = new Properties();

h.put(Context.INITIAL_CONTEXT_FACTORY,

"weblogic.jndi.WLInitialContextFactory");

h.put(Context.PROVIDER_URL, url);

System.out.println("Connecting weblogic...");

ctx = new InitialContext(h);

}

catch (NamingException ne)

{

System.out.println("We were unable to get a connection to the

WebLogic server at "+url);

System.out.println("Please make sure that the server is running.");

System.out.println(ne);

cafe.daum.net/weblogic

- 82 - 서진권 [email protected]

return;

}

try

{

home = (ShopingCartHome)

PortableRemoteObject.narrow(ctx.lookup(JNDI_NAME), ShopingCartHome.class);

sc = (ShopingCart) PortableRemoteObject.narrow(home.create(),

ShopingCart.class);

System.out.println("이제 쇼핑카트에 추가 합니다.");

sc.addProduct("001", "빨간 양말", 1, 300);

sc.addProduct("002", "노란 양말", 2, 300);

sc.addProduct("003", "초록 양말", 1, 300);

sc.addProduct("004", "무좀 양말", 3, 50);

sc.addProduct("005", "찢어진 양말", 3, 50);

System.out.println("총항목은 " + sc.getCountProduct() + "개, 총가격은 "

+ sc.getTotAmount() + "이군요");

System.out.println("무좀양말은 맘에 안들어요. 삭제합시다.");

sc.removeProduct("004");

System.out.println("주문하기전 확인해 볼까요 ?");

Object[] cartEnum = sc.cartArray();

CartProduct cp = null;

System.out.println("항목₩t제품코드₩t제품명₩t수량₩t가격₩t금액");

System.out.println("---------------------------------------

--------------");

for(int i = 0; i < cartEnum.length; ++i)

{

cp = (CartProduct)cartEnum[i];

System.out.println(i + 1 + "₩t₩t" +

cp.getProductCode() + "₩t" +

cp.getProductName() + "₩t" +

cp.getQty() + "₩t" +

cp.getPrice() + "₩t" +

cafe.daum.net/weblogic

- 83 - 서진권 [email protected]

cp.getAmount());

}

System.out.println("---------------------------------------

--------------");

System.out.println("총항목은 " + sc.getCountProduct() + "개, 총가격은 "

+ sc.getTotAmount() + "이군요");

}

catch (Exception e)

{

System.out.println("The client was unable to lookup the EJBHome.

Please make sure ");

System.out.println("that you have deployed the ejb with the JNDI name

"+JNDI_NAME+" on the WebLogic server at "+url);

System.out.println(e);

return;

}

finally

{

System.out.println("Release EJB to EJBPools.");

try

{

if (sc != null)

sc.remove();

}catch(Exception e){}

}

}

}

결과적으로 이 Client.java 클래스가 하는일은 서버에 ShpoingCart stateful session bean에

접속한다음에 몇가지 제품을 등록, 삭제 하고, 최종적으로 목록을 보여준다. 그리고 stateless session

bean과 마찬가지로 EJB를 닫게 된다. 그리고 가장 중요하게 눈여겨 볼 것은 remove()를 호출하기

전까지 서버에서 클라이언트가 작업한 내역을 가지고 있다는 것이다. 나름대로

ShopingCartBean.java파일과 Client.java파일을 분석해보기 바란다. Client에서 ShopingCartBean에게

어떠한 작업을 요청하는지, 또 ShopingCartBean은 어떠한 정보를 가지고 있는지를 생각해보면

stateful과 stateless가 비교가 될 것이다.

cafe.daum.net/weblogic

- 84 - 서진권 [email protected]

build.xml

<project name="ejb-stateful-ShopingCart" default="all" basedir=".">

<!-- set global properties for this build -->

<property environment="env"/>

<property file="../ejbcomp.properties"/>

<property name="build.compiler" value="${JAVAC}"/>

<property name="source" value="."/>

<property name="build" value="${source}/build"/>

<property name="dist" value="${source}/dist"/>

<target name="all" depends="clean, init, compile_ejb, jar_ejb, ejbc, ear_app,

compile_client"/>

<target name="init">

<!-- Create the time stamp -->

<tstamp/>

<!-- Create the build directory structure used by compile

and copy the deployment descriptors into it-->

<mkdir dir="${build}"/>

<mkdir dir="${build}/META-INF"/>

<mkdir dir="${dist}"/>

<copy todir="${build}/META-INF">

<fileset dir="${source}">

<include name="ejb-jar.xml"/>

<include name="weblogic-ejb-jar.xml"/>

</fileset>

</copy>

</target>

<!-- Compile ejb classes into the build directory (jar preparation) -->

<target name="compile_ejb">

<javac srcdir="${source}" destdir="${build}"

includes="CartProduct.java, ShopingCart.java, ShopingCartHome.java,

ShopingCartBean.java"/>

cafe.daum.net/weblogic

- 85 - 서진권 [email protected]

</target>

<!-- Update ejb jar file or create it if it doesn't exist, including XML

deployment descriptors -->

<target name="jar_ejb" depends="compile_ejb">

<jar jarfile="${dist}/ShopingCart.jar"

basedir="${build}"

update="yes">

</jar>

</target>

<!-- Run ejbc to create the deployable jar file -->

<target name="ejbc" depends="jar_ejb">

<java classname="weblogic.ejbc" fork="yes" failonerror="yes">

<sysproperty key="weblogic.home" value="${WL_HOME}/server"/>

<arg line="-verbose -compiler javac ${dist}/ShopingCart.jar"/>

<classpath>

<pathelement path="${CLASSPATH}"/>

</classpath>

</java>

</target>

<!-- Put the ejb into an ear, to be deployed from the ${APPLICATIONS} dir -->

<target name="ear_app" depends="jar_ejb">

<ear earfile="${APPLICATIONS}/ShopingCart.ear" appxml="${source}/application.xml">

<fileset dir="${dist}" includes="ShopingCart.jar"/>

</ear>

</target>

<!-- Compile client app into the clientclasses directory, and move the client jar file (created by

ejbc) there as well -->

<target name="compile_client">

<move file="${source}/ShopingCart_Client.jar"

tofile="${CLIENT_CLASSES}/ShopingCart_Client.jar"/>

<javac srcdir="${source}"

destdir="${CLIENT_CLASSES}"

cafe.daum.net/weblogic

- 86 - 서진권 [email protected]

includes="CartProduct.java, Client.java"

classpath="${CLASSPATH};${CLIENT_CLASSES}/ShopingCart_Client.jar"

/>

</target>

<target name="clean">

<delete dir="${build}"/>

</target>

<!-- Run the example -->

<target name="run">

<java classname="ejb.stateful.ShopingCart.Client" fork="yes" failonerror="true">

<classpath>

<pathelement path="${CLASSPATH};${CLIENT_CLASSES}/ShopingCart_Client.jar"/>

</classpath>

</java>

</target>

</project>

우리가 작성한 EJB클래스들, CartProduct.java 그리고 Client.java파일을 컴파일 하기 위한

build.xml파일이다. 자, 이제 모든 소스와 관련된 파일은 모두 준비 되었고, 컴파일을 해보자.

$ ant

Buildfile: build.xml

clean:

[delete] Deleting directory /project/ejbsource/ShopingCart/build

init:

[mkdir] Created dir: /project/ejbsource/ShopingCart/build

[mkdir] Created dir: /project/ejbsource/ShopingCart/build/META-INF

[copy] Copying 2 files to /project/ejbsource/ShopingCart/build/META-INF

compile_ejb:

[javac] Compiling 4 source files to /project/ejbsource/ShopingCart/build:

:

cafe.daum.net/weblogic

- 87 - 서진권 [email protected]

:

:

[java] ejbc successful.

ear_app:

[ear] Building ear: /project/ejbdomain/applications/ShopingCart.ear

compile_client:

[move] Moving 1 files to /project/ejbdomain/ejbserver/clientclasses

[javac] Compiling 2 source files to /project/ejbdomain/ejbserver/clientclasses

all:

BUILD SUCCESSFUL

Total time: 23 seconds

컴파일도 다 됐고, EJB배치도 다 돼었으니 이제 실행하는 일만 남았다.

$ $ ant run

Buildfile: build.xml

run:

[java] Connecting weblogic...

[java] 이제 쇼핑카트에 추가 합니다.

[java] 총항목은 5개, 총가격은 1500.0이군요

[java] 무좀양말은 맘에 안들어요. 삭제합시다.

[java] 주문하기전 확인해 볼까요 ?

[java] 항목 제품코드 제품명 수량 가격 금액

[java] -----------------------------------------------------

[java] 1 001 빨간 양말 1 300 300.0

[java] 2 002 노란 양말 2 300 600.0

[java] 3 003 초록 양말 1 300 300.0

[java] 4 005 찢어진 양말 3 50 150.0

[java] -----------------------------------------------------

[java] 총항목은 4개, 총가격은 1350.0이군요

[java] Release EJB to EJBPools.

cafe.daum.net/weblogic

- 88 - 서진권 [email protected]

BUILD SUCCESSFUL

Total time: 14 seconds

위와 같은 화면이 출력 되었다면, 성공적으로 모든 절차가 마무리 된것이다. 실패한 사람들은 다시

천천히 해보기를 바라고, stateless와 stateful과의 차이점을 다시한번 간략하게 정리해보자면,

stateless는 어떠한 메소드를 호출하면 끝나버리는 것이고, stateful은 클라이언트가 create시점부터

remove까지의 정보를 모두다 가지고 있다는 얘기이고, 이러한 메커니즘은 자바의 직렬화에 기반한

것이다.

실제 EJB는 모두 jsp나 서브릿 같은 클라언트들이 호출하게 되는데 J2EE의 HttpSession 과

HttpSessionBindEvent 객체를 잘 활용하면 stateful session bean으로 인증서버, 장바구니 등을 쉽게

구현할 수 있을 것이다.

cafe.daum.net/weblogic

- 89 - 서진권 [email protected]

제 5장 CMP (Container Managed Persistent) Entity

Bean

엔티티 빈은 데이터 관련 빈이다. 테이블에서 가장 기본적으로 객체화 할수 있는 것은 바로 레코드

이다. 컬럼을 객체화 해봤자 그 컬럼에 값을 쓰고, 읽어오는 것 뿐이다. 하지만 레코드는 어떠한

사물(객체)에 대해서 클래스로 표현할 수 있는 가장 좋은 대상(Object)이다. 이 엔티티 빈 예제는

오라클을 처음 교육 받는 사람들에게 친숙한 SCOTT user의 테이블들을 사용한다. 처음 예제이니

오라클에서 했던 것처럼 EMP 테이블을 가지고 엔티티빈을 설명하겠다. 그럼 간단히 EMP 테이블의

구조를 보자(물론 다 알겠지만…) 오라클의 DESC 명령의 결과를 그대로 떼온 것이다.

이름 널 유형 설명

------------------------------------------------------------

EMPNO NOT NULL NUMBER(4) 사원번호

ENAME VARCHAR2(10) 성명

JOB VARCHAR2(9) 직무

MGR NUMBER(4) 상관 사원번호

HIREDATE DATE 지급일자

SAL NUMBER(7,2) 급여액

COMM NUMBER(7,2) 나두 솔직히 모르겠다.(^^)

DEPTNO NUMBER(2) 부서번호

엔티티빈은 쉽게 말해서 위처럼 구조화 되어있는 테이블을 Sun의 규약에 맞게 객체지향적 이면서도,

기업에서 실질적으로 사용할 수 있게 정의하는 것이다. 규약에 따라서 정의를 한다고 해도 DBMS의

기본적인 SQL구문은 들어가기 마련이다. 단지 이러한 SQL구문(스키마 또는 이벤트)을

EJB컨테이너가 정의를 하느냐 개발자가 정의를 하느냐에 따라서 엔티티빈이 두가지 종류로 나뉘어

지는데, SQL구문을 컨테이너가 관리를 하면 CMP(Container Managed Persistent)라고 하고, 사용자가

정의하면 다음에 배울 BMP(Bean Managed Persistent)라고 한다. CMP같은경우 SQL스키마는

사용자가 정의를 하지는 않지만, 적어도 테이블이 어떠한 구조라다는 것은 컨테이너에게 알려줘야

한다. 다음에 더 구체적으로 말하겠지만, 어차피 CMP 엔티티빈을 생성한다고 해도, 나중에 실제

배치된 것을 디컴파일 해보면, BMP와 거의 흡사하다. 단지, 사용자의 노고를 줄여준다는 있점이

있으며, EJB 2.0에서 1.1 보다 CMP의 대폭적인 지원이 많아졌다. 자 그럼, 위의 EMP테이블을 어떻게

객체화 하였는가를 CMP 엔티티빈 예제소스를 통해 분석해 보자.

cafe.daum.net/weblogic

- 90 - 서진권 [email protected]

EmpCMPHome.java(Home Interface)

package ejb.cmp.EmpCMP;

import javax.ejb.CreateException;

import javax.ejb.EJBHome;

import javax.ejb.FinderException;

import java.rmi.RemoteException;

import java.util.Collection;

public interface EmpCMPHome extends EJBHome

{

public EmpCMP create(int empno, String ename, String job,

int mgr, java.sql.Date hiredate,

double sal, double comm, int deptno )

throws CreateException, RemoteException;

public EmpCMP findByPrimaryKey(EmpCMPPK pk)

throws FinderException, RemoteException;

}

세션빈들과 비교를 해보면 약간 추가되었다는 것을 알수 있다. 상단부 라인 소스야 세션빈과 거의

흡사하고 다른점이 몇가지 있다. create() 메소드가 인수가 추가된것인데, 눈치를 챈 사람들이라면

이게 대충 뭐하는 메소드인가를 알수 있을것이다. 바로 위에 설명했던 EMP테이블에 새로 데이터를

Insert하라는 것이다. 두말하면 잔소리지만 인수도 테이블의 컬럼에 따라 달라진다. 클라이언트

프로그램이 Home Interface를 create()하면 실제 DB서버에 데이터가 Insert가 되고, Insert된 정보를

담고 있는 객체가 바로 리턴하는 EmpCMP Remote Interface이다.

그 다음 세션빈과 차이점이 한가지 더 있다면 바로 findByPrimaryKey 메소드가 추가 되었다는

것이다. 이것도 눈치를 챈 사람들은 데이터베이스의 Primary Key컬럼을 통해 검색된 EmpCMP

Remote Interface를 구한다는 것을 알수 있을것이다. 인수로 EmpCMPPK 타입의 클래스가

들어오는데, 이 클래스는 EmpCMPPK클래스가 Primary Key 데이터값을 가지고 있는것이다.

findByPrimaryKey 메소드 같은 것들을 EJB에서는 Finder라고 부른다. 어떠한 엔티티빈이던지 간에

꼭 Finder하나를 가져야 하는데 findByPrimaryKey는 반드시 있어야 한다는 말이다. 이게 없으면

컴파일이 되지 않는다.

꼭 findByPrimaryKey 하나만 있어야 하는 것은 아니다. 사용자가 요구하는 어떠한 Finder가 있으면

findByPrimary외에 더 추가를 하면 된다. 예를들어서 사원명으로 검색하고 싶으면

findByEName(String ename) 이런식으로 추가 하면 되는것이다. Finder가 많으면 많을수록 해당

EJB는 기능이 막강해 진다. 그렇다고 쓸데없이 클라이언트가 요구하지도 않는 Finder를 만든다면

cafe.daum.net/weblogic

- 91 - 서진권 [email protected]

그것은 분명 개발시간 낭비다.

EmpCMP.java(Remote Interface)

package ejb.cmp.EmpCMP;

import java.rmi.RemoteException;

import javax.ejb.*;

public interface EmpCMP extends EJBObject

{

public int getEmpno() throws RemoteException;

public String getEname() throws RemoteException;

public String getJob() throws RemoteException;

public int getMgr() throws RemoteException;

public java.sql.Date getHiredate() throws RemoteException;

public double getSal() throws RemoteException;

public double getComm() throws RemoteException;

public int getDeptno() throws RemoteException;

public void setEmpno(int empno) throws RemoteException;

public void setEname(String ename) throws RemoteException;

public void setJob(String job) throws RemoteException;

public void setMgr(int mgr) throws RemoteException;

public void setHiredate(java.sql.Date hiredate) throws RemoteException;

public void setSal(double sal) throws RemoteException;

public void setComm(double comm) throws RemoteException;

public void setDeptno(int deptno) throws RemoteException;

}

메소드들의 이름을 유심히 보면 알겠지만, 모두 EMP테이블의 컬럼속성을 따온 것을 알수 있다.

만약 클라이언트쪽에서 getEmpno() 메소드를 호출하면 DB서버에서 레코드의 컬럼값을 구해온다.

만약 값을 수정하고 싶으면 setEmpno(1234) 이런식으로 호출해주면 될것이다. 소스 구조상 크게

보자면 가져오는 메소드는 상단에 위치하고 값을 기입하는 메소드는 중간부분부터 마지막 까지라고

볼수 있다. 다음장에 공부할 BMP 엔티티빈에서 더 구체적으로 배우겠고, Java의 메소드 Naming에

대해서 이미 알겠지만, 가져오는 메소드는 get메소드라고 부르며, 값을 기입하는 메소드는

set메소드라고 부른다.

cafe.daum.net/weblogic

- 92 - 서진권 [email protected]

EmpCMPBean.java (Bean Class)

package ejb.cmp.EmpCMP;

import javax.ejb.CreateException;

import javax.ejb.DuplicateKeyException;

import javax.ejb.EJBException;

import javax.ejb.EntityBean;

import javax.ejb.EntityContext;

import javax.ejb.FinderException;

import javax.ejb.NoSuchEntityException;

import javax.ejb.ObjectNotFoundException;

import javax.ejb.RemoveException;

import javax.naming.InitialContext;

import javax.naming.NamingException;

import javax.sql.DataSource;

abstract public class EmpCMPBean implements EntityBean

{

private EntityContext ctx;

public EmpCMPBean() {};

public void setEntityContext(EntityContext ctx)

{

this.ctx = ctx;

}

public void unsetEntityContext()

{

this.ctx = null;

}

abstract public int getEmpno();

abstract public void setEmpno(int empno);

cafe.daum.net/weblogic

- 93 - 서진권 [email protected]

abstract public String getEname();

abstract public void setEname(String ename);

abstract public String getJob();

abstract public void setJob(String job);

abstract public int getMgr();

abstract public void setMgr(int mgr);

abstract public java.sql.Date getHiredate();

abstract public void setHiredate(java.sql.Date hiredate);

abstract public double getSal();

abstract public void setSal(double sal);

abstract public double getComm();

abstract public void setComm(double comm);

abstract public int getDeptno();

abstract public void setDeptno(int deptno);

public void ejbActivate()

{

}

public void ejbPassivate()

{

}

public void ejbLoad()

{

}

public void ejbStore()

{

}

cafe.daum.net/weblogic

- 94 - 서진권 [email protected]

public void ejbRemove() throws RemoveException

{

}

public EmpCMPPK ejbCreate(int empno, String ename, String job,

int mgr, java.sql.Date hiredate,

double sal, double comm, int deptno ) throws

CreateException

{

setEmpno(empno);

setEname(ename);

setJob(job);

setMgr(mgr);

setHiredate(hiredate);

setSal(sal);

setComm(comm);

setDeptno(deptno);

return null;

}

public void ejbPostCreate(int empno, String ename, String job,

int mgr, java.sql.Date hiredate,

double sal, double comm, int deptno )

{

}

}

Bean Class소스를 보면 클래스가 EntityBean으로부터 상속을 받고, 클래스 자체가

abstract(추상클래스) 로 선언이 되있는데, 이말은 컨테이너가 다시 재정의를 한다는 말이다. 위의

소스를 유심이 보면, DB관련 프로그램인데도, SQL스크립트라고는 단 한문장도 들어가 있지 않다. 그

비 은 바로 Bean Class가 abstract로 추상화 되있고, 컴파일 과정에서 이 클래스를 상속받아

컨테이너가 다시 정의를 하는데, 이때 사용자에게는 보이지 않지만, 해당 SQL구문이 들어가는 것이다.

이말에 의심이 가는 사람은 다음에 EJB가 익숙해 질쯤 jar파일을 압축 해재 한후, 디컴파일 해보면

내말이 무슨 말인지 알 수 있을것이다.

그 다음 클래스 소스 내부는 세션빈과 같기 때문에 설명을 피하고, abstract public … get..()와

cafe.daum.net/weblogic

- 95 - 서진권 [email protected]

abstract public … set..() 함수들이 보인다. 이것도 마찬가지로 abstract로 메소드가 선언되있기

때문에 후에 누군가(컨테이너) 이것을 재정의 한다는 것이다. getEmpno() 라고 치자면 내부적으로

SELECT ENAME FROM EMP 이런식으로 SQL스크립트가 재정의 할 때 서버로 전송해서 그 결과를

리턴하는 것이다.

조금 전에 봤던 EmpCMP Remote Interface에서 getXXX(), setXXX()에서 선언한 메소드들이 바로

지금 EmpCMPBean의 abstract getXXX(), setXXX() 추상 메소드 들과 연관되어 있음을 의심해 볼

필요가 있을것이다. 즉, 실제 클라이언트에서 Remote Interface의 getXXX()를 호출하면,

CMP내부적으로 다시 EmpCMPBean클래스의 abstract getXXX()를 호출하는데, 이것은 추상적인

메스드이므로, CMP에서 다시 정의하게 된다. 고로 CMP에서 다시 정의하게 되는 getXXX()메소드를

호출하는 것이다. 그리고 그때서야 다시정의된 getXXX()메서드 내부에서 테이블의 테이터를 가져오는

것이다.

소스상으로 보면 Remote Inteface와 Bean Class의 getXXX(), setXXX()메소드가 아무런 연관관계가

없지만, 그일을 잠시후 분석하게 될 ejb-jar.xml, weblogic-cmp-rdbms-jar.xml 파일이 정보를

제공하며, 직접적인 연관을 지어주는 소스생성을 웹로직 ejb컴파일러가 해주는 것이다.

ejbxxxxx()로 시작하는 이벤트성 메소드가 몇가지 있는데, ejbLoad()는 클라이언트에서 Finder로

원하는 데이터를 찾고, 그 데이터를 가져올 때 발생되는 메소드이고, ejbStore()는 값을 저장할 때,

ejbRemove() 또한 역시 레코드를 삭제 할 때 발생되는 이벤트 메소드이다. CMP이기 때문에

사용자가 특별히 정의를 안해도 컨테이너가 알아서 정의를 해준다.

ejbCreate()메소드는 클라이언트에서 로우를 insert하기 위해서 Home Interface를 통해 create()할시

발생하는 이벤트이다. 그리고 setXXX() 메소드를 호출하는 것을 볼 수 있는데, ejbCreate()에서

입력된 값들을 인수로 하여 호출한다. 이러한 해동을 하는 이유는, CMP 내부적으로 각 컬럼들에 대한

값을 가지고 있는데, 그 값들이 setXXX()메소드를 통하면, 다시 설정되는 것이다. 지금 이해가

안가더라도 BMP를 배울 때 더 구체적으로 설명하겠다.

EmpCMPPK(Primary Key 데이터의 정보를 가지고 있는 클래스)

package ejb.cmp.EmpCMP;

public class EmpCMPPK implements java.io.Serializable

{

public int empno;

public EmpCMPPK() {}

public EmpCMPPK(int empno)

{

cafe.daum.net/weblogic

- 96 - 서진권 [email protected]

this.empno = empno;

}

public int hashCode()

{

return Integer.toString(empno).hashCode();

}

public boolean equals(Object other)

{

if(other == null || !(other instanceof EmpCMPPK)) return false;

return (((EmpCMPPK)other).empno == this.empno);

}

}

세션빈에 비해서 한가지 추가된 클래스가 있는데, 다 알겠지만, 테이블은 반드시 유일무이한 어떠한

컬럼을 한 개 이상가지고 있고, 이것을 객체화 해놓은게 바로 엔티티빈에서 PK클래스라고 부른다.

Primary Key 값만 가지고 있기 때문에 이클래스의 별다는 기능은 없는데, 꼭 이러한 PK클래스를 사용

하지 않고도, EJB생성이 가능하다. 하지만 통상적으로 한 개이상의 컬럼을 가지고 있는 Primary Key

라면 PK클래스가 오히려 더 편하다는 것을 알수 있다. 이 클래스가 가지고 있는 기능은 단순히 다른

PK클래스와 비교하는 기능과 인스턴스의 바코드 같은 hashCode()함수를 가지고 있다. 그리고 이 클

래스는 java.io.Serializable로부터 상속받아 직렬화를 하는데, 이것은 PK클래스가 클라이언트에서 작

업할경우 원거리와 통신을 자주 하기 때문이다.(실제로 개발할 때도 빈번하다.)

ejb-jar.xml

<?xml version="1.0"?>

<!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>

<enterprise-beans>

<entity>

<ejb-name>EmpCMP</ejb-name>

cafe.daum.net/weblogic

- 97 - 서진권 [email protected]

<home>ejb.cmp.EmpCMP.EmpCMPHome</home>

<remote>ejb.cmp.EmpCMP.EmpCMP</remote>

<ejb-class>ejb.cmp.EmpCMP.EmpCMPBean</ejb-class>

<persistence-type>Container</persistence-type>

<prim-key-class>ejb.cmp.EmpCMP.EmpCMPPK</prim-key-class>

<reentrant>False</reentrant>

<cmp-version>2.x</cmp-version>

<abstract-schema-name>EmpCMPBean</abstract-schema-name>

<cmp-field>

<field-name>empno</field-name>

</cmp-field>

<cmp-field>

<field-name>ename</field-name>

</cmp-field>

<cmp-field>

<field-name>job</field-name>

</cmp-field>

<cmp-field>

<field-name>mgr</field-name>

</cmp-field>

<cmp-field>

<field-name>hiredate</field-name>

</cmp-field>

<cmp-field>

<field-name>sal</field-name>

</cmp-field>

<cmp-field>

<field-name>comm</field-name>

</cmp-field>

<cmp-field>

<field-name>deptno</field-name>

</cmp-field>

</entity>

</enterprise-beans>

<assembly-descriptor>

cafe.daum.net/weblogic

- 98 - 서진권 [email protected]

<container-transaction>

<method>

<ejb-name>EmpCMP</ejb-name>

<method-name>*</method-name>

</method>

<trans-attribute>Required</trans-attribute>

</container-transaction>

</assembly-descriptor>

<ejb-client-jar>EmpCMP_Client.jar</ejb-client-jar>

</ejb-jar>

엔티티빈에서 ejb-jar.xml파일이 하는일은 DB의 테이블과 EJB간에 어떠한 방식으로 매치를 시킬것

인 가를 정의한다. <ejb-name>,<home>, <remote>, <ejb-class> 는 세션빈과 똑같다.

<persistent-type>속성은 바로 이 엔티티빈은 CMP라는 얘기이다. CMP일경우 Container라고 기입하

고 BMP일경우 Bean이라고 기입하는 것이다.

<prim-key-class>속성은 PK클래스를 기입하는 곳이다. 마찬가지로 여기도 패키지 풀패스와 클래스

명을 기입한다.

<cmp-version>은 우리가 지금 이강좌에서 사용하는 모든 EJB가 2.x대 버전이므로 여기도 마찬가지

로 2.x라고 기입한다.

<abstract-schema-name>속성은 Bean클래스를 말하는데 왜냐하면 Bean클래스가 바로 abstract로

선언되있기 때문이다. <abstract-schema-name>과 잠시후 설명할 weblogic-cmp-rdbms.xml파일에

서 테이블과 매치가 되게 된다. 쉽게 말해 <abstract-schema-name>은 우리가 사용할 객체

(EmpCMP)의 속성이고, weblogic-cmp-rdbms.xml 에서 정의 하는 테이블명과 컬럼은 실제 물리적인

테이블정의 라고 할 수 있다. 이렇게 <abstract-schema-name>속성과 weblogic-cmp-rdbms.xml 매

치관계 때문에, 따지고 보면 개발자가 SQL구문없이도 컨테이너에서 알아서 할 수 있는 것이라는 것

을 알 수 있다.

<cmp-field>

<field-name>empno</field-name>

</cmp-field>

위의 속성을 보자면 <field-name>이라고 정의 된곳이 바로 EmpCMPBean.java에서 작성했던

abstract public getEmpno() 메소드를 가리키는 것이다. 앞에 get이 빠져이고, e가 대문자라는 것을

제외하는고는 empno라는 것을 알수 있다. 이 <filed-name>에서 정의한 것은 바로 get, set메소드가

된다. 그리고 get, set메소드를 정의할때는, 반드시 get다음에는 대문자가 와야 한다. 이런것을 지키지

않으면 컴파일이 되지 않느다. 나머지 <field-name>속성도 항목만 다르지 이미 설명한대로 이해하기

바란다.

cafe.daum.net/weblogic

- 99 - 서진권 [email protected]

<assembly-descriptor> 이후는 이 EJB가 어떻게 트랜잭션을 처리할까 라는 정보를 기입하는 곳인

데, 이후 Transaction 관련장에서 따로 설명할 것이니, 여기서는 그러려니 하기 바란다.

weblogic-ejb-jar.xml

<?xml version="1.0"?>

<!DOCTYPE weblogic-ejb-jar PUBLIC

"-//BEA Systems, Inc.//DTD WebLogic 7.0.0 EJB//EN"

"http://www.bea.com/servers/wls700/dtd/weblogic-ejb-jar.dtd" >

<weblogic-ejb-jar>

<weblogic-enterprise-bean>

<ejb-name>EmpCMP</ejb-name>

<entity-descriptor>

<persistence>

<persistence-use>

<type-identifier>WebLogic_CMP_RDBMS</type-identifier>

<type-version>6.0</type-version>

<type-storage>META-INF/weblogic-cmp-rdbms-jar.xml</type-storage>

</persistence-use>

</persistence>

</entity-descriptor>

<jndi-name>ejb-cmp-EmpCMP</jndi-name>

</weblogic-enterprise-bean>

</weblogic-ejb-jar>

weblogic-ejb-jar.xml 파일도 세션빈과 별반 다를것이 없이 JNDI 이름을 기입하는 것이다. 그 외 중

간쯤 보면 뭔가가 추가되었는데, 바로 아래와 같은 속성이다.

<type-identifier>WebLogic_CMP_RDBMS</type-identifier>

<type-version>6.0</type-version>

<type-storage>META-INF/ weblogic-cmp-rdbms-jar.xml </type-storage>

<type-identifier>는 이 EJB가 CMP빈이라는 것을 나타내며 <type-version>은 사용할 웹로직 버전을

나타내고 <type-storage>은 위에서 언뜻 설명을 했지만 바로 실제 DB테이블과 EJB함수를 어떻게 매

치시킬 것인가를 나타내는 xml인 weblogic-cmp-rdbms-jar.xml을 뜻하는것이다. 그럼 실제

weblogic-cmp-rdbms-jar.xml 파일을 봐보자.

cafe.daum.net/weblogic

- 100 - 서진권 [email protected]

weblogic-cmp-rdbms-jar.xml(엔티티빈의 속성과 DB테이블의 컬럼을 매치시키는 xml)

<!DOCTYPE weblogic-rdbms-jar PUBLIC

'-//BEA Systems, Inc.//DTD WebLogic 7.0.0 EJB RDBMS Persistence//EN'

'http://www.bea.com/servers/wls700/dtd/weblogic-rdbms20-persistence-700.dtd'>

<weblogic-rdbms-jar>

<weblogic-rdbms-bean>

<ejb-name>EmpCMP</ejb-name>

<data-source-name>EJBDBPoolTxDataSource</data-source-name>

<table-map>

<table-name>EMP</table-name>

<field-map>

<cmp-field>empno</cmp-field>

<dbms-column>EMPNO</dbms-column>

</field-map>

<field-map>

<cmp-field>ename</cmp-field>

<dbms-column>ENAME</dbms-column>

</field-map>

<field-map>

<cmp-field>job</cmp-field>

<dbms-column>JOB</dbms-column>

</field-map>

<field-map>

<cmp-field>mgr</cmp-field>

<dbms-column>MGR</dbms-column>

</field-map>

<field-map>

<cmp-field>hiredate</cmp-field>

<dbms-column>HIREDATE</dbms-column>

</field-map>

<field-map>

<cmp-field>sal</cmp-field>

<dbms-column>SAL</dbms-column>

</field-map>

<field-map>

cafe.daum.net/weblogic

- 101 - 서진권 [email protected]

<cmp-field>comm</cmp-field>

<dbms-column>COMM</dbms-column>

</field-map>

<field-map>

<cmp-field>deptno</cmp-field>

<dbms-column>DEPTNO</dbms-column>

</field-map>

</table-map>

</weblogic-rdbms-bean>

<create-default-dbms-tables>False</create-default-dbms-tables>

</weblogic-rdbms-jar>

<data-source-name>속성부터 설명해 보자면, 자바의 DataSource객체를 설정하는 곳인데, 웹로직에

DB컨넥션 풀링 서비스와 DataSource서비스를 제공하기 때문에 이것에 사용할 DataSource이름을 기

입하는 것이다. 즉, EJB에서 DataSource를 통하여 컨넥션 풀을 구해와 데이터 작업을 행하는 것이고,

DataSource를 가지고 트랜잭션을 관리하는 것이다. 우리는 컨넥션풀, DataSource설정을 제2장 강좌

에서 설정했다.

<table-name>은 실제 DB서버의 테이블을 말한다.

<field-map>

<cmp-field>empno</cmp-field>

<dbms-column>EMPNO</dbms-column>

</field-map>

위의 필드맵 항목이 바로 엔티티빈의 함수와 DB컬럼을 매치시키는 것인데, 엔티티빈의 empno와

DB테이블의 EMPNO컬럼을 매치시키는 것이다. 이전에도 설명을 했지만, 클라이언트가 getXXX()를 호

출하면, <field-map>속성에 컬럼 정보가 있기 때문에 행당되는 컬럼을 가져오는 것이다. 마찬가지로

다른 <field-map>도 똑 같은 내용이다.

마지막 속성으로 <create-default-dbms-tables> 속성이 있는데 이것을 True로 설정해놓으면 웹로

직 컨테이너에서 자동으로 테이블을 새로 생성한다.

application.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE application PUBLIC '-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN'

'http://java.sun.com/dtd/application_1_3.dtd'>

cafe.daum.net/weblogic

- 102 - 서진권 [email protected]

<application>

<display-name>Oracle EMP Table ContainerManaged ejb</display-name>

<description>Oracle EMP Table ContainerManaged ejb</description>

<module>

<ejb>EmpCMP.jar</ejb>

</module>

</application>

application.xml은 세션빈과 별반 다를게 없다. 자, 이렇게 해서 CMP빈 소스 설명과 작성이 모두 끝

났다. 이제 클라이언트 쪽 프로그램을 만들어보겠다. Session Bean 예제에서는 자바 어플리케이션으

로 실행했는데, 이제는 jsp로 실행해보자. 그리고 덤으로 이제까지의 예제들 처럼 자바 어플리케이션

클라이언트도 만들어 뒀으니, jsp로 하고 싶은면 jsp, 기존 그대로 하고 싶으면 Client.java를 참고 하

기 바란다.

2장에서 DefaultWebApp 설정할때 설명했겠지만, Jsp 파일들은 DefaultWebApp 디렉토리에 위치시

키면 된다.

Client.java

package ejb.cmp.EmpCMP;

import java.rmi.RemoteException;

import java.util.Properties;

import javax.ejb.CreateException;

import javax.ejb.RemoveException;

import javax.naming.Context;

import javax.naming.InitialContext;

import javax.naming.NamingException;

import javax.rmi.PortableRemoteObject;

public class Client

{

private static final String JNDI_NAME = "ejb-cmp-EmpCMP";

private static final String url = "t3://192.168.1.150:7001";

public static void main(String args[])

{

cafe.daum.net/weblogic

- 103 - 서진권 [email protected]

Context ctx = null;

EmpCMPHome home = null;

EmpCMP emp = null;

try

{

Properties h = new Properties();

h.put(Context.INITIAL_CONTEXT_FACTORY,

"weblogic.jndi.WLInitialContextFactory");

h.put(Context.PROVIDER_URL, url);

System.out.println("Connecting weblogic...");

ctx = new InitialContext(h);

}

catch (NamingException ne)

{

System.out.println("We were unable to get a connection to the

WebLogic server at "+url);

System.out.println("Please make sure that the server is running.");

System.out.println(ne);

return;

}

try

{

home = (EmpCMPHome)

PortableRemoteObject.narrow(ctx.lookup(JNDI_NAME), EmpCMPHome.class);

emp = home.findByPrimaryKey(new EmpCMPPK(7369));

System.out.println(emp.getEmpno());

System.out.println(emp.getEname());

System.out.println(emp.getJob());

System.out.println(emp.getMgr());

System.out.println(emp.getHiredate());

System.out.println(emp.getSal());

System.out.println(emp.getComm());

cafe.daum.net/weblogic

- 104 - 서진권 [email protected]

System.out.println(emp.getDeptno());

}

catch (Exception e)

{

System.out.println("The client was unable to lookup the EJBHome.

Please make sure ");

System.out.println("that you have deployed the ejb with the JNDI name

"+JNDI_NAME+" on the WebLogic server at "+url);

System.out.println(e);

return;

}

}

}

index.jsp(사원번호로 조회하는 페이지)

<%@ page contentType="text/html; charset=euc-kr" %>

<html>

<body>

<form name="emp_search" method=post action="emp_result.jsp">

<br><br>

사원번호를 입력하세요.<br>

<input type=edit name="EMPNO" value="" size=20>

<input type=submit name="submit_btn" value="조 회">

</form>

</body>

</html>

사원번호를 입력받는 페이지 이다. emp_result.jsp로 EMP_NO(사원번호)를 Post하면 emp_result에서

CMP엔티티빈을 통해 자료를 구해낸다.

emp_result.jsp(사원번호에 해당하는 데이터를 구해낸다.)

<%@ page language="java" import="java.rmi.*, javax.ejb.*, javax.rmi.*, javax.naming.*,

ejb.cmp.EmpCMP.*" %>

<%@ page contentType="text/html; charset=euc-kr" %>

<html>

<body>

cafe.daum.net/weblogic

- 105 - 서진권 [email protected]

<table border=1 cellpadding=3 cellspacing=1>

<tr>

<td>사원번호</td>

<td>성명</td>

<td>직무번호</td>

<td>상관사원번호</td>

<td>급여일자</td>

<td>급여액</td>

<td>???</td>

<td>부서번호</td>

</tr>

<tr>

<%

Context ctx = null;

EmpCMPHome home = null;

EmpCMP emp = null;

String searchEmpno = request.getParameter("EMPNO") == null ?

"0" : request.getParameter("EMPNO");

try

{

ctx = new InitialContext();

home = (EmpCMPHome)

PortableRemoteObject.narrow(ctx.lookup("ejb-cmp-EmpCMP"),

EmpCMPHome.class);

emp = home.findByPrimaryKey(new

EmpCMPPK(Integer.parseInt(searchEmpno)));

%>

<td>&nbsp;<%=emp.getEmpno()%></td>

<td>&nbsp;<%=emp.getEname()%></td>

<td>&nbsp;<%=emp.getJob()%></td>

<td>&nbsp;<%=emp.getMgr()%></td>

<td>&nbsp;<%=emp.getHiredate()%></td>

cafe.daum.net/weblogic

- 106 - 서진권 [email protected]

<td>&nbsp;<%=emp.getSal()%></td>

<td>&nbsp;<%=emp.getComm()%></td>

<td>&nbsp;<%=emp.getDeptno()%></td>

<%

}

catch (Exception e)

{

%>

<td colspan=8 align=center>자료를 발견할수 없습니다.</td>

<%

System.out.println(e);

return;

}

%>

</tr>

</table>

</body>

</html>

emp_result.jsp 소스에서 다른 것들은 제외하고 이제까지 클라이언트 프로그램과 약간은 좀더 축소

된 것을 볼 수 있는데…

ctx = new InitialContext();

바로 위처럼 Context객채를 구해오는 것이다. 이제까지의 클라이언트 프로그램은 웹로직 컨테이너와

클라이언트 프로그램이 서로 다른 곳에서 실행 되었기 때문에 클라이언트에서 바인딩할 EJB컨테이너

의 서버주소와 기타 보안(사용자ID, 암호등..)정보를 기입했던 것이다. 하지만 jsp가 클라이언트 일 경

우, 같은 컨테이너에서 실행 되기 때문에 그러한 정보는 필요 없이도 컨텍스트를 구해 올 수 있는것

이다. 왜냐하면 System.getProerties()함수에 웹로직에 관련된 모든 정보가 있기때문이다. 그렇다고

서버정보를 Hashtable객체나 Property객체로 정보를 실어 Context 객체를 구해온다고 해도 웹로직서

버가 망가지지 않으니 걱정하지 말고 바인딩 해도 좋다. 단지 그럴필요가 없어서 사용하지 않는다.

또 이런경우가 있을수 있다. jsp컨테이너는 다른 서버이고, 웹로직에서만 EJB를 제공할 경우에는 당

연지사 어떻게 바인딩(url, 사용자정보등…)할 것인지를 던져준다음 Context를 구해와야 할 것이다. 실

제로 jsp는 Resine, Tomcat, Jrun등을 이용해 구현하고, EJB 컨테이너는 웹로직을 사용하는 경우도

많이 봤다.

emp = home.findByPrimaryKey(new EmpCMPPK(Integer.parseInt(searchEmpno)));

cafe.daum.net/weblogic

- 107 - 서진권 [email protected]

위의 라인은 emp Remote Interface 인스턴스를 구해오는 것인데, 세션빈과 마찬가지로 Home

Interface를 통하여 구해온다. Home Interface에 Finder메소드를 호출하여 구해온 것이다. Index.jsp페

이지에서 사원 번호를 입력하면, 사원번호가 PK이기 때문에 거기에 해당하는 로우를 DB에서 찾고,

Emp Remote Interface를 초기화 한후, 클라이언트에게 전송을 해주는 것이다. 이제 Finder메소드로

원하는 데이터를 찾았다면 컬럼의 데이터를 가져오는 것은 시간문제이다. 우리가 작성했던 예제는

Finder가 기본적인 findByPrimaryKey Finder하나 였는데, 예전에 말했던 것처럼 Finder가 많으면 많을

수록 그 해당 엔티티빈의 기능이 강력해 지는 것이다.

참고적으로 만약 Finder에서 원하는 데이터를 찾지 못 한다면, EJBNotFound Exception을 발생하게

된다.

<td>&nbsp;<%=emp.getEmpno()%></td>

위의 소스라인은 Finder를 통해 구해온 emp객체의 사원번호를 구하는 것이다. 나머지 emp.xxxx()는

설명한대로 모두 똑같다. 그리고 만약 Finder를 통해 찾은 emp객체의 데이터를 변경할 경우에는…

예를 들어 사원이름을 수정할 경우에는 다음과 같이 될것이다.

emp.setEname(“홍길동”);

전체적인 소스를 보면 또하나 세션빈과 차이점이 한가지 있는데, 세션빈 같은경우 세션빈의 모든 기

능을 모두 사용했을 경우 반드시 remove()함수를 호출했는데, 엔티티빈에서 remove() 멋모르고 사용

했다가는 정말로 낭패를 본다. 엔티티빈에서의 remove()함수는 바로 DB의 로우를 삭제하는 것이기

때문이다. 반대로 create(…)함수는 새로운 로우를 삽입한다.

자 이제 모든 CMP 엔티티빈의 소스 설명이 끝났다. 이제는 컴파일을 해보자. 여기서도 마찬가지로

ant를 사용한다.

build.xml

<project name="ejb-cmp-EmpCMP" default="all" basedir=".">

<!-- set global properties for this build -->

<property environment="env"/>

<property file="../ejbcomp.properties"/>

<property name="build.compiler" value="${JAVAC}"/>

<property name="source" value="."/>

<property name="build" value="${source}/build"/>

<property name="dist" value="${source}/dist"/>

cafe.daum.net/weblogic

- 108 - 서진권 [email protected]

<target name="all" depends="clean, init, compile_ejb, jar_ejb, ejbc, ear_app,

compile_client"/>

<target name="init">

<!-- Create the time stamp -->

<tstamp/>

<!-- Create the build directory structure used by compile

and copy the deployment descriptors into it-->

<mkdir dir="${build}"/>

<mkdir dir="${build}/META-INF"/>

<mkdir dir="${dist}"/>

<copy todir="${build}/META-INF">

<fileset dir="${source}">

<include name="*.xml"/>

<exclude name="build.xml"/>

<exclude name="application.xml"/>

</fileset>

</copy>

</target>

<!-- Compile ejb classes into the build directory (jar preparation) -->

<target name="compile_ejb">

<javac srcdir="${source}" destdir="${build}"

includes="EmpCMPPK.java, EmpCMP.java, EmpCMPHome.java, EmpCMPBean.java"/>

</target>

<!-- Update ejb jar file or create it if it doesn't exist, including XML

deployment descriptors -->

<target name="jar_ejb" depends="compile_ejb">

<jar jarfile="${dist}/EmpCMP.jar"

basedir="${build}"

update="yes">

</jar>

</target>

cafe.daum.net/weblogic

- 109 - 서진권 [email protected]

<!-- Run ejbc to create the deployable jar file -->

<target name="ejbc" depends="jar_ejb">

<java classname="weblogic.ejbc" fork="yes" failonerror="yes">

<sysproperty key="weblogic.home" value="${WL_HOME}/server"/>

<arg line="-verbose -compiler javac ${dist}/EmpCMP.jar"/>

<classpath>

<pathelement path="${CLASSPATH}"/>

</classpath>

</java>

</target>

<!-- Put the ejb into an ear, to be deployed from the ${APPLICATIONS} dir -->

<target name="ear_app" depends="jar_ejb">

<ear earfile="${APPLICATIONS}/EmpCMP.ear" appxml="${source}/application.xml">

<fileset dir="${dist}" includes="EmpCMP.jar"/>

</ear>

</target>

<!-- Compile client app into the clientclasses directory, and move the client jar file (created by

ejbc) there as well -->

<target name="compile_client">

<copy file="${source}/EmpCMP_Client.jar" tofile="${EX_WEBAPP}/WEB-

INF/lib/EmpCMP_Client.jar"/>

<move file="${source}/EmpCMP_Client.jar" tofile="${CLIENT_CLASSES}/EmpCMP_Client.jar"/>

<javac srcdir="${source}"

destdir="${CLIENT_CLASSES}"

includes="Client.java"

classpath="${CLASSPATH};${CLIENT_CLASSES}/EmpCMP_Client.jar"

/>

</target>

<target name="clean">

<delete dir="${build}"/>

</target>

<!-- Run the example -->

cafe.daum.net/weblogic

- 110 - 서진권 [email protected]

<target name="run">

<java classname="ejb.cmp.EmpCMP.Client" fork="yes" failonerror="true">

<classpath>

<pathelement path="${CLASSPATH};${CLIENT_CLASSES}/EmpCMP_Client.jar"/>

</classpath>

</java>

</target>

</project>

CMP엔티티빈에 관한 설명과 소스는 여기까지이고, 컴파일과 디플로이후 실행을 해보자. 화면은 이

예제를 실행한 화면이다.

7788 사원번호에 해당하는 사원을 조회할 경우 다음과 같은 조회 결과 하면이 나오게 된다.

cafe.daum.net/weblogic

- 111 - 서진권 [email protected]

5.1 Finder와 EJB QL(Query Language)

이전에 지나가는 말로 언급했겠지만, 엔티티빈에 강력한 기능을 제공할려면 Finder가 많으면 많을수

록 기능이 강력해 진다. 이전 예제에서는 테이블의 가장 기본적인 키인 Primary Key로 Finder를 구현

한 것이었고, 실제 프로젝트에서는 findByPrimaryKey는 개발자나 시스템을 잘 아는 사용자만 그 기능

을 이용할 뿐 최종사용자는 별로 의미 없는 기능이다.

그러면 findByPrimaryKey Finder메소드는 Primary Key에 해당하는 컬럼만 알려주면 컨테이너가 자

동으로 처리할 수 있다 치더라도 SQL없이 어떻게 사용자가 원하는 새로운 Finder를 추가할 수 있을

까 ? EJB1.1 규약에서 웹로직은 WLQL(WebLogic Query Language)라는 문장을 제공했다. EJB 2.0에

서는 EJB QL 이라는 것을 제공하며, 웹로직7 은 WLQL(EJB 1.1), EJB QL(EJB 2.0)을 모두 지원한다.

하지만 공식적인 Sun의 EJB2.0은 EJB-QL을 사용한다. 그리고 다음장에서 배울 BMP에서는 이러한

각 컨테이너의 QL이 없이 손수 SQL작성해서 Finder메소드를 구현하게 된다.

자, 그럼 우리가 원하는 Finder메소드는 어떤것이냐면, 직종(job)으로 결과를 조회할수 있게 파인더

를 추가한다. 예를들어, “MANAGER” 라고 검색을 하면 거기에 해당하는 직종을 가지고 있는 사원들

을 리턴한다. 모든 로우가 Remote Interface의 Collection(JDBC의 RowSet과 같은개념으로 생각해도

좋다.)으로 리턴이 되는것이다. 파인더를 추가하기 위해서는 먼저 Home Interface에 Finder 메소드를

선언하고, ejb-jar.xml에 EJB QL구문을 작성하게되다. weblogic-cmp-rdbms.xml 파일에는 Finder 명

을 작성해야 한다. 자 그럼 기존에 작성되었던 소스에다가 Finder를 추가해 보자…

EmpCMP.java

package ejb.cmp.EmpCMP;

import javax.ejb.CreateException;

import javax.ejb.EJBHome;

import javax.ejb.FinderException;

import java.rmi.RemoteException;

import java.util.Collection;

public interface EmpCMPHome extends EJBHome

{

public EmpCMP create(int empno, String ename, String job,

int mgr, java.sql.Date hiredate,

double sal, double comm, int deptno )

throws CreateException, RemoteException;

public EmpCMP findByPrimaryKey(EmpCMPPK pk)

cafe.daum.net/weblogic

- 112 - 서진권 [email protected]

throws FinderException, RemoteException;

public Collection findByJob(String job)

throws FinderException, RemoteException;

}

Finder를 추가하기 위한 첫번째 과정은 Remote Interface에 Finder명을 선언하는 것이다. 위의 소스

중 적색의 사각 박스안의 소스가 바로 그것인데…

import java.util.Collect;

findByJob Finder로 검색을 시작하면 한 개 이상의 로우에 해당하는 Remote Interface 인스턴스가

리턴(엄 히 말하면 PK클래스 집합이 리턴된다. 기억해 둘 것…)이 되기 때문에 자바의 Collection 객

체로 받아 온다. 이전의 EJB 1.1(웹로직 5.1 이전)에서는 대개 Finder메소드를 통해서 가져온 셋트들

을 java.util.Enumeration으로 리턴을 했는데, EJB2.0(웹로직 6.1이후)부터는 거의 java.util.Collection

개체로 받아온다. java.util.Enumeration과 java.util.Collection객체의 차이점과 기능에 대해서는 별로

신경쓸 필요는 없다. 어차피 이전의 java.util.Enumeration도 집합체를 다루는 객체이고, 거기에 더 발

전한 개념이 java.util.Collection 객체 이기 때문이다. 특별히 EJB2.0에서 java.util.Enumeration을 사

용한다고 해서 문제가 되는 것은 없다.

public Collection findByJob(String job)

throws FinderException, RemoteException;

두번째의 적색 사각박스의 소스를 보면… 이것이 바로 우리가 추가할 Finder 메소드이다. 메소드 리

턴값은 위의 추가된 import 구문에서 잠시 언급한 java.util.Collection객체를 리턴해준다. 메소드 이름

은 findByEname 이며 인수로는 검색할 직종이 들어오게 된다. 메소드명 findByJob은 다음에 또 정의

할 ejb-jar.xml의 메소드명과 반드시 동일해야 한다.

cafe.daum.net/weblogic

- 113 - 서진권 [email protected]

ejb-jar.xml

<?xml version="1.0"?>

<!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>

<enterprise-beans>

<entity>

<ejb-name>EmpCMP</ejb-name>

<home>ejb.cmp.EmpCMP.EmpCMPHome</home>

<remote>ejb.cmp.EmpCMP.EmpCMP</remote>

<ejb-class>ejb.cmp.EmpCMP.EmpCMPBean</ejb-class>

<persistence-type>Container</persistence-type>

<prim-key-class>ejb.cmp.EmpCMP.EmpCMPPK</prim-key-class>

<reentrant>False</reentrant>

<cmp-version>2.x</cmp-version>

<abstract-schema-name>EmpCMPBean</abstract-schema-name>

<cmp-field>

<field-name>empno</field-name>

</cmp-field>

<cmp-field>

<field-name>ename</field-name>

</cmp-field>

<cmp-field>

<field-name>job</field-name>

</cmp-field>

<cmp-field>

<field-name>mgr</field-name>

</cmp-field>

<cmp-field>

<field-name>hiredate</field-name>

</cmp-field>

<cmp-field>

<field-name>sal</field-name>

cafe.daum.net/weblogic

- 114 - 서진권 [email protected]

</cmp-field>

<cmp-field>

<field-name>comm</field-name>

</cmp-field>

<cmp-field>

<field-name>deptno</field-name>

</cmp-field>

<query>

<query-method>

<method-name>findByJob</method-name>

<method-params>

<method-param>java.lang.String</method-param>

</method-params>

</query-method>

<ejb-ql>

<![CDATA[SELECT OBJECT(a) FROM EmpCMPBean AS a WHERE a.job = ?1 ]]>

</ejb-ql>

</query>

</entity>

</enterprise-beans>

<assembly-descriptor>

<container-transaction>

<method>

<ejb-name>EmpCMP</ejb-name>

<method-name>*</method-name>

</method>

<trans-attribute>Required</trans-attribute>

</container-transaction>

</assembly-descriptor>

<ejb-client-jar>EmpCMP_Client.jar</ejb-client-jar>

</ejb-jar>

ejb-jar.xml에서 추가할 Finder메소드명과 파라미터, Finder 가 사용할 EJB-QL을 작성하게 된다.

<method-name>항목에 Home Interface에서 선언한 함수명과 똑같이 findByJob이 기입된걸 볼 수

있다. <method-params>절은 메소드 파라미터를 정의하는 곳은데 서브 태그 속성을 보면 <method-

cafe.daum.net/weblogic

- 115 - 서진권 [email protected]

param>에 해당 파라미터에 타입만 지정해 주면 된다. 우리는 문자열로 검색을 하기 때문에

java.lang.String으로 기입한다. 만약 파라미터가 여러 개 일 경우 <method-param> 속성을 계속 추

가하기만 하면 된다.

중반 이후쯤 보면 <ejb-ql>속성이 있는데, 이곳이 바로 EJB-QL을 정의 하는 곳이다.

<![CDATA[SELECT OBJECT(a) FROM EmpCMPBean AS a WHERE a.job = ?1 ]]>

위의 xml속성이 어떠한 의미이냐면 파라미터 첫번째로 들어온 인수를 JOB 컬럼에서 SELECT 하는

한가지 주의해야 할 것이 있는데, job은 DB컬럼을 말하는 것이 아니라 ejb-jar.xml파일에서 정의한

<cmp-filed> 필드중 하나를 말하는 것이다. 그렇기 때문에 다음과 같이 DB의 컬럼을 바로 사용할 경

우에는 EJB에서 인식을 하지 못한다.

<![CDATA[SELECT OBJECT(a) FROM EmpCMPBean AS a WHERE JOB = ?1 ]]>

개발자가 JOB을 DB컬럼이라는 의미로 사용했는데, 여기서 요구하는 컬럼은 DB컬럼이 아닌

<cmp-filed>의 항목중 하나이다. 그렇기 때문에 JOB을 사용하는 것이 아니고, a.job을 사용했던 것이

다.

다른 것은 그렇다 치더라도 파라미터를 표현할 때는 $기호와 파라미터 번호를 붙인다는 것은 꼭 알

아두자. $1, $2, $3… 첫번째 파리미터, 두번째 파라미터, 세번째 파라미터… 이런식이 되는 것이다.

5.2 PK클래스 셋트와 이벤트성 메소드의 관계

Finder메소드를 추가 할경우, 리턴되는 인스턴스의 객체는 다음과 같을 것이다.

DB에서 로우를 못 찾을 경우(인스턴스 0개) Exception 발생

로우를 하나만 찾은경우 (주로 PK, UK 또는 논리적으로 유일무이하다고 생각하는 컬럼들을

조회하는경우)

로우를 여러 개 찾은경우 (문자열 검색, 테이블 조인등…)

첫번째 경우는 Finder Exception을 발생하기 때문에 특별히 설명할 것은 없고, 지금부터 설명할것들

이 주로 두번째, 세번째 경우인데…

두번째 경우, 기본적인 Finder인 findByPrimaryKey 메소드를 통하여 자료를 구해낼 때, 일단 컨테이

너는 findByPrimaryKey 메소드 내부에서 JDBC를 통하여 다음과 같은 SQL이 DB에 전달된다.

SELECT … 테이블명 WHERE [PK컬럼] = [PK검색데이터]

findByPrimaryKey가 단순히 데이터가 존재 유무만 검색하고, 만약에 있으면 그것을 다시 ejbLoad(),

cafe.daum.net/weblogic

- 116 - 서진권 [email protected]

ejbStore() 이벤트 메소드에 전달하게 된다.

ejbLoad(), ejbStore()에서 내부적으로 하는일은 다음과 같다.

SELECT * 테이블명 WHERE [PK컬럼] = [PK검색데이터]

위의 SQL이 DB서버에 또 한번 전달하게 되며, 이때에는 각컬럼에 해당하는 데이터를 내부 변수에

초기화 하게 된다. 일단 ejbLoad()이벤트를 거치게 되었다면, getxxxx() 통하여 자료를 가져올 경우에

는 항상 ejbLoad()이벤트를 거친후, 초기화된 내부 변수의 값을 리턴하게 되는것이다.

ejbStore()이벤트 같은 경우는 다음과 같은 SQL이 서버로 전송된다.

UPDATE 테이블명 SET [컬럼명] = [초기화데이터] …

WHERE WHERE [PK컬럼] = [PK검색데이터] …

위에서 말한 초기화 데이터란 ejbLoad()에서 초기화 했던 데이터를 말하는 것이다. 그렇기 때문에

흐름상 처음에는 클라이언트에서 Finder 메소드를 호출하고, 그 다음에 ejbLoad()이벤트를 컨테이너에

서 실행하며, 마지막으로 ejbStore() 이벤트 메소드를 호출하는 것이다.

한가지 중요한 사실이 있는데, 만약 Finder로 검색된 데이터가 여러 개 일경우 해당 Finder에서

Remote Interface를 초기화 하는 것이 아니다. 데이터가 한 개만 검색된 경우(findByPrimaryKey)는 위

의 매카니즘으로 동작하는데, 여러 개의 로우가 검색되는 경우 우리는 분명히 java.util.Collection 객

체로 데이터를 받기로 하였다. 클라이언트 입장에서 바라본 Collection 객체의 내부적인 집합은

Remote Interface(EmpCMP)이지만, EJB컨테이너 입장에서 바라본 관점은 약간 다르다. EJB컨테이너

에서 Finder메소드는 어떻게 정의가 되냐면, PK들의 집합으로 Collection객체를 초기화 한다는 것이다.

그렇게 해야지만 검색된 각 하나의 PK들이 다시 ejbLoad(), ejbStore()을 거처가게 된다는 것이다. 클

라이언트 입장에서 본다면 그냥 Finder 메소드가 Remote Interface(EmpCMP)를 초기화 하여 집합체

를 리턴하는 것처럼 보이지만, 컨테이너 입장에서 Remote Interface가 아닌 검색된 데이터의 PK클래

스들을 리턴한다는 말이다. 다음장에 배울 BMP 엔티티빈에서 이 모든 비 을 풀어 헤처보고 일단 여

기서 이제 클라이언트쪽 jsp를 만들어보자.

index.jsp

<%@ page contentType="text/html; charset=euc-kr" %>

<html>

<body>

<form name="emp_search" method=post action="emp_result.jsp">

<br><br>

사원번호를 입력하세요.<br>

<input type=edit name="EMPNO" value="" size=20>

<input type=submit name="submit_btn" value="조 회">

</form>

<br>

cafe.daum.net/weblogic

- 117 - 서진권 [email protected]

<form name="emp_search" method=post action="emp_job_result.jsp">

<br><br>

직종을 입력하세요.<br>

<input type=edit name="JOB" value="" size=20>

<input type=submit name="submit_btn" value="조 회">

</form>

</body>

</html>

사원번호 외에 사원성명으로 검색할 화면이다. Post가 새로 작성하게 될 emp_job_result.jsp로 되어

있다.

emp_job_result.jsp

<%@ page language="java" import="java.util.*, java.rmi.*, javax.ejb.*, javax.rmi.*, javax.naming.*,

ejb.cmp.EmpCMP.*" %>

<%@ page contentType="text/html; charset=euc-kr" %>

<html>

<body>

<table border=1 cellpadding=3 cellspacing=1>

<tr>

<td>사원번호</td>

<td>성명</td>

<td>직종</td>

<td>상관사원번호</td>

<td>급여일자</td>

<td>급여액</td>

<td>???</td>

<td>부서번호</td>

</tr>

<%

Context ctx = null;

EmpCMPHome home = null;

Collection emps = null;

Iterator empsset = null;

EmpCMP emp = null;

cafe.daum.net/weblogic

- 118 - 서진권 [email protected]

String searchJob = request.getParameter("JOB") == null ?

"" : request.getParameter("JOB");

try

{

ctx = new InitialContext();

home = (EmpCMPHome)

PortableRemoteObject.narrow(ctx.lookup("ejb-cmp-EmpCMP"),

EmpCMPHome.class);

emps = home.findByJob(searchJob);

%>

<tr>

<td align=left colspan=8>검색조건 : "<%=searchJob%>", 검색된 사원수 :

<%=emps.size()%></td>

</tr>

<%

empsset = emps.iterator();

while(empsset.hasNext())

{

emp =

(EmpCMP)PortableRemoteObject.narrow(empsset.next(), EmpCMP.class);

%>

<tr>

<td>&nbsp;<%=emp.getEmpno()%></td>

<td>&nbsp;<%=emp.getEname()%></td>

<td>&nbsp;<%=emp.getJob()%></td>

<td>&nbsp;<%=emp.getMgr()%></td>

<td>&nbsp;<%=emp.getHiredate()%></td>

<td>&nbsp;<%=emp.getSal()%></td>

<td>&nbsp;<%=emp.getComm()%></td>

<td>&nbsp;<%=emp.getDeptno()%></td>

<tr>

<%

}

cafe.daum.net/weblogic

- 119 - 서진권 [email protected]

}

catch (Exception e)

{

%>

<td colspan=8 align=center>자료를 발견할수 없습니다.</td>

<%

System.out.println(e);

return;

}

%>

</table>

</body>

</html>

이 jsp는 index.jsp에서 직종을 입력하게 되면 post될 페이지이다. 소스 코드 상위 부분부터 설명하

자면, 다음과 같은 첨보는 소스가 있을것이다.

Collection emps = null;

Iterator empsset = null;

자바 java.util.Collection 객체로 emps를 선언했다. 즉, 직종으로 검색한 결과가 여기로 저장이 된다.

Iterator는 java.util.Enumaration쯤이라고 생각하면된다. Collection과 Iterator의 관계는 기존

Hashtable과 Enumeration이라고 생각하면 될것이다. 암튼, 중요한 것은 emps에 직종으로 검색된 결

과가 있다는 것만 기억해 두자.

다른 부분 소스 코드는 이전 예제의 jsp에서 설명을 했으니 피하고, 가장 중요한 우리가 작성한 파인

더를 호출하는 부분이다.

emps = home.findByJob(searchJob);

이 소스코드가 바로 우리가 작성한 파인더를 호출하여 Collection에 결과를 담는 부분이다. 그리고

다음과 같은 소스 코드로 다시 Interator 객체를 구한다.

empsset = emps.iterator();

마지막으로 구해진 Iterator를 구해진 수만큼, 웹브라우져에 출력하는 것이다. 나머지 소스는 특별히

설명을 하지 않아도, 문제 없으리라 본다.

cafe.daum.net/weblogic

- 120 - 서진권 [email protected]

다음 화면은 예제를 실행한 화면이다.

“SALESMAN”으로 조회할 경우 검색결과…

cafe.daum.net/weblogic

- 121 - 서진권 [email protected]

여기까지 CMP에 대해서 알아보았다. CMP에서 가장 중요한 것은 EJB-QL을 얼마나 잘 다루느냐의 문

제인데, EJB-QL의 보다더 구체적인 문법과 사양은 Sun의 EJB스펙이나, 웹로직의 EJB 문서를 참과

하기 바란다. 다음장에는 개발은 어렵지만, 엔티티빈을 상세하게 제어하는 BMP에 대해서 알아보겠다.

cafe.daum.net/weblogic

- 122 - 서진권 [email protected]

제 6장 BMP(Bean Managed Persistent) Entity Bean

CMP와 BMP의 가중 중요한 차이점은 바로 SQL스키마를 컨테이너가 관리하냐 개발자가

정의하느냐의 차이점이었는데, 이미 전장에서 언급했듯이 BMP는 개발자가 SQL스키마를 다 관리한다.

개발자가 SQL스키마를 정의하는 장소는 Bean(Bean 클래스)이기 때문에 Bean Managed(빈에 의해

관리)라는 이름이 붙여진 것이다. CMP소스를 분석할 때, get/set 메소드, Finder, ejbLoad(),

ejbStore()를 작성할 경우 우리는 Java소스 상에서 단한줄의 SQL구문도 사용하지 않았다. 그에 반해

BMP는 이러한 메소드나 비즈니스 로직에 개발자가 원하는 SQL을 기입할 수 있는것이다. 물론

CMP도 안되는 것은 아니다. 하지만, 결정적으로 아주 세 한 작업과 정교한 비즈니스 로직을 요구할

경우는 CMP로는 불가능한 경우가 종종있다.

BMP는 CMP에 비해서 분명 해줄일이 굉장히 많다. 개발시간도 많이 걸리기도 한다. 하지만,

EJB초보자들이 알아둬야할 것이 있는데, EJB규약에 어딜 찾아봐도 개발시간 단축이라는 말은 없다.

EJB를 사용한다고 해서 결코 개발시간 단축이 된다는 얘기가 아니며, EJB를 사용하는 목적이 거기에

맞춰진 것도 아니기 때문이다. 실제 프로젝트에서 CMP를 많이 사용하는 경우가 있다. 이러한 경우

업무상 BMP가 필요없을 경우에는 뭐 할말이 없지만, CMP로 해야될 일이 있고, BMP로 해야될

일이있다. 그리고 BMP로 작성이 되면, 개발시간은 많이 늘어나지만, EJB튜닝부터 SQL튜닝까지, 또

문제가 생기면, 모든게 Bean에서 작성된 소스에 의해서 동작하기 때문에 디버깅도 굉장히 쉬운

장점이 있으며, 튜닝후의 CMP와 BMP속도차이는 비교도 안된다는 것이다.

CMP와 BMP가 모두 웹로직 컴파일러에 의해 컴파일 된후 jar파일을 다시 디 컴파일 해보면, 둘 다

나중에는 똑같이 BMP와 같이 동작한다는 것을 알 수 있다. 즉 CMP라 할지 라도 내부적으로 컴파일

된 후에는 거의 BMP와 흡사한 소스를 가지고 있는 것이다.

저번 CMP강좌에서 SCOTT user의 EMP테이블에 대해서 CMP 엔티티빈을 만들어 보았는데,

이번에도 CMP엔티티빈과 기능이 그대로 똑 같은 BMP빈을 만들어 보려고 한다. 그렇게 하면

아무래도 CMP와 BMP를 비교하기 쉽기 때문에 독자 여러분들이 쉽게 이해 할 수 있으리라 보기

때문이다.

EmpBMPHome.java(Home Interface)

package ejb.bmp.EmpBMP;

import javax.ejb.CreateException;

import javax.ejb.EJBHome;

import javax.ejb.FinderException;

import java.rmi.RemoteException;

import java.util.Collection;

cafe.daum.net/weblogic

- 123 - 서진권 [email protected]

public interface EmpBMPHome extends EJBHome {

public EmpBMP create(int empno,

String ename,

String job,

int mgr,

java.sql.Date hiredate,

double sal,

double comm,

int deptno)

throws CreateException, RemoteException;

public EmpBMP findByPrimaryKey(EmpBMPPK primaryKey)

throws FinderException, RemoteException;

public Collection findByJob(String job)

throws FinderException, RemoteException;

}

Home Interface는 CMP의 Home Interface와 별반 다를 게 없고, 기본적인 findByPrimaryKey

파인더와 직업으로 사원을 찾는 findByJob 파인더가 있다. CMP와 동일하기 때문에 설명은 피하겠다.

EmpBMP.java(Remote Interface)

package ejb.bmp.EmpBMP;

import java.rmi.RemoteException;

import javax.ejb.EJBObject;

public interface EmpBMP extends EJBObject

{

public int getEmpno() throws RemoteException;

public String getEname() throws RemoteException;

public String getJob() throws RemoteException;

public int getMgr() throws RemoteException;

public java.sql.Date getHiredate() throws RemoteException;

public double getSal() throws RemoteException;

cafe.daum.net/weblogic

- 124 - 서진권 [email protected]

public double getComm() throws RemoteException;

public int getDeptno() throws RemoteException;

public void setEmpno(int empno) throws RemoteException;

public void setEname(String ename) throws RemoteException;

public void setJob(String job) throws RemoteException;

public void setMgr(int mgr) throws RemoteException;

public void setHiredate(java.sql.Date hiredate) throws RemoteException;

public void setSal(double sal) throws RemoteException;

public void setComm(double comm) throws RemoteException;

public void setDeptno(int deptno) throws RemoteException;

}

Remote Interface도 CMP의 Remote Interface와 동일하다.

EmpBMPBean.java (Bean Class) package ejb.bmp.EmpBMP; import java.io.Serializable; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collection; import java.util.Vector; import javax.ejb.CreateException; import javax.ejb.DuplicateKeyException; import javax.ejb.EJBException; import javax.ejb.EntityBean; import javax.ejb.EntityContext; import javax.ejb.FinderException; import javax.ejb.NoSuchEntityException; import javax.ejb.ObjectNotFoundException; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; public class EmpBMPBean implements EntityBean { final static private boolean VERBOSE = true; private EntityContext ctx; private int empno; private String ename; private String job; private int mgr; private java.sql.Date hiredate; private double sal; private double comm; private int deptno; public void setEntityContext(EntityContext ctx) { log("setEntityContext called"); this.ctx = ctx; } public void unsetEntityContext() { log("unsetEntityContext"); this.ctx = null; } public void ejbActivate() {

cafe.daum.net/weblogic

- 125 - 서진권 [email protected]

log("ejbActivate (" + curPrimaryKey() + ")"); } public void ejbPassivate() { log("ejbPassivate (" + curPrimaryKey() + ")"); } public void ejbLoad() { log("ejbLoad: (" + curPrimaryKey() + ")"); Connection con = null; PreparedStatement ps = null; empno = ((EmpBMPPK) ctx.getPrimaryKey()).empno; try { con = getConnection(); ps = con.prepareStatement("SELECT * FROM EMP WHERE EMPNO = ?"); ps.setInt(1, empno); ps.executeQuery(); ResultSet rs = ps.getResultSet(); if (rs.next()) { ename = rs.getString("ENAME"); job = rs.getString("JOB"); mgr = rs.getInt("MGR"); hiredate = rs.getDate("HIREDATE"); sal = rs.getDouble("SAL"); comm = rs.getDouble("COMM"); deptno = rs.getInt("DEPTNO"); } else { String error = "ejbLoad: EmpBMPBean (" + empno + ") not found"; log(error); throw new NoSuchEntityException (error); } } catch (SQLException sqe) { log("SQLException: " + sqe); throw new EJBException(sqe); } finally { cleanup(con, ps); } } public void ejbStore() { log("ejbStore (" + curPrimaryKey() + ")"); Connection con = null; PreparedStatement ps = null; try { con = getConnection(); ps = con.prepareStatement("UPDATE EMP SET ENAME = ?, JOB = ?, MGR = ?, HIREDATE = ?, SAL = ?, COMM = ?, DEPTNO = ? WHERE EMPNO = ?"); ps.setString(1, ename); ps.setString(2, job); ps.setInt(3, mgr); ps.setDate(4, hiredate); ps.setDouble(5, sal); ps.setDouble(6, comm); ps.setInt(7, deptno); ps.setInt(8, empno); if (!(ps.executeUpdate() > 0)) { String error = "ejbStore: EmpBMPBean (" + empno + ") not updated"; log(error); throw new NoSuchEntityException (error); } } catch(SQLException sqe) { log("SQLException: " + sqe); throw new EJBException (sqe); } finally { cleanup(con, ps); } }

cafe.daum.net/weblogic

- 126 - 서진권 [email protected]

public EmpBMPPK ejbCreate(int empno, String ename, String job, int mgr, java.sql.Date hiredate, double sal, double comm, int deptno) throws CreateException { log("EmpBMPBean.ejbCreate()"); this.empno = empno; this.ename = ename; this.job = job; this.mgr = mgr; this.hiredate = hiredate; this.sal = sal; this.comm = comm; this.deptno = deptno; Connection con = null; PreparedStatement ps = null; try { con = getConnection(); ps = con.prepareStatement("INSERT INTO EMP(EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO) VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); ps.setInt(1, empno); ps.setString(2, ename); ps.setString(3, job); ps.setInt(4, mgr); ps.setDate(5, hiredate); ps.setDouble(6, sal); ps.setDouble(7, comm); ps.setInt(8, deptno); if (ps.executeUpdate() != 1) { String error = "JDBC did not create any row"; log(error); throw new CreateException (error); } return new EmpBMPPK(empno); } catch (SQLException sqe) { // Check to see if this SQLException is due to a unique constraint // violation on our database table (ie. there is already a pk with the // value of accountId in the table). If so, throw a // DuplicateKeyException else throw a CreateException. try { ejbFindByPrimaryKey(new EmpBMPPK(empno)); } catch(ObjectNotFoundException onfe) { String error = "SQLException: " + sqe; log(error); throw new CreateException (error); } String error = "An Emp already exists in the database with Primary Key " + empno; log(error); throw new DuplicateKeyException(error); } finally { cleanup(con, ps); } } public void ejbPostCreate(int empno, String ename, String job, int mgr, java.sql.Date hiredate, double sal, double comm, int deptno) { log("ejbPostCreate (" + curPrimaryKey() + ")"); } public void ejbRemove() { log("ejbRemove (" + curPrimaryKey() + ")"); Connection con = null; PreparedStatement ps = null;

cafe.daum.net/weblogic

- 127 - 서진권 [email protected]

try { con = getConnection(); empno = ((EmpBMPPK)ctx.getPrimaryKey()).empno; ps = con.prepareStatement("DELETE FROM EMP WHERE EMPNO = ?"); ps.setInt(1, empno); if (!(ps.executeUpdate() > 0)) { String error = "EmpBMPBean (" + empno + " not found"; log(error); throw new NoSuchEntityException (error); } } catch (SQLException sqe) { log("SQLException: " + sqe); throw new EJBException (sqe); } finally { cleanup(con, ps); } } public EmpBMPPK ejbFindByPrimaryKey(EmpBMPPK pk) throws ObjectNotFoundException { log("ejbFindByPrimaryKey (" + pk.toString() + ")"); Connection con = null; PreparedStatement ps = null; try { con = getConnection(); ps = con.prepareStatement("SELECT * FROM EMP WHERE EMPNO = ?"); ps.setInt(1, pk.empno); ResultSet rs = ps.executeQuery(); if (rs.next()) { empno = rs.getInt("EMPNO"); ename = rs.getString("ENAME"); job = rs.getString("JOB"); mgr = rs.getInt("MGR"); hiredate = rs.getDate("HIREDATE"); sal = rs.getDouble("SAL"); comm = rs.getDouble("COMM"); deptno = rs.getInt("DEPTNO"); } else { String error = "ejbFindByPrimaryKey: EmpBMPBean (" + pk.toString() + ") not found"; log(error); throw new ObjectNotFoundException (error); } } catch (SQLException sqe) { log("SQLException: " + sqe); throw new EJBException (sqe); } finally { cleanup(con, ps); } log("ejbFindByPrimaryKey (" + pk.toString() + ") found"); return pk; } public Collection ejbFindByJob(String searchJob) { log("ejbFindByJob (searchJob = " + searchJob + ")"); Connection con = null; PreparedStatement ps = null; try { con = getConnection(); ps = con.prepareStatement("SELECT EMPNO FROM EMP WHERE JOB = ?"); ps.setString(1, searchJob); ps.executeQuery(); ResultSet rs = ps.getResultSet(); Vector v = new Vector(); EmpBMPPK pk; while (rs.next())

cafe.daum.net/weblogic

- 128 - 서진권 [email protected]

{ pk = new EmpBMPPK(rs.getInt("EMPNO")); v.addElement(pk); } return v; } catch (SQLException sqe) { log("SQLException: " + sqe); throw new EJBException (sqe); } finally { cleanup(con, ps); } } public int getEmpno() { return empno; } public String getEname() { return ename; } public String getJob() { return job; } public int getMgr() { return mgr; } public java.sql.Date getHiredate() { return hiredate; } public double getSal() { return sal; } public double getComm() { return comm; } public int getDeptno() { return deptno; } public void setEmpno(int empno) { this.empno = empno; } public void setEname(String ename) { this.ename = ename; } public void setJob(String job) { this.job = job; } public void setMgr(int mgr) { this.mgr = mgr; } public void setHiredate(java.sql.Date hiredate) { this.hiredate = hiredate; } public void setSal(double sal) { this.sal = sal; } public void setComm(double comm) { this.comm = comm; } public void setDeptno(int deptno)

cafe.daum.net/weblogic

- 129 - 서진권 [email protected]

{ this.deptno = deptno; } private Connection getConnection() throws SQLException { InitialContext initCtx = null; try { initCtx = new InitialContext(); DataSource ds = (javax.sql.DataSource) initCtx.lookup("EJBDBPoolTxDataSource"); return ds.getConnection(); } catch(NamingException ne) { log("Failed to lookup JDBC Datasource. Please double check that"); log("the JNDI name defined in the resource-description of the "); log("EJB's weblogic-ejb-jar.xml file is the same as the JNDI name "); log("for the Datasource defined in your config.xml."); throw new EJBException(ne); } finally { try { if(initCtx != null) initCtx.close(); } catch(NamingException ne) { log("Error closing context: " + ne); throw new EJBException(ne); } } } private void log(String s) { if (VERBOSE) System.out.println(s); } private String curPrimaryKey() { return "PK = " + ((EmpBMPPK)ctx.getPrimaryKey()).toString(); } private void cleanup(Connection con, PreparedStatement ps) { try { if (ps != null) ps.close(); } catch (Exception e) { log("Error closing PreparedStatement: "+e); throw new EJBException (e); } try { if (con != null) con.close(); } catch (Exception e) { log("Error closing Connection: " + e); throw new EJBException (e); } } }

설명을 하기전 CMP의 Bean 클래스와는 비교도 안될정도로 소스 라인이 늘어난 걸 볼 수 있다.

시작하기도 전에 EJB하나 짜는데, 뭐가 이리 소스가 많지… 라고 말하려는 사람들도 많을 것이다.

그러나 막상 분석해보면 그다지 CMP랑 다를게 없다. CMP에서 컨테이너가 했던일을 이곳에 그냥

풀어서 작성했다고 생각하면 될 것이다.

Bean 클래스 내부의 멤버 변수들은 다음과 같은것들이 있다.

final static private boolean VERBOSE = true;

private EntityContext ctx;

cafe.daum.net/weblogic

- 130 - 서진권 [email protected]

private int empno;

private String ename;

private String job;

private int mgr;

private java.sql.Date hiredate;

private double sal;

private double comm;

private int deptno;

VERBOSE 변수는 다른 것은 아니고, 이번 예제에서는 엔티티빈이 어떠한 동작으로 돌아가는 지

이해하기 위해서 약간의 로그 문자를 남길려고 한다. 소스 마지막쯤 보면 log()라는 함수를

호출하는데, VERBOSE가 true로 되어있으면, log 함수의 인수를 인쇄하고, false이면 인쇄하지 않는다.

empno 멤버 변수부터 deptno 멤버 변수까지는 모두 EMP테이블의 컬럼에 해당하는 값을

내부적으로 가지고 있는 변수들이다.

Bean 클래스를 크게 보았을 때, 다음과 같은 내부 함수가 있다.

public void ejbActivate()

public void ejbPassivate()

public void ejbLoad()

public void ejbStore()

public EmpBMPPK ejbCreate()

public void ejbPostCreate()

public void ejbRemove()

public EmpBMPPK ejbFindByPrimaryKey()

public Collection ejbFindByJob()

public XXX getXXX()

public void setXXX(XXX)

위 함수들이 BMP를 작성할 반드시 필요한 함수와 중요한 함수들이다. 나머지

getConnection()함수는 웹로직 서버에서 TxDataSource를 통한 JDBC Connection을 가져오며,

log()함수는 디버그를 위한 문장 출력을, curPrimaryKey() 함수는 현재 Bean이 초기화된 로우의

Primary Key값들을 구한다. 마지막으로 cleanup() 함수는 사용했던 JDBC Connection객체를 다시

반환 하는 함수 이다.

ejbActivate()함수가 호출되는 시점은 파인더로 원하는 데이터를 찾고 난후 최초 발생하는 함수이며,

엔티티빈이 활성화 되었을 경우이다. 그 와 반대로 비활성화 되었을 경우(컨테이너 EJB풀링에 반환

될 경우)에는 ejbPassivate()함수가 호출 되게 되는 것이다. 이 예제에서는 특별히 ejbActive(),

ejbPassivate() 함수에 기업한 소스는 없고, 단지 호출되었다는 것만 문장으로 출력한다.

cafe.daum.net/weblogic

- 131 - 서진권 [email protected]

ejbLoad()는 클라이언트에서 파인더를 호출 후, ejbActivate()실행한 다음 호출되는 함수이다.

이곳에서 테이블의 데이터를 조회하여, 엔티티빈의 내부변수에 초기화 한다. ejbLoad()를 호출하는

시점은 이미 findByPrimaryKey() 함수를 호출한 시점이므로, findByPriamryKey로 들어온 인수를

가지고, 다시 SQL의 SELECT문으로 테이블에서 조회한다. 테이블을 조회후 초기화된 내부 변수들은

getXXX()메소드에서 참조한다. 또 그 반대로 클라이언트에서 setXXX()메소드를 호출하면, 인수로

들어온 데이터를 엔티티빈의 내부변수에 설정하고, 설정된 데이터는 다시 ejbStore()에서 테이블에

Update되게 되는 것이다.

ejbCreate()가 호출되는 시점은 클라이언트에서 새로운 로우를 생성할 경우, Home Interface에서

Create()함수를 호출하는데, 인수로 들어온 값들은 추가할 로우의 데이터 값들이다. 그리고

서버에서는 ejbCreate()함수가 호출되며, 그 내부에서는 테이블에 SQL의 INSERT INTO명령어를

사용하여 로우를 추가하게 된다. ejbPostCreate()함수는 추가된 후에 호출되는 함수인데, 여기에 추가

되고 난후 적절한 작업을 하게 된다. BMP를 작성할 경우, 중요 함수 앞에 꼭 ejb문자가 붙어있음을

기억해 두자. 마찬가지로 findByPrimaryKey 뿐만 아니라 findByJob도 Bean 클래스에서는 앞에 ejb가

붙는다. 눈여겨 볼 것은 ejbCreate후에 EmpBMPPK 클래스로 리턴되는데, Home Interface의

Create()함수는 EmpBMP Remote Interface가 리턴된다. Home Interface야 클라이언트에서

Create()함수를 호출시 당연히 EmpBMP함수를 호출하는개 말이 되지만, ejbCreate()함수에서

리턴되는 결과는 Remote Interface가 아닌 EmpBMPPK 클래스이다. 엔티티빈에서는 ejbCreate()가

실행되어, 자료 저장이 성공적으로 될 경우, 생성된 PK값으로 EmpBMPPK클래스를 생성후 리턴해

버린다. 그 반대로 저장을 못할 경우, CreateException이 발생한다. 즉 EmpBMPPK가 리턴된다는

말은 성공적으로 테이블에 기입되었다는 말이다. Bean 클래스에서는 내부변수만 초기화 하고, 자기의

유일무이한 식별자(EmpBMPPK)만 가지고 있으면 된다는 얘기이다. 이러한 메커니즘은 파인더에서도

나타난다. ejbFindByPrimaryKey()호출후 리턴되는 값도 EmpBMPPK이고, ejbFindByJob() 패인더도

호출후 EmpBMPPK의 셋트 값을 가지는 Collection을 리턴한다.

ejbFindByPrimaryKey()함수는 Home Interface에서 선언한 findByPrimaryKey()와 연관이 되는데,

클라이언트에서 Home Interface의 findByPrimaryKey()를 호출하면 Bean 클래스의

ejbFindByPrimaryKey()가 호출된다. 마찬가지로 findByJob()호출시, 내부적으로 Bean클래스의

ejbFindByJob()함수가 호출되는 것이다. ejbFindByJob()함수일 경우, 조회한 함수들의 모든 PK값들을

다시 EmpBMPPK클래스로 만들어 모두 Vector에 저장후 리턴한다. 클라이언트 입장에서는 Home

Interface에서는 PK아닌 Bean 클래스의 인스턴스를 받는다.

getXXX()/setXXX() 함수는 내부적인 데이터값을 가지고 잇는 변수들을 리턴하거나 설정한다.

ejbLoad()발생후, getXXX()함수를 통하여 클라이언트에서 데이터를 가지고 갈수 있으며, setXXX()

함수를 호출하여 클라이언트에서 값을 변경한후, ejbStore()함수가 호출되는 것이다.

EmpBMPPK(Primary Key 데이터의 정보를 가지고 있는 클래스)

package ejb.bmp.EmpBMP;

cafe.daum.net/weblogic

- 132 - 서진권 [email protected]

public class EmpBMPPK implements java.io.Serializable

{

public int empno;

public EmpBMPPK() {}

public EmpBMPPK(int empno)

{

this.empno = empno;

}

public int hashCode()

{

return Integer.toString(empno).hashCode();

}

public boolean equals(Object other)

{

if(other == null || !(other instanceof EmpBMPPK)) return false;

return (((EmpBMPPK)other).empno == this.empno);

}

public String toString()

{

return "(" + empno + ")";

}

}

이번 예제에서는 마지막 부분에서 toString()이라는 함수가 추가 되었는데, 현재 설정된 PK 값을

리턴해준다.

ejb-jar.xml

<?xml version="1.0"?>

<!DOCTYPE ejb-jar PUBLIC

'-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN'

cafe.daum.net/weblogic

- 133 - 서진권 [email protected]

'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>

<ejb-jar>

<enterprise-beans>

<entity>

<ejb-name>EmpBMP</ejb-name>

<home>ejb.bmp.EmpBMP.EmpBMPHome</home>

<remote>ejb.bmp.EmpBMP.EmpBMP</remote>

<ejb-class>ejb.bmp.EmpBMP.EmpBMPBean</ejb-class>

<persistence-type>Bean</persistence-type>

<prim-key-class>ejb.bmp.EmpBMP.EmpBMPPK</prim-key-class>

<reentrant>False</reentrant>

<resource-ref>

<res-ref-name>EJBDBPoolTxDataSource</res-ref-name>

<res-type>javax.sql.DataSource</res-type>

<res-auth>Container</res-auth>

</resource-ref>

</entity>

</enterprise-beans>

<assembly-descriptor>

<container-transaction>

<method>

<ejb-name>EmpBMP</ejb-name>

<method-name>*</method-name>

</method>

<trans-attribute>Required</trans-attribute>

</container-transaction>

</assembly-descriptor>

<ejb-client-jar>EmpBMP_Client.jar</ejb-client-jar>

</ejb-jar>

CMP의 ejb-jar.xml에 비하여 스키마 정보가 없기 때문에(Bean 클래스에서 다 만들었으므로)매우

단순하다는 것을 알수 있다. <persistence-type>태그가 값이 Bean으로 설정되어 있으면 바로,

BMP라는 얘기이다. 또 중간쯤 보면 다음과 같은 태그가 추가 되었다.

<res-ref-name>EJBDBPoolTxDataSource</res-ref-name>

<res-type>javax.sql.DataSource</res-type>

<res-auth>Container</res-auth>

cafe.daum.net/weblogic

- 134 - 서진권 [email protected]

</resource-ref>

이는 Bean클래스 내에서 우리가 웹로직 서버에서 설정했던 Data Source를 사용한다는 것을

알려준다.

weblogic-ejb-jar.xml

<?xml version="1.0"?>

<!DOCTYPE weblogic-ejb-jar PUBLIC

'-//BEA Systems, Inc.//DTD WebLogic 7.0.0 EJB//EN'

'http://www.bea.com/servers/wls700/dtd/weblogic-ejb-jar.dtd'>

<weblogic-ejb-jar>

<weblogic-enterprise-bean>

<ejb-name>EmpBMP</ejb-name>

<reference-descriptor>

<resource-description>

<res-ref-name>EJBDBPoolTxDataSource</res-ref-name>

<jndi-name>EJBDBPoolTxDataSource</jndi-name>

</resource-description>

</reference-descriptor>

<jndi-name>ejb-bmp-EmpBMP</jndi-name>

</weblogic-enterprise-bean>

</weblogic-ejb-jar>

BMP에서는 CMP의 weblogic-cmp-rdbms-jar.xml 파일을 참고하게끔 하는 <persistence-

use>태그가 없어지고, 중간쯤 보면 <resource-description>태그가 새롭게 작성 된 것을 볼수 있다.

<resource-description>

<res-ref-name>EJBDBPoolTxDataSource</res-ref-name>

<jndi-name>EJBDBPoolTxDataSource</jndi-name>

</resource-description>

사용할 DataSource이름과 DataSource를 구할 때, JNDI로 접속하게될 이름을 기입하는 것이다.

<res-ref-name>태그의 속성은 DataSource객체를 말하는 것이며, <jndi-name>은 웹로직 서버에

접속할 JNDI이름을 말하는 것이다. 우리는 이설정을 제 2장에서 설정했을 때, 똑 같은 이름으로 했기

때문에, 했갈리지 말라는 차원에서 설명한다.

application.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE application PUBLIC '-//Sun Microsystems, Inc.//DTD J2EE Application 1.3//EN'

cafe.daum.net/weblogic

- 135 - 서진권 [email protected]

'http://java.sun.com/dtd/application_1_3.dtd'>

<application>

<display-name>Oracle EMP Table BeanManaged ejb</display-name>

<description>Oracle EMP Table BeanManaged ejb</description>

<module>

<ejb>EmpBMP.jar</ejb>

</module>

</application>

Client.java

package ejb.bmp.EmpBMP;

import java.rmi.RemoteException;

import java.util.Properties;

import javax.ejb.CreateException;

import javax.ejb.RemoveException;

import javax.naming.Context;

import javax.naming.InitialContext;

import javax.naming.NamingException;

import javax.rmi.PortableRemoteObject;

public class Client

{

private static final String JNDI_NAME = "ejb-bmp-EmpBMP";

private static final String url = "t3://192.168.1.150:7001";

public static void main(String args[])

{

Context ctx = null;

EmpBMPHome home = null;

EmpBMP emp = null;

try

{

cafe.daum.net/weblogic

- 136 - 서진권 [email protected]

Properties h = new Properties();

h.put(Context.INITIAL_CONTEXT_FACTORY,

"weblogic.jndi.WLInitialContextFactory");

h.put(Context.PROVIDER_URL, url);

System.out.println("Connecting weblogic...");

ctx = new InitialContext(h);

}

catch (NamingException ne)

{

System.out.println("We were unable to get a connection to the

WebLogic server at "+url);

System.out.println("Please make sure that the server is running.");

System.out.println(ne);

return;

}

try

{

home = (EmpBMPHome)

PortableRemoteObject.narrow(ctx.lookup(JNDI_NAME), EmpBMPHome.class);

emp = home.findByPrimaryKey(new EmpBMPPK(7369));

System.out.println(emp.getEmpno());

System.out.println(emp.getEname());

System.out.println(emp.getJob());

System.out.println(emp.getMgr());

System.out.println(emp.getHiredate());

System.out.println(emp.getSal());

System.out.println(emp.getComm());

System.out.println(emp.getDeptno());

}

catch (Exception e)

{

System.out.println("The client was unable to lookup the EJBHome.

Please make sure ");

cafe.daum.net/weblogic

- 137 - 서진권 [email protected]

System.out.println("that you have deployed the ejb with the JNDI name

"+JNDI_NAME+" on the WebLogic server at "+url);

System.out.println(e);

return;

}

}

}

이번 예제에서는 jsp 없이 Client.java만 사용하는데, CMP나 BMP나 클라이언트 프로그램은 동일하기

때문이다. BMP를 jsp로 실행해보고 싶은 독자는 기존 EmpCMP예제에서의 jsp를 복사해다가 패키지

이름과 클래스이름만 수정하여 그대로 사용하기 바란다.

build.xml

<project name="ejb-bmp-EmpBMP" default="all" basedir=".">

<!-- set global properties for this build -->

<property environment="env"/>

<property file="../ejbcomp.properties"/>

<property name="build.compiler" value="${JAVAC}"/>

<property name="source" value="."/>

<property name="build" value="${source}/build"/>

<property name="dist" value="${source}/dist"/>

<target name="all" depends="clean, init, compile_ejb, jar_ejb, ejbc, ear_app,

compile_client"/>

<target name="init">

<!-- Create the time stamp -->

<tstamp/>

<!-- Create the build directory structure used by compile

and copy the deployment descriptors into it-->

<mkdir dir="${build}"/>

<mkdir dir="${build}/META-INF"/>

<mkdir dir="${dist}"/>

<copy todir="${build}/META-INF">

<fileset dir="${source}">

cafe.daum.net/weblogic

- 138 - 서진권 [email protected]

<include name="*.xml"/>

<exclude name="build.xml"/>

<exclude name="application.xml"/>

</fileset>

</copy>

</target>

<!-- Compile ejb classes into the build directory (jar preparation) -->

<target name="compile_ejb">

<javac srcdir="${source}" destdir="${build}"

includes="EmpBMPPK.java, EmpBMP.java, EmpBMPHome.java, EmpBMPBean.java"/>

</target>

<!-- Update ejb jar file or create it if it doesn't exist, including XML

deployment descriptors -->

<target name="jar_ejb" depends="compile_ejb">

<jar jarfile="${dist}/EmpBMP.jar"

basedir="${build}"

update="yes">

</jar>

</target>

<!-- Run ejbc to create the deployable jar file -->

<target name="ejbc" depends="jar_ejb">

<java classname="weblogic.ejbc" fork="yes" failonerror="yes">

<sysproperty key="weblogic.home" value="${WL_HOME}/server"/>

<arg line="-verbose -compiler javac ${dist}/EmpBMP.jar"/>

<classpath>

<pathelement path="${CLASSPATH}"/>

</classpath>

</java>

</target>

<!-- Put the ejb into an ear, to be deployed from the ${APPLICATIONS} dir -->

<target name="ear_app" depends="jar_ejb">

<ear earfile="${APPLICATIONS}/EmpBMP.ear" appxml="${source}/application.xml">

cafe.daum.net/weblogic

- 139 - 서진권 [email protected]

<fileset dir="${dist}" includes="EmpBMP.jar"/>

</ear>

</target>

<!-- Compile client app into the clientclasses directory, and move the client jar file (created by

ejbc) there as well -->

<target name="compile_client">

<copy file="${source}/EmpBMP_Client.jar" tofile="${EX_WEBAPP}/WEB-

INF/lib/EmpBMP_Client.jar"/>

<move file="${source}/EmpBMP_Client.jar" tofile="${CLIENT_CLASSES}/EmpBMP_Client.jar"/>

<javac srcdir="${source}"

destdir="${CLIENT_CLASSES}"

includes="Client.java"

classpath="${CLASSPATH};${CLIENT_CLASSES}/EmpBMP_Client.jar"

/>

</target>

<target name="clean">

<delete dir="${build}"/>

</target>

<!-- Run the example -->

<target name="run">

<java classname="ejb.bmp.EmpBMP.Client" fork="yes" failonerror="true">

<classpath>

<pathelement path="${CLASSPATH};${CLIENT_CLASSES}/EmpBMP_Client.jar"/>

</classpath>

</java>

</target>

</project>

ant로 컴파일 후, 위의 클라이언트 예제를 실행하면 기존 CMP의 결과처럼 나올 것이다.

$ ant run

Buildfile: build.xml

cafe.daum.net/weblogic

- 140 - 서진권 [email protected]

run:

[java] Connecting weblogic...

[java] 7369

[java] SMITH

[java] CLERK

[java] 7902

[java] 1980-12-17

[java] 800.0

[java] 0.0

[java] 20

BUILD SUCCESSFUL

Total time: 15 seconds

뭐…실행결과야 우리가 예측한대로 CMP와 똑같다. 하지만 눈여겨 불것이 있는데, 웹로직 서버가

남기는 로그나 nohup.out 파일을 보기 바란다. 위의 클라이언트 프로그램을 실행후 다음과 같은

문장이 남아 있을것이다.

<YYYY-MM-DD 오전 HH시MI분SS초> <Info> <Management> <140009> <Configuration changes for

domain saved to the repository.>

setEntityContext called

ejbFindByPrimaryKey ((7369))

ejbFindByPrimaryKey ((7369)) found

ejbLoad: (PK = (7369))

ejbStore (PK = (7369))

ejbLoad: (PK = (7369))

ejbStore (PK = (7369))

ejbLoad: (PK = (7369))

ejbStore (PK = (7369))

ejbLoad: (PK = (7369))

ejbStore (PK = (7369))

ejbLoad: (PK = (7369))

ejbStore (PK = (7369))

ejbLoad: (PK = (7369))

ejbStore (PK = (7369))

cafe.daum.net/weblogic

- 141 - 서진권 [email protected]

ejbLoad: (PK = (7369))

ejbStore (PK = (7369))

ejbLoad: (PK = (7369))

ejbStore (PK = (7369))

바로 Bean클래스에서 우리가 작성했던 log()함수가 출력한 문장들이다. 첨부터 보면 클라이언트에서

바인딩한후 엔티티빈에서 결과를 가져가는 과정이 보인다. 클라이언트에서 바인딩시 setEntityContext

함수가 실행되었으며, 그 다음 findByPrimaryKey()함수가 호출되었고, 그후 get/setXXX()메소드들이

들이 호출되어 ejbLoad()/ejbStore()가 진행됨을 보여준다. 이러한 메커니즘을 알아야지만, BMP속도를

대폭적으로 향상시킬수 있는 튜닝을 처리할 수 있다. 이 강좌의 마지막 부분에 이 BMP튜닝은 따로

설명하고, 정리가 안되면, 다시 클라이언트 프로그램과 BMP빈과의 관계, 위의 로그를 셋이서

상관관계를 지어 보면 이해가 가리라 보고 이만 BMP 엔티티빈 설명을 접는다.