레이블이 아키텍처인 게시물을 표시합니다. 모든 게시물 표시
레이블이 아키텍처인 게시물을 표시합니다. 모든 게시물 표시

2009년 2월 16일 월요일

[SA강좌] Part 4-34 상세 설계에서 해 야할 작업

상세 설계에서 해 야할 작업

상세 설계는 개념 설계를 구현이 가능한 수준의 설계 산출물로 변환하는 작업이다. 다음과 같은 작업을 수행한다.

  • 개념 설계의 클래스에 대한 속성 타입 설정
  • 오퍼레이션의 매개변수와 반환값 설정
  • 클래스의 relation association 클래스 등으로 구체화
  • 메커니즘을 구현하기 위한 클래스 추가
  • 설계 패턴이 적용되는 클래스 추가
  • DB 전문가와 협의하여 엔터티 클래스의 상속 제거, 정규화
  • 방향 association 방향으로 수정
  • 불필요한 association 제거
  • PK FK 설정
  • 오퍼레이션 DB에서 구현할 것을 표시함(stored procedure, trigger, check)
  • transaction, 보안, isolation level 위한 stereotype 설정
  • 오퍼레이션에 대한 알고리즘 작성

[SA강좌] Part 4-33 데이터베이스 관련 상세 설계 산출물

데이터베이스 관련 상세 설계 산출물(Database Modeling in UML)

DB와 관련한 설계 산출물 정의는 아래와 같다.

그림 -36. DB 설계 산출물 정의

 

그림 -37. 클래스, 속성 그리고 오퍼레이션

그림 -38. UML Class model notation

 

그림 -39. Aggregation Relationships

 

DB설계 과정 시 클래스들과의 관계(association), 상속(inheritance) 그리고 집합(aggregation)을 포함시켜 클래스 모델을 작성한다. Model-View-Control 설계 패턴을 사용하여 지속성(persistent)을 요구하는 클래스를 식별한다.

그림 -40. UML Relationship

 

식별된 지속성 클래스에 대하여 관계형 테이블 맵을 작성한다.

그림 -41. 클래스와 테이블의 매핑

[SA강좌] Part 4-32 서버 관련 상세 설계 산출물

서버 관련 상세 설계 산출물

서버 관련 엔터티빈 설계 정의는 아래와 같다.

그림 -34. 엔터티 빈 설계 정의 예제

 

서버 관련 세션 빈 설계 정의는 아래와 같다.

 

그림 -35. 세션 빈 설계 정의 예제

 

서버 관련 설계 산출물 정의시 세션빈에 대한 알고리즘 설계서와 시퀀스 다이어그램을 포함하여야한다.

[SA강좌] Part 4-31 UI 상세 설계 산출물

UI 상세 설계 산출물

UI 상세 설계 산출물 구조는 아래와 같다.

그림 Ⅴ-32. UI네비게이션 모델

 

UI에 대한 화면 정의는 아래와 같다.

화면 개요

병원 시스템을 사용할 환자가 시스템의 사용자로 등록하기 위한 화면으로 크게 기본정보, 연락처 정보, 기타를 입력하고 등록을 요청한다.

그림 -33. UI 화면 정의 예제

화면 구현 방법 기술

  • 화면의 입력 제약 사항

이름은 20자리로 제한하시요.

주민등록번호는 14자리로 제한하시요.

생년월일은 화면에서 받은 데이터를 YYYYMMDD 형식으로 저장하시요.

성별은 남자는 string "M", 여자는 string "F"로 저장하시요.

혈액형은 "A","B","AB","O"로 구분하시요.

RH형은 "RH+","RH-"로 구분하시요.

우편번호는 10자리로 제한하시요.

주소는 두줄이며 첫번째 줄은 집주소, 번째 주소는 직장 주소가 들어갑니다. 모두 50자리로 제한하시요.

집전화번호와 직장 전화번호는 20자리로 제한하고 사용하여 번호를 구분하시요.(: 02-3060-4050)

이메일은 20자리로 제한하시요.

개인 가족 병력은 100자리로 제한하시요.

 

  • 알고리즘 설계서

PatientValue 객체를 생성한다.( PatientValue patient = new PatientValue() )

PatientValue 객체의 속성에 화면에서 입력한 값을 넣는다.
(address1:
집주소, address2: 회사주소, bloodType: 혈액형, citizenNum: 주민등록번호, dateOfBirth: 생년월일, email: 이메일, gender: , patientName: 이름, phoneHome: 전화번호, phoneOffice: 사무실 전화번호, thType: RH 타입, zipCode: 우편번호, note: 개인 가족 병력)

PatientControlBean.registerPatient 호출하고 입력값은 PatientValue 객체를 넣는다.)

3 처리할 화면의 로직에서 TX 시작과 끝을 명시하지 (이미 PatientControlBean에서 tx 시작하고 끝내도록 만들어져 있습니다.)

PatentControlBean.registerPatient에서 넘어온 exception 화면에서 잡아서 messagebox 뿌려줄 .(exception 발생하면 환자 정보 화면으로 변경되지 않습니다.)

3 정상적으로 처리되면 리턴값 PatientValue 객체를 받고 환자 정보 화면으로 바뀌도록

환자 정보 화면은 PatientValue 객체를 사용하여 화면을 보여줄

환자 정보에서 진찰등록증 보기를 누르면 진찰 등록증 화면이 popup으로 뜨고 등록번호는 PatientValue 객체의 patientCode 속성을 사용하여 진찰등록증 화면을 보여줄 .

취소 버튼을 누르면 환자 등록 화면에 들어간 데이터가 삭제되고 화면이 보이도록 하시요.

[SA강좌] Part 4-3 상세 설계 산출물

상세 설계 산출물

UML 표기법을 최대한 활용하고 개발자들이 상세 설계 문서를 보고 개발을 진행할 수 있도록 하는 상세 설계 산출물을 정의한다.

그림 -31. 설계 산출물 구조

2009년 1월 14일 수요일

[SA강좌] Part 4-24 설계 패턴 적용 방법

설계 패턴 적용 방법

설계 패턴을 적용하기 전에 컨트롤러는 도메인 객체를 호출하기만 할 뿐 업무 로직을 가지고 있지 않다고 생각한다. 즉 도메인 객체가 비즈니스 로직을 나누어 가지고 있다.

도메인 객체는 Transaction Script, Table Module, Domain Model로 구별되며 가장 단순하게 구현할 수 있는 방법은 Transaction Script이다. Domain Model은 가장 익숙해지기 어려운 방식이다. 그러나 Domain Model을 사용하면 복잡한 모델도 쉽게 구현할 수 있다. 또한 Domain Model은 데이터베이스와 연결하는 부분이 복잡하다. Table Module은 이 두가지 사이에서 적절한 선택이 될 수 있다.

Data Source Layer에 대해서는 Transaction Script의 경우는 Row Data Gateway나 Table Data Gateway를 사용한다. RecordSet을 사용하고 데이터베이스와의 연결이 자주 끊어지는 경우에는 Table Data Gateway를 사용하는 것이 좋다. Table Modeul은 Record Set이나 Table Data Gateway와 함께 사용하는 것이 좋다. Domain Model의 경우에는 단순할 경우에는 Active Record를 사용하고 도메인 모델과 데이터베이스 연결을 분리할 경우에는 Table Data Gateway나 Row Data Gateway를 사용한다. 모델이 더 복잡한 경우는 Data Mapper를 사용한다.

J2EE의 경우에는 단순한 경우 Session Bean을 Transaction Script로 사용하고 entity bean은 Row Data Gateway로 사용한다. Domain Model을 사용하는 경우는 현재는 entity bean을 사용하는 것이 최상의 방법이다. 이 경우 entity bean은 Active Record로 사용된다. 이 경우 session bean은 remote façade로서 사용된다. 도메인 모델이 더 복잡하면 entity bean을 배제하고 일반 자바 클래스로 구현하면 된다. JDBC row set을 사용하는 경우는 Table Module을 사용한다.

.NET의 경우에는 가장 일반적인 패턴은 Table Module이다. 물론 Domain Model을 사용할 수도 있지만 이것이 .NET의 경우 최선의 선택이다.

Stored procedure는 remote call을 줄이는 좋은 방법이지만 이식성에 문제가 있다. 그러나 성능의 문제가 있을 경우는 도메인 로직을 제거하고 stored procedure로 옮기는 것이 좋다. Stored procedure를 사용하는 가장 좋은 방법은 Table Data Gateway를 사용하는 것이다.

 

 

[SA강좌] Part 4-23 Query Object

Query Object 패턴의 정의

데이터베이스 쿼리를 표현하는 객체

그림 -30. Query Object

 

[SA강좌] Part 4-22 Metadata Mapping

Metadata Mapping 패턴의 정의

메타 데이터 안에 OR 매핑에 대한 상세한 사항을 집어넣는다.

그림 -29. metadata mapping 패턴의 구조

OR 매핑을 취급하는 많은 코드들이 데이터베이스의 필드가 객체의 필드와 어떻게 매핑되는가를 정의한다. 결과적인 코드는 복잡하고 동일한 코드가 반복되는 경우가 많다. Metadata Mapping을 사용하면 개발자가 단순한 테이블 형태의 매핑을 정의하여 데이터를 읽고, 쓰고 업데이트하는 코드를 자동으로 생성할 수 있다.

어떻게 작동하는가?

Metadata mapping을 사용하기 위한 가장 큰 결정사항은 메타데이터의 정보를 실행중의 코드에 어떻게 반영하는가이다. 두 가지 방법이 있는데 첫 번째는 코드 자동화를 사용하는 것이고 두 번째는 reflection을 사용하는 프로그램이다.

코드 자동화를 사용하면 입력이 메타 데이터이고 출력이 매핑을 수행하는 클래스의 소스 코드이다.

Reflection을 사용하면 프로그램은 객체에게 예를 들어 setName이라는 메소드를 요청하고 적절한 파라메터를 사용하여 setName을 호출한다.

코드 자동화는 변경사항이 생기면 재컴파일하고 재배치해야 하기 때문에 덜 동적인 접근 방법이다. Reflection을 사용하면 사용자는 매핑 데이터 파일을 변경하고 이미 만들어진 클래스가 새로운 메타데이터를 사용한다. 사용자는 실행중에 이 작업을 수행할 수 있다.

Reflection에서 사용하는 메타 데이터는 XML을 사용하는 것이 좋은 방법이다.

언제 사용하는가?

Metadata mapping은 데이터베이스 매핑을 처리하는데 필요한 작업량을 줄인다. 그러나 셋업하는데 많은 시간이 소요되기 때문에 OR Mapping 툴을 사용하는 것도 좋은 방법이다. 스스로 메타데이터 매핑을 수행하려면 수작업으로 코드 생성기를 만드는데 걸리는 시간을 고려해봐야 한다. Reflection을 사용할 경우는 성능을 고려해야 한다.

[SA강좌] Part 4-21 Single Table Inheritance 패턴

단일 테이블 상속 패턴의 정의

단일 테이블 상속 패턴은 클래스들의 상속도을 다양한 클래스들의 모든 필드를 위한 컬럼을 가진 단일 테이블로 표현하는 것이다.

그림 -28. 단일 테이블 상속 패턴의 구조

단일 테이블 상속 패턴의 설명

관계형 데이터베이스는 다중 상속을 지원하지 않는다. 이러한 이유로 개체들을 데이터베이스에 매핑할 때 상속 구조를 관계의 테이블로 어떻게 표현할지를 고려해야한다. 관계형 테이터베이스에 매핑할 때, 다중 테이블에 있는 상속 구조를 처리할 때 최소의 조인을 할 수 있게 해야한다. 단일 테이블 상속 패턴은 상속 구조의 모든 클래스들의 모든 필드을 단일 테이블에 매핑한다.

단일 테이블 상속 패턴은 언제 사용하는가?

단일 테이블 상속은 상속구조를 관계형 데이터베이스에 있는 필드들의 매핑을 위한 선택 중에 하나이다. 이러한 매핑 패턴의 대안은 클래스 테이블 상속과 컨크리트 테이블 상속이 있다.

단일 테이블 상속의 장점은 다음과 같다.

  • 데이터베이스에 대한 고민은 단지 단일 테이블만 있다.
  • 추출되는 데이터에 대한 조인은 없다.

상속에 대한 필드의 추가 및 삭제은 데이터베이스에 대한 변경을 요청하지 않는다.

단일 테이블 상속의 단점

직접적으로 테이블을 이용하는 사람들에게 때때로 관련과 비관련 필드에 대하여 혼란을 준다.

몇몇 서브 클래스에 의해 사용되는 컬럼들은 데이터베이스에 대한 공간의 활용을 저하한다

2008년 12월 27일 토요일

[SA강좌] Part 4-20 Serialized LOB 패턴

시리얼라이즈 LOB(Serialized LOB) 패턴

시리얼라이즈 LOB 패턴의 정의

시리얼라이즈 LOB 패턴은 동기화된 개체들을 데이터베이스 필드에 저장된 단일 큰 개체 개체의 그래프를 저장한다.

그림 -27. 시리얼라이즈 LOB 패턴의 구조

시리얼라이즈 LOB 패턴의 설명

시리얼라이즈 LOB 패턴의 예제:XML에 있는 부서 계층을 시리얼라이즈

 

class Customer ...

 

private String name;

private List departments = new ArraryList();

 

class Department ...

 

private String name;

private List subsidaries = new ArraryList();

이 경우 데이터베이스는 한개 테이블만 가진다.

 

create table customers(ID int primary key, name varchar, departments varchar)

활동 레코드(Active Record) 패턴과 같이 고객 테이블은 다루어지고, 입력 행동을 가진 데이터 쓰기를 설명한다.

class Customer ...

 

public Long insert() {

PreparedStatment insertStatment = null;

try {

insertStatment = DB.prepare(insertStatementString);

setID(findNextDatabaseId();

insertStatment.setInt(1, getID().intValue());

insertStatment.setString(2, name);

insertStatment.setString(3, XmlStringer.write(deapertmentsToXmlElement());

insertStatment.execuete();

Registry.addCustomer(this);

return getID();

} catch(SQLException e) {

throw new ApplicationException(e);

} finally { DB.cleanUp(insertStatment);

}

}

 

public Element departmentsToXmlElement() {

Element root = new Element("departmentList");

Interator i = departments.iterator();

while (i.hasNext()) {

Department dept = (Department) i.next();

root.addContent(dep.toXmlElement());

}

return root;

}

 

class Department ...

 

Element toXmlElement() {

Element root = new Element("department");

root.setAttribute("name", name);

Interator i = subsidiaries.iterator();

while (i.hasNext()) {

Department dept = (Department) i.next();

root.addContent(dep.toXmlElement());

}

return root;

}

[SA강좌] Part 4-19 Embedded Value 패턴

내재된 값(Embedded Value) 패턴

내재된 값 패턴의 정의

내재된 값 패턴은 개체를 다른 개체의 테이블의 몇몇 필드들에 매핑한다.

그림 -26. 내재된 패턴의 구조

내재된 값 패턴의 설명

많은 작은 개체들은 테이터베이스의 테이블들 처럼 의미적이지 않은 개체지향 시스템 안에서 의미있게 한다. 예로 동시 발생 인식 메모리 개체와 일자 범위를 포함한다. 비록 기본적 생각은 개체를 테이블과 같이 저장하고, 같지 않은 사원은 메모리 값을 테이블이 원한다.

내재된 값은 개체의 값을 개체 소유자의 레코드에 있는 필드에 매핑한다.

내재된 값 패턴의 예제: 값 개체

내포된 값과 매핑되는 값 개체의 예는 아래와 같다. 다음과 같은 필드들을 가진 간단한 요청 클래스로 시작한다.

 

class ProductOffering ...

 

private Product product;

private Money baseCost;

private Integer ID;

위의 필드에서 ID 필드는 일치 필드(Identtity Field) 패턴을 사용하고, product 는 일반적 레코드 매핑이다.

 

class ProductOffering ...

 

public static ProductOffering load(ResultSet rs) {

try {

Integer id = (Integer) rs.getObject("ID");

BigDecimal baseCostAmount = rs.getBigDecimal("base_cost_amount");

Currency baseCostCurrency = Registry.getCurrency(rs.getString("base_cost_currency"));

Money baseCost = new Money(baseCostAmount, baseCostCurrency);

Integer productID = (Integer) rs.getObject("product");

Product product = Product.find(Integer) rs.getObject("product"));

return new ProductOffering(id, product, baseCost);

} catch(SQLException e) {

throw new ApplicationExcetion(e);

}

}

아래는 업데이트 행동이다. 다시 업데이터에서 간단한 변수이다.

 

class ProductOffering ...

 

public void update() {

PreparedStatment stmt = null;

try {

stmt = DB.prepare(updateStatmentString);

stmt.setBigDecimal(1, baseCost.amount);

stmt.setString(2, baseCost.currency().code());

stmt.setInt(3, ID.intValue());

stmt.execute();

} catch(Exception e) {

throw new ApplicationException(e);

} finally { DB.cleanUp(stmt);

}

private String updateStatementString =

" UPDATE product_offerings " +

" SET base_cost_amount = ?, base_cost_currency = ? " +

" WHERE id = ? ";

[SA강좌] Part 4-18 Dependent Mapping 패턴

의존 매핑(Dependent Mapping) 패턴

의존 매핑 패턴의 정의

의존 매핑 패턴은 자식 클래스를 위한 데이터베이스 매핑을 수행하는 하나의 클래스를 가진다.

그림 -24. 의존 매핑 패턴의 구조

 

의존 매핑 패턴의 설명

몇몇 개체들은 자연적으로 다른 개체의 컨텍스트에 나타난다. Album을 추적하면 album 진행시 적재되거나 저장될 때 적재되거나 저장된다. 데이터베이스에서 임의의 테이블에 의해서 참조되지 않는다면, 추적을 위한 매핑 수행을 하는 앨범 매퍼에 의해 매핑 절차를 단순화 할 수 있다. 의존 매핑은 매퍼로 처리한다.

의존 매핑 패턴은 언제 사용하는가?

다른 개체에 의해 참조되는 개체를 가지고 있으면 의존 매핑을 사용한다. 이경우 하나의 개체가 의존 집합을 가지고 있을 때 일반적으로 발생한다.

의존 매핑을 위한 사전 조건은 다음과 같다.

  • 의존은 반드시 한명의 소유자만 있어여 한다.
  • 의존을 위한 소유자 보다 임의 개체에서 참조가 없어야 한다.

의존 매핑 패턴의 예제: Album과 Tracks

아래의 그림과 같은 도메인 모델에서 Album은 Track들의 집합을 소유한다. 이 경우 간단한 애플리케이션은 Track를 참고하기 위한 어떤 것도 필요하지 않다. 의존 매핑을 위한 분명한 후보이다.

그림 -25. 의존 매핑 패턴의 예제

track은 단지 title만을 가진다. 아래에 클래스에 대한 정의가 있다.

 

class Track ...

 

private final String title;

public Track(String title) {

this.title = title;

}

 

public String getTitle() {

return title;

}

 

track들은 album 클래스 안에 포함된다.

 

class Album ...

 

private List tracks = new ArrayList();

public void addTrack(Track arg) {

tracks.add(arg);

}

public void removeTrack arg) {

tracks.remove(arg);

}

public void removeTrack(int i) {

tracks.remove(i);

}

public Track[] getTracks() {

return (Track[]) tracks.toArray(new Track[tracks.size()]);

}

앨범 매퍼 클래스는 트랙을 위한 모든 SQL를 조작하고, 트랙 테이블 접근을 하는 SQL 문을

저장한다.

 

class AlbumMapper ...

 

protected String findStatment() {

return

" SELECT ID, a.title, t.title as trackTitle" +

" FROM ablums a, tracks t " +

" WHERE a.ID = ? AND t.albumID = a.ID "+

" ORDER BY t.seq ";

트랙은 앨범이 적재될때 마다 앨범 안에 로딩된다.

 

class AlbumMapper ...

 

protected DomainObject doLoad(Long id, ResultSet rs) throws SQLException {

String title = rs.getString(2);

Album result = new Album(id, title);

loadTracks(result, rs);

return result;

}

public void loadTracks(Album arg, ResultSet rs) throws SQLException {

arg.addTrack(newTrack(rs));

while (rs.next()) {

arg.addTrack(newTrack(rs));

}

}

 

private Track newTrack(ResultSet rs) throws SQLException {

String title = rs.getString(3);

Track newTrack = new Track(title);

}

앨범이 모든 트랙이 업데이트 될때 삭제된다.

 

class AlbumMapper ...

 

public void update(DomainObject arg) {

PreparedStatment updateStatment = null;

try {

updateStatement = DB.prepare("UPDATE albums SET title = ? WHERE id = ?");

updateStatement.setLong(2, arg.getID().longValue();

Album album = (Album) arg;

updateStatement.setString(1, album.getTitle());

updateStatement.execute();

updateTracks(album);

} catch(SQLException e) {

throw new ApplicationException(e);

} finally { DB.cleanUp(updateStatement);

}

}

public void updateTracks(Album arg) throws SQLException {

PreparedStatement deleteTrackStatement = null;

try {

deleteTrackStatement = DB.prepare("DELETE from tracks WHERE albumID = ?");

deleteTrackStatement.setLong(1, arg.getID().longValue();

deleteTrackStatement.execute();

for (int i = 0; i < arg.getTacks().length; i++) {

Track track = arg.getTracks()[i];

insertTrack(track, i + 1, arg);

 

} catch(SQLException e) {

throw new ApplicationException(e);

} finally { DB.cleanUp(deleteTrackStatement);

}

}

public void insert(Track track, int seq, Album album) throws SQLException {

PreparedStatment insetTrackStatment = null;

try {

insetTrackStatment =

DB.prepare("INSERT INTO tracks(seq, albumID, title) VALUES(?,?,?)");

insetTrackStatment.setLong(1, seq);

insetTrackStatment.setLong(2, album.getID().longValue());

insetTrackStatment.setString(3, track.getTitle());

insetTrackStatment.exeute();

} catch(SQLException e) {

throw new ApplicationException(e);

} finally { DB.cleanUp(insetTrackStatment);

}

}

2008년 12월 13일 토요일

[SA강좌] Part 4-17 Association Table Mapping 패턴

연관 테이블 매핑(Association Table Mapping) 패턴

연관 테이블 매핑 패턴의 정의

연관 테이블 매핑은 외래키를 갖는 테이블을 연관 관계의 테이블에 저장하는 패턴이다.

그림 -23. 연관 테이블 매핑 패턴의 구조

연관 테이블 매핑 패턴의 설명

개체는 다중값 필드들을 필드 값처럼 집합(collection)을 사용하여 쉽게 조작할 수 있다. 관계형 데이터베이스는 이러한 특성이 없고, 단일 필드 값에 의해서 제약된다. 일-대-일 관계를 매핑할 때는 외래키 매핑(Foreign Key Mapping)을 이용하여 처리 할 수 있다. 근본적으로 연관의 단일 값을 위한 외래키를 사용한다. 그러나, 다-대-다 관계는 이와 같이 할 수 없다. 왜냐하면 외래키를 가지는 단일 값이 없기 때문이다.

이러한 문제의 해결은 분해를 위해 관계의 데이터를 사용하는 전통적 해결 방법을 사용하는 것이다. 즉, 레코드 관계에서 추출 테이블을 생성하는 것이다. 이후 연관 테이블 매핑을 이러한 링크 테이블의 다중값 필드에 매핑에 사용하는 것이다.

연관 테이블 매핑 패턴은 언제 사용하는가?

연관 테이블 매핑을 위한 정규화의 경우는 다-대-다 관계이다. 이러한 이유는 이 경우에 대한 다른 어떤 대안이 없기 때문이다.

연관 테이블 매핑은 연관의 다른 형식을 위해 사용된다. 그러나, 외래키 매핑과 외부 조인을 포함하는 것 보다 더 복잡한하기 때문에 좋은 선택이 아니다.

관계형 데이터베이스에서 관계에 대한 정보를 수행하는 연관 테이블은 자주 설계된다. 예를 들어서 사원의 회사에 대한 고용에 대한 정보를 담고있는 사원/회사 관계 테이블이 있다. 이 경우 사원/회사 테이블은 도메인 개체에 대응된다.

연관 테이블 매핑 패턴의 예제: 직접 SQL 사용

데이터베이스에 직접적으로 접근을 할때, 질의를 최소화하는 것이 중요하다. 아래는 테이블을 위한 DDL 을 보여주고 있다.

 

create table employee(ID int primary key, firstname varchar, lastname varchar)

create table skills(ID int primary key, name varchar)

create table employeeSkills(empoyeeID int, skillID int, primary key(employeeID, skillID))

단일 사원 로딩를 위해 아래와 같은 비슷한 방법을 사용한다. 사원 매퍼는 레이어 수퍼형(Layer Sypertype)에서 추상 메서드를 위해 비슷한 지정을 정의한다.

 

class EmployeeMapper ...

 

public Employee find(long key) {

return find(new Long key);

}

public Employee find(Long key) {

retrun (Employee) abstractFind(key);

}

protected String findStatement() {

return "SELECT " + CLUMN_LIST + " FROM employee WHERE ID = ? ";

}

public static final String CLUMN_LIST = " ID, lastname, firstname ";

 

class AbstractMapper ...

 

protected DomainObject abstractFind(Long id) {

DomainObject result = (DomainObject) loadMap.get(id);

if (result != null) return result;

PreparedStatment stmt = null;

ResultSet rs = null;

 

try {

stmt = DB.prepare(findStatment());

stmt.setLong(1, id.longValue());

rs = stmt.executeQuery();

rs.next();

result = load(rs);

return result;

} catch (SQLException e) {

throw new ApplicationException(e);

} finally {DB.cleanUp(stmt, rs);

}

}

abstract protected String findStatment();

proteceted Map loadedMap = new HashMap();

이후 find 메서든 로드 메서드를 호출한다. 추상 로드 메서드는 employee를 위한 실제 데이터은 employee의 매퍼에서 로드되고 있는 동안ID 로딩을 처리한다.

 

class AbstractMapper ...

 

protected DomainObject load(ResultSet rs) throws SQLException {

Long id = new Long(rs.getLong(1));

return load(id, rs);

}

public DomainObject load(Long id, ResultSet rs) throws SQLException {

if (hashLoaded(id)) return (DomainObject) loadedMap.get(id);

DomainObject result = doLoad(id, rs);

loadedMap.put(id, result);

return result;

}

abstract protected DomainObject doLoad(Long id, ResultSet rs) throws SQLException;

 

class EmployeeMapper ...

 

protected DomainObject doLoad(Long id, ResultSet rs) throws SQLException {

Employee result = rew Employee(id);

result.setFirstName(rs.getString("firstname"));

result.setLastName(rs.getString("lastname"));

result.setSills(loadSills(id));

return result;

}

사원은 스킬 로드를 위해 다른 질의가 필요하다. 그러나, 이 질의는 단일 질의에서 모든 스킬들을 쉽게 로드할 수 있다. 이러한 일을 하는 것은 특정 스킬을 위한 데이터에서 로드를 위한 스킬 매퍼를 호출한다.

 

class EmployeeMapper ...

 

protected List loadSkills(Load employeeID) {

PreparedStatment stmt = null;

ResultSet rs = null;

 

try {

List result = new ArraryList();

stmt = DB.prepare(findSkilsStatment);

stmt.setObject(1, employeeID());

rs = stmt.executeQuery();

while (rs.next()) {

Long skillId = new Long(rs.getLong());

result.add(Skill) MapperRegistry.skill().loadRow(skillId, rs);

}

return rsult;

} catch(SQLException e) {

throw new ApplicationException(e);

} finally {DB.cleanUp(stmt, rs);

}

}

private static final String findSkillsStatment =

"SELECT skill.ID " + skillMapper.CULUM_LIST +

" FROM skills skill, employeeSkills es " +

" WHERE es.employeeID = ? AND skill.ID = es.skillID";

 

class SkillMapper ....

 

public static final String COLUMN_LIST = " skill.name skillname";

 

class AbstractMapper ...

 

protected DomainObjct loadRow(Long id, ResultSet rs) throws SQLException {

return load(id, rs);

}

 

class SkillMapper ...

 

protected DomainObject loadRow(Long id, ResultSet rs) throws SQLException {

return load(id, rs);

}

 

class SkillMapper ...

 

protected DomainObject doLoad(Long id, ResultSet rs) throws SQLException {

Skill result = new Skill(id);

result.setName(rs.getStrig("skillName");

return result;

}

추상 매퍼는 또한 employee 검색을 돕니다.

 

class EmployeeMapper ...

 

public List findAll() {

return findAll(findAllStatment);

}

 

private static find String findAllStatment =

" SELECT " + COLUMN_LIST +

" FROM employees employee " +

" ORDER BY employee.lastname ";

 

class AbstractMapper ...

 

protected List find(String sql) {

PreparedStatment stmt = null;

ResultSet ts = null;

 

try {

List result = new ArraryList();

stmt = DB.prepared(sql);

rs = stmt.executeQuery();

while (rs.next())

result.add(load(rs));

return result;

} catch(SQLException e) {

throw new ApplicationException(e);

}

}

연관 테이블 매핑 패턴의 예제:다중 사원을 위한 단일 질의를 사용한다.

 

class EmployeeMapper ...

 

protected String findStatment() {

return

"SELECT " + COLUMN_LIST +

" FROM employees employee, skills skill, employeeSkills es" +

" WHERE employee.ID = es.employeeID AND skill.ID = es.skillID AND employee.ID = ?";

}

 

public static final String COLUML_LIST =

" employee.ID, employee.lastname, employee.firstname " +

" es.skillID, es.employeeID, skill.ID skillID " +

SkillMapper.COLUMN_LIST;

 

class EmployeeMapper ...

 

protected DomainObject doLoad(Long id, ResultSet rs) throws SQLException {

Employee result = (Employee) loadRow(id, rs);

result.setFirstName(rs.getString("firstname");

result.setLastName(rs.getString("lastname");

return result;

}

 

private boolean rowIsForSameEmployee(Long id, ResultSet rs) throws SQLException {

return id.equals(new Long(rs.getLong(1)));

}

 

private void loadSkillData(Employee person, ResultSet rs) throws SQLException {

Long skillID = new Long(rs.getLong("skillID");

person.addSkill((skill)MapperRegistry.skill().loadRow(skillId, rs);

}

 

 

class EmployeeMapper ...

 

 

public List findAll() {

return findAll(findAllStatment);

}

private static final String findAllStatment =

"SELECT " + COLUMN_LIST +

" FROM employees employee, skills skill, employessSkills es " +

" WHERE employee.ID = es.employee AND skill.ID = es.skillID " +

" ORDER BY employee.lastname ";

proteceted List findAll(String sql) {

AssociationTableLoader loader = new AssociationTableLoader(this, new SkillAdder());

return loader.run(findAllStatment);

}

 

class AssociationTableLoader ...

 

private AbstractMapper sourceMapper;

private Adder targetAdder;

public AssociationTableLoader(AbstractMapper primaryMapper, Adder targetAdder) {

this.sourceMapper = primaryMapper;

this.targetAdder = targetAdder;

}

 

class AssociationTableLoader ...

 

protected List run(String sql) {

loadData(sql);

addAllNewObjectToIdentityMap();

return formResult();

}

 

class AssociationTableLoader ...

 

private ResultSet rs = null;

private void loadData(String sql) {

PreparedStatment stmt = null;

try {

stmt = DB.prepared(sql);

rs = stmt.executeQuery();

while (rs.next()) loadRow();

} catch(SQLException e) {

throw new ApplicationException(e);

} finally(DB.cleanUp(stmt, rs);

}

}

 

class AssociationTableLoader ...

 

private List resultIds = new ArraryList();

private Map inProcess = new Hashmap();

private void loadRow() throws SQLException {

Long ID = new Long(rs.getLong(1));

if (!resultIds.contains(ID)) resultIds.add(ID);

if (!sourceMapper.hasLoaded(ID)) {

if(!inProcess.keySet().contains(ID))

inProcess.put(ID, sourceMapper.loadRow(ID,rs);

}

}

 

class AbstractMapper ...

 

boolean hasLoaded(Long id) {

return loadedMap.containsKey(id);

}

 

class EmployeeMapper ...

 

private static class SkillAdder implements AssociationTableLoader.Adder {

public void add(DomainObject host, ResultSet rs) throws SQLException {

Employee emp = (Employee) host;

Long skillId = new Long(rs.getLong("skillId"));

emp.addSkill((skill)MapperRegistry.skill().loadRow(skillId, rs);

}

 

 

class AssociationTableLoader ...

 

private void addAllNewObjectsToIdentityMap() {

for (Iterator it = inProcess.values().iterator();it.hasNext();)

sourceMapper.putAsLoader((DomainObject)it.next());

}

 

class AbstractMapper ...

 

void putAsLoaded(DomainObject obj) {

loadedMap.put(obj.getID(), obj);

}

 

class AssociationTableLoader ...

 

private List fromResult() {

List result = new ArraryList();

for (Iterator it = resultIds.iterator(); it.hasNext();) {

Long id = (Long)it.next();

result.add(sourceMapper.lookUp(id));

}

return result;

}

 

class AbstractMapper ...

 

protected DomainObject lookUp(Long id) {

return (DomainObject) loadedMap.get(id);

}

[SA강좌] Part 4-16 Foreign Key Mapping 패턴

외래키 매핑(Foreign Key Mapping) 패턴

외래키 매핑 패턴의 정의

외래키 매핑 패턴은 개체들 사이의 연관성을 테이블 사이의 외래 키 참조에 매핑한다.

그림 -21. 외래키 매핑 패턴의 구조

외래키 매핑 패턴의 설명

개체들은 개체 참조에 의해서 직접적으로 서로를 참조한다. 심지어 매우 간단한 개체지향 시스템은 의도된 서로 연결된 개체들을 포함한다. 이들 개체들을 데이터베이스에 저장하기 위해 개체 참조를 저장하는 것은 중요하다. 그러나, 데이터는 실행 프로그램의 한정된 인스턴스로 한정되므로 행 데이터 값을 단지 저장 할 수 없다. 좀더 복잡한 것은 개체들은 다른 개체들을 참조하는 집합을 포함한다. 외래키 매핑은 개체 참조를 데이터베이스에 있는 외래키와 매핑한다.

왜래키 매핑 패턴은 언제 사용하는가?

외래키 매핑은 클래스들 사이에서 거의 모든 연관성을 위해 사용된다. 가장 공통적인 경우는 다-대-다 연관성이다. 외래키는 단일 값이고, 첫번째 전형적인 형식은 단일 필드에 다중 외래키를 저장할 수 없다.

만일 후진 포인터 없는 필드를 포함하고 있다면 많은 면이 의존성 매핑(Dependent Mapping) 인지를 고려해야한다. 이 경우 집합 조작은 간단하다. 만일 관련된 개체가 값 개체 패턴(Value Object)이면 내포 값(Embedded Value) 패턴을 사용할 수 있다.

왜래키 매핑 패턴의 예제:단일 값 참조

아래의 예제는 비교적 간단한 경우이고, album은 artist를 단일 참조한다.

 

class Artist ...

 

private String name;

public Artist(Long ID, String name) {

super(ID);

this.name = name;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

 

class Alum ...

 

private String title;

private Artist artist;

public Album(Long ID, String title, Artist artist) {

super(ID);

this.title = title;

this.artist = artist;

}

public String getTitle() {

return title;

}

public void setTitle(String title) {

this.title = title;

}

public Artist getArtist() {

return artist;

}

public void setArtist(Artist artist) {

this.artist = artist;

}

 

그림 -22. 단일 값 필드 로딩을 위한 sequence diagram

그림 -22은 어떻게 앨범이 로드를 하는지를 보여준다. 앨범 매퍼는 질의어에 대한 데이터베이스에서 특정한 앨범 로드를 알린다. 그리고, 결과 집합을 되돌려준다. 이후 개별 외래키 필드를 위한 결과 집합을 질의한다. 그리고, 해당 개체를 찾는다. 적절한 발견된 개체를 가진 앨범을 생성한다. 만일 아티스트 개체가 이미 메모리에 있다면 캐쉬에서 가져온다. 그러므로, 같은 방법으로 데이터베이스에서 로딩된다.

find 오퍼레이션은 구분 맵(Identity Map)을 조작하기 위해 추상적 행동을 사용한다.

 

class AlbumMapper ...

 

public Album find(Long id) {

return (Album) abstractFind(id);

}

protected String findStatement() {

return "SELECT ID, title, artistID FROM albums WHERE ID = ?";

}

 

class AbstractMapper ...

 

abstract protected String findStatement();

protected DomainObject abstractFind(Long id) {

DomainObject result = (DomainObject) loadedMap.get(id);

if (result != null) return result;

PreparedStatement stmt = null;

ResultSet rs = null;

try {

stmt = DB.prepare(findStatment);

stmt.setLong(1, id.longValue());

rs = stmt.executeQuery();

rs.next();

result = load(rs);

return result;

} catch(SQLException e) {

throw new ApplicationException(e);

} finally { cleanUp(stmt, rs); }

}

 

private Map loaderMap = new HashMap();

 

find 오퍼레이션은 album에 데이터를 로딩을 위해서 로딩 오퍼레이션을 호출한다.

 

class AbstractMapper ...

 

protected DomainObject load(ResultSet rs) throws SQLException {

Load id = new Long(rs.getLong(1));

if (loadedMap.containsKey(id)) return (DomainObject)loadeMap.get(id);

DomainObject result = doLoad(id,rs);

doRegister(id, result);

return result;

}

 

protected void doRegister(Long id, DomainObject result) {

Assert.isFalse(loadedMap.containsKey(id));

loadedMap.put(id, result);

}

abstract protected DomainObject doLoad(Long id, ResultSet rs) throws SQLException;

 

class AlbumMapper ...

 

protected DomainObject doLoad(Long id, ResultSet rs) throws SQLException {

String title = rs.getString(2);

long artisID = rs.getLong(3);

Artist artist = MapperRegistry.artist.find(artistID);

Album result = new Album(id, title, artist);

}

album 외래키 값을 업데이트하는 것은 연결된 artist 개체에서 얻어진다.

 

class AbstractMapper ...

 

abstract public void update(DomainObject arg);

 

class AlbumMapper ...

 

public void upate(DomainObject arg) {

PreparedStatement statement = null;

try {

statement = DB.prepare("UPDATE album SET title = ?, artistID = ? WHERE id = ?");

statement.setLong(3, arg.getID().longValue());

Album album = (Album) arg;

statement.setString(1, ablum.getTitle());

statement.setLong(2, album.getArtist().getID().longValue());

statement.execute();

} catch(SQLException e) {

throw new ApplicationException(e);

} finally {

cleanUp(statment);

}

}

2008년 12월 7일 일요일

[SA강좌] Part 4-15 Identity Map 패턴

일치 맵(Identity Map) 패턴

일치 맵 패턴의 정의

일치 맵 패턴은 맵에 로드된 모든 개체의 유지에 의하여 개별 개체는 한번만 로드됨을 확신한다. 이러한 개체들을 참고 할 때 맵을 사용한 개체 검색을 하는 패턴이다.

그림 -18. 일치 맵 패턴의 구조

일치 맵 패턴의 설명

일치 맵 패턴은 단일 비즈니스 트랜잭션에 있는 데이터베이스에서 읽은 모든 개체의 패턴을 유지 시킨다. 객체를 원하는 경우에는 이미 가지고 있다면 일치 맵 패턴을 처음에 확인한다.

일치 맵 패턴의 사용

  • 비일치성을 막는다.
  • 수행성을 향상시킨다.

일반적으로 일치 맵 패턴은 데이터베이스에서 가져온 개체를 관리하기 위해 사용한다. 중요한 이유는 메모리 상에 두개의 개체가 하나의 데이터베이스 레코드에 대응 되기를 원하지 않기 때문이다. 만일 비 일관성적으로 두개 레코드를 수정한다면 데이터베이스 매핑은 혼란을 가져온다.

다른 이유는 데이터베이스를 읽기 위한 캐쉬와 같은 행동을 한다.

 

일치 맵 패턴의 예제

일치 맵을 위한 맵 필드와 접근자들은 다음과 같다.

 

private Map people = new HashMap();

public static void addPerson(Person arg) {

soleInstance.people(arg.getId(), arg);

}

 

public static void getPerson(Long key) {

return (Person) soleInstance.people.get(key);

}

 

public static void addPerson(Person arg) {

return getPerson(new Long(key));

}

자바(Java)의 애로사항 중에 하나는 long 이 객체가 아니라는 것이다. 이러한 이유로 맵을 위한 인덱스로 사용할 수 없다.