| Developer: Java
Oracle Designer Legacy Table API와 Oracle JDeveloper 11g ADF Business Components의 통합
저자 – Chris Muir
Oracle Designer Table API와 Oracle JDeveloper를 통합하고 Designer 기반 레거시 시스템에 새로운 생명을 불어넣는 방법을 배워 보십시오.
게시일 2007년 10월
Oracle Designer는 이제 본격적인 개발이 중단되어 유지 보수 모드로 관리되고 있지만, 여전히 Designer의 흔적(자동 생성된 Table API와 cg_ref_codes 등)이 여기 저기 남아 있는 레거시 데이터베이스 스키마를 쉽게 찾아 볼 수 있습니다. 비교적 최근에 오라클 개발에 입문한 이들에게는 이러한 구성 요소들이 낯설게 느껴지겠지만, 이러한 접근법도 한 때 최신 기술로 떠받들어진 시절이 있었습니다.
다행스럽게도, 오라클이 Oracle JDeveloper 11g의 Application Development Framework Business Components(ADF BC) 기능을 새롭게 손질하면서, 이러한 레거시 코드를 더 이상 무시 또는 변경할 필요가 없게 되었습니다. ADF BC의 데이터베이스 PL/SQL 호출 지원 기능을 활용하여 레거시 Oracle Designer 데이터베이스 시스템에 새로운 생명을 불어 넣을 수 있습니다.
본 문서는 특히, Oracle Designer Table API와 Oracle JDeveloper 11g(Technical Preview 2 버전 기준)의 통합에 논의의 초점을 맞추고 있습니다.
Oracle Designer의 변신
오늘날의 오라클 개발 환경은 불과 10년 전과 비교하더라도 극적인 차이를 보입니다. 요즘에는 Oracle SOA Suite, Oracle XML DB, Oracle Application Express, Oracle JDeveloper의 ADF와 같은 최신 툴이 대세를 이루고 있습니다. 하지만 과거에는 Oracle Forms와 Reports가 대세를 이루었으며, 오라클의 CASE 툴인 Oracle Designer만으로 구현된 Forms, 데이터베이스 애플리케이션은 첨단을 달리는 기술로 떠받들어졌습니다.
당시에는 Oracle Designer만을 활용하여 모듈(Forms)를 100퍼센트 정의하고 설계하는 것이 개발자들의 목표가 되었습니다. 이러한 접근법이 주목 받을 수 있었던 이유를 특히, 모듈이 종속되는 테이블 정의를 쉽게 관리할 수 있다는 장점에서 찾을 수 있습니다. Oracle Designer에서 테이블 정의를 변경하면 정의된 모듈에, 또는 (Oracle Forms 모듈의 경우) 정의된 블록에 자동으로 적용됩니다. 예를 들어 테이블 컬럼 사이즈를 증가시키거나 컬럼을 삭제하는 경우, 관련된 Form에서도 해당 컬럼 사이즈가 증가되거나 컬럼이 삭제됩니다. 만족할만한 결과물이 생성된 후, 개발자는 Oracle Designer로부터 실질적인 Form을 "생성"하여 운영 환경에서 활용할 수 있습니다.
Oracle Designer의 인기가 시들해진 요즘이지만 여전히 많은 레거시 시스템, 특히 데이터베이스에서 Oracle Designer를 통해 생성된 코드를 쉽게 찾아 볼 수 있습니다. 테이블 API, 저널 테이블, cg_ref_codes와 같은 데이터베이스 구조물들은 다양한 스키마에서 발견됩니다.
Oracle JDeveloper 11g의 ADF와 같은 현대적인 툴을 활용할 때 이러한 레거시 데이터베이스 구조물들이 통합 상의 문제를 야기할까요? 본 문서를 통해, 특히 Oracle Designer의 Table API를 ADF에 통합시키는 기능에 대한 설명에서 확인하실 수 있겠지만 "전혀 그렇지 않습니다."
미리 알고 있어야 할 것들
본 문서는 독자가 Entity Object, View Object 등을 포함하는 Oracle JDeveloper ADF BC에 대해, 그리고 Java 코딩 및 JDBC 프로그래밍에 대해 이해하고 있다고 가정하고 있습니다. SQL, PL/SQL에 관련된 지식도 글의 내용을 이해하는데 도움이 될 것입니다.
데이터 모델
다음과 같은 단순한 데이터 모델을 고려해 봅시다:

그림 1 데이터 모델
데이터 모델은 두 개의 테이블(organization, event), 그리고 "one-to-many" 관계를 포함하고 있습니다. 한 예로, Sage Computing Services 조직은 호주에서 개최되는 많은 Oracle 교육 이벤트를 주관하고 있습니다.
다운로드
본 문서를 위한 Oracle JDeveloper 11g 애플리케이션 데모를 이곳에서 다운로드할 수 있습니다. 본 문서에서는 코드의 일부가 생략되어 있는 반면, 데모 애플리케이션은 완전한 소스 코드를 포함하고 있습니다.
Oracle JDeveloper 애플리케이션 데모의 프로젝트 데이터베이스에는 데모 스키마와 관련 데이터베이스 오브젝트의 생성을 위한 스크립트가 포함되어 있습니다. 임의의 데이터베이스 개발용 계정을 사용하여 SQL*Plus에서 install.sql 스크립트를 실행하여 샘플 스키마를 생성하시기 바랍니다. 그런 다음 Oracle JDeveloper 애플리케이션의 디폴트 연결 정보를 여러분의 데이터베이스/스키마 환경에 맞게 변경해야 합니다.
Oracle Designer 데이터베이스 아티팩트: Table API
Oracle Designer 데이터베이스 아티팩트의 대표적인 예로 Table API를 들 수 있습니다.
Oracle Designer는 Oracle Forms를 생성하는 것과 별도로, 지정된 스키마 테이블의 래핑(wrapping)을 위한 일련의 패키지를 생성하는데 이를 Table Application Programming Interface(Table API)라 부릅니다. Table API PL/SQL 패키지를 이용하면 호줄 프로그램에서 테이블의 데이터를 간접적으로 조회, 삽입, 변경, 삭제할 수 있습니다.
여기서 생성된 Oracle Form이 DML을 통해 테이블에 직접적으로 읽기/쓰기를 수행하는 대신, 데이터베이스 패키지가 제공하는 Oracle Form 기반 블록 레벨 기능을 사용하고 있다는 점이 중요합니다. Oracle Designer는 Table API를 통해 특정 비즈니스 룰이 생성되는 것을 허용하며, 생성된 비즈니스 룰은 관련된 종속 Oracle Form에 적용됩니다. 일부 기업 환경에서는 Table API 구조를 이용하여 엄청난 양의 프로그래밍 로직을 개발하여 활용하고 있기도 합니다.
데모 데이터 모델의 events 테이블을 위해 생성된 Table API 패키지가 아래와 같습니다. (아래에서는 Oracle Designer를 통해 실제로 생성되는 내용의 일부만을 포함하고 있습니다. 위의 "다운로드" 섹션에서 샘플 데이터 모델을 위한 전체 Table API 예제를 확인하실 수 있습니다.)
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 |
CREATE OR REPLACE PACKAGE cg$EVENTS IS
TYPE cg$row_type IS RECORD
(EVENT_NO cg$row.EVENT_NO%TYPE
,ORG_ID cg$row.ORG_ID%TYPE
,DESCRIPTION cg$row.DESCRIPTION%TYPE
,CONTACT_NAME cg$row.CONTACT_NAME%TYPE
,START_DATE cg$row.START_DATE%TYPE
,END_DATE cg$row.END_DATE%TYPE
,COMMENTS cg$row.COMMENTS%TYPE
,the_rowid ROWID);
TYPE cg$ind_type IS RECORD
(EVENT_NO BOOLEAN DEFAULT FALSE
,ORG_ID BOOLEAN DEFAULT FALSE
,DESCRIPTION BOOLEAN DEFAULT FALSE
,CONTACT_NAME BOOLEAN DEFAULT FALSE
,START_DATE BOOLEAN DEFAULT FALSE
,END_DATE BOOLEAN DEFAULT FALSE
,COMMENTS BOOLEAN DEFAULT FALSE);
PROCEDURE ins
(cg$rec IN OUT cg$row_type,
cg$ind IN OUT cg$ind_type,
do_ins IN BOOLEAN DEFAULT TRUE);
PROCEDURE upd
(cg$rec IN OUT cg$row_type,
cg$ind IN OUT cg$ind_type,
do_upd IN BOOLEAN DEFAULT TRUE,
cg$pk IN cg$row_type DEFAULT NULL);
PROCEDURE del
(cg$pk IN cg$pk_type,
do_del IN BOOLEAN DEFAULT TRUE);
PROCEDURE lck
(cg$old_rec IN cg$row_type,
cg$old_ind IN cg$ind_type,
nowait_flag IN BOOLEAN DEFAULT TRUE);
PROCEDURE slct
(cg$sel_rec IN OUT cg$row_type);
END cg$EVENTS; |
이 패키지는 하부 테이블에 저장된 데이터의 조회, 삽입, 수정, 삭제, 락 설정 등을 위한 프로시저(procedure)를 제공합니다.
cg$EVENTS.ins 프로시저는 다음과 같은 매개변수를 취합니다:
- cg$rec: 테이블의 모든 컬럼을 포함하는 동일 패키지에 정의된 PL/SQL 레코드 타입입니다. 프로시저를 통해 전달되는 값은 events 테이블에 INSERT 처리됩니다. Table API는 전달된 값을 수정할 수 있습니다. cg$rec은 OUT 매개변수로 반환되며, 따라서 호출한 모듈은 이 변경 값을 취한 후 자체적으로 관리하는 데이터 셋에 적용할 수 있습니다.
- cg$ind: cg$EVENTS 패키지 내에서 정의되는 두 번째 PL/SQL 레코드 타입으로, 어떤 cg$rec 컬럼이 입력되었는지 확인하는데 사용되는 일련의 불리언(Boolean) 컬럼으로 구성됩니다. cg$ind는 Table API에 의해 수정 또는 디폴트 처리된 값이 있는 경우 Table API 내부의 ins 프로시저를 통해 반환됩니다. 호출한 모듈은 반환된 값을 이용하여 어떤 컬럼이 업데이트 되었는지 확인할 수 있습니다. .
- do_ins: 프로시저에 실제로 INSERT 작업을 지시하는 간단한 불리언 플래그입니다.
cg$EVENTS.upd 프로시저는 이와 유사한 일련의 매개변수를 취하여 ins 프로시저를 실행합니다. 여기에는 업데이트할 레코드의 프라이머리 키 값을 저장한 cg$pk 레코드 타입, 그리고 업데이트 작업을 수행하는데 사용되는 플래그 등이 포함됩니다.
cg$EVENTS.upd 프로시저는 이와 유사한 일련의 매개변수를 취하여 ins 프로시저를 실행합니다. 여기에는 업데이트할 레코드의 프라이머리 키 값을 저장한 cg$pk 레코드 타입, 그리고 업데이트 작업을 수행하는데 사용되는 플래그 등이 포함됩니다.
cg$EVENTS.lck 프로시저는 레코드를 데이터베이스에서 수정 또는 삭제하기 전에 락(lock)을 설정하는 용도로 사용됩니다. 이 프로시저는 업데이트 또는 삭제 작업이 수행되기 이전의 모든 기존 값들을 포함하는 cg$old_rec 레코드 타입, 그리고 어떤 컬럼을 업데이트할 것인지 지정하기 위한 cg$old_ind 레코드 타입을 취합니다.
불리언 타입의 nowait_flag는 lock-wait 상태를 설정하기 위해 no-wait 옵션을 이용하여 lock 구문이 실행되었음을 지정하는데 사용됩니다.
ADF Business Components를 이용한 Table API의 활용
Oracle JDeveloper의 ADF BC에서는 일반적으로 애플리케이션에서 사용할 데이터베이스 스키마 내의 모든 테이블을 참조하기 위해 Entity Object(EO)를 생성하여 활용합니다. EO는 사용자가 애플리케이션에 관련된 데이터를 변경하는 동안 데이터를 대조하고, DML INSERT, UPDATE, DELETE 구문을 실행하고, row lock 구문을 실행하기 위해 사용됩니다.
The Oracle Application Development Framework Developer's Guide for Forms/4GL Developers의 섹션 26.4에서 데이터베이스 PL/SQL 패키지 API의 EO를 설계하는 방법을 확인하실 수 있습니다. 아래는 Oracle Designer Table API에 맞게 EO를 설계하는 방법을 간략하게 설명하고 있습니다.
디폴트 EO DML(data manipulation language) 기능은 EO의 커스텀 EntityImpl 클래스 내에 포함된 초크포인트 DML 메소드 doDML()을 변경함으로써 무시(override) 처리할 수 있습니다. 디폴트 doDML() 메소드가 아래와 같습니다:
01
02
03 |
protected void doDML(int operation, TransactionEvent e) {
super.doDML(operation, e);
} |
이 메소드의 매개변수는 INSERT, UPDATE, DELETE 중 어떤 작업을 수행할 것인지 정의하는데 사용됩니다. 따라서 개발자는 doDML() 메소드를 아래와 같이 변경하여 각각의 DML 구문을 개별적으로 처리할 수 있습니다.
01
02
03
04
05
06
07
08
09
10 |
protected void doDML(int operation, TransactionEvent e) {
if (operation == DML_INSERT) {
// Insert logic
} else if (operation == DML_UPDATE) {
// Update logic
} else if (operation == DML_DELETE) {
// Delete logic
} else // unlikely but you never know
super.doDML(operation, e);
} |
각각의 if 코드 블록에서 개발자가 원하는 로직을 직접 작성하여 데이터베이스 패키지 프로시저를 호출할 수 있음을 참고하시기 바랍니다. 이를 위해 JDBC를 이용하여 EO 데이터를 대조하고 직접 구현한 호출을 데이터베이스에 대해 실행함으로써, EO의 값을 전달한 후 cg$EVENTS 프로시저를 통해 변경된 값을 승인할 수 있습니다.
오라클 오브젝트 타입의 JDBC 제약 사항 극복
JDBC에서는 단순한 형태의 데이터타입을 전달하는 데이터베이스 프로시저를 호출하는데 어려움을 겪을 이유가 없습니다. 하지만 cg$EVENTS.ins 프로시저는 두 가지 PL/SQL 레코드 타입을 취하고 있는데, 이 레코드 타입이 JDBC에 의해 직접적으로 지원되지 않는다는 것이 문제입니다.
가장 간단한 해법은 JDBC가 지원하는 데이터베이스 오브젝트 타입을 생성하여 활용하는 것입니다. 이를 위해 아래와 같은 방법을 사용할 수 있습니다:
- 두 가지 PL/SQL 레코드 타입을 위해, 레코드 타입과 동일한 수의 컬럼, 그리고 rowid를 저장하기에 충분한 문자열을 포함하는 데이터베이스 오브젝트 타입을 생성해야 합니다. 예를 들어 cg$EVENTS.cg$row_type PL/SQL 레코드 타입의 구조를 모방한 cg$events_cg$row_type을 아래와 같이 구현할 수 있습니다.
01
02
03
04
05
06
07
08
09 |
CREATE OR REPLACE TYPE cg$events_cg$row_type AS OBJECT
(event_no NUMBER(6)
,org_id NUMBER(4)
,description VARCHAR2(75)
,contact_name VARCHAR2(30)
,start_date DATE
,end_date DATE
,comments VARCHAR2(1000)
,the_rowid VARCHAR2(256)); |
- 또 아래와 같이 cg$EVENTS.cg$ind_type PL/SQL 레코드 타입의 구조를 반영하여 cg$events.cg$ind_type을 구현할 수 있습니다. 하지만 여기서, 불리언 데이터타입을 숫자(1)로 변경하여 TRUE=1, FALSE=0과 같이 설정해 주어야 함에 주의하시기 바랍니다. Oracle Object Type에서는 불리언 데이터타입이 지원되지 않기 때문입니다.
01
02
03
04
05
06
07
08 |
CREATE OR REPLACE TYPE cg$events_cg$ind_type AS OBJECT
(event_no NUMBER(1)
,org_id NUMBER(1)
,description NUMBER(1)
,contact_name NUMBER(1)
,start_date NUMBER(1)
,end_date NUMBER(1)
,comments NUMBER(1)); |
- 다음으로 cg$EVENTS.ins를 위한 래퍼 프로시저(wrapper procedure)를 작성하여 위의 오브젝트 인스턴스를 받아들인 후 관련된 PL/SQL 레코드 타입으로 변환하고, 다시 cg$EVENTS.ins에 전달해 주어야 합니다. 래퍼 프로시저를 위한 새로운 패키지 events_pkg를 아래와 같이 생성합니다.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 |
CREATE OR REPLACE PACKAGE BODY events_pkg AS
PROCEDURE ins
(row IN OUT cg$events_cg$row_type
,ind IN OUT cg$events_cg$ind_type
,doInsert IN BOOLEAN)
IS
rowPlsqlItem cg$events.cg$row_type;
indPlsqlItem cg$events.cg$ind_type;
FUNCTION to_int(aBool BOOLEAN)
RETURN number IS
BEGIN
IF aBool THEN RETURN 1; ELSE RETURN 0; END IF;
END;
BEGIN
rowPlsqlItem.event_no := row.event_no;
rowPlsqlItem.org_id := row.org_id;
rowPlsqlItem.description := row.description;
rowPlsqlItem.contact_name := row.contact_name;
rowPlsqlItem.start_date := row.start_date;
rowPlsqlItem.end_date := row.end_date;
rowPlsqlItem.comments := row.comments;
rowPlsqlItem.the_rowid := row.the_rowid;
indPlsqlItem.event_no := ind.event_no = 1;
indPlsqlItem.org_id := ind.org_id = 1;
indPlsqlItem.description := ind.description = 1;
indPlsqlItem.contact_name := ind.contact_name = 1;
indPlsqlItem.start_date := ind.start_date = 1;
indPlsqlItem.end_date := ind.end_date = 1;
indPlsqlItem.comments := ind.comments = 1;
cg$events.ins(rowPlsqlItem, indPlsqlItem, doInsert);
row.event_no := rowPlsqlItem.event_no;
row.org_id := rowPlsqlItem.org_id;
row.description := rowPlsqlItem.description;
row.contact_name := rowPlsqlItem.contact_name;
row.start_date := rowPlsqlItem.start_date;
row.end_date := rowPlsqlItem.end_date;
row.comments := rowPlsqlItem.comments;
row.the_rowid := rowPlsqlItem.the_rowid;
ind.event_no := to_int(indPlsqlItem.event_no);
ind.org_id := to_int(indPlsqlItem.org_id);
ind.description := to_int(indPlsqlItem.description);
ind.contact_name := to_int(indPlsqlItem.contact_name);
ind.start_date := to_int(indPlsqlItem.start_date);
ind.end_date := to_int(indPlsqlItem.end_date);
ind.comments := to_int(indPlsqlItem.comments);
END ins; |
본 문서에서는, 코드의 가독성과 이해도를 높이기 위해 코드의 모듈화(modularize) 처리를 하지 않았음을 참고하시기 바랍니다. 물론 모듈화 과정을 거치면 코드의 재활용성이 향상됩니다. 다운로드 섹션에서 제공되는 샘플 코드는 코드의 중복 요소를 제거하기 위해 별도의 프로시저와 함수를 활용하고 있습니다.
또 제시된 코드의 복잡성을 최소화하기 위해 events_pkg의 업데이트, 삭제, 락 관련 함수에 대한 설명을 생략하였음을 참고하십시오. 물론 다운로드 섹션의 샘플 코드에서는 전체 코드를 확인할 수 있습니다.
또 events_pkg.lck 프로시저는 아래와 같은 오브젝트 타입을 추가로 요구합니다.
01
02
03 |
CREATE OR REPLACE TYPE cg$events_cg$pk_type AS OBJECT
(event_no NUMBER(6)
,the_rowid VARCHAR2(256)); |
커스텀 EntityImpl 수퍼클래스의 생성
The Oracle Application Development Framework Developer's Guide for Forms/4GL Developers의 섹션 26.4.3에서 설명되고 있듯, 작성된 JDBC 로직 코드의 재활용성을 높이기 위해 커스텀 EntityImpl 수퍼클래스를 생성하는 것이 도움이 됩니다. 가이드에서 설명된 대로, PlsqlEntityImpl 수퍼클래스를 아래와 같이 생성합니다:
01
02
03
04
05 |
package common;
import oracle.jbo.server.EntityImpl;
public class PlsqlEntityImpl extends EntityImpl { } |
그런 다음 JDBC 로직을 요구하는 EO들을 위해, EO에서 새로운 수퍼클래스를 사용할 수 있도록 확장해 줍니다.
01 |
public class EventsImpl extends PlsqlEntityImpl |
이와 같은 방법으로 관련 데이터베이스 패키지에 대한 INSERT, UPDATE, DELETE, LOCK JDBC 호출을 처리하기 위해 PlsqlEntityImpl 수퍼클래스에 커스텀 루틴을 작성할 수 있습니다.
JDBC Insert/Update 함수의 작성
PlsqlEntityImpl 수퍼클래스를 생성하였다면, INSERT, UPDATE 작업을 수행하기 위한 함수를 작성합니다(Table API의 스펙은 거의 동일합니다). 함수에 대한 설명을 코드 뒷부분에서 확인할 수 있습니다.:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 |
public void insertUpdate(int[] attributes
,String dbRowTypeName
,String dbIndTypeName
,String dbStatement) {
try {
Object preRowArray[] = new Object[attributes.length + 1];
Object postRowArray[] = new Object[attributes.length + 1];
Boolean preIndArray[] = new Boolean[attributes.length];
Object postIndArray[] = new Object[attributes.length];
for (int i = 0, size = attributes.length; i < size; i++)
preRowArray[i] = getAttribute(i);
for (int i = 0, size = attributes.length; i < size; i++)
preIndArray[i] = isAttributeChanged(i);
Connection conn =
getDBTransaction().createStatement(1).getConnection();
StructDescriptor rowStructDesc =
StructDescriptor.createDescriptor(dbRowTypeName, conn);
StructDescriptor indStructDesc =
StructDescriptor.createDescriptor(dbIndTypeName, conn);
oracle.sql.STRUCT rowStruct =
new STRUCT(rowStructDesc, conn, preRowArray);
oracle.sql.STRUCT indStruct =
new STRUCT(indStructDesc, conn, preIndArray);
OracleCallableStatement statement =
(OracleCallableStatement)conn.prepareCall(dbStatement);
statement.setSTRUCT(1, rowStruct);
statement.setSTRUCT(2, indStruct);
statement.registerOutParameter(1, OracleTypes.STRUCT,
dbRowTypeName);
statement.registerOutParameter(2, OracleTypes.STRUCT,
dbIndTypeName);
statement.executeUpdate();
rowStruct = statement.getSTRUCT(1);
indStruct = statement.getSTRUCT(2);
postRowArray = rowStruct.getAttributes();
postIndArray = indStruct.getAttributes();
for (int i = 0, size = attributes.length; i < size; i++) {
Boolean postInd =
((BigDecimal)postIndArray[i]).toString().equals("1");
if (preIndArray[i] != postInd) {
String attributeType =
getStructureDef().getAttributeDef(
attributes[i]).getJavaType().getName();
// This code is incomplete and does not handle all the
// datatypes that could come back from the database
if (postRowArray[i] == null)
setAttribute(attributes[i], null);
else if (attributeType.equals("oracle.jbo.domain.Date"))
setAttribute(attributes[i]
,new Date((Timestamp)postRowArray[i]));
else if (attributeType.equals(
"oracle.jbo.domain.Number"))
setAttribute(attributes[i]
,new Number((BigDecimal)postRowArray[i]));
else if (attributeType.equals("java.lang.String"))
setAttribute(attributes[i]
,(String)postRowArray[i]);
else
throw new JboException(
"PlsqlEntityImpl.insertUpdate() datatype?");
}
}
} catch (SQLException ex) {
throw new JboException(ex);
}
} |
여기에서도 코드에 대한 이해도를 높이기 위해 모듈화(modularization) 과정을 거치지 않았습니다. 하지만 재활용 가능한 여러 함수로 insertUpdate() 함수를 쉽게 분리해내실 수 있을 것입니다.
insertUpdate() 함수는 다음과 같은 매개변수들을 취합니다:
- int[] attributes: EO에서 데이터베이스에 기록할 각 속성의 인덱스 포지션 정보를 포함하는 어레이입니다. 이 어레이는 EntityImpl 클래스의 정적 int 속성 상수를 통해 생성됩니다. 여기에 대해서는 잠시 후에 설명하겠습니다.
- String dbRowTypeName: 데이터베이스 로우 오브젝트 타입의 이름입니다(예: CG$EVENTS_CG$ROW_TYPE)
- String dbIndTypeName: 데이터베이스 인디케이터 오브젝트 타입의 이름입니다(예: CG$EVENTS_CG$IND_TYPE)
- String dbStatement: 실제로 실행할 PL/SQL 호출입니다. 그 실행 예가 아래와 같습니다:
BEGIN events_pkg.ins(?, ?, TRUE); END;
또는
BEGIN events_pkg.upd(?, ?, TRUE); END;
insertUpdate() 함수는 아래와 같은 작업을 수행합니다:
- 라인 08-18: 현재의 EO에 대해 모든 EO 속성 값을 포함하는 preRowArray 어레이, EO 속성 변경 인디케이터를 포함하는 preIndArray 어레이, 그리고 이후 실행되는 JDBC 호출로부터 결과를 반환하기 위한 두 개의 빈(blank) 어레이를 생성합니다.
- 라인 20-31: 생성된 속성 어레이와 변경 인디케이터 어레이를 이용하여 2개의 JDBC STRUCT 값을 생성하고, 데이터베이스 오브젝트 타입과 연계하고, 어레이에 데이터를 입력합니다.
- 라인 33-34: PL/SQL 패키지대한 JDBC 호출과 dbStatement에 정의된 함수를 준비합니다.
- 라인 36-42: 2개의 STURCT 값을 PL/SQL 함수에 기록하고, 2개의 OUT 매개변수를 가져올 준비를 합니다.
- 라인 44: 데이터베이스를 호출하여 PL/SQL 함수 호출을 실행하고 STRUCT 값을 전달한 뒤 OUT 매개변수를 가져옵니다.
- 라인 46-50: 데이터베이스 INSERT/UPDATE 루틴은 결과로 로우 값과 (어떤 값이 변경되었는지 확인해 주는) 플래그에 대한 인디케이터를 반환합니다. 이 부분에서는 JDBC prepareCall() 구문으로부터 결과를 수집하고, postRowArray와 postIndArray에 데이터를 입력하는 과정을 구현하고 있습니다.
- 라인 52-74: postRowArray에 대한 반복 실행을 통해, postIndArray를 통해 변경 인디케이터를 갖는 각각의 속성에 대해 새로운 속성을 가져오고 EO 속성으로 기록합니다. 예제 코드의 라인 61 이후에서는 전체 데이터타입 중 일부만을 다루고 있음을 참고하시기 바랍니다. 완전한 솔루션을 구현하려면 데이터베이스로부터 반환되는 모든 데이터타입(LOB 포함)을 처리해야 할 것입니다.
JDBC Delete 함수의 작성
JDBC를 통해 DELETE 작업을 처리하기 위한 방법은 insertUpdate() 함수와 유사합니다:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 |
public void delete(int[] pkAttributes
,String dbPkTypeName
,String dbStatement) {
try {
Object preRowArray[] = new Object[pkAttributes.length + 1];
for (int i = 0, size = pkAttributes.length; i < size; i++)
preRowArray[i] = getAttribute(i);
Connection conn =
getDBTransaction().createStatement(1).getConnection();
StructDescriptor rowStructDesc =
StructDescriptor.createDescriptor(dbPkTypeName, conn);
oracle.sql.STRUCT rowStruct =
new STRUCT(rowStructDesc, conn, preRowArray);
OracleCallableStatement statement =
(OracleCallableStatement)conn.prepareCall(dbStatement);
statement.setSTRUCT(1, rowStruct);
statement.registerOutParameter(1, OracleTypes.STRUCT
,dbPkTypeName);
statement.executeUpdate();
} catch (SQLException ex) {
throw new JboException(ex);
}
} |
delete() 함수는 다음과 같은 매개변수들을 취합니다:
지금까지 확인한 것처럼, delete() 함수는 insertUpdate() 함수와 매우 유사합니다. 단, 반환된 데이터의 처리가 전혀 불필요하다는 차이가 있습니다.
JDBC Lock 함수의 작성
락(lock)을 처리하는 방법 역시 앞에서 설명한 함수들과 유사합니다:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 |
public void lock(int[] attributes
,String dbRowTypeName
,String dbIndTypeName
,String dbStatement) {
OracleCallableStatement statement;
try {
Object preRowArray[] = new Object[attributes.length + 1];
Boolean preIndArray[] = new Boolean[attributes.length];
// Note call to getPostedAttribute rather than getAttribute
for (int i = 0, size = attributes.length; i < size; i++)
preRowArray[i] = getPostedAttribute(i);
for (int i = 0, size = attributes.length; i < size; i++)
preIndArray[i] = isAttributeChanged(i);
Connection conn =
getDBTransaction().createStatement(1).getConnection();
StructDescriptor rowStructDesc =
StructDescriptor.createDescriptor(dbRowTypeName, conn);
StructDescriptor indStructDesc =
StructDescriptor.createDescriptor(dbIndTypeName, conn);
oracle.sql.STRUCT rowStruct =
new STRUCT(rowStructDesc, conn, preRowArray);
oracle.sql.STRUCT indStruct =
new STRUCT(indStructDesc, conn, preIndArray);
statement =
(OracleCallableStatement)conn.prepareCall(dbStatement);
statement.setSTRUCT(1, rowStruct);
statement.setSTRUCT(2, indStruct);
statement.registerOutParameter(1, OracleTypes.STRUCT
,dbRowTypeName);
statement.registerOutParameter(2, OracleTypes.STRUCT
,dbIndTypeName);
statement.executeUpdate();
} catch (SQLException e) {
if (e.getMessage().indexOf("TAPI--54") > 0)
throw new RowInconsistentException(getKey());
else
throw new JboException(e);
}
} |
lock() 함수의 매개변수는 insertUpdate() 메소드와 거의 비슷하지만 한 가지 중요한 차이가 있습니다. getAttributes()에 대한 호출을 통해 현재의 EO 속성 값을 가져오는 대신, 라인 14에서 함수는 getPostedAttributes()를 호출하여 DML 작업 이전의 속성 값을 가져오고 있습니다. 이것은 데이터베이스 레코드의 변경 여부를 확인하기 위해 Table API lck 메소드가 기존의 속성 값을 참조해야 하기 때문입니다. 이를 위해 데이터베이스의 현재 값과 기존의 값을 비교하는 작업이 수행됩니다. 만일 값이 변경되었다면, 다른 사용자가 레코드를 업데이트한 것으로 볼 수 있습니다. 값이 변경되지 않았다면 루틴은 선택된 로우에 락을 설정합니다.
또 라인 45-49의 SQLException에서 반환된 데이터베이스 익셉션 메시지에 "TAPI-54"라는 문자열이 포함되어 있는지 확인하고 있습니다. 이 메시지는 일반적으로 Table API lck 메소드에 의해 생성되며, 다른 사용자가 레코드에 대해 락을 설정하고 있음을 알리기 위해 사용됩니다. 이 메시지가 확인된 경우 JBO 익셉션 RowInconsistentException을 발생시켜 에러를 표시하게 됩니다. RowInconsistentException은 로우 락 에러 처리를 위해 일반적으로 사용되는 ADF BC 메커니즘입니다.
작성된 JDBC 함수의 호출을 위해 EO doDML() 메소드를 무시(override) 처리
INSERT, UPDATE, DELETE, 락 설정을 위한 메소드 작성을 완료하였다면, 이제 아래 코드를 이용하여 전체 DML 작업을 위한 doDML() 메소드를 완성할 차례입니다:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 |
static final int attributes[] =
new int[] { EVENTNO, ORGID, DESCRIPTION, CONTACTNAME,
STARTDATE, ENDDATE, COMMENTS };
static final int pkAttributes[] = new int[] { EVENTNO };
static final String rowType = "CG$EVENTS_CG$ROW_TYPE";
static final String indType = "CG$EVENTS_CG$IND_TYPE";
static final String pkType = "CG$EVENTS_CG$PK_TYPE";
protected void doDML(int operation, TransactionEvent e) {
try {
if (operation == DML_INSERT) {
super.insertUpdate(attributes, rowType, indType,
"BEGIN events_pkg.ins(?, ?, TRUE); END;");
} else if (operation == DML_UPDATE) {
super.insertUpdate(attributes, rowType, indType,
"BEGIN events_pkg.upd(?, ?, TRUE); END;");
} else if (operation == DML_DELETE) {
super.delete(pkAttributes, pkType,
"BEGIN events_pkg.del(?, ?, TRUE); END;");
} else // unlikely but you never know
super.doDML(operation, e);
} catch (SQLException ex) {
throw new JboException(ex);
}
} |
이 코드는 다음과 같은 작업을 수행합니다:
- 라인 01-04: EntityImpl 내에서, 개별 EO 속성을 위한 인덱스 어레이와 EO의 프라이머리 키 속성의 인덱스 어레이를 생성합니다. 이 인덱스들은 JDBC 메소드 그리고 Table API 메소드에 전달하기 위해 활용합니다.
- 라인 06-08: Oracle Object Type 이름에 대한 정보를 포함하는 정적 문자열입니다.
- 라인 13-20: 각각의 작업 유형에 대해 PlsqlEntityImpl 수퍼클래스 내의 JDBC 메소드를 호출하고, 관련 어레이, Oracle Object Type 네임, 그리고 호출할 PL/SQL 패키지 함수를 전달합니다.
작성된 JDBC 함수의 호출을 위해 EO lock() 메소드를 무시(override) 처리
마지막으로 Table API cg$EVENTS.lck 메소드를 간접적으로 호출하기 위해 EO lock() 메소드를 무시 처리할 차례입니다. 아래에서 확인할 수 있듯, 코드는 doDML() 메소드의 경우보다도 간단합니다:
01
02
03
04 |
public void lock() {
super.lock(attributes, rowType, indType,
"BEGIN events_pkg.lck(?, ?); END;");
} |
결론
위의 모든 과정을 완료하였다면, 이제 Oracle JDeveloper 코드를 Oracle Designer 데이터베이스 Table API와 함께 실행할 수 있습니다.
레거시 코드를 새로운 시스템에 통합하는 것은 쉽지 않은 일입니다. 하지만 Oracle JDeveloper의 ADF를 활용하면 Oracle Designer의 Table API를 쉽게 통합할 수 있습니다. Oracle JDeveloper의 ADF는 JDBC 데이터베이스 호출을 위한 다양한 기능과 프레임워크 초크포인트 메소드를 지원하며, 개발자가 필요에 따라 커스텀 솔루션을 직접 작성할 수도 있습니다. 이러한 기능을 활용함으로써 새로운 테크놀로지의 도입에 따르는 부담을 최소화하고 레거시 데이터베이스 구조물들을 쉽게 통합함으로써 한층 안정된 환경을 구현할 수 있을 것입니다.
Chris Muir [http://one-size-doesnt-fit-all.blogspot.com]는 호주에 거주하는 Oracle ACE Director(Oracle Fusion Middleware)이자 SAGE Computing Services의 선임 컨설턴트 겸 오라클 강사로 활동하고 있습니다. 오라클 환경에서 10년 넘게 경험을 쌓아온 Miur는, 최근 들어 Oracle JDeveloper와 ADF에 관련된 작업, 강의 활동을 적극적으로 펼쳐 왔습니다.
|