티스토리 뷰
발생일: 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 을 이용해 서브 쿼리를 필요할 때 실행해서 가져오도록 하던데,
이 방법도 적합하게 사용된다면 아주 유용할 것이라 생각한다.
문제:
정확히 어디서부터 이 얘기가 시작되었는 지는 잘 모르겠다.
프렌드 홍과 스트럿츠의 복잡한 요청에 대한 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 을 이용해 서브 쿼리를 필요할 때 실행해서 가져오도록 하던데,
이 방법도 적합하게 사용된다면 아주 유용할 것이라 생각한다.
반응형
댓글
공지사항