Lazy Bean 구현 - 관계형 DB 에서 지연 로딩 객체 구현

발생일: 2010.02.16

문제:
정확히 어디서부터 이 얘기가 시작되었는 지는 잘 모르겠다.

프렌드 홍과 스트럿츠의 복잡한 요청에 대한 ActionForm 구현에 대해 이야기하다가,
자연스럽게 관계형 DB 에서 지연 로딩 객체 구현에 대한 주제로 넘어가게 됐다.

일반적인 OR Mapping 툴에서 대부분 lazy loading 을 지원한다는데,
(실제 사용해 본 적이 없어 정확히는 모르겠으나, iBatis 의 lazy loading 부분을 책에서 읽어봐 대충 감이 잡혀있긴 하다.)
얘네들은 아무래도 객체형 DB 에 적합할 것 같다는 생각이 들었다.

그럼 객체 자체에 지연 로딩을 적용해 보면 어떨까.

예를 들어, user 객체는 userId 만 가지고 있다가,
userUserName() 과 같은 요청이 들어왔을 때 DB 에 접근해서 사용자 이름을 가져오는 방식으로 말이다.


해결책:
객체 자체에 지연 로딩을 적용해보기 전에 일단 VO 객체가 올바르게 객체 지향으로 재사용되도록 잡아줘야 할 것 같다.

예를 들어, 데이터베이스에 아래와 같은 정보가 있다고 가정해보자.

User (userId, userName, deptId, ... ) // 사용자 정보
Dept (deptId, deptName, ... ) // 부서 정보

각 사용자는 자신의 부서 정보를 포함하고 있다.

몇몇 급조된 프로젝트 - 적어도 홍과 내가 겪었던 - 에서는 사용자 정보와 매핑된 부서 정보를 가져오는 객체를
아래와 같이 한 객체에 정의했다.

class User {
    private String userId;
    private String userName;
    private String deptId; // 사용자 객체에 부서 코드도 포함되어 있다.
    private String deptName; // 부서 이름을 가져오기 위해 DB 에서 부서명도 함께 조회한다.

    // 그 외 getter/setter
}

class Dept { } // 때로 부서 정보만 조회할 때에는 따로 Dept 클래스를 사용하곤 한다.


만약, 게시판 내용을 가져오는 BoardList 라는 VO 객체를 만들었다고 한다면,
위 User 객체를 재사용하지 않고 보통은 아래와 같이 해버리곤 한다. (대부분 '바쁘니까...')

class BoardList { // 게시판 목록 VO 객체
    // 등록자 정보
    private String regUserId;
    private String regUserName;
    private String regUserDeptId;
    private String regUserDeptName;
    // 최근 수정자 정보
    private String updateUserId;
    private String updateUserName;
    private String updateUserDeptId;
    private String updateUserDeptName;

    // 그 외 getter/setter
}


가끔은 deptId 부분이 쓰이지 않을 것 같으면 그냥 deptId 에 deptName 을 넣기도 한다.


이 User 객체를 아래와 같이 좀 더 객체 지향으로 리팩토링 할 수 있다.

class User {
    private String userId;
    private String userName;
    private Dept dept; // 부서 정보는 부서 객체를 사용한다.

    // getter / setter
}

class Dept {
    private String deptId;
    private String deptName;

    // getter / setter
}

BoardList 에서도 User 객체를 활용한다.

class BoardList {
    private User regUser;
    private User updateUser;
}



이제 어느 정도 객체 지향적인 모습을 띄었다.
이제 이 객체에 지연 로딩(lazy loading) 을 적용해 보려고 한다.

기본적으로 primary key 가 되는 값만 전달해주고, 추가 요청이 생길 경우 상세 내용을 DB에서 가져오는 방식이다.

class User {   
    String userId;
    String userName;
    Dept dept;
   
    public User(String userId) {
        this.userId = userId;
    }
   
    public void set() {
        // DB 에서 user 정보를 가져오고, 같은 row 내 deptId 로 dept 객체를 생성해둔다.
        // set 하는 부분은 구현하기 나름이겠다.
        Map result = dao.getUserMap(userId);
        userName = (String) result.get("userName");
        dept = new Dept((String) result.get("deptId"));
    }
   
    public String getUserId() {
        return userId;
    }
   
    public Strin getUserName() {
        if (userName == null) set(); // userName 등 상세정보가 없을 경우 set() 한다
        return userName;
    }
   
    public Dept getDept() {
        return dept;
    }
}

class Dept() {   
    String deptId;
    String deptName;
   
    public Dept(String deptId) {
        this.deptId = deptId;
    }
   
    void set() {
        // 디비에서 deptId에 해당하는 부서 정보를 가져온다.
        dao.getDept(deptId);
    }
   
    public String deptName() {
        if (deptName == null) set(); // 역시 lazy loading
        return deptName;
    }
}


이렇게 할 경우, userId 만 있으면 필요한 시점에 상세 정보를 DB 에서 가져올 수 있다.
코드가 간략해지고 짧아짐은 물론이다.

만약, 전체 목록을 가져오는 등의 상황을 고려한다면 퍼포먼스 측면에서 그리 추천할 만한 방법은 아닐 것 같다.
(홍은 목록이 10~20 여개 정도라면 크게 문제되지 않을 것 같다고 한다)

아직 실제로 구현해보지는 않았으나, 이런 방식으로 빈을 만든다면 상세 정보 호출에 대한 수고가 크게 줄어들 게 된다.

예를 들어, 게시물에 대한 Board 라는 VO 를 생각해보자.

class Board {
    private String title;
    private String content;
    private User author;

    public void set() {
        // Board 객체도 set() 메서드가 있다고 가정하고,
        // DB 에서 author 에 대한 userId 값을 가지고 user 객체를 생성해뒀다고 치자.
        ... (설정 중략) ...
        ahthor = new User((String) dao.getBoardDetail("author_id")); // author_id 가 작성자 id라고 가정
    }
    // getter / setter
}

board 객체만 가져오면 그 이후부터는 필요할 때마다 객체에서 직접 상세 내용을 가져오도록 할 수 있다.

board.getAuthor().getUserName()
board.getAuthor().getDept().getDeptName()

과 같이 상세 내용을 호출하면 필요할 때 그 내용을 직접 가져오게 된다.

코드도 굉장히 짧아지고, 각자 자기 역할을 충실히 해 낸다.

공통되는 set() 부분이나 null check 부분을 인터페이스로 뽑아내도 좋을 것 같고, (LazyBean 같은 이름으로)
아니면 dao 를 가지고 있는 부모 클래스를 두고 확장하도록 해도 괜찮을 것 같다.

iBatis 에서는 result-map 을 이용해 서브 쿼리를 필요할 때 실행해서 가져오도록 하던데,
이 방법도 적합하게 사용된다면 아주 유용할 것이라 생각한다.





카테고리

분류 전체보기 (730)
About me. (6)
Daylogs (695)
영어공부 (0)
My works - 추억 (29)
비공개 (0)