기술자료

DBMS, DB 구축 절차, 빅데이터 기술 칼럼, 사례연구 및 세미나 자료를 소개합니다.

Data Access Objects를 사용하여 DB2 또는 MySQL 데이터베이스에 있는 데이터에 액세스 하기

작성자
dataonair
작성일
2000-01-01 00:00
조회
550






Data Access Objects를 사용하여 DB2 또는 MySQL 데이터베이스에 있는 데이터에 액세스 하기

웹 서비스를 사용하여 서비스 지향 아키텍처에서 자바 애플리케이션 클라이언트에 사용할 수 있는 데이터 만들기

060919_data_01.gif

Peter Wansch _ Software Engineer, IBM

IBM Rational Application Developer version 6.0을 사용하여 IBM DB2나 MySQL 데이터베이스에 데이터를 저장하는 트레이닝 관리 웹 서비스를 만드는 방법을 설명합니다. 이 웹 서비스를 기존 IBM WebSphere application server (version 6.0)에 저장하고, DB2와 MySQL 데이터베이스에 대한 JDBC 데이터 액세스를 설정하고, Apache Axis를 SOAP 프로세서처럼 사용하는 독립형 자바 웹 서비스 클라이언트를 만드는 방법도 설명합니다.

머리말

Data access objects (DAO)는 코어 Java™ 2, Enterprise Edition (J2EE™) 플랫폼 패턴이다. 엔터프라이즈 정보 티어에 저장된 데이터에 액세스 하는 엔터프라이즈 애플리케이션을 위한 추상 레이어를 구현한다. 그와 같은 정보 티어에 액세스 하는 방법은 스토리지 유형(관계형 데이터베이스, 객체 지향 데이터베이스, 플랫 파일 등)과 벤더 구현에 따라 매우 다양하다. SQL과 JDBC™ (Java Database Connectivity)를 사용하여 IBM?? DB2??, IDS, Derby, SQL Server, Oracle, MySQL 같은 관계형 데이터베이스에 액세스만 할 것이라도 데이터 소스에 대한 모든 액세스를 캡슐화 하는 것은 매우 유용한 패턴이다. 이는 미묘한 벤더들간 차이를 수용할 뿐만 아니라 코드의 관리성과 품질을 향상시킨다. 패턴에 대한 자세한 내용은 참고자료 섹션을 참조하라.

이 글에서는 IBM?? Rational?? Application Developer version 6.0 (이후 IRAD)를 사용하여 DAO를 사용하는 트레이닝 관리 웹 서비스를 생성 및 전개하는 방법을 설명하겠다. 이 웹 서비스를 사용하여 사용자들은 코스, 학생, 등록을 만들고 추가할 수 있다. 또한 Apache 액세스를 SOAP 프로세서로서 사용하는 자바 애플리케이션 클라이언트를 구현하는 방법도 설명한다. 이 애플리케이션에는 모든 학생들과 코스가 들어있고 등록을 처리하게 된다.

이 글의 첫 번째 부분은 DB2나 MySQL 데이터베이스에 저장된 데이터에 액세스 하기 위해 JDBC를 사용하는 SQL 문을 캡슐화 하여 DAO를 구현하는 방법을 설명한다.

두 번째 부분에서는 클라이언트가 코스, 학생, 등록을 볼 수 있도록 하는 웹 서비스를 구현하는 방법을 설명한다. 이 웹 서비스는 Java Transaction API (JTA)의 범위 안에서 DAO를 사용하여 데이터베이스에 저장된 데이터에 액세스 한다.

세 번째 부분에서는 Apache Axis를 사용하여 Java™ 2, Server Edition (J2SE™) 클라이언트를 구현하는 방법을 설명한다. J2SE 클라이언트는 단순한 웹 클라이언트와 비교할 때 보다 복잡한 엔드 유저 태스크를 단순화 하는 풍부한 GUI를 제공한다.

사전 조건

다음의 소프트웨어가 설치되어야 한다.

IRAD. IBM?? Rational?? Product Updater를 사용하여 version 6.0.1.1로 업데이트 하고 모든 임시 픽스에 적용한다.
IBM?? WebSphere?? Application Server Version 6.0. version 6.0.2.5이나 그 이후 버전으로 업데이트 한다.
IBM?? Rational?? Agent Controller. *
IBM DB2 Version 8.1(Fixpak 7 이상), 또는 MySQL 4.1.13(또는 그 이상), MySQL Connector/J 3.1.11(또는 그 이상)
Apache Axis 1.3.
SunSM JavaBeans™ Activation Framework 1.0.2.
SunSM JavaMail™ 1.3.3_01. **

다운로드 링크는 참고자료 섹션을 참조하라.

주:
* 에이전트 컨트롤러는 네트워크 전개에만 필요하다. ** JavaMail이 필요하다. SOAP with Attachments를 사용할 경우에만 필요하다. 하지만 앞으로 웹 서비스에 SOAP with Attachments를 사용하려면 설치하는 것이 좋다.

WebSphere 애플리케이션 서버 인스턴스 server1과 DB2 또는 MySQL이 시작했는지를 확인하라. 이 글은 여러분이 IRAD를 사용하는 컴퓨터에 WebSphere 애플리케이션 서버를 실행하는 것으로 가정한다.

Data Access Objects 구현하기

전송 객체 구현하기

다음은 시스템 준비 과정이다

1. IRAD에서 Window > Open Perspective > Web을 선택하여 웹 퍼스펙티브로 전환한다.
2. File > New > Dynamic Web Project를 선택하고 프로젝트 이름을 TAWebService로 입력한다.
3. Show Advanced를 클릭하고 서블릿 버전에 2.4를, Target server에 WebSphere Application Server v6.0을 선택한다.
여러분이 유일하게 선택할 수 있는 것이 이라면 Target server 드롭다운 리스트 옆에 있는 New를 클릭하여 새로운 서버 런타임을 정의한다.

서버 런타임을 정의한 후에 IRAD에서 서버를 정의해야 한다.

4. File > New > Other > Server > Server를 선택한다.
5. 호스트 네임으로 localhost를 입력하고 서버 유형으로 WebSphere v6.0 Server를 선택한다. Next를 클릭한다.
6. 모든 설정이 server1 인스턴스의 세팅과 매치하는지를 확인하고(그림 1) Finish를 클릭한다.

060919_data_02.gif
그림 1. 새로운 서버 런타임 정의하기

7. TAWebServiceEAR이라는 EAR 프로젝트가 추가되었는지, 콘텍스트 루트가 TAWebService로 설정되었는지 확인한다. (그림 2) Next를 클릭한다.

060919_data_03.gif
그림 2. 새로운 동적 웹 프로젝트 생성하기

8. 모든 Web Project 기능들을 지우고 Finish를 클릭한다. 웹 퍼스펙티브가 열릴 것이다.

우선, 코스, 학생, 등록에 대한 전송 객체를 만든다. 전송 객체들은 웹 서비스 메소드 호출에 대한 매개변수로서 사용되는 단순한 빈으로서 데이터를 클라이언트에 리턴하거나 클라이언트에서 데이터를 받는다. 다음은 빈의 최소 조건들이다.

공용 무인자 구조체(public no-argument constructor)를 제공한다.

java.io.Serializable을 구현한다.

속성을 위해 set/get 메소드를 요청한다.

쓰레드 보안이 된다.

다음은 전송 객체를 만드는 과정이다.

1. Project Explorer에서 Web Projects, TAWebService, Java Resources를 확장하여 JavaSource 폴더를 선택한다.
2. 오른쪽을 클릭하여 New > Class를 선택한다.
3. 패키지 이름으로 com.ibm.ta.webservice를 클래스 이름으로 Course를 입력한다.
4. java.io.Serializable을 인터페이스로 추가하고 모든 메소드 스텁들을 지운다. (그림 3)
5. Finish를 클릭한다.

060919_data_04.gif
그림 3. 새로운 클래스, Course 생성하기

6. 인스턴스 변수 id와 name을 추가하고 이들을 구조체에서 초기화 한다. (그림 5)
7. Source Code Editor 윈도우를 오른쪽 클릭하고 Source > Generate Setters and Getters를 선택한다.
8. 그림 4처럼 모든 것을 선택한다.
9. Click OK를 클릭한다.

060919_data_05.gif
그림 4. Course용 게터와 세터 생성하기

결과 소스 코드는 Listing 1과 같다

Listing 1: Course.java

package com.ibm.ta.webservice;
import java.io.Serializable;

public class Course implements Serializable {

private long id;
private String name;
public Course() {
this.id = 0;
this.name = null;
}

public Course(long id, String name) {
this.id = id;
this.name = name;
}

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

위 과정을 Student (Listing 2)와 Enrollment (Listing 3)에도 적용한다.

Listing 2: Student.java

package com.ibm.ta.webservice;

import java.io.Serializable;

public class Student implements Serializable {

private long id;
private String name;

public Student() {
this.id = 0;
this.name = null;
}

public Student(long id, String name) {
this.id = id;
this.name = name;
}

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

Listing 3: Enrollment.java

package com.ibm.ta.webservice;

import java.io.Serializable;

public class Enrollment implements Serializable {
private long studentID;
private long courseID;
public Enrollment() {
this.studentID = 0;
this.courseID = 0;
}

public Enrollment(long studentID, long courseID) {
this.studentID = studentID;
this.courseID = courseID;
}

public long getCourseID() {
return courseID;
}

public void setCourseID(long courseID) {
this.courseID = courseID;
}

public long getStudentID() {
return studentID;
}

public void setStudentID(long studentID) {
this.studentID = studentID;
}
}

DAO 인터페이스 만들기

DAO 인터페이스는 영속 객체인 Courses, Student, Enrollments에 대해 DAO 메소드를 정의한다. 이들은 각 지원 데이터베이스에 MySQLCourseDAO나 DB2CourseDAO 처럼 구체적 DAO 구현으로서 만들어진다.

구체적 DAO 구현은 각각의 데이터베이스에 액세스 할 때(예를 들어, 데이터베이스에 연결할 수 없을 때) 시스템 예외가 생긴다. 이들은 매개변수가 특정 기준에 부합되지 못하면 애플리케이션 스팩의 예외를 던진다. 예를 들어, 매개변수는 한계를 벗어나거나, 기존 데이터를 중복시키거나, 불완전 할 수 있다.

DAO 인터페이스는 데이터 소스 스팩의 예외를 콜러(caller)에게 전달해서는 안된다. 대신 이들을 의미가 있고, 데이터 소스와 독립적인, 애플리케이션 스팩의 예외로서 다시 발생되어야 한다. 이 예제에서 DAO 구현이 회복할 수 없는 시스템 에러를 만나면 이들은 DAOException을 던진다.

1. 새로운 패키지 com.ibm.ta.dao에 있는 예외를 확장한 DAOException 클래스를 만든다. (Listing 4)

Listing 4: DAOException.java

package com.ibm.ta.dao;
public class DAOException extends Exception {
public DAOException(String message, Throwable cause) {
super(message, cause);
}

public DAOException(String message) {
super(message);
}
}

2. 무효 매개변수가 DAO 메소드에 전달되면 인터페이스는 DAOParameterException 이라고 하는 특별한 예외를 던진다. 이것은 DAOException을 하위 클래스로 만든다. (Listing 5)

Listing 5: DAOParameterException.java

package com.ibm.ta.dao;
public class DAOParameterException extends DAOException {
public DAOParameterException(String message, Throwable cause) {
super(message, cause);
}

public DAOParameterException(String message) {
super(message);
}
}

이제 DAO 인터페이스를 만들어야 한다.

3. com.ibm.ta.dao 패키지를 오른쪽 클릭하고 New > Interface를 선택한다.
4. 인터페이스 이름으로 CourseDAO를 입력하고 Finish를 클릭한다.
5. 필요한 메소드를 추가한다. (Listing 6)

Listing 6: CourseDAO.java

package com.ibm.ta.dao;
import com.ibm.ta.webservice.Course;
public interface CourseDAO {
public Course[] selectCourses() throws DAOException;
public void insertCourse(String name) throws DAOException;
}

이 과정을 StudentDAO (Listing 7)와 EnrollmentDAO (Listing 8)에도 반복한다.

Listing 7: StudentDAO.java

package com.ibm.ta.dao;
import com.ibm.ta.webservice.Student;
public interface StudentDAO {
public Student[] selectStudents() throws DAOException;
public void insertStudent(String name) throws DAOException;
}

Listing 8: EnrollmentDAO.java

package com.ibm.ta.dao;
import com.ibm.ta.webservice.Student;
import com.ibm.ta.webservice.Course;

public interface EnrollmentDAO {
public void insertEnrollment(long studentID, long courseID)
throws DAOException;
public Student[] getEnrolledStudents(long courseID) throws DAOException;
public Course[] getCourseEnrollments(long studentID) throws DAOException;
}

MySQL과 DB2를 위한 구체적 DAO 구현을 만들기 전에 각각의 데이터베이스를 만들어야 한다.

트레이닝 관리 데이터베이스 만들기

이 섹션에서는 명령행 인터페이스를 사용하여 DB2나 MySQL 서버 상에 트레이닝 관리 데이터베이스를 만드는 방법을 설명한다. 다운로드 Zip 파일에는 TAWebService\sql 디렉토리에 있는 스크립트가 포함되어 있다.

DB2 데이터베이스 서버를 사용하여 트레이닝 관리 데이터베이스 만들기

1. 명령행에 db2cmd를 입력하여 DB2 명령어 윈도우를 연다.
2. TAWebService 디렉토리로 변경하고 다음을 입력한다: mkdir sql
3. sql 디렉토리를 변경하고 노트패드를 사용하여 createdb.db2라고 하는 파일을 만든다. (Listing 9)
4. db2 -tf createdb.db2를 입력하여 DB2 데이터베이스를 만든다.

데이터베이스가 성공적으로 구현되었다면 다음과 같은 아웃풋을 볼 수 있다.

C:\source\TAWebService\sql>db2 -tf createdb.db2
SQL1013N The database alias name or database name "TA " could not be
found. SQLSTATE=42705

DB20000I The CREATE DATABASE command completed successfully.
Database Connection Information
Database server = DB2/NT 8.2.0
SQL authorization ID = PETER
Local database alias = TA

DB20000I The SQL command completed successfully.
DB20000I The SQL command completed successfully.
DB20000I The SQL command completed successfully.
DB20000I The SQL command completed successfully.
DB20000I The SQL command completed successfully.
DB20000I The SQL command completed successfully.
DB20000I The SQL command completed successfully.

Listing 9: createdb.db2

DROP DATABASE ta;
CREATE DATABASE ta;
CONNECT TO ta;
SET SCHEMA ta;
CREATE TABLE course
(
course_id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START
WITH 0, INCREMENT BY 1, NO CACHE),
course_name VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (course_id)
);

CREATE TABLE student
(
student_id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START
WITH 0, INCREMENT BY 1, NO CACHE),
student_name VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (student_id)
);

CREATE TABLE enrollment
(
course_id BIGINT NOT NULL,
student_id BIGINT NOT NULL,
PRIMARY KEY (course_id, student_id),
FOREIGN KEY (course_id) REFERENCES course(course_id)
ON DELETE RESTRICT,
FOREIGN KEY (student_id) REFERENCES student(student_id)
ON DELETE RESTRICT
);

INSERT INTO course (course_name) VALUES ('Java Programming in the
Sun');
INSERT INTO student (student_name) VALUES ('Peter Wansch');
CONNECT RESET;

MySQL 데이터베이스 서버를 사용하여 트레이닝 관리 데이터베이스 만들기

1. 명령행에서 sql 디렉토리로 변경하고 노트패드를 사용하여 createdb.mysql 파일을 만든다. (Listing 10) 이 스크립트도 "password"와 함께 "ta"라는 새로운 MySQL 사용자를 만든다.
2. mysql -h localhost -u root -p < createdb.mysql을 입력하여 MySQL 데이터베이스를 만든다.

MySQL 데이터베이스가 성공적으로 구현되었다면 다음과 같은 아웃풋을 볼 수 있다:

C:\source\TAWebService\sql>mysql -h localhost -u root -p < createdb.mysql
Enter password: ********

Listing 10: createdb.mysql

DROP DATABASE IF EXISTS ta;

CREATE DATABASE ta;
GRANT ALL PRIVILEGES ON ta.* TO ta@localhost IDENTIFIED BY 'password';
USE ta;

CREATE TABLE course
(
course_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
course_name VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (course_id)
);

CREATE TABLE student
(
student_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
student_name VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (student_id)
);

CREATE TABLE enrollment
(
course_id BIGINT UNSIGNED NOT NULL,
student_id BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (course_id, student_id),
FOREIGN KEY (course_id) REFERENCES course(course_id)
ON DELETE RESTRICT,
FOREIGN KEY (student_id) REFERENCES student(student_id)
ON DELETE RESTRICT
);

INSERT INTO course (course_name) VALUES ('Java Programming in the Sun');
INSERT INTO student (student_name) VALUES ('Peter Wansch');

DB2와 MySQL용 데이터 소스 정의하기

많은 코드를 작성하기 전에 웹 서비스를 전개할 애플리케이션 서버에서 데이터베이스에 액세스 할 수 있는지를 확인해야 한다.
브라우저에 http://localhost:9060/ibm/console/을 입력하여 WebSphere Administrative로 로그인 한다.

MySQL 데이터베이스로의 액세스 설정

Guided Activities를 확장하고 Connecting to a database를 선택한다. 마법사를 통해 다음 단계를 수행한다:
1. 안전한 데이터베이스 액세스를 위해 크리덴셜을 설정한다. 다음과 같은 속성을 사용하여 새로운 J2C 인증 앨리어스를 만든다.

  • Alias: mysqlta
  • User ID: ta
  • Password: password
  • Description: TA MySQL database user
2. JDBC 드라이버를 설정한다. 다음 속성을 사용하여 새로운 JDBC 프로바이더를 만든다:
  • Name: MySQL JDBC Provider
  • Description: MySQL JDBC Provider
  • Class path: ${MYSQL_JDBC_DRIVER_PATH}/mysql-connector-java-3.1.11-bin.jar
  • Implementation class name: com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource
3. WebSphere 변수를 설정한다. 다음 속성으로 새로운 WebSphere 변수를 만든다:
  • Name: MYSQL_JDBC_DRIVER_PATH
  • Value: C:\Program Files\MySQL\mysql-connector-java-3.1.11
4. MySQL JDBC Provider용 데이터 소스를 설정한다. MySQL JDBC Provider를 클릭한다. Additional Properties 밑에, link Data sources를 클릭한다. New를 선택하여 다음과 같은 매개변수를 가진 새로운 데이터 소스를 만든다:
  • Name: MySQLTA
  • JNDI name: jdbc/mysqlta
  • Use this data source in container managed persistence (CMP) 선택
  • Description: MySQL TA JDBC Datasource
  • 데이터 소스 헬퍼(com.ibm.websphere.rsadapter.GenericDataStoreHelper) 가 선택되었는지를 확인한다.
  • Component-managed authentication alias: mysqlta
  • Authentication alias for XA recovery 밑에 Select Use component-managed authentication alias가 선택되었는지를 확인한다.
5. OK를 클릭하여 데이터 소스를 저장하여 추가 속성들을 편집할 수 있도록 한다. Additional Properties 밑에, 다음과 같은 Connection 풀 매개변수들이 설정되었는지를 확인한다.
  • Connection timeout: 180
  • Reap time: 180
  • Unused timeout: 1800
  • Aged timeout: 3600
  • Purge policy: FailingConnectionOnly
6. Custom Properties를 추가한다:
  • databaseName=ta
  • port=3306
  • serverName=localhost
주: Aged timeout을 3600 초 (1 시간)으로 설정하는 것이 중요하다. MySQL 서버는 지정된 비활성 기간 후에 연결을 닫기 때문이다. (MySQL 서버 타임아웃 설정 참조)
설정을 저장하고 Test Connection을 클릭한다. 데이터 소스를 정확히 설정했다면 연결이 성공적으로 이루어질 것이다. 서버를 재시작 할 필요는 없다.

DB2 데이터베이스로 액세스 설정하기

다음 과정을 반복하여 DB2 데이터 소스를 설정한다.
1. 안전한 데이터베이스 액세스를 위해 크리덴셜을 설정한다. 다음과 같은 속성을 사용하Alias: db2ta

User ID: db2admin
Password: < password >
Description: TA DB2 database user 2. JDBC 드라이버를 설정한다. 다음 속성을 사용하여 새로운 JDBC 프로바이더를 만든다:
  • Database type: DB2
  • Provider type: DB2 Universal JDBC Provider
  • Implementation type: XA data source
3. WebSphere 변수를 설정한다. 다음 속성으로 새로운 WebSphere 변수를 만든다:
  • DB2UNIVERSAL_JDBC_DRIVER_PATH = C:\Program Files\IBM\SQLLIB\java
  • DB2UNIVERSAL_JDBC_DRIVER_NATIVEPATH = C:\Program Files\IBM\SQLLIB\BIN
4. DB2 JDBC Provider용 데이터 소스를 설정한다. DB2 JDBC Provider를 클릭한다. Additional Properties 밑에, link Data sources를 클릭한다.
  • New를 선택하여 다음과 같은 매개변수를 가진 새로운 데이터 소스를 만든다:
  • name=DB2TA
  • jndiName=jdbc/db2ta
  • description=DB2 TA JDBC Datasource
  • 데이터 소스 헬퍼 클래스로서 DB2 Universal data store helper를 선택한다.
  • 컴포넌트 관리형 인증 앨리어스로서 db2ta를 설정한다.
  • Database name=ta
  • Driver type=2
5. OK를 클릭하여 데이터 소스를 저장하여 추가 속성들을 편집할 수 있도록 한다.
6. 다음과 같은 Custom Properties를 추가한다:
  • currentSchema=ta
  • Server 이름과 Port 넘버에 있는 엔트리를 삭제하여 빈 공간으로 만든다.
설정을 저장하고 Test Connection을 클릭한다. 데이터 소스를 정확히 설정했다면 연결이 성공적으로 이루어질 것이다. 서버를 재시작 할 필요는 없다.

Data Access Objects Strategy용 팩토리

데이터베이스 설정을 캡슐화 하려면 com.ibm.ta.webservice 패키지에 유틸리티 클래스 DBConfig를 만든다. (Listing 11) 이 데이터베이스 설정에는 실제 데이터소스 JNDI 이름이 있는 웹 컴포넌트 전개 디스크립터와 연결된 리소스 레퍼런스가 포함되어 있다. 따라서 웹 애플리케이션이 JNDI 이름을 하드 코딩 할 필요가 없고, 전개 시 웹 서비스가 각각 DB2나 MySQL에 대해 설정될 수 있다.

Listing 11: DBConfig.java

package com.ibm.ta.webservice;
public class DBConfig {
// List of supported databases

public static final int DB2 = 1;
public static final int MYSQL = 2;
private String resRef;
private int dbType;
public DBConfig(String resRef, int dbType) {
this.resRef = resRef;
this.dbType = dbType;
}

public int getDbType() {
return dbType;
}

public String getResRef() {
return resRef;
}
}

시스템 예외나 애플리케이션 스팩의 예외가 발생하면 웹 서비스는 TAServiceException이라고 하는 서비스 스팩의 예외를 리턴한다. TAServiceException 클래스를 java.io.Serializable을 구현하고 예외를 확장하는 com.ibm.ta.webservice 패키지에 추가한다. (Listing 12)

Listing 12: TAServiceException.java

package com.ibm.ta.webservice;
import java.io.Serializable;

public class TAServiceException extends Exception implements Serializable {
public TAServiceException(String message, Throwable cause) {
super(message, cause);
}

public TAServiceException(String message) {
super(message);
}
}

ServiceLocator 라고 하는 헬퍼 클래스를 만들어서 리소스 레퍼런스에서 데이터 소스를 가져와야 한다. 네임드 리소스를 찾으려면 J2EE 컴포넌트는 먼저 객체를 만들고 InitialContext에 있는 환경 네이밍 콘텍스트를 찾는다. (리소스 레퍼런스의 이름으로 잘린 java:comp/env 밑에 있다.) 퍼포먼스 때문에 초기 콘텍스트와 데이터 소스를 캐싱해야 한다. 또한 ServiceLocator를 com.ibm.ta.webservice 패키지에 추가한다. (Listing 13)

Listing 13: ServiceLocator.java

package com.ibm.ta.webservice;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import javax.transaction.UserTransaction;

public class ServiceLocator {
private InitialContext ic;
private Map cache;
private static ServiceLocator sl = null;
private ServiceLocator() throws TAServiceException {
cache = Collections.synchronizedMap(new HashMap());

try {
ic = new InitialContext();
} catch (NamingException ne) {
throw new TAServiceException(ne.getMessage(), ne);
}
}

static public void initializeInstance() throws TAServiceException {
if (sl == null) {
sl = new ServiceLocator();
}
}

static public ServiceLocator getInstance() {
return sl;
}

public DataSource getDataSource(String dataSourceName)
throws TAServiceException {
DataSource dataSource = null;

try {
if (cache.containsKey(dataSourceName)) {
dataSource = (DataSource) cache.get(dataSourceName);
} else {
dataSource = (DataSource) ic.lookup(dataSourceName);
cache.put(dataSourceName, dataSource);
}
} catch (NamingException ne) {
throw new TAServiceException(ne.getMessage(), ne);
}

return dataSource;
}
}

Abstract Factory와 Factory Method 패턴을 채택하여 DAO 패턴을 유연하게 만든다. DB2와 MySQL을 지원하기 위해 구체적 DAO 팩토리에서 상속 받고 구현된 추상 클래스인 DAOFactory 라고 하는 기본적인 DAO 팩토리를 만든다. 데이터베이스 설정에 기반하여 클라이언트는 DB2DAOFactory 같은 구체적 DAO 팩토리 구현을 가져와서, 이를 사용하여 특정 구현과 작동하는 구체적 DAO를 가져올 수 있다.

그림 5는 팩토리 메소드를 사용하는 DB2 DAO의 클래스 다이어그램이다.

060919_data_06.gif
그림 5. DB2 DAO의 클래스 다이어그램

com.ibm.ta.dao 패키지에 DAOFactory 추상 클래스를 만든다. (Listing 14)

Listing 14: DAOFactory.java

package com.ibm.ta.dao;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import com.ibm.ta.webservice.DBConfig;
import com.ibm.ta.webservice.ServiceLocator;
import com.ibm.ta.webservice.TAServiceException;
import com.ibm.ta.dao.db2.DB2DAOFactory; import com.ibm.ta.dao.mysql.MySQLDAOFactory;

public abstract class DAOFactory {
private DBConfig dbConfig;

public abstract CourseDAO getCourseDAO()

public abstract StudentDAO getStudentDAO();

public abstract EnrollmentDAO getEnrollmentDAO();

public static DAOFactory getDAOFactory(DBConfig dbConfig) {
switch (dbConfig.getDbType()) {
case DBConfig.DB2:
return new DB2DAOFactory(dbConfig);
case DBConfig.MYSQL:
return new MySQLDAOFactory(dbConfig);
default:
return null;
}
}
public DAOFactory(DBConfig dbConfig) {
this.dbConfig = dbConfig;
}

// Create database connections from the values in dbConfig
public Connection createConnection() throws DAOException {
try {
String str = "java:comp/env/" + dbConfig.getResRef();
DataSource dataSource = (DataSource) ServiceLocator.getInstance()
.getDataSource(str);
return dataSource.getConnection();
} catch (SQLException se) {
throw new DAOException(se.getMessage(), se);
} catch (TAServiceException tase) {
throw new DAOException(tase.getMessage(), tase);
}
}
}

이 클래스를 저장하면 다음과 같은 에러들이 Problems 뷰에 나타난다.

The import com.ibm.ta.dao.db2 cannot be resolved
The import com.ibm.ta.dao.mysql cannot be resolved
DB2DAOFactory cannot be resolved or is not a type
MySQLDAOFactory cannot be resolved or is not a type

DB2 (DB2DAOFactory, Listing 15)와 MySQL (MySQLDAOFactory, Listing 16)를 지원하는 구체적 DAO 팩토리를 만들면 이것은 사라진다. 클래스를 만들어 보자.

Listing 15: DB2DAOFactory.java

package com.ibm.ta.dao.db2;
import com.ibm.ta.dao.CourseDAO;
import com.ibm.ta.dao.DAOFactory;
import com.ibm.ta.dao.EnrollmentDAO;
import com.ibm.ta.dao.StudentDAO;
import com.ibm.ta.webservice.DBConfig;

public class DB2DAOFactory extends DAOFactory {
private DBConfig dbConfig;

public DB2DAOFactory(DBConfig dbConfig) {
super(dbConfig);
}

public CourseDAO getCourseDAO() {
return new DB2CourseDAO(this);
}

public StudentDAO getStudentDAO() {
return new DB2StudentDAO(this);
}

public EnrollmentDAO getEnrollmentDAO() {
return new DB2EnrollmentDAO(this);
}
}

Listing 16: MySQLDAOFactory.java

package com.ibm.ta.dao.mysql;
import com.ibm.ta.dao.CourseDAO;
import com.ibm.ta.dao.DAOFactory;
import com.ibm.ta.dao.EnrollmentDAO;
import com.ibm.ta.dao.StudentDAO;
import com.ibm.ta.webservice.DBConfig;

public class MySQLDAOFactory extends DAOFactory {
private DBConfig dbConfig;
public MySQLDAOFactory(DBConfig dbConfig) {
super(dbConfig);
}

public CourseDAO getCourseDAO() {
return new MySQLCourseDAO(this);
}

public StudentDAO getStudentDAO() {
return new MySQLStudentDAO(this);
}
public EnrollmentDAO getEnrollmentDAO() {
return new MySQLEnrollmentDAO(this);
}
}

그런 다음, 실제 JDBC 코드와 SQL 문을 포함하고 있는 다음과 같은 구체적 DAO 클래스를 구현하여 데이터베이스에 액세스 한다.

  • DB2CourseDAO (Listing 18)
  • DB2StudentDAO (Listing 19)
  • DB2EnrollmentDAO (Listing 20)
  • MySQLCourseDAO (Listing 21)
  • MySQLStudentDAO (Listing 22)
  • MySQLEnrollmentDAO (Listing 23)
먼저 com.ibm.ta.dao에 DAOUtil (Listing 17) 클래스를 만든다. 이 클래스에는 리소스를 릴리스 할 때 유용한 헬퍼 함수가 포함되어 있다.

Listing 17: DAOUtil.java

package com.ibm.ta.dao;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class DAOUtil {
public static void closeResources(ResultSet rs, Statement stmt1,
Statement stmt2, Connection con) {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
// Ignore
}

try {
if (stmt1 != null) {
stmt1.close();
}
} catch (SQLException e) {
// Ignore
}

try {
if (stmt2 != null) {
stmt2.close();
}
} catch (SQLException e) {
// Ignore
}

try {
if (con != null) {
con.close();
}
} catch (SQLException e) {
// Ignore
}
}
}

Listing18: DB2CourseDAO.java

package com.ibm.ta.dao.db2;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.ibm.ta.dao.CourseDAO;
import com.ibm.ta.dao.DAOException;
import com.ibm.ta.dao.DAOParameterException;
import com.ibm.ta.dao.DAOUtil;
import com.ibm.ta.webservice.Course;

public class DB2CourseDAO implements CourseDAO {
private DB2DAOFactory factory;
public DB2CourseDAO(DB2DAOFactory factory) {
this.factory = factory;
}

public Course[] selectCourses() throws DAOException {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
List results = new ArrayList();

try {
con = factory.createConnection();
ps = con.prepareStatement("SELECT course_id, course_name from ta.course");
rs = ps.executeQuery();
while (rs.next()) {
Course course = new Course(rs.getLong(1), rs.getString(2));
results.add(course);
}
} catch (SQLException sqle) {
throw new DAOException(sqle.getMessage(), sqle);
} finally {
DAOUtil.closeResources(rs, ps, null, con);
}
return (Course[]) results.toArray(new Course[results.size()]);
}

public void insertCourse(String name) throws DAOException,
DAOParameterException {
Connection con = null;
PreparedStatement ps = null;

// Check the parameter
if (name == null || name.trim().length() == 0) {
throw new DAOParameterException(
"Parameter course name is invalid or null");
}

try {
con = factory.createConnection();
ps = con.prepareStatement("INSERT INTO course (course_name) VALUES ()");
ps.setString(1, name);
ps.executeUpdate();
} catch (SQLException sqle) {
throw new DAOException(sqle.getMessage(), sqle);
} finally {
DAOUtil.closeResources(null, ps, null, con);
}
}
}

Listing 19: DB2StudentDAO.java

package com.ibm.ta.dao.db2;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.ibm.ta.dao.StudentDAO;
import com.ibm.ta.dao.DAOException;
import com.ibm.ta.dao.DAOParameterException;
import com.ibm.ta.dao.DAOUtil;
import com.ibm.ta.webservice.Student;

public class DB2StudentDAO implements StudentDAO {
private DB2DAOFactory factory;
public DB2StudentDAO(DB2DAOFactory factory) {
this.factory = factory;
}

public Student[] selectStudents() throws DAOException {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
List results = new ArrayList();

try {
con = factory.createConnection();
ps = con.prepareStatement("SELECT student_id, student_name from ta.STUDENT");
rs = ps.executeQuery();
while (rs.next()) {
Student student = new Student(rs.getLong(1), rs.getString(2));
results.add(student);
}
} catch (SQLException sqle) {
throw new DAOException(sqle.getMessage(), sqle);
} finally {
DAOUtil.closeResources(rs, ps, null, con);
}
return (Student[]) results.toArray(new Student[results.size()]);
}

public void insertStudent(String name) throws DAOException,
DAOParameterException {
Connection con = null;
PreparedStatement ps = null;

// Check the parameter
if (name == null || name.trim().length() == 0) {
throw new DAOParameterException(
"Parameter student name is invalid or null");
}

try {
con = factory.createConnection();
ps = con.prepareStatement("INSERT INTO student (student_name) VALUES ()");
ps.setString(1, name);
ps.executeUpdate();
} catch (SQLException sqle) {
throw new DAOException(sqle.getMessage(), sqle);
} finally {
DAOUtil.closeResources(null, ps, null, con);
}
}
}

Listing 20: DB2EnrollmentDAO.java

package com.ibm.ta.dao.db2;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.ibm.ta.dao.EnrollmentDAO;
import com.ibm.ta.dao.DAOException;
import com.ibm.ta.dao.DAOUtil;
import com.ibm.ta.webservice.Course;
import com.ibm.ta.webservice.Student;

public class DB2EnrollmentDAO implements EnrollmentDAO {
private DB2DAOFactory factory;

public DB2EnrollmentDAO(DB2DAOFactory factory) {
this.factory = factory;
}

public void insertEnrollment(long studentID, long courseID)
throws DAOException {
Connection con = null;
PreparedStatement ps = null;

try {
con = factory.createConnection();
ps = con.prepareStatement
("INSERT INTO ta.enrollment (course_id, student_id) VALUES (, )");
ps.setLong(1, studentID);
ps.setLong(2, courseID);
ps.executeUpdate();
} catch (SQLException sqle) {
throw new DAOException(sqle.getMessage(), sqle);
} finally {
DAOUtil.closeResources(null, ps, null, con);
}
}

public Student[] getEnrolledStudents(long courseID) throws DAOException {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
List results = new ArrayList();
try {
con = factory.createConnection();
ps = con.prepareStatement<