개발 등/SPRING

[펀글] Spring Framework의 구조에 대해서

darkhorizon 2009. 3. 30. 22:55
728x90
반응형

Inversion of Control in Spring Framework

작성자 : 김문규 (MyMK)
최초 작성일 : 2008. 7.10

IoC는 Spring Framework의 장점을 꼽으라면 가장 먼저 언급되는 개념입니다. 한국어로 변역하면 '제어의 역행'! 한국어가 더 어려워 보입니다. 비슷한 말로 Dependency Injection(DI)라는 말고 있습니다. 한국말로는 의존성 삽입! 아하~ 조금 이해가 되시나요?

간단하게 이해하기 쉽게 같이 알아보지요.

1. 개념
 객체 간의 의존관계를 객체 내부에 선언 또는 정의하지 않고, 외부의 조립기를 이용하여 의존 관계를 설명한다는 것

2. 예제
객체간의 연관성을 선언하는 3가지 방법을 보고, 문제점이 어떻게 IoC(DI)를 이용해서 해결되는 지 알아보지요.

1) 직접 선언하는 방법

public class WriteArticleServiceImpl {
    private ArticleDao articleDao = new MysqlArticleDao();
    ...
}

 - 쉽습니다.
 - 하지만, 이를 테스트하기 위해서는 MySqlArticleDao가 정상적으로 동작해야 합니다. 어렵습니다.
 - 또한, OracleArticleDao를 사용하기로 바뀌었다면 코드를 고쳐야 하지요~ 물론 컴파일도 다시요. 귀찮습니다.

2) Factory 패턴, JNDI를이용하는 방법

public class WriteArticleServiceImpl {
    private ArticleDao articleDao = ArticleDaoFactory.create();
    ...
}

 - 조금 나아졌나요? 최소한 Oracle로 바뀌어도 코드 수정은 안해도 되겠네요~ ^^
 - 근데 테스트 측면에서는 전혀! 나아진게 없어 보입니다. 올바르게 동작하는 Factory와 JNDI에 등록된 객체가 필요합니다.

3) 외부 조립자를 이용하는 방법

public class WriteArticleServiceImpl {
    private ArticleDao articleDao;
    public WriteArticleServiceImpl(ArticleDao articleDao) {
        this.articleDao = articleDao;
    }
    ...
}

외부 설정 파일 (applicationContext.xml)
<bean name="writeArticleService" class="com.sec.service.WriteArticleServiceImpl">
    <constructor-arg><ref-bean="articleDao" /></constructor-arg>
</bean>

 - 외부 설정(applicationContext.xml)에서 객체간의 의존성을 설명하고 있다는 감이 오시지요? 바로 이겁니다. 외부에서 객체 의존성을 정의하고 있는 것이지요. 책에서는 조립한다고 설명하더군요. (Nice!)
 - 여기서는 생성자를 이용한 방법을 사용하지만 setter를 이용하는 방법도 있습니다. 요건 나중에 차차..
 - 이제 위에서 말한 2가지 문제점이 다 해결되어 보이지요? 아하~ 굳입니다. ^^

3. 참조
1) 웹 개발자를 위한 스프링 2.5 프로그래밍
2)
http://martinfowler.com/articles/injection.html
 - 다음은 IoC 설명의 정석으로 불려지는 Martin Fowler의 글입니다. 시간이 되시면 읽어보세요. 위의 설명이 좀 더 잘 이해되실 겁니다.


-----------------------------------------------------------------------------------------------

Aspect Oriented Programming (AOP) in Spring Framework

작성자 : 김문규
최초 작성일 : 2008. 7.10

1. 정의
AOP는 Spring Framework의 중요한 특징 중 하나입니다.
AOP란, 기능을 핵심 비지니스 로직과과 공통 모듈로 구분하고, 핵심 로직에 영향을 미치지 않고 사이사이에 공통 모듈을 효과적으로 잘 끼워넣도록 하는 개발 방법입니다.
공통 모듈은 보안 인증, 로깅 같은 요소들이 해당됩니다.

예를 들어 다시 설명하면, 로그를 찍기위해 로그 출력 모듈을 만들어서 직접 코드 사이사이에 집어 넣을 수 있겠지요? 이런건 AOP 적이지 않은 개발입니다.
반면에 로그 출력 모듈을 만든 후에 코드 밖에서 이 모듈을 비지니스 로직에 삽입하는 것이 바로 AOP 적인 개발입니다. 코드 밖에서 설정된다는 것이 핵심입니다.

2. 예제
1) AOP스럽지 못한 코드

public class InventoryController implements Controller {
    protected final Log logger = LogFactory.getLog(getClass());
    private ProductManager productManager;
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String now = (new java.util.Date()).toString();
        logger.info("returning hello view with " + now);
        Map<String, Object> myModel = new HashMap<String, Object>();
        myModel.put("now", now);
        myModel.put("products", this.productManager.getProducts());
        return new ModelAndView("hello", "model", myModel);
    }
    public void setProductManager(ProductManager productManager) {
        this.productManager = productManager;
    }
}

로그를 찍고 싶은 지점에 로깅 코드를 직접 삽입하는 방법입니다. 물론 이렇게 하는 것이 효율적일 수도 있습니다. 하지만, 클래스 진입 시점마다 로그를 찍는 것과 같이 동일한 패턴이 있는 경우에는 rule을 정의하고 여기에 따라서 동일한 모듈이 호출된다고 하면 매우 직관적이고 간결하면서 유지 보수가 편하게 구현이 될것으로 생각됩니다. 이것을 지원하는 것이 바로  AOP이며 spring에서는 이를 지원하고 있습니다.

2) Spring에서 AOP를 사용하는 방법
Spring에서는 크게
 - Spring API를 이용하는 방법
 - XML schema을 이용하는 방법
 - Annotation 기능을 이용한 방법
이 있습니다.
여기서는 2번째 XML schema를 이용하는 방법의 예제를 소개합니다.
전체 소스를 첨부합니다.


loggingaspect.java

package springapp.common;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
public class LoggingAspect {
 protected final Log logger = LogFactory.getLog(getClass());
// pointcut method 호출 전에 실행 시킬 로깅 함수
public String beforeLogging(JoinPoint joinPoint) {
  String methodName = joinPoint.getSignature().getName();
  logger.info("calling: " + methodName);
  return methodName;
 }

// pointcut method 결과 리턴 후에 실행 시킬 로깅 함수
 public void returningLogging(JoinPoint joinPoint, Object ret) {
  String methodName = joinPoint.getSignature().getName();
  logger.info("called successfully: " + methodName + " returns " + ret);
 }

// pointcut method에서 예외 발생시에 실행 시킬 로깅 함수
 public void throwingLogging(JoinPoint joinPoint, Throwable ex) {
  String methodName = joinPoint.getSignature().getName();
  logger.info("exception occured: " + methodName + " throws "
     + ex.getClass().getName());
 }

// pointcut method 종료 후에 실행 시킬 로깅 함수
 public void afterLogging(JoinPoint joinPoint) {
  String methodName = joinPoint.getSignature().getName();
  logger.info("finish call: " + methodName);
 }
}


applicationContext.xml

.... (다른 설정 생략)

<bean id="logging" class="springapp.common.LoggingAspect" />
 
<aop:config>
  <aop:pointcut id="publicMethod" expression="execution(public * springapp.service..*.*(..))" />
  <aop:aspect id="loggingAspect" ref="logging">
      <aop:before pointcut-ref="publicMethod" method="beforeLogging" />
      <aop:after-returning pointcut-ref="publicMethod" method="returningLogging" returning="ret" />
      <aop:after-throwing pointcut-ref="publicMethod" method="throwingLogging" throwing="ex" />
      <aop:after pointcut-ref="publicMethod" method="afterLogging" />
  </aop:aspect>
</aop:config>

.... (다른 설정 생략)


먼저 AOP 관련 용어를 설명하도록 하겠습니다.
 - advice : 언제 어떤 기능을 적용할 지에 대한 정의
 - joinpoint : 공통 기능 적용 가능 지점 (spring에서는 메서드 호출만이 가능합니다.)
 - pointcut : joinpoint 중에 실제로 적용할 지점
 - weaving : 핵심 로직에 공통 로직을 삽입하는
 - aspect : 여러 객체에 공통으로 적용되는 공통 관심 사항

설정 정보는 아래의 구조로 생성합니다.
<aop:config>
    <aop:aspect> : aspect를 설정
        <aop:before> : method 실행 전
        <aop:after-returning> : method 정상 실행 후
        <aop:after-throwing> : method 예외 발생 시
        <aop:after> : method 실행 후 (예외 발생 예부 상관 없음)
        <aop:around> : 모든 시점 적용 가능
    <aop:pointcut> : pointcut 설정

pointcut 설정은 AspectJ의 표현식에 따릅니다.

execution( 수식어패턴 리턴타입패턴 패키지패턴.메소드이름패턴(파라미터패턴) )


이제 모든 설정은 끝났습니다. 그닥 힘들이지 않고 설정할 수 있습니다. 하지만 joinpoint에 해당되는 지점만 적용이 가능하기 때문에 사용해야 할 시점을 잘 선택해야 할 듯 합니다.

3. 맺음말
지금까지 필터같은 기술을 이용해서 이와 비슷한 기능을 구현 했었지요. 실제 대형 과제를 할 경우에는 더욱더 필요한 기능이 아닌가 싶습니다. 아무리 모듈화를 하고 그 모듈을 적용한다 하더라도 일일이 코드에 적용하는 것은 정말 귀찮은 일일뿐 아니라 오류의 소지도 많아지게 됩니다. 그렇기 때문에 AOP는 정말 유용한 기능이 아닐 수 없습니다. 개인적인 생각으로는 이거 하나만으로도 spring을 써야할 이유가 아닐까 합니다. 제가 드린 예제를 기반으로 꼭 여러분의 과제에 적용해 보시길 바랍니다. 감사합니다.

4. 참조
마지막으로 AOP를 이해하는 것에 많은 도움을 준 칼럼을 소개하고자 합니다. 한빛미디어에 김대곤님이 기고하신 글입니다.

less (닫기)..

AOP 개요

저자:
김대곤(private@roadtohome.com)

본 기사는 Aspect Oriented Programming에 대해 간략한 소개글이다. 아직까지는 생소한 분야일 수 있겠지만, 점점 더 많이 듣게 되리라 생각된다. AOP를 설명하는데 있어서 자주 등장하는 네 개의 용어들(Aspect, Cross-cutting concern, Point-cut, Advice)를 설명함으로서 AOP가 왜 등장하게 되었으며, AOP가 제시하는 해결책에 대해 살펴볼 것이다. 먼저 "Aspect", "Oriented", "Programming"에서 생소한 단어는 단연 "Aspect"일 것이다. 야후 사전의 정의에 따르면, "Aspect"은 "사물의 면, 국면, 관점"으로 정의되어 있다. 소프트웨어 시스템은 여러가지 관점에서 바라볼 수 있다, 또는 여러 가지 단면을 가지고 있고 있다. 예를 들어, 자금 이체를 하는 프로그램을 작성한다고 생각해 보자. 출금계좌와 입금계좌, 그리고 이체금액을 입력받아 SQL 문장 또는 함수 한 번 돌리는 것으로 끝나는가? 절대 아니다. 먼저, 해킹을 방지하기 위해 사용자가 적절한 보안 프로그램을 설치했는지 점검하는 코드도 있어야 하고, 사용자가 인증되었는지 점검하는 코드도 써야 하고, 상대방 은행에서 적절하게 처리되었는지도 점점해야 하고, 혹시 사용자가 이체버튼을 두 번 누른 것은 아닌가 체크해야 하고, 시스템 로그도 남겨야 한다. 즉, 구현하려고 하는 기능 뿐 아니라 보안, 인증, 로그, 성능와 같은 다른 기능들도 녹아 있어야 한다. 어쩌면 이체를 위한 코드보다 잡다한 다른 측면의 문제들을 다루는 코드가 더 길어질 수 있다. 이런 코드들은 입금이나 출금 같은 다른 곳에서 들어가야 한다. 구현하려고 하는 비즈니스 기능들을 Primary(Core) Concern, 보안, 로그, 인증과 같이 시스템 전반적으로 산재된 기능들을 Cross-cutting concern이라고 부른다. AOP는 Cross-cutting concern를 어떻게 다룰 것인가에 대한 새로운 패러다임이라고 할 수 있다.

AOP는 구조적 방법론에서 객체지향 방법론으로 전환처럼 시스템 개발에 관한 전체적인 변화는 아니다. Object-Oriented Programming이 Aspect-Oriented Programming으로 대체되는 일은 없을 것이다. AOP는 구조적 방법론에도 적용될 수 있고, 다른 방법론에도 다 적용될 수 있지만, 주로 객체지향방법론이 가지는 단점을 보완하는 것으로 묘사되고 있다. 그러면 객체지향 프로그래밍이 또는 다른 이전의 프로그래밍 기법들이 Cross-cutting Concern를 어떻게 다루는지 알아보자. 매우 간단하다. Primary Concern를 구현한 프로그램에 함께 포함시켰다. 그것이 단 한 줄의 메소드 호출이라 하더라도. 많은 프로그래머들은 거의 모든 프로그램에 산재된 로그하는 단 한 줄의 코드를 찾아서 바꾸어 본 경험이 있을 것이다. 또는 간단하게 생각하고 프로그램을 수정하려고 했는데, 도데체 어디를 수정해야 되는지 모르게 코드가 길고, 알 수 없는 코드들이 자리를 차지하고 있을 때의 난감함. Primary concern, Cross-cutting concern이 하나의 프로그램 안에 들어가게 되면, 프로그램을 이해하기가 힘들고, Cross-cutting concern 코드가 여기저기에 산재되어 수정하기 힘들게 된다. 당연히 생산성 떨어지고, 품질 떨어지고, 유지보수 비용 많이 들게 된다.

그럼 AOP는 Cross-cutting concern를 어떻게 처리하는가? 이것도 매우 간단하다. 새로운 아이디어라고 할 수도 없다. Primary Concern 구현하는 코드 따로, Cross-cutting concern 구현하는 코드 따로 쓰고, 나중에 두 개 조합하게 완벽한 어플리케이션 만들겠다는 것이다. 기술 용어로 쓰면, Advice(Cross-cutting concern 구현한 코드)와 Primary concern 구현한 코드를 Point-cut 정보를 이용해서 Weaving(조합)하는 것이 AOP가 이 문제를 다루는 방법이다.

사용자 삽입 이미지


기술적 용어로서의 "Aspect"은 "Advice"와 "Point-cut"을 함께 지칭하는 단어이다. Point-cut은 어떤 Advice를 Code 어느 위치에 둘 것인가 하는 것이다. 예를 들면, 로그 기능을 구현한 Advice는 Code 속에 있는 모든 public 메소드가 수행되고 나면, 그 마지막에 실행되어라 라고 지정한 것이라 할 수 있다.

이전까지의 객체지향 프로그래밍은 Cross-cutting concern을 정적으로 어플리케이션에 결합시킨 반면 AOP는 동적으로 Cross-cutting concern를 다룬다고 표현하기도 합니다. 용어에서도 알 수 있듯이 AOP는 소프트웨어 엔지니어링 원칙 중에 하나인 "Separation of concern"를 구현하려고 하고 있습니다. 이러한 문제들을 다루고 있는 분야 중에 하나는 디자인 패턴할 수 있고, 예를 들어, Visitor 패턴은 정적인 구조를 동적으로 바꾸려고 합니다. AOP가 현재까지 나온 방법들 중에서 Cross-cutting concern를 다루는 가장 좋은 방법인가 하는 질문엔 아직 답하긴 힘들 것 같습니다. 그럼에도 분명 언제가는 책상 위에 관련 서적 한 권 있어야 할 것 같은 분야가 될 것 같습니다.

출처 : http://network.hanb.co.kr/view.php?bi_id=968


--------------------------------------------------------------------------------------------------
Developing a Spring Framework MVC application step-by-step

작성자 : 김문규 (MyMK)
최초 작성일 : 2008.07.22

다음의 내용은 spring framework 공식 홈페이지(www.springframework.org)의 튜토리얼을 정리한 것입니다. 저와 같은 spring 초보자를 위해서 개인적인 방식으로 재구성하였습니다. 처음 spring을 접하는 분들이 학습에 참고 자료로 활용하시길 바랍니다.  

코드 자체의 세세한 부분은 다루지 않을 것입니다. spring 프레임워크을 이용해서 기본적인 웹 서비스를 구현하는 방법을 보여주는 것에 주력할 것입니다. 따라서, 이 글에서는 설정 파일의 작성에 대해서 중점적으로 논의하겠습니다.
(이 글은 기본적인 자바 웹 개발에 경험이 있는 분을 기준으로 작성합니다.)

1. Source Structure


사용자 삽입 이미지

튜토리얼치고는 소스와 설정파일이 많은 편입니다. 이는 Spring 자체가 MVC를 지원하는 프레임워크이고 설정 파일도 논리적으로 분리하는 것을 권장하기 때문입니다. 튜토리얼에서도 이 개념은 유지하고 있습니다. 이는 차차 설명하기로 합니다.

1) 소스 구조
 최상위 폴더는 아래와 같습니다.
┌ bin - 컴파일된 class 저장
├ db - DB와 관련된 스크립트
├ src - 말 그대로 소스들
└ war - WAS에 deploy될 것들
을 가리킵니다.

각각의 폴더에 대해 조금 더 자세히 알아보도록 하겠습니다. 조금 생소한 부분만 설명합니다. 너무 쉬운 부분은 넘어가도록 하겠습니다.

/src
springapp은 최상위 package이고 여기에 domain, repository, service, web이라는 하위 package가 존재합니다. 아래의 기준으로 클래스 모듈을 위치 시킵니다.
. domain - data 모델링 객체
. repository - DAO 과 관련된 객체
. service - 비지니스 로직
. web - web 프리젠테이션 관련 객체

□ /war
. /WEB-INF/tld/spring-form.tld
spring에서 사용할 수 있는 tag library의 정의입니다. tag library는 기존 jsp에서 사용하던 tag를 spring에서 재정의 한 것으로 좀 더 사용이 편합니다.
. /WEB-INF/*.xml
설정 파일들입니다. 실질적으로 spring을 사용함에 있어 가장 먼저 이해하여야 할 부분입니다. 뒤에서 자세히 설명하도록 하겠습니다.






















2. Configuration 관련

1) web.xml
  □ org.springframework.web.context.ContextLoaderListener
     . 계층별로 나눈 xml 설정파일을 web.xml에서 load되도록 등록할 때 사용.
     . 기본값으로 applicationContext.xml을 사용함
   예시)
   <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
   </listener>
   <context-param>
         <param-name>contextConfigLocation</param-name>
         <param-value>
              /WEB-INF/mars-ibatis.xml
              /WEB-INF/mars-service.xml
         </param-value>
   </context-param>

  □ <taglib>
     . spring tag library의 위치 설정

2) applicationContext.xml
  □ ContextLoaderListener(또는 ContextLoaderServlet)가 ApplicationContext를 만들 때 사용

  □ ApplicationContext란, 웹에 독립적인 애플리케이션의 모든 영역(dao, service, manager, 기타 등등) 에 대한 정의를 말합니다.

  □ connection pool에 대한 정의
     . org.apache.commons.dbcp.BasicDataSource
        : datasource를 정의, db driver, 보안 정보등을 알림
     . org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
        : 읽어들일 property 파일에 대한 정보를 알림

  □ transaction에 대한 지원을 위한 한 방법
  이 부분은 나를 비롯한 spring 초보자의 입장에서 아직 이해하기 어려운 부분이기 때문에 아래의 코드가 AOP 기법을 활용하여 transaction을 지원하고 있다는 것만 알아두기로 하자.

transaction 지원 코드 보기


3) springapp-servlet.xml
  □ DispatcherServlet이 WebApplicationContext를 만들 때 사용

  □ WebApplicationContext이란, 특정 웹(DispatcherServlet)에 종속되는 영역(Controller 및 각종 웹관련 설정 bean들)에 대한 정의를 말합니다. (ApplicationContext과의 차이를 확인하세요.)

  □ 주로 웹 페이지 리소스에 대한 핸들러 클래스 빈의 정의가 존재합니다.

  □ org.springframework.context.support.ResourceBundleMessageSource
     . message.properties 파일에 정의된 속성값을 사용할 수 있게 합니다.
. 국제화에 응용되면 아주 좋아 보입니다~

  □ org.springframework.web.servlet.view.InternalResourceViewResolver
     . prefix와 suffix 값의 정의에 따라 리소스 표현을 축약할 수 있도록 함.
     예시)
    <bean id="viewResolver"
     class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass"
          value="org.springframework.web.servlet.view.JstlView"></property>
        <property name="prefix" value="/WEB-INF/jsp/"></property>
        <property name="suffix" value=".jsp"></property>       
    </bean>
     라고 선언하면 /WEB-INF/jsp/testfile.jsp은 비지니스 로직 내부에서 testfile로 간단하게 표현 가능 함



3. Inside Code
spring web service는 아래의 흐름을 가집니다.
①(사용자) jsp 호출
②(웹서버) 설정 검색
③(웹서버) 요청 처리 클래스 호출
④(웹서버) 비지니스 로직 수행
⑤(웹서버) view로 결과 반환
⑥(사용자) jsp 화면


1) Controller
  □ 사용자에게 jsp가 호출되면 xxxxx-servlet.xml에 정의된 클래스 빈을 호출합니다.

  □ 예제에는 DB에 있는 정보를 가져다가 저장하는 페이지와 원하는 값을 저장하는 페이지 두가지가 있습니다. hello.jsp가 전자에 해당하고 priceincrease.jsp는 후자에 해당합니다. 각 페이지를 확인하시고 각 페이지의 핸들러 클래스 빈을 springapp-servlet.xml에서 추적해 보시길 바랍니다.

  □ 일반적인 controller의 사용 예 : springapp.web.InventoryController.java
     . 일단 기본적인 Controller의 예시로 Controller interface를 구현합니다. 대부분의 경우에 사용하면 되고, request에 대한 처리를 위해 handleRequest()로 진입합니다.
     . 여기서 비지니스 로직을 처리한 후, ModelAndView 객체를 반환합니다. 이 때 넘기고자 하는 데이터를 파라미터에 담아서 보낼 수 있습니다.

    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String now = (new java.util.Date()).toString();
        logger.info("returning hello view with " + now);
        Map<String, Object> myModel = new HashMap<String, Object>();
        myModel.put("now", now);
        myModel.put("products", this.productManager.getProducts());
        return new ModelAndView("hello", "model", myModel);
    }

  □ 폼을 처리하는 controller의 예 : springapp.web.PriceIncreaseFormController.java
     .  jsp가 폼을 다루고 있을 경우에는 SimpleFormController Class를 상속하여 확장합니다.
     . onSubmit()은 jsp에서 버튼이 눌려진 경우 콜백 지점을 가리키도 formBacjingObject는 폼에 기본값을 저장하고자 할 때 사용합니다.
     . formBackngObject는 domain에 존재하는 모델링 객체를 이용하고 있음에 주의합니다.

    public ModelAndView onSubmit(Object command)
            throws ServletException {
        int increase = ((PriceIncrease) command).getPercentage();
        logger.info("Increasing prices by " + increase + "%.");
        productManager.increasePrice(increase);
        logger.info("returning from PriceIncreaseForm view to " + getSuccessView());
        return new ModelAndView(new RedirectView(getSuccessView()));
    }
    protected Object formBackingObject(HttpServletRequest request) throws ServletException {
        PriceIncrease priceIncrease = new PriceIncrease();
        priceIncrease.setPercentage(20);
        return priceIncrease;
    }

2) Model
  □ 비지니스 로직을 수행 중에 데이터를 읽고 쓰고 수정하고 삭제하기 위해 데이터 베이스에 대한 접근을 수행합니다. 해당 예제는 HSQLDB를 사용하고 있으며 이를 JDBC로 연결하고 있습니다.

  □ JDBC와 관련하여 몇개의 클래스를 추가 지원하고 있지만 미미합니다. 실제로는 iBatis, Hibernate를 지원하고 있다는 것이 더 큰 특징이라 하겠습니다.

  □ 간단한 예제를 확인하시길 바라며, 이 부분은 설명하지 않겠습니다.

3) View
  □ 예제에서는 JSTL(Java Server page Standard Tag Library)이라는 것을 사용하고 있기에 약간의 공부가 필요합니다.
     . 예를 들면, <input type='text' /> 이렇게 사용하던 것을 <form:input path="percentage"/> 이런식으로 사용합니다.
     . iteration, print 같은 주요 제어문과 폼이 tag로 정리되어 있습니다.
     . 더 자세한 내용은 http://java.sun.com/products/jsp/jstl/ 참조 바랍니다.



4. Ending
이 문서의 목적은 spring을 자세하게 설명하는 것이 아니라, 튜토리얼데로 spring MVC를 간단하게 한번 만들어 보는 것에 그 의의를 두고 있습니다. 튜토리얼과의 차별화를 위해 Top-down 방식을 취했으며 전체의 구성을 설명하고자 했습니다.
저도 약 2주전부터 틈틈히 공부하면서 알게된 것을 정리한 것이라 부족한 면이 많습니다. 시간이 나는데로 잘못된 부분은 수정하고 필요한 내용은 추가하겠습니다.
긍적적이고 발전적인 커멘트를 기다립니다. 감사합니다.

----------------------------------------------------------------------------------------------------
Persistance Support of Spring Framework

작성자 : 김문규
최초 작성일 : 2008. 8. 1

Spring에서 DB에 연결하는 방법에 대해서 알아보려 합니다. 다음과 같이 세가지 방법이 있습니다.
 - JDBC를 이용하는 방법
 - JdbcDaoSupport 클래스를 이용해서 JDBC를 조금 편하게 템플릿화하는 방법
 - SqlMapClientDaoSupport 클래스를 이용해서 iBatis Framework를 연동하는 방법

각 방법이 어떻게 다른지를 설명드리고 튜토리얼 수준의 예제를 보여드리겠습니다.

1. JDBC
일반적으로 JDBC를 이용할 경우 아래의 순서를 따라서 구현됩니다. 
 - DriverManager에 해당 DBMS Driver를 등록
 - 해당 Driver로 부터 Connection 객체 획득
 - Connection 객체로부터 Statement 객체 획득
 - Statement의 method를 이용하여 SQL실행
 - ResultSet 으로 받아서 처리(executeUpdate 의 경우엔 제외)
 - 객체 close() (ResultSet, Statement, Connection)

여기서 실제로 개발자의 비지니스 로직이 들어가는 부분은 4번과 5번의 일부입니다. 나머지는 음... 기계적인 코드입니다. 실제 코드를 예로 들면 아래와 같습니다.

Connection.java

.........
Connection con = null;
Statement st = null;
ResultSet rs = null;
 
try {
    con=DriverManager("jdbc:mysql://localhost/dbname","root","1234");  // dbname에는 사용하는 database 이름, root 계정, 패스워드는 1234

    Statement st=con.createStatement();
    ResultSet rs=st1.executeQuery("select * form names");
 
    if( rs.next() ) {
        do {
            // result set을 잘 정리합니다. 물론 일일이
            ...
            // 비지니스 로직을 수행합니다.
            ...
        } while( rs.next() )
    }
} catch (Exception ex) {
    // 적절한 예외 처리
} finally {
    if( rs != null) rs.close();
    if( st != null ) st.close();
    if( con != null ) conn.close();
}
.........

의외로 너무 많은 부분인 반복되는 느낌입니다. 이런 부분은 Spring을 사용하면 많이 줄어들게 됩니다. 다음절에서 확인해 보지요.

2. DaoSupport in Spring Framework
Spring에는 Template이라는 것을 이용해서 이 과정을 알아서 처리해 줍니다.
 - JdbcTemplate
 - NamedParameterJdbcTemplate
 - SimpleJdbcTemplate
 
이를 편하게 사용하기 위해서 주로 아래의 Dao 클래스를 이용합니다.
 - JdbcDaoSupport
 - NamedParameterJdbcDaoSupport
 - SimpleJdbcDaoSupport

여기서 가장 다양한 기능을 가진 SimpleJdbcDaoSupport 클래스의 사용법을 코드에서 확인해 보도록 하겠습니다. (전체 코드는 이전 4번 튜토리얼 분석 포스트에서 사용했던 예제를 보시면 됩니다.)

applicationContext.xml

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
 
<bean id="productDao" class="springapp.repository.JdbcProductDao">
    <property name="dataSource" ref="dataSource"/>
</bean>

ProductDao.java

public class JdbcProductDao extends SimpleJdbcDaoSupport implements ProductDao {
    public int selectCount() {
        return getSimpleJdbcTemplate().queryForInt(
                "select count(*) from products");
    }
}

음..확실히 간편해 진것 같습니다.

하지만, 쿼리문이 코드 내부에 하드코딩되어 있다는 점이 약간 아쉬운 부분입니다. 쿼리문 변경 시에 코드도 같이 변경되고 재컴파일도 되어야 합니다. (뭐 솔직히 컴파일 하면 되긴 해요... ^^) 그리고 따로 설명하지 않겠지만 ParameterizeRowMapper 인터페이스를 이용해서 domain 객체와 결과를 연결하는 방법이 있는데 이 역시 하드코딩 되기 때문에 유지 보수의 유연성이 떨어지는 면이 없지 않습니다.

그래서, 쿼리 및 쿼리의 결과와 객체간의 연결을 외부에서 따로 관리하는 기능을 요구하게 되고 이런 기능을 가진 iBatis가 최근 많이 사용되고 있습니다. 다음 절에서 iBatis의 연동에 대해 알아보겠습니다.
 
3. Spring Framework와 iBatis 연동
1) iBatis
국내 SI 현실에 가장 잘 어울린다고 평가받고 있는 persistance framework 입니다. 그 이유는 SQL 쿼리문을 그대로 사용하기 때문에 기존 JDBC 개발자들에게 거부감이 덜하고  경험적(?)인 시스템의 이해가 빠르기 때문일 겁니다. 여타의 ORM framwork는 배우기도 힘들고 또는 제 3자 입장에서 이해하기도 힘들어서 우리나라 SI 업계처럼 개발팀이 이합집산하는 문화에는 어울리지 않는다는 군요.

각설하고 간단한 예를 보도록 하지요. iBatis에 대해서는 제가 따로 설명하지 않을 것입니다. 아래의 자료들을 보시면 간단하게 이해하실 수 있을 겁니다. 그리고, googling 하시면 JPetStore라는 예제가 있습니다. 이를 한번 실행시켜 보시는 것도 도움이 될 것입니다.

다음은 iBatis 관련해서 많은 자료를 만드신 이동국님의 Tutorial 변역본입니다. 8장밖에 안되지만 iBatis의 동작에 대해서 이해하는데 많은 도움이 되실 것입니다.
별첨으로 하나 더 첨부합니다. 이 파일은 sqlMap의 사용법에 대해 설명된 문서이고 역시 이동국님이 번역하신 자료입니다.

자~! 이제 iBatis의 사용법에 대해서 간단히 감을 잡으셨나요?
앞 절에서 언급한 것처럼 제 생각에는 2가지의 큰 장점을 가진다고 생각됩니다. (다른 장점이 있으시면 사알짝 좀 알려주세요~~)
 - SQL 쿼리문이 외부 설정 파일에서 관리된다.
 - domain 객체와 SQL result set의 연결이 외부 설정 파일에서 관리된다.

그런데 JDBC와 마찬가지로 iBatis에도 단순하게 기계적으로 반복되는 코드가 있습니다. 바로 SqlMapClient를 선언하는 부분이지요! (아래 코드 참조)
public MyAppSqlConfig {
    private static final SqlMapClient sqlMap;
    static {
        try {
            String resource = “com/ibatis/example/sqlMap-config.xml”;
            Reader reader = Resources.getResourceAsReader (resource);
            sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);
        } catch (Exception e) {
            .......
    }
}
 
JDBC를 지원하기 위해 JdbcDaoSupport 클래스가 있지요? 그럼 SqlMapClient를 지원해 주는 것은? 네! SqlMapClientDaoSupport 클래스 입니다. 그럼 드디어 sqlMapClientDaoSupport 클래스에 대해 알아보겠습니다.

2) sqlMapClientDaoSupport
앞서 알아본 JdbcDaoSupport와 이름도 비슷하지만 사용법도 매우 유사합니다. 설정 파일과 관련된 자바 코드를 보시지요.
applicationContext.xml - sqlMapConfig.xml - Product.xml 이 연쇄적인 관계를 가지고 있습니다. 여기서 applicationContext.xml은 spring 설정 파일이고 나머지는 iBatis 설정 파일입니다.
비지니스 로직 수행 중 iBatisMessageDao의 함수들이 요청될 것이고 이떄 Product.xml에 있는 쿼리문들을 호출해서 수행하게 됩니다.
주석문을 달고 색깔로 구분해 놓았습니다. 연관 관계에 유의하시면서 주의 깊게 확인하시길 바랍니다. (오랜만에 칼라풀한 포스트 입니다. ^^)

applicationContext.xml

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
</bean>

// sqlMapClient를 선언해야 합니다.
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
     <property name="dataSource" ref="dataSource" />
     <property name="configLocation" value="WEB-INF/sqlmap/sqlMapConfig.xml" />
</bean>

// DAO 입니다.
<bean id="productDao" class="springapp.repository.iBatisProductDao">
     <property name="sqlMapClient" ref="sqlMapClient" />
</bean>

<bean id="productManager" class="springapp.service.SimpleProductManager">
        <property name="productDao" ref="productDao" />
</bean>

sqlMapConfig.xml

// classpath가 root 디렉토리 임에 유의 하셔야 합니다.
<sqlMapConfig>
    <sqlMap resource="../sqlmap/Product.xml" />
</sqlMapConfig>

Product.xml

<sqlMap namespace="ProductNamespace">
    // Product 도메인 객체 입니다. 여기저기서 사용되게 됩니다.
    <typeAlias alias="Product" type="springapp.domain.Product" />

    <!-- getProductList -->
    // getProductList와 관련된 결과 값의 형태를 정의합니다.
    <resultMap id="getProductResultMap" class="Product">
        <result property="id" column="id" />
        <result property="description" column="description" />
        <result property="price" column="price" />
    </resultMap>
 
    <select id="getProductList" resultMap="getProductResultMap" resultClass="Product">
        select id, description, price from products
    </select>

    <!-- saveProduct -->
    // saveProduct와 관련된 입력 값의 형태를 정의합니다.
    <parameterMap id="saveProductParamMap" class="Product">
        <parameter property="id"  />
        <parameter property="description"  />
        <parameter property="price"  />
    </parameterMap>
 
    // parameter를 지정하는 방식에 유의 하시길 바랍니다.
    <update id="saveProduct" parameterClass="Product" >
        update products set description = (#description#), price = (#price#) where id = (#id#)
    </update>
</sqlMap>

iBatisMessageDao.java
 
// 코드 내부에는 전혀 쿼리 문이 없습니다!! 여기에 별 5개 꼭 확인하세요!
public class iBatisProductDao extends SqlMapClientDaoSupport implements ProductDao {
    @SuppressWarnings("unchecked")
    public List<Product> getProductList() {
        List<Product> products = getSqlMapClientTemplate().queryForList("getProductList");
        return products;
    }

    public void saveProduct(Product prod) {
        getSqlMapClientTemplate().update("saveProduct", prod);
    }
}

2절에서 설명한 JdbcDaoSupport 클래스의 사용법과 비교했을 때 iBatis는 이를 한단계 더 추상화 되어 있음을 알 수 있습니다. 모든 쿼리문과 해당 쿼리 결과와 객체간의 관계를 XML 외부 설정으로 따로 관리하고 있다는 것이죠.
그렇기 때문에, 앞에서 말씀드린데로 이 방법은 java 코드내에 하드코딩하는 것보다 개발의 유연성을 높이고 추후 관리시에 유리한 장점을 가집니다. (추상화란 처음에는 귀찮고 나중에는 편리한 것이지요!)

다음은 이전 4번째 spring 프레임워크 포스트에서 사용했던 튜토리얼을 iBatis로 연동시킨 예제입니다. 위에 설명드린 내용이 어떻게 적용되어 있는지 한번 더 확인하실 수 있을 것으로 생각됩니다.


4. 맺음말
휴..정말 긴 튜토리얼 리뷰였습니다. 매일 생각없이 JDBC로 개발하던 저에게는 참으로 재미난 것이 아닐 수 없었습니다.
물론 때에 따라서는 JDBC 자체가 빛나는 순간이 있겠지만, 대부분의 경우에는 iBatis가 우리를 편하게 해주지 않을까 생각됩니다. iBatis 관련 공부 중 이런 글이 있었습니다. "iBatis는 80%의 JDBC 기능을 20%의 자바 코드로 구현한 간단한 프레임워크이다."
맞습니다. 이는 만병통치약이 아닙니다. 다만 대부분에 경우에 잘 먹히는 고마운 녀석인 것이지요. 뭐 종합감기약 정도 ^^;
그렇기 때문에 여러분도 한번 직접 경험해 보시고 개인적인 판단을 세우시길 바랍니다. 여러분에게 맞지 않을 수 있으니까요. 이상입니다. 감사합니다.

5. 참고자료
1) 웹 개발자를 위한 스프링 2.5 프로그래밍 (가메출판사, 최범균 지음)





출처 : http://www.iamcorean.net/
728x90