저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의
객체와 데이터베이스 사이의 그리고 매퍼 자체의 독립성을 유지하는 매퍼 레이어에 대해서 살펴본다.

메타 데이터 매퍼

본 글에서 소개하는 패턴은 '엔터프라이즈 어플리케이션 아키텍처 패턴' (마틴 파울러 저)에 소개된 패턴으로서, 필자가 파울러가 소개한 패턴을 보다 쉽게 이해할 수 있도록 재구성한 것이다.

'데이터 매퍼(Data Mapper)'는 이 글의 시리즈 글인 '데이터 소스 아키텍처 패턴 1'에서 살펴봤던 테이블 데이터 게이트웨이와 상당히 유사하다. 데이터 매퍼는 테이블 데이터 게이트웨이와 마찬가지로 도메인 모델 객체와 데이터베이스 테이블 사이에 존재하는 레이어로서 도메인 모델 객체와 데이터베이스 테이블 사이의 데이터 이동을 처리해준다.

테이블 데이터 게이트웨이는 테이블과 관련된 CRUD 작업을 처리하는 데 초점을 맞추고 있다면, 데이터 매퍼는 도메인 모델 객체와 테이블 사이의 매핑을 처리하는 데에 초점을 맞추고 있다. 이런 관점에서 테이블 데이터 게이트웨이는 테이블과 관련된 값을 RecordSet과 같은 범용타입을 통해서 처리해도 무방하지만, 데이터 매퍼의 경우는 정확한 타입의 객체와 테이블 사이의 매핑을 처리해주어야 한다.

마틴 파울러의 책에서 보여주는 데이터 매퍼의 구현 방법은 사실상 테이블 데이터 게이트웨이의 구현 코드에 '이미 로딩한 객체의 캐싱' 및 '추상 클래스를 통한 공통 코드 처리'를 추가 구현한 정도에 지나지 않으며, 같은 책의 뒤에서 소개하고 있는 메타 데이터 매퍼가 실제로 쓰임새 있는 패턴이라고 볼 수 있다. 메타 데이터 매퍼 패턴은 데이터 매퍼 개념을 범용적으로 확장한 것이므로 본 글에서는 메타 데이터 매퍼에 대해서 살펴볼 것이다.

메타 데이터 매퍼 패턴은 아래 그림과 같이 구성된다.


위 그림에서 DataMapper는 모델 객체가 테이블가 어떻게 연결되는지를 정의한 매핑 정보를 저장하고 있다. 예를 들어, DataMapper는 Team 객체의 id 프로퍼티와 데이터베이스의 TEAM 테이블의 TEAM_ID 필드가 매핑된다는 정보를 갖고 있다. 이 매핑 정보를 이용해서 DataMapper는 TEAM 테이블로부터 데이터를 읽어와 Team 객체를 생성하게 되며, Team 객체에 저장되어 있는 정보를 TEAM 테이블에 반영하게 된다.

DataMapper는 <객체, 테이블> 매핑 정보만 갖고 있으면 모든 CRUD 작업을 처리할 수 있게 된다. 설명에서 느꼈겠지만, 마틴 파울러가 설명한 데이터 매퍼 및 메타 데이터 매퍼 패턴은 객체(Object)-관계 데이터베이스(Relation Database) 매핑, 즉, OR 매핑에 대한 패턴이다.

구현 방법

메타 데이터 매퍼 패턴, 즉, OR 매핑을 구현하기 위해서는 다음의 정보가 필요하다.

  • 객체와 매핑할 테이블은 무엇인가?
  • 객체의 프로퍼티를 테이블의 어떤 필드에 매핑할 것인가?
보통은 위의 매핑 정보를 설정 파일로 처리한다. 예를 들어, 아래와 같은 설정 파일을 사용할 수 있다.

   <?xml version="1.0" ?>
   
   <mapping>
       <object class="madvirus.javacan.Employ" table="EMPLOY">
           <property name="id" field="EMPLOY_ID" />
           <property name="name" field="NAME" />
           <property name="address" field="ADDRESS" />
           
           <primary-key>
               <property>id</property>
           <primary-key>
       </object>
   </mapping>

위의 설정 파일을 보면 Employ 객체와 매핑되는 테이블이 EMPLOY 테이블이며, 객체의 id 프로퍼티, name 프로퍼티, address 프로퍼티는 각각 EMPLOY 테이블의 EMPLOY_ID 필드, NAME 필드, ADDRESS 필드와 매핑된다는 것을 나타내고 있음을 쉽게 알 수 있다.

DataMapper는 위와 같은 형태의 설정 파일로부터 매핑 정보를 읽어와 객체와 테이블 사이의 매핑을 알맞게 처리하게 된다. 위의 매핑 정보는 다음과 같은 형태의 클래스를 사용해서 저장될 것이다.

    public class MetaData {
        private Class object;
        private String tableName;
        private List<FieldMapping> fieldMappingList = new ArrayList<FieldMapping>();
        private List<FieldMapping> pkFieldList = new ArrayList<FieldMapping>();
        
        public List<FieldMapping> getFieldMappingList() {
            return fieldMappingList
        }
        public List<FieldMapping> getPKFieldList() {
            return pkFieldList;
        }
        ...
    }
    
    public class FieldMapping {
        private String property; // <property> 태그의 name 속성값
        private String field; // <property> 태그의 field 속성값
        
        ...
    }

DataMapper는 위의 메타 정보를 사용해서 알맞은 쿼리를 생성한다. 예를 들어, 특정 테이블로부터 데이터를 읽어오는 select() 메소드는 다음과 같은 형태가 될 것이다.

    public class DataMapper {
        Map<MetaData> map = new java.util.HashMap<MetaData>(); // <Object타입, MetaData> 매핑 저장
        
        public Object select(Class objectType, Map keyValue) {
            MetaData metaData = map.get(objectType);
            List<FieldMapping> fieldList = metaData.getFieldMappingList();
            List<FieldMapping> pkList = metaData.getFieldMappingList();
            
            StringBuffer query = new StringBuffer();
            query.append("select ");
            for (int i = 0 ; i < fieldList.size() ; i++) {
                query.append(fieldList.get(i).getField());
                if (i < fieldList.size() - 1) query.append(", ");
            }
            query.append(" from ").append(metaData.getTableName()).append(" where ");
            
            for (int i = 0 ; i < pkList.size() ; i++) {
                query.append(pkList.get(i).getField());
                query.append(" = ? ");
                if (i < pkList.size() - 1) query.append(" and ");
            }
            
            String sql = query.toString();
            
            PreparedStatement pstmt = null;
            ResultSet rs = null;
            
            try {
                pstmt = DB.prepareStatement(sql);
                for (int i = 0 ; i < pkList.size() ; i++) {
                    Object val = keyValue.get(pkList.get(i).getProperty());
                    
                    // setParameter 메소드는 PreparedStatement의 IN 파라미터값을
                    // val 객체의 타입에 알맞게 처리해준다고 가정하자.
                    setParameter(pstmt, i+1, val); 
                }
                rs = pstmt.executeQuery();
                if (rs.next()) {
                    Object obj = objectType.newInstance();
                    ... // rs의 값을 obj에 복사.
                    return object;
                }
            } finally {
                ...
            }
        }
    }

위 코드에서 볼 수 있듯이 객체와 테이블 사이의 관계를 DataMapper가 모두 처리해주기 때문에, 객체는 테이블에 대해서 전혀 알 필요가 없으며, 심지어 DataMapper의 존재에 대해서도 전혀 알 필요가 없다. 도데인 영역의 객체가 테이블과 DataMapper에 의존적이지 않기 때문에 데이터베이스를 변경하거나 테이블이 변경될 경우 모델에 끼치는 영향을 최소화할 수 있다.

데이터 매퍼의 장점

웹 어플리케이션 프로그래밍의 90% 이상이 데이터베이스와 연동하는 코드를 포함하고 있다. CRUD 코드를 일일이 작성하는 것은 굉장히 지루하고 실수를 유발하기 쉬울 뿐만 아니라 코딩 시간의 많은 부분을 차지하는 작업인데, 데이터 매퍼를 사용함으로써 개발 시간을 상당량 줄일 수 있게 되며 잘못된 코딩에 따른 버그도 줄일 수 있게 된다.

CRUD 코드를 작성하는 데 소모되는 시간을 줄일 수 있다는 것은 비즈니스 로직 부분의 코드를 작성하는데 더 많은 시간을 투자할 수 있다는 것을 의미한다. 로직 부분에 집중할 수 있는 시간이 늘어남으로써 빠른 시간에 원하는 어플리케이션을 구현할 수 있게 된다.

또한, 데이터 매퍼를 사용하게 되면, 테이블이 변경될 때 객체의 변경을 최소화시킬 수 있다. 예를 들어, 테이블 스키마에서 필드 하나를 제거했다고 해 보자. 이 경우 객체는 전혀 변경할 필요가 없이 매핑 정보만 변경하면 된다. 필드가 삭제되었다고 해서 변경해야 할 쿼리는 하나도 없는 것이다.

반대로, 테이블에 저장해야 하는 객체의 프로퍼티가 새롭게 추가되었다고 해 보자. 이때 해야 할 작업은 테이블에 관련 필드를 추가하고 매핑 정보를 변경하는 것이다. 앞의 경우와 마찬가지로 변경해야 할 쿼리는 전혀 없다.

마지막으로 객체가 데이터 매퍼에 의존적이지 않기 때문에, 데이터 매퍼를 독립적으로 유지할 수 있다. 즉, 객체에 상관없이 데이터 매퍼를 수정할 수 있으며, 심지어 데이터 매퍼 구현 클래스를 변경할 수도 있다.

데이터 매퍼를 직접 구현할 필요는 없다!

의욕이 높거나 한번 해보고 싶은 마음에 데이터 매퍼를 직접 구현해보는 것은 좋으나, 도메인 모델에 맞는 데이터 매퍼를 직접 구현하는 것은 쉬운 일이 아니다. 또한, 데이터 매퍼를 구현하는 시간이 짧지도 않다. 따라서, 미리 구현해둔 데이터 매퍼가 없거나 또는 기존에 구현한 데이터 매퍼가 프로젝트에서 사용하는 도메인 모델에 잘 맞지 않을 경우에는, 데이터 매퍼를 직접 구현하는 것 보다는 이미 존재하는 데이터 매퍼 라이브러리를 사용하는 것이 가장 좋다. 예를 들어, Hibernate와 같이 널리 사용되는 OR 매핑 도구를 사용하는 것이 직접 구현하는 것보다 프로젝트 개발 시간을 단축시키는 데 도움이 될 것이다.

관련링크:

 

Posted by 최범균 madvirus

댓글을 달아 주세요