본문 바로가기

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

[SA강좌] Part 4-13 Data Mapper패턴

데이터 매퍼(Data Mapper) 패턴

 

데이터 매퍼 패턴의 정의

매퍼의 레이어는 개체와 데이터베이스 사이에 데이터를 이동시킨다.

그림 -16. 데이터 매퍼 패턴의 구조

데이터 매퍼 패턴의 설명

개체와 관계형 데이터베이스는 데이터를 구조화하는 메커니즘이 서로 다르다. 집합과 상속과 같은 개체의 많은 부분들이 관계형 데이터베이스에는 존재하지 않는다.

데이터 매퍼는 데이터베이스에서 메모리 위에 있는 개체를 분리하는 소프트웨어 레이어 이다.

데이터 매퍼 패턴은 언제 사용하는가?

데이터 매퍼를 위한 주요한 이슈는 데이터베이스 스키마와 독립적으로 포함된 개체 모델을 원할 때이다.

데이터 매퍼 패턴의 예제 : 간단한 데이터베이스 매퍼

 

class Person

 

private String lastName;

private String firstName;

private int numberOfDenedents;

 

데이터베이스 스키마는 다음과 같다.

 

cateate table people (

ID int primary key,

lastname varchar,

firstname varchar,

number_of_dependnets int

)

 

class PersonMapper ...

 

protected String findStatement() {

return "SELECT " + COLUMNS +

" FROM people " +

" WHERE id = ? ";

}

public static final String COLUMNS = " id, lastname, firstname, number_of_dependents ";

public Person find(Long id) {

return (Person) abstractFind(id);

}

public find(long id) {

return find(new Long(id));

}

 

class AbstractMapper ...

 

protected Map loadedMap = new HashMap();

abstract protected String findStatment();

protected DomainObject abstractFind(Long id) {

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

if (result != null) return result;

PreparedStatment findStatement = null;

try {

findStatement = DB.prepare(findStatment);

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

ResultSet rs = findStatement.executeQuery();

rs.next();

result = load(rs);

return result;

} catch(SQLException e) {

throw new ApplicationException(e);

} finally {

DB.cleanUp(findStatement);

}

}

검색 메서드는 로드 메서드를 호출한다.

 

class AbstractMapper ...

 

protected DomainObject load(ResultSet rs) throws SQLException {

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

if (loadedMap.contractKey(id) return (DoaminObject) loadedMap.get(id);

DomainObject result = doLoad(id, rs);

loadedMap,put(id, result);

return result;

}

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

 

class PersonMapper ...

 

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

String lastNameArg = rs.getString(2);

String firstNameArg = rs.getString(3);

int numDependentsArg = rs.getInt(4);

return new Person(id, lastNameArg, firstNameArg, numDependentsArg);

}

일치 맵(Identity Map) 패턴이 abstractFind와 load 메서드에 의해 두번 확인됨을 주의한다.

 

class PersonMapper ...

 

private static String findLastNameStatment =

" SELECT " + COLUMNS +

" FROM people " +

" WHERE UPPER(lastName) LIKE UPPER(?) " +

" ORDER BY lastname ";

public List findByLastName(String name) {

PreparedStatement stmt = null;

ResultSet rs = null;

try {

stmt = DB.prepare(findLastNameStatement);

stmt.setString(1, name);

rs = stmt.executeQuery();

return loadAll(rs);

} catch(SQLException e) {

throw new ApplicationException(e);

} finally {

DB.cleanUp(stmt, rs);

}

}

 

class AbstractMapper ...

 

protected loadAll(ResultSet rs) throws SQLException {

List result = new Array();

while (rs.next())

result.add(load(rs));

return result;

}

 

 

class AbstractMapper ...

 

public List findMany(StatementSource source) {

PreparedStatment stmt = null;

ResultSet rs = null;

try {

stmt = DB.prepare(source.sql());

for (int i = 0; i < source.paraments().length; i++)

stmt.setObject(i+1, source.paraments()[i]);

rs = stmt.executeQuery();

return loadAll(rs);

} catch(SQLException e) {

throw new ApplicationException(e);

} finally {

DB.cleanUp(stmt, rs);

}

 

interface StatmentSource ...

 

String sql();

Object[] paraments();

 

class PersonMapper ...

 

public List findByLastName2(String pattern) {

return findMany(new FindByLastName(pattern));

}

static class FindByLastName impletements StatemnetSource {

private String lastname;

public FindByLastName(String name) {

this.lastName = lastName;

}

public String sql() {

return

" SELECT + " COLUMNS +

" FROM people " +

" WHERE BY lastname ";

}

public object[] parameters() {

Object[] result = { lastName; }

return result;

}

}

 

 

class PersonMapper ..

 

private static final String updateStatementString =

" UPDATE people " +

" SET lastname = ?, firstname = ?, number_of_dependents = ?" +

" WHERE id = ? ";

public void update(Person subject) {

PreparedStatement updateStatement = null;

try {

updateStatement = DB.prepare(updateStatementString);

updateStatement.setString(1, subject.getLastName());

updateStatement.setString(2, subject.getFirstName());     

updateStatement.setInt(3, subject.getNumberOfDependents());

updateStatement.setInt(4, subject.getID(),intValue());

updateStatement.execute();

} catch(Exception e) {

throw new ApplicationException(e);

} finally {

DB.cleanUp(updateStatement);

}

}

 

입력을 위한 몇몇 코드는 레이어 수퍼형에 보관된다.

 

class AbstractMapper ...

 

public Long insert(DomainObject subject) {

PreparedStatment updateStatement = null;

try {

insertStatement = DB.prepare(insertStatment());

subject.setID(findNextDatabaseId());

insertStatement.setInt(1, subject.getID().intValue());

doInsert(subject, insertStatement);

insertStatement.execute();

loadMap.put(subject.getID(), subject);

return subject.getID());

} catch(SQLException e) {

throw new ApplicationException(e);

} finally {

DB.cleanUp(updateStatemet);

}

}

abstract protected String insertStatement();

abstract protected void doInsert(DomainObject subject, PreparedStatement insertStatement)

throws SQLException;

 

class PersonMapper ...

 

protected String insertStatement() {

return "INSERT INTO people VALUES(?,?,?,?)";

}

protected void doInsert(DomainObject abstractSubject,

PreparedStatement stmt)

throws SQLException

{

Person subject = (Person) abstractSubject;

stmt.setString(2, subject.getLastName());

stmt.setString(3, subject.getFirstName());

stmt.setInt(4, subject.getNumberOfDependents());

}