본문 바로가기

Dev.../소프트웨어 아키텍처

[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);

}

}