토비의 스프링 3에 포함된 예제 애플리케이션인 springusergroup에 대한, 책에는 없는 이야기 두 번째.

Springusergroup은 의도적으로 JPA와 SpringJDBC를 함께 사용하도록 만들었다. User와 Group이라는 many-to-one 관계에 있는 엔티티에 대해서 UserDao는 SpringJDBC를 사용해서 만들고 GroupDao는 JPA를 사용하도록 만들었다. 스프링에서 엔티티별로 다른 DA기술을 적용하는 것은 간단한 일이다. 트랜잭션 통합 기능을 활용하면 JDBC-iBatis-하이버네이트/JPA/JDO로 만든 DAO를 하나의 트랜잭션으로 묶어서 스프링의 선언적 트랜잭션의 효과를 그대로 유지할 수 있다. 뭐, JTA를 이용해도 되고.

문제는 JDBC와 JPA로 만든 DAO의 작업을 하나의 트랜잭션으로 묶어주는 것이 아니다. 그보다는 DAO패턴의 의도대로 DA(data access)기술에 투명한 방식으로 DAO를 사용할 수 있도록 만들어주는 것이다. DAO의 내부 구현 기술이나 방식에 상관없이 동일한 DAO 인터페이스를 통해서 가져온 오브젝트(도메인 오브젝트/엔티티)는 동일한 방식으로 사용할 수 있어야 한다. 그래야지만 DAO 내부 구현과 DAO를 사용하는 서비스 계층의 코드의 결합도를 낮출 수 있고, 계층형 아키텍처를 도입한 효과를 얻을 수 있다.

단지 기능의 종류에 따라서 빈을 분리하는 것으로 계층형 아키텍처를 적용했다고 할 수 없다. 계층 사이에 인터페이스를 두었다고 해도 마찬가지다. 제대로 된 계층 구조라면 계층 사이의 인터페이스의 규약이 동일하다면 구현 기술/방법을 변경해도 이를 사용하는 클라이언트에 영향을 주지 않아야 한다. 하지만 실제로는 그렇지 않은 경우가 많다.

예를 들어보자.

User와 Group은 n:1 관계에 있다고 하자. 즉 User는 하나의 Group에 소속된다. DB에서는 FK/PK를 써서 User테이블에 group_id 같은 필드를 두어서 간접적인 관계를 만들어둔다. 반면에 직접 레퍼런스를 가질 수 있는 자바에서는 User에 직접 Group 오브젝트를 참조할 수 있도록 다음과 같이 만드는 것이 좋다.

class User {
Group group;

문제는 이렇게 도메인 모델을 반영한 오브젝트 구조를 만들었을 때 DAO의 동작 방식이다. UserDao에 다음과 같은 메소드가 있다고 해보자.

public User getUser(int id);

id를 가지고 User 오브젝트를 가져오는 DAO 메소드다. 그런데 여기서 가져온 User 오브젝트의 group 프로퍼티에는 과연 Group 오브젝트가 들어있을까, 없을까? Group 오브젝트를 항상 넣어줘야 할까, 아닐까?

전통적인 DB 개발스타일을 DAO에 적용하는 개발자라면 사실 DAO 메소드에 따라서 어떤 정보를 가져오는지를 명확하게 만들려고 할 것이다. 예를 들어 getUser()는 User만 달랑 가져온다거나, getUserWithGroup()은 Group까지 가져온다거나 하는 식이다. User의 기본 정보만 있으면 충분한 로직에서 Group까지 가져오는 것은 낭비기 때문이다. 생성되는 SQL 입장에서 보더라도 전자는 User테이블 정보만 읽어오고, 후자는 User와 Group테이블을 조인해서 읽어오도록 만들게 된다.

문제는 이렇게 DAO를 사용하는 코드에서 각 DAO 메소드가 돌려주는 데이터의 범위를 정확히 알고 있어야 한다는 것은 DAO와 이를 사용하는 코드 사이의 결합을 강하게 만든다는 것이다. 또, 가져오는 정보의 범위에 따라서 비슷한 SQL을 가진 메소드가 중복되서 만들어진다. 결국 전통적인 트랜잭션 스크립트 스타일의, 업무로직 하나에만 종속되는 코드 그룹을 만들기 쉽상이고, 그에 따라서 재사용성은 떨어지고 중복은 늘어난다. 그럴 바엔 아예 DAO를 따로 두지 말고 DA코드와 로직을 한데 섞는 것이 나을지도 모르겠다.

DAO의 메소드가 어떤 정보를 돌려주는지, 그 범위는 어디까지인지를 아는 것은 비즈니스 로직을 담고 있는 서비스 코드만의 문제는 아니다. 나중에 이 정보를 렌더링할 뷰도 이를 알고 있어야 한다. 따라서 웹 프레젠테이션 계층 – 서비스 계층 – DAO 계층이 강하게 결합되고 DAO의 구현에 대해서 구체적인 정보를 알고 있어야 하는 구조의 코드가 만들어지기 쉽상이다. 흔히 보게 되는 화면의 기능 하나당 계층별로 1:1:1의 코드가 만들어지게 될 수도 있다.

결국 도메인 오브젝트 그래프 안에 정보를 담는 것에 대한 회의가 들게 되고, 어짜피 DAO가 어떤 정보를 돌려줄지를 이를 사용하는 나머지 계층이 알고 있어야 한다면 차라리 맵에 담든지 그때마다 DTO를 하나씩 따로 만들어서 사용하는게 낫다고 생각할지 모르겠다. 굳이 User-Group 도메인 오브젝트 구조를 사용할 필요가 없게된다는 말이다. 그 결과 비즈니스 로직이 도메인 오브젝트에 들어갈 기회는 차단될 것이고, 로직은 SQL이나 거대한 서비스 계층의 코드에 분산되고 중복되서 나타나게 될 것이다.

뭐, 이런 개발 방법은 잘못됐고 나쁘다고 말하고 싶은 것은 아니다. 이런 아키텍처와 스타일을 선택하고 그 장점(주로 초기에 개발자별로 독립적으로 빠르게 개발이 가능하는 것 등)을 살려서 만들 수도 있다. 간단한 앱이거나 단순한 조회가 전부이거나 대부분 리포트 쿼리로 되어있는 경우라면 나쁘지 않다.

반면에 도메인 모델을 반영한 오브젝트 그래프를 계층 사이의 데이터 전송 매체로 삼고 주요 도메인 로직을 도메인 오브젝트 안에 두고 싶거나, 아예 도메인 계층을 따로 두는 아키텍처를 지향한다면 DAO가 돌려주는 데이터의 범위에 강하게 결합되는 코드를 만드는 것은 피해야 한다. 그래서 JPA나 하이버네이트와 같은 ORM이 이런 경우에 더 적합하다. JPA나 하이버네이트는 도메인 오브젝트 그래프를 유지한 채로 정보를 읽어올 뿐만 아니라, 지능적인 방식으로 사용할 데이터의 범위를 다이내믹하게 조정할 수 있게 해준다.

예를 들어 getUser()에서는 User오브젝트만 읽어오지만, 이를 사용하는 로직 어디에선가 Group의 이름을 알고 싶다면 user.getGroup().getName()으로 오브젝트를 따라서 네비게이션해서 정보를 읽어올 수 있게 만들어준다. 중요한 것은 이 때 Group 정보를 어떻게 가져오는지는 이를 사용하는 코드 내에서는 신경쓰지 않아도 된다는 것이다. DAO 내부에서 미리 User와 Group을 함께 조인해서 읽어왔을 수도 있고, 아니면 lazy-loading을 이용해서 일단 User만 읽어두고 Group 내부의 정보를 사용하려고 시도할 때 즉석에서 Group을 읽어오게 할 수도 있다. 또는 Group을 2nd-level 캐시 같은 캐시에 저장해 두었다가 필요할 때 캐시에서 꺼내오게 할 수도 있다. 핵심은 Group 정보를 어떻게 가져오는지는 DAO 내부의 구현이고 이는 DAO를 사용하는 코드에서는 전혀 신경 쓰지 않아도 되며 나중에라도 언제든지 변경할 수 있고 그 변경이 DAO를 사용하는 코드에는 영향을 주지 않는다는 것이다.  이게 ORM 기술의 매력이다.

많은 사람들이 ORM은 최적화된 SQL을 각각 따로 만들어서 사용하는 방식에 비해서 성능이 떨어진다고 생각한다. 그 이유 중의 하나는 오브젝트 단위로 정보를 읽어오고, 이를 위해서 최적화되지 않은 쿼리가 자주 만들어진다는 것이다. 흔히 말하는 n+1 문제이다. 하지만 ORM은 일단 도메인 오브젝트를 이용해서 애플리케이션을 모두 개발한 뒤에, 코드에는 영향을 주지 않은 채로 ORM이 생성하는 SQL을 효과적으로 튜닝할 수 있게 해준다. SQL을 직접 작성하는 경우에는 두 개의 테이블의 정보를 따로 읽어오는 것을 한방에 읽도록 만들려면 DAO 코드를 수정해야 한다. 또, 이를 사용하는 코드도 수정해야 한다. User만 읽었다가, User 내의 groupid 값을 가지고 다시 Group을 읽었던 것을 User와 Group을 한방에 읽어주는 메소드를 사용하도록 수정하는 등의 작업 말이다. 하지만 지능적인 ORM에서는 그런 수고가 필요없다.

그래서 JPA와 같은 ORM은 DAO계층이 제대로 분리된 독립적인 계층으로 존재할 수 있도록 만들어준다. 구현 기술은 물론이고 가져오는 데이터의 범위도 신경쓰지 않아도 된다. User를 가져와서 User 테이블 내의 정보만 사용하든지 Group 내의 정보까지 사용하든지 상관없다. 마치 DB가 아니라 메모리 내에 존재하는 거대한 오브젝트 정보를 사용하듯이 사용하면 된다. 개발은 편해지고, 중복은 제거되고, 분석과 설계 모델과 일치하고 연동하는 코드를 유지할 수 있으며, 테스트 편의성이 증대된다. 결국 개발생산성은 올라가고, 적절한 튜닝을 적용하면 SQL을 직접 짜는 것과 성능면에서 거의 차이 없는 코드를 만들 수 있다. 리포트성 쿼리도 얼마든지 QL을 이용하거나 네이티브 쿼리를 사용해서 혼합해서 사용하면 된다.

이 때 백그라운드에서 중요한 역할을 해주는 기술이 바로 lazy-loading이다. DAO 입장에서는 User를 읽을 때 Group까지 필요한지 아닌지 모른다. 물론 애플리케이션을 개발완료하고 사용 패턴을 분석해보니 90% 이상이 User를 쓸 때는 Group도 쓴다고 하면 최적화를 해서 항상 조인을 이용해서 두 엔티티를 eager fetching 해오는 것이 좋다. 반면에 개발 중에는 일단 각각 독립적으로 가져오는 구조로 만들어두는 것이 낫다. 즉, getUser()를 실행하면 일단 User만 가져오고 Group은 DB에서 읽지 않는다. 하지만 이 User 오브젝트를 가져간 코드에서 Group 내부의 정보까지 접근하면 그때 Group을 DB에서 읽는 기능이 백그라운드에서 동작하고 그 결과를 즉석에서 넣어주면 된다. 만약 끝까지 Group의 정보에는 접근하지 않는다면 그대로 두어도 좋다. 중요한 것은 이 과정이 투명해야 한다는 것이다. 즉 서비스 계층 코드에서 user.getGroup().getName() 하거나, 뷰에서 ${user.group.name}과 같이 해서 오브젝트 관계를 따라서 접근하려고 하면 자동으로 lazy-loading이 일어나야지, 명시적으로 lazyLoad(user.group); 과 같은 코드를 사용하도록 해서는 안된다는 것이다. JPA나 하이버네이트라면 이런 건 신경쓰지 않아도 알아서 되니 편하다.

문제는 SQL을 명시적으로 사용해서 DAO를 작성하는 JDBC나 iBatis 방식의 DAO이다. JDBC를 그대로 쓰는 DAO를 만들 땐 어쩔 수 없이 강한 결합을 가지고, 구현 내용을 외부에서 알고 있어야 하는 DAO를 만들 수 밖에 없을까?

물론 그건 아니다. 이 때도 원한다면 lazy-loading 사용하게 할 수 있다.

사실 토비의 스프링 3에서는 이런 얘기를 하면서 ‘도메인 오브젝트 중심의 코드를 만들려면 가능한 JPA/하이버네이트를 사용하면 좋겠지만, 굳이 SpringJDBC를 사용해야 한다면 lazy-loading을 직접 구현하셈’이라고만 하고 넘어가버렸다. 나중에 생각해보니 조금 무책임한 설명인 듯 해서, springusergroup예제의 스프링JDBC를 이용한 DAO에서 lazy-loading을 적용한 예를 직접 만들어서 넣었다. Lazy-loading 구현은 사실 어렵지 않다. 나는 TDD로 해서 한 30분 만에 만들었다.

그 덕분에 JPA와 JDBC를 사용한 두 개의 DAO를 함께 사용하지만 이를 사용하는 코드에서는 어떤 기술을 사용했는지에 대해서 신경쓰지 않고 도메인 오브젝트의 구조를 따라서 코드를 작성할 수 있게 만들 수 있었다. 어떤 식으로 만들었고, 어떻게 사용하는 지에 대해서는 다음 번으로 미뤄야 겠다. 오늘은 얘기가 너무 길었네.

Related posts:

  1. [토스3] 스프링 JDBC DAO에 lazy-loading 적용하기 (2)
  2. Spring 3.0 (15) Jdbc 모듈의 선택 라이브러리 분석
  3. [토스3] 매핑 가능한 BeanPropertySqlParameterSource
  4. Spring 3.0 @MVC 메소드에서 자동으로 리턴 모델에 추가되는 것들
  5. [토스3] 스프링 3.0.4 <mvc:default-servlet-handler/>를 이용해서 UrlRewriteFilter없이 깔끔한 URL을 만들기
  6. [토스3] 테스트를 위한 필드 주입 유틸
  7. Spring Expressions(SpEL)를 이용한 Mockito Argument Matcher 만들기
  8. 토비의 스프링 3이 나오기까지 (7)
  9. 인터페이스 프로그래밍과 IDE 코드네비게이션편의성
  10. 하이버네이트에 대한 오해, 미신 그리고 무지
  11. AppFuse DAO Test 코드의 문제점
  12. Hibernate 성능 & 최적화
  13. Generic DAO Pattern with JDK5.0
  14. 토비의 스프링 3이 나오기까지 (13)
  15. Dependency Injection의 Dependency란 무엇인가?

Facebook comments:

to “[토스3] 스프링 JDBC DAO에 lazy-loading 기능 적용하기”

  1. Respect to post author, some wonderful entropy.

  2. Hello.This post was really interesting, especially since I was looking for thoughts on this matter last Sunday.

  3. Great goods from you, man. I’ve take into account your stuff prior to and you’re just extremely great. I actually like what you’ve got right here, really like what you’re saying and the way through which you are saying it. You make it entertaining and you still care for to keep it sensible. I cant wait to read far more from you. That is really a terrific site.

  4. Wow! This could be one particular of the most useful blogs We have ever arrive across on this subject. Actually Wonderful. I’m also an expert in this topic so I can understand your hard work.

  5. I like this website so much, saved to fav. “I don’t care what is written about me so long as it isn’t true.” by Dorothy Parker.

  6. Thank you for the sensible critique. Me and my neighbor were just preparing to do a little research on this. We got a grab a book from our area library but I think I learned more clear from this post. I am very glad to see such fantastic information being shared freely out there.

  7. It’s laborious to search out educated folks on this matter, but you sound like you know what you’re speaking about! Thanks

  8. Enjoyed reading this, very good stuff, appreciate it. “A man may learn wisdom even from a foe.” by Aristophanes.

  9. I like what you guys are up also. Such smart work and reporting! Keep up the superb works guys I have incorporated you guys to my blogroll. I think it will improve the value of my site :)

  10. I loved as much as you will obtain performed right here. The caricature is attractive, your authored subject matter stylish. nonetheless, you command get bought an edginess over that you would like be turning in the following. ill undoubtedly come more until now once more as exactly the similar nearly very frequently within case you defend this hike.

  11. I gotta favorite this website it seems invaluable very beneficial

  12. Of course, what a fantastic blog and enlightening posts, I surely will bookmark your website.Have an awsome day!

  13. Thanks on your marvelous posting! I quite enjoyed reading it, you could be a great author.I will remember to bookmark your blog and will eventually come back from now on. I want to encourage you to continue your great job, have a nice weekend!

  14. Simply desire to say your article is as astonishing. The clarity in your post is simply excellent and i could assume you’re an expert on this subject. Fine with your permission let me to grab your RSS feed to keep up to date with forthcoming post. Thanks a million and please carry on the gratifying work.

  15. Hiya, I am really glad I’ve found this information. Today bloggers publish only about gossips and internet and this is actually annoying. A good web site with interesting content, this is what I need. Thank you for keeping this website, I’ll be visiting it. Do you do newsletters? Can not find it.

  16. I like the valuable info you provide on your articles. I will bookmark your blog and test once more right here frequently. I am rather certain I will learn many new stuff right here! Best of luck for the following!

  17. Really informative article post.Thanks Again. Really Great.

  18. It is really a nice and useful piece of information. I am glad that you shared this helpful information with us. Please keep us up to date like this. Thank you for sharing.

  19. Normally I don’t read post on blogs, however I would like to say that this write-up very compelled me to take a look at and do so! Your writing style has been surprised me. Thank you, quite nice article.

  20. Way cool! Some extremely valid points! I appreciate you writing this article and the rest of the website is extremely good.

  21. I really liked your article post.Much thanks again. Really Great.

  22. Say, you got a nice post.Much thanks again.

  23. You ave made some really good points there. I looked on the internet for additional information about the issue and found most people will go along with your views on this website.

  24. pretty practical material, overall I believe this is really worth a bookmark, thanks

  25. I simply could not depart your site prior to suggesting that I extremely loved the usual info a person supply on your guests? Is going to be again continuously to investigate cross-check new posts

  26. Major thanks for the blog article. Keep writing.

  27. Thanks-a-mundo for the blog.Really thank you!

  28. Some genuinely interesting information, well written and broadly user pleasant.

  29. I truly appreciate this article.Much thanks again. Fantastic.

  30. Say, you got a nice blog.Really looking forward to read more. Want more.

  31. Very good article post.Much thanks again. Fantastic.

  32. Thanks, I ave been looking for information about this topic for ages and yours is the best I have located so far.

  33. This awesome blog is without a doubt entertaining as well as amusing. I have discovered many handy stuff out of this blog. I ad love to go back again and again. Thanks a lot!

  34. Would you be occupied with exchanging hyperlinks?

  35. Looking forward to reading more. Great article post.Much thanks again. Awesome.

Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

© 2017 Toby's Epril Suffusion theme by Sayontan Sinha