메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

JDO(Java Data Object) 사용하기

한빛미디어

|

2002-04-09

|

by HANBIT

8,636

저자: 다이온 알마에르, 역 김대곤

XML 데이터 바인딩에 대한 기사에서 우리는 자바 객체를 XML파일로 저장하는 방법을 보았다.

이번 기사에는 썬의 Java Data Object 표준에 대하여 살펴볼 것이다. Java Data Objects는 트랙잭션 관리는 물론이고 여러 명의 동시 사용자를 지원하면서 자바 객체를 저장할 수 있도록 한다. JDO는 SQL이나 데이터베이스와 관련된 사항을 전혀 알 필요가 없다는 면에서 JDBC와 다르다. 그리고 트랙잭션 관리 및 다중 사용자가 지원된다는 면에서 본다면 직렬화와도 다르다. JDO 표준은 자바 개발자들이 객체 모델을 데이터 모델로 사용할 수 있다. 따라서 "객체"와 "데이터" 사이를 오고 가기 위해 시간을 낭비할 필요가 없다.

CocoBase, WebGain 사의 TopLink , CastorJDO를 포함한 다양한 제품들은 객체 모델을 그대로 데이터 모델로 사용할 수 있게 계속해서 노력하고 있다. 이와 같은 작업을 위한 표준을 사용하여, 데이터의 저장소에 관계없이 하나의 방법만 배워서 사용할 수 있다. 이것은 JDBC가 등장해서 드라이버를 지원하는 모든 데이터베이스를 접속할 수 있는 것과 마찬가지라고 볼 수 있다. 정말 대단한 일이 아닌가?

우리는 XML 데이터 바인딩에 대한 기사의 주소록 예제를 JDO를 사용하여 구현할 것이다. 내가 사용할 JDO 구현제품은 Prism Technologies의 OpenFusion JDO이다. 차차 알게 되겠지만 PrismTech의 API를 사용하는 부분은 오직 한 곳 뿐이다. 나머지는 모두 표준 JDO API를 사용한다(내부적으로 감추어진 구현클래스와 함께).

우리는 다음과 같은 작업을 차례로 수행할 것이다.
  1. 데이터 객체 : 데이터베이스에 저장하고자 하는 Person객체를 만든다.
  2. 저장 : Person객체를 데이터베이스(DataStore)에 저장, 데이터베이스(DataStore)로부터의 조회, 수정을 담당할 PersonPersist객체를 만든다.
  3. JDO Enhancer : JDO Enhancer에 작업내용을 알려주는 JDO 설명파일(Descripter)을 만든다.
  4. Build : build 단계를 수행하고, 실행한다.
데이터 객체: Person 객체를 만든다

XML기사에서 정의했던 Person객체를 그대로 사용하여 시작해 보자. 이 객체는 자바빈즈(JavaBeans)의 규칙을 따라 set, get 메소드들을 정의하였다. 저장할 객체이지만 일반 객체와의 다른 특별한 차이가 없다는 점에 주목하자. 저장에 관련된 인터페이스를 구현할 필요도 없으며 기본 클래스를 상속받을 필요도 없다. 저장될 클래스에 요구되는 사항은 다음과 같다.
  1. JDO클래스가 필드(변수)들에 접근가능해야 한다(public 필드이거나 set* 메소드).
  2. 필드의 데이터 타입은 JDO스팩을 따라야 한다(자세한 사항은 Section 6.4를 참조).
  3. 어떤 필드들은 지원되지 않을 수도 있다(Thread, File, Socket 객체들과 같이 직렬화 될 수 없는 필드들).
이러한 요구사항을 염두에 두고, Person.java 파일을 살펴보자.
public class Person {
  private String name;
  private String address;
  private String ssn;
  private String email;
  private String homePhone;
  private String workPhone;

// -- allows us to create a Person via the constructor
public Person(String name, String address, String ssn, 
           String email, String homePhone, String workPhone) {
    this.name = name;
    this.address = address;
    this.ssn = ssn;
    this.email = email;
    this.homePhone = homePhone;
    this.workPhone = workPhone;
  }

// -- accessors
  public String getName() { return name; }
  public String getAddress() { return address; }
  public String getSsn() { return ssn; }
  public String getEmail() { return email; }
  public String getHomePhone() { return homePhone; }
  public String getWorkPhone() { return workPhone; }

// -- mutators
  public void setName(String name) { this.name = name; }
  public void setAddress(String address) {
    this.address = address;
  }
  public void setSsn(String ssn) { this.ssn = ssn; }
  public void setEmail(String email) { this.email = email;  }
  public void setHomePhone(String homePhone) {
    this.homePhone = homePhone;
  }
  public void setWorkPhone(String workPhone) {
    this.workPhone = workPhone;
  }
}
저장: 저장을 담당할 PersonPersist객체를 만든다

우리는 이미 저장하고자 하는 객체인 Person객체를 가지고 있다. 이제 우리는 저장을 담당할 객체가 필요하다. 이제 PersonPersist.java 파일을 살펴보자. 다음과 같은 작업이 수행되는 것을 볼 수 있게 될 것이다.
  1. JDO Persistence Manager의 초기화
  2. 세 개의 "Person" 객체를 데이터베이스에 저장
  3. 데이터베이스로부터 저장된 객체를 읽어 화면에 보여줌
  4. 저장된 Person 객체의 이름을 변경
  5. 저장된 Person 객체 하나를 삭제
  6. main() 메소드로 이러한 작업을 실행
단계 1: JDO Persistence Manager의 초기화

PersonPersist 객체의 시작부이다. 표준 JDO 클래스와 OpenFusion 구현 클래스로부터 ManagedConnectionFactory 클래스를 임포트(import)한다. 물론 표준 JDO 클래스와 OpenFusion클래스를 사용한 독립된 클래스로 추상화할 수 있다. 생성자는 javax.jdo.PersistenceFactoryClass 속성을 설정하여 Connection Factory를 지정한다. 이것은 JDBC에서 데이터베이스 드라이버를 설정하는 것과 같다.
package addressbook;

import java.util.*;
import javax.jdo.*;

import 
com.prismt.j2ee.connector.jdbc.ManagedConnectionFactoryImpl;

public class PersonPersist
{
  private final static int SIZE         = 3;
  private PersistenceManagerFactory pmf = null;
  private PersistenceManager pm         = null;
  private Transaction transaction       = null;

// Array of people to persist
  private Person[] people;
// Vector of current object identifiers
  private Vector id = new Vector(SIZE); 

  public PersonPersist() {
   try {
    Properties props = new Properties();
         
props.setProperty("javax.jdo.PersistenceManagerFactoryClass", 
"com.prismt.j2ee.jdo.PersistenceManagerFactoryImpl");
    pmf = JDOHelper.getPersistenceManagerFactory(props);
    pmf.setConnectionFactory( createConnectionFactory() );
   } catch(Exception ex) {
    ex.printStackTrace();
    System.exit(1);
   }
}
Connection Factory는 static 메소드인 CreateConnectionFactory()를 사용하여 얻어낸다. 이 Factory는 JDBC URL, JDBC 드라이버, 사용자이름, 비밀번호가 필요하다. OpenFusion JDO는 사용할 데이터베이스의 드라이버, URL, 사용자 이름, 비밀번호 등 관련 정보를 파일에서 읽어오는 방식도 제공한다.
public static Object createConnectionFactory() {
  ManagedConnectionFactoryImpl mcfi = new   
ManagedConnectionFactoryImpl();
  Object connectionFactory = null;

  try {
    mcfi.setUserName("scott");
    mcfi.setPassword("tiger");
    mcfi.setConnectionURL(
         "jdbc:oracle:thin:@localhost:1521:thedb");

    mcfi.setDBDriver("oracle.jdbc.driver.OracleDriver");

	 connectionFactory = mcfi.createConnectionFactory();
  } catch(Exception e) {
    e.printStackTrace();
	 System.exit(1);
  }
  return connectionFactory;
}
자바 프로그래밍 실전 테크닉 300
단계 2: 세 개의 Person객체를 저장

지금부터는 실제적인 작업이다. PersistPeople메소드는 Person.java에 정의된 생성자를 사용하여 세 개의 Person 객체를 생성하고 나서 JDO가 작업하는 것을 보게 된다. 우리의 첫 번째 작업은 getPersistenceManager() 메소드를 통해 Persistence Manager를 얻는 것이다. 그 후 우리의 작업을 수행할 트랜잭션 객체를 생성한다. Person 객체를 저장하기 위해서 우리는 단순히 makePersistentAll ( Object[] )만 호출하면 된다. 마지막 코드의 for () 루프는 저장된 객체의 식별값을 얻어 후속 작업에 사용할 수 있도록 저장하는 작업을 수행하고 있다.
public void persistPeople() {
  // create an array of Person"s
  people = new Person[SIZE];

  // create three people
  people[0] = new Person("Gary Segal", "123 Foobar Lane", 
              "123-123-1234", "gary@segal.com", 
              "(608) 294-0192", "(608) 029-4059");
  people[1] = new Person("Michael Owen", 
              "222 Bazza Lane, Liverpool, MN", 
              "111-222-3333", "michael@owen.com", 
              "(720) 111-2222", "(303) 222-3333");
  people[2] = new Person("Roy Keane", 
              "222 Trafford Ave, Manchester, MN", 
              "234-235-3830", "roy@keane.com", 
              "(720) 940-9049", "(303) 309-7599)");

  // persist the array of people
  pm = pmf.getPersistenceManager();
  transaction = pm.currentTransaction();
  pm.makePersistentAll(people);
  transaction.commit();

  // retrieve the object ids for the persisted objects
  for(int i = 0; i < people.length; i++) {
    id.add(pm.getObjectId(people[i]));
  }

  // close current persistence manager to ensure that 
  // objects are read from the db not the persistence 
  // manager"s memory cache.
  pm.close();
}
Persistence Manager가 호출할 수 있는 몇 가지 메소드들이 있다. 이러한 메소드는 아래와 같이 세 가지로 분류할 수 있다.
  • 객체를 저장하는 메소드. 메모리 상의 객체를 저장
  • 저장된 객체를 삭제하는 메소드. 데이터 저장소에 저장된 정보를 삭제
  • 객체와 저장소간의 연결을 끊는 메소드. Persistence Manager와 객체를 분리. 데이터 저장소는 객체정보를 가지고 있지 않다.
객체를 저장하는 메소드
저장된 객체를 삭제하는 메소드
객체와 저장소간의 연결을 끊는 메소드
makePersistent(Object o) deletePersistent(Object o) makeTransient(Object o)
makePersistentAll(Object[] os) deletePersistentAll(Object[] os) makeTransientAll(Object[] os)
makePersistentAll(Collection os) deletePersistentAll(Collection os) makeTransientAll(Collection os)

단계 3: 데이터베이스로부터 "Person" 객체 정보를 읽어 보여주는 작업

모든 메소드들이 그렇듯이 보여주는 작업을 위한 코드도 Persistence Manager를 얻어내는 것으로 시작한다. 저장된 객체를 얻어오기 위해 PersistPeople() 메소드의 마지막 부분에서 저장된 객체 식별자를 사용한다. 객체를 얻어 내면 그 객체가 가진 메소드를 호출할 수 있다. 이 경우에는 데이터를 가져오는 get 메소드가 해당된다. 이 때 객체를 저장하기 위해 많은 코드가 필요한 것이 아님을 알 수 있다.
public void display(int end) {
  Person person;
  int max = end <= SIZE ? end : SIZE;

  // get a new persistence manager
  pm = pmf.getPersistenceManager();
  // retrieve objects from datastore and display
  for(int i = 0; i < max; i++) {
	 person = (Person) pm.getObjectById(id.elementAt(i), 
                                       false);
	 System.out.println("Name      : " + person.getName());
	 System.out.println("Address   : " + 
                                    person.getAddress());
	 System.out.println("SSN       : " + person.getSsn());
	 System.out.println("Email     : " + person.getEmail());
	 System.out.println("Home Phone: " + 
                                    person.getHomePhone());
	 System.out.println("Work Phone: " + 
                                    person.getWorkPhone());
  }
  pm.close();
}
단계 4: 저장된 Person 객체의 이름을 변경

데이터스토어에 저장된 Person 객체 정보를 변경하는 코드도 역시 단순하다. 저장된 "Person" 객체의 정보를 보여 주는 코드와 매우 유사하다. 여기서도 우리는 Row를 변경할 것이므로 트랜잭션 객체를 생성하여 우리가 정의한 setName() 메소드를 사용하여 이름을 변경하고 마지막으로 변경사항을 반영하기 위해서 트랜잭션을 커밋(Commit)한다. 이 메소드와 일반 객체 작업의 실제적인 차이는 트랙잭션을 생각한다는 것 뿐이다.
public void change() {
  Person person;

  // retrieve objects from datastore
  pm = pmf.getPersistenceManager();
  transaction = pm.currentTransaction();
  // change DataString field of the second persisted object
  person = (Person) pm.getObjectById(id.elementAt(1), 
                                     false);
  person.setName("Steve Gerrard");
  // commit the change and close the persistence manager
  transaction.commit();
  pm.close();
}
단계 5: Person 객체의 삭제

데이터스토어에 저장된 두 번째 Person객체정보를 삭제하는 코드를 작성할 수 있겠는가? 이미 모든 정보는 알고 있다. 아래의 코드를 보면 단계2의 Persistence Manager의 메소드들에 언급된 deletePersistent() 메소드를 사용하고 있는 것을 알 수 있다.
public void delete() {
  // retrieve objects from datastore
  pm = pmf.getPersistenceManager();
  transaction = pm.currentTransaction();
  // delete the 2nd persisted object from the datastore and
  // its id from Vector id.
  pm.deletePersistent(pm.getObjectById(id.remove(1), 
                      false));
  // commit the change and close the persistence manager
  transaction.commit();
  pm.close();
}
단계 6: main() 메소드에서 이전 단계에서 정의한 메소드를 호출한다.

마지막으로 실행되는 main() 메소드를 정의한다. main() 메소드는 Person객체를 저장하고 그 중 한 객체를 변경한 후 한 객체를 삭제한다. 이 프로그램을 실행하면 각 단계마다 주소록의 변화되는 정보를 보여줄 것이다.
public static void main(String[] args) {
  System.out.println("Create PersonPersist");
  PersonPersist personPersist = new PersonPersist();

  System.out.println("Setup and persist a group of people");
  personPersist.persistPeople();

  System.out.println("Display the persisted people");
  personPersist.display(SIZE);

  System.out.println("Change a name ");
  personPersist.change();
  personPersist.display(SIZE);

  System.out.println("Delete a person ");
  personPersist.delete();
  personPersist.display(SIZE - 1);
}
JDOEnhancer: JDOEnhancer를 위한 JDO Descriptor를 만든다

이제 우리는 애플리케이션 코드는 모두 작성하였다. 다음 작업은 JDOEnhancer가 사용할 JDO Descriptor를 만드는 것이다. 도대체 JDOEnhancer가 뭔데?"라는 비명 소리가 들린다. JDO 아키텍처는 JDO 구현 클래스들이 저장객체 클래스의 바이트 코드를 수정하여 필요한 기능을 추가할 수 있다는 개념을 기반으로 구성되었다. 예를 들어 JDOEnhancer는 클래스가 PersistanceCapable 인터페이스를 구현하도록 할 수 있고(그래서 직접 구현하지 않아도 됨) 인터페이스의 메소드들을 구현하도록 할 수 있다. 우리는 작성한 파일을 컴파일 한 후 바이트코드의 변경을 위해 JDOEnhancer를 실행해야 한다(바로 이 점이 Thought.Inc가 JDO 바이트코드를 수정하는 것을 좋아하지 않는 이유). 우리는 저장하고자 하는 클래스에 대한 정보를 제공하는 Descriptor파일을 만들어야 한다. 다음과 같은 형태가 될 것이다.



   
      
      
    

이것은 어쨌든 간에 우리가 필요한 부분은 충족하고 있으므로 비교적 기본적(간단한) 파일이라고 볼 수 있다. 더욱 복잡한 매핑도 할 수 있는데 더 자세한 사항을 알고 샆더묜 JDO 스팩 Section18 : XML Metadata을 참조하는 것이 좋을 것이다. OpenFusion 예제에서 제공하는 약간 더 복잡한 파일 예제도 있다.










이제 자바 파일과 JDO Descriptor가 완성되었으니, 이것을 사용하여 시스템 구축하는 법을 살펴보자.

Build: 시스템의 단계별 생성 및 실행

우리가 작성한 시스템을 Build하기 위해 거쳐야 할 몇 가지 단계는 다음과 같다.
  1. 자바 파일 컴파일
  2. JDOEnhancer 실행
  3. JDOEnhancer의 결과를 사용할 데이터베이스 설정
  4. 애플리케이션 실행
단계 1: 자바 파일의 컴파일

자바 컴파일러 javac의 사용법은 모두 알고 있을 것이다. 컴파일 전에 CLASSPATH가 올바르게 설정되었는지 확인하자. 윈도우에서 예제는 다음과 같다.
% set OPENFUSION_DIR=D:\Apps\OpenFusionJDO
% set 
CLASSPATH=%OPENFUSION_DIR%\lib\connector.jar;%OPENFUSION_DIR%\
lib\jndi.jar;%OPENFUSION_DIR%\lib\log4j.jar;%OPENFUSION_DIR%\l
ib\xerces.jar;%OPENFUSION_DIR%\lib\classes12.zip;%OPENFUSION_D
IR%\lib\jdo.jar;%OPENFUSION_DIR%\lib\jta-
spec1_0_1.jar;%OPENFUSION_DIR%\lib\ofjdo.jar;.

% javac -d . Person*.java
단계 2 : JDOEnhancer를 실행

JDOEnhancer를 실행하려면 작성한 자바파일을 컴파일한 클래스와 앞에서 작성했던 JDO Descriptor파일이 필요하다. OpenFusion JDOEnhancer를 실행하는 문법은 아래와 같다.
java com.prismt.j2ee.jdo.enhancer.JDOEnhancer

Mandatory Options:
-cp	base directory to begin searching for classes to be 
         enhanced. This is not the CLASSPATH, just where our 
         compiled persistent classes are
-oc	directory to place the enhanced classes
-pd	JDO descriptor file(s)

Optional:
-db	specific target database [oracle, sybase, etc]	
-od	directory to generate SQL scripts to
우리가 작성한 애플리케이션을 위한 JDOEnhancer의 실행 예제는 아래와 같다.
% java com.prismt.j2ee.jdo.enhancer.JDOEnhancer -oc . -pd 
person.jdo -db oracle -od db -cp .
단계 3 : JDOEnhancer에서의 결과를 위한 데이터베이스 설정

JDOEnhancer는 -db, -od 옵션을 사용하여 데이터베이스 설정을 위한 데이터베이스 스크립트를 작성할 수 있다. JDOEnhancer을 사용하여 데이터베이스 스크립트를 생성하면 여러 개의 파일이 만들어 질 것이다. 그 중에 load_all.sql이라는 파일 있다. 이 파일을 열어 당신이 즐겨 쓰는 SQL 프롬프트(예를 들면 오라클을 위한 sqlplus)에 로드하여 실행시켜라.

우리가 만들 애플리케이션을 위한 오라클 버젼의 파일을 살펴보자.
CREATE SEQUENCE instid_seq INCREMENT BY 1
;

CREATE TABLE JDO_addressbook_Person_SCO
(
    inst_id INTEGER NOT NULL,
    class INTEGER NOT NULL,
    JDO_address VARCHAR2(255),
    JDO_email VARCHAR2(255),
    JDO_homePhone VARCHAR2(255),
    JDO_name VARCHAR2(255),
    JDO_ssn VARCHAR2(255),
    JDO_workPhone VARCHAR2(255)
)
;
CREATE TABLE JDO_addressbook_Person
(
    inst_id INTEGER NOT NULL,
    class INTEGER NOT NULL,
    JDO_address VARCHAR2(255),
    JDO_email VARCHAR2(255),
    JDO_homePhone VARCHAR2(255),
    JDO_name VARCHAR2(255),
    JDO_ssn VARCHAR2(255),
    JDO_workPhone VARCHAR2(255)
)
;
CREATE  TABLE prismjdoProp
(
    name VARCHAR2(255) PRIMARY KEY,
    value VARCHAR2(255)
)
;
CREATE TABLE prismjdoExtents
(
    class_id NUMBER(38,0) PRIMARY KEY,
    class_name VARCHAR2(255) UNIQUE,
    app_key VARCHAR2(255)
)
;
ALTER TABLE JDO_addressbook_Person_SCO ADD PRIMARY KEY 
(inst_id, class)
;
ALTER TABLE JDO_addressbook_Person ADD PRIMARY KEY (inst_id, 
class)
;

INSERT INTO prismjdoExtents VALUES(0, "addressbook.Person", 
"com.prismt.j2ee.jdo.spi.DBKey")
;
COMMIT WORK
;

INSERT INTO prismjdoProp VALUES("USE.RDBMS.TRIGGERS", "true")
;
COMMIT WORK
;
단계 4: 애플리케이션의 실행

이제 데이터베이스가 설정도 끝났으니 애플리케이션을 실행하여 결과를 살펴보자.
% java addressbook.PersonPersist
프로그램 소스를 복사하여 직접 시도해 보자!

결론

우리는 OpenFusion JDO를 사용하여 새로운 JDO 표준이 어떻게 사용되어지는지 살펴보았다. 이것은 개발자들이 굳이 SQL 전문가가 아니라고 하더라도 객체를 사용하여 비즈니스 필요 사항에 더욱 집중할 수 있게 하는 새로운 장을 열었다. 업계 일부 사람들은 JDO스팩이 아직 온전하지 않다고 생각하지만 필자는 JDO가 널리 사용되기를 희망하는 바이다.

Dion Almaer는 EJB/J2EE와 B2B 기술의 교육 분야의 선도 기업이며 TheServerSide.com의 J2EE의 커뮤니티를 후원하고 있는 미들웨어 회사(www.middleware-company.com)의 책임 기술자이다.
TAG :
댓글 입력
자료실