Spring MVC

springmvc.egloos.com


포토로그


2012/02/20 20:05

MyBatis + 커넥션풀 + 트랜잭션 2장 Spring MVC

이제 거의 막바지에 왔다. 미리 만들어둔 users 테이블의 자바빈 클래스를 만들어 보자.

users테이블에 대한 자바빈 클래스를 만들었다면 이 자바빈 클래스을 MyBatis가 객체로 인식할 수 있게끔 mybatis-config.xml에 등록시켜야 한다. MyBatis 한글 레퍼런스 문서를 보면 이에 대한 상세한 설명이 담겨져 있으므로 꼭 한번 정독해보길 바란다.

위의 이미지처럼 한다면 User 클래스를 MyBatis에 매핑시키는 작업은 일단락 된 것이나 마찬가지다. 이제 users테이블에서 사용할 쿼리문을 users-sql.xml에 매핑시킬 차례다.

매퍼 XML파일을 이용해 쿼리를 생성하는 방법은 매우 쉽고 다양하다. 이에 대한 자세한 설명 역시 레퍼런스 문서를 참조하며 공부하는 것이 제일일 것이다. 그러므로 쿼리문을 생성하는 과정에 대해서는 깊게 설명하지 않겠다. 대신 users 테이블에 대한 기능을 만들기 전에 생각해야될 사항에 대해 잠시 말하고자 한다.

먼저 1장을 유심히 읽어보았다면 스프링이란 프레임워크를 고안해낸 창시자 로드 존슨이 "항상 프레임워크 기반으로 접근하라"라는 말을 했다고 했다. 이 말은 곧 자신이 만들 서비스에 대한 프레임워크를 만드는 방식으로 개발해야 한다는 말이다. 그렇다면 어떻게 해야 제대로된 프레임워크 기반으로 제작하는 것이 될까?

우리는 일단 프레임워크의 표준모델을 인식해야만 한다. java의 가장 표준적인 프레임워크는 map과 list같은 컬렉션 프레임워크를 들 수 있는데 우리가 만들 프레임워크 또한 이런 표준 프레임워크의 설계를 어느 정도 따라야 앞으로 만들어질 프레임워크를 제 3자가 사용하는데 있어서 쓸데없는 혼동과 학습과정을 줄일 수 있을 것이다. 예를 들어 map에는 데이터를 넣기 위해 'put'이란 메서드를 사용하는데 우리가 만들 프레임워크에는 동일한 기능의 메서드를 'vodkazzangzzangjoa'와 같이 지었다면 어떻게 될까?

사람들은 혼란에 빠지게 될 것이고 업무는 쓸데없는 창작 메서드명을 기억하는데 할애될 수 밖에 없을 것이다. 게다가 다른 모델을 본받지 않는 독자적인 설계모델이 올바르다고 보장할 수 있겠는가? 이런 기초가 없는 프레임워크 모델은 얼마 못가 한계를 보이게 되고 부득이한 수정과정을 거쳐야만 할 것이다. 그렇다면 우리가 이런 불필요한 문제를 해결하고 훌륭한 단독 프레임워크를 만들기 위해서는 어떻게 해야 할까?

이런 문제를 해결하기 위해선 우리가 처음 프레임워크를 개발할 때에 어느 정도 기존의 성공적인 모델을 본받고 이 표준프레임워크 모델을 모방하는 과정을 거쳐야 한다는 것이다. 그리고 점차 개발해 나가면서 표준프레임워크 모델로는 불충분 하다면 거기서 확장하는 방식으로 접근해 나가면 된다. 이런 접근법을 위해선 우리는 가급적 구체적으로 표준모델을 정하고 메서드 명 뿐만 아니라 리턴타입까지 일치시킬 수 있도록 노력 해야 한다.

이런 관점에서 예를 들어보자면 방금 전 예를 들었던 Map 인터페이스의 'put' 메서드는 데이터를 프레임워크에 저장하는 역할을 수행하며 저장의 성공여부를 불리언 값으로 리턴 해주고 있다. 혹시나 독자는 이런 불리언 리턴값이 불필요하다 생각들어도 만에 하나 이런 기능을 유용하게 사용하고 있는 사람들을 고려하여 최대한 비슷한 구조로 프레임워크를 만들 수 있도록 해야 한다.

다시 본론으로 돌아와 1부에서 예고 했던대로 SqlSessionDaoSupport를 상속받아 나만의 UserDao를 만들어 보도록 하자. 위의 예제는 users 테이블의 데이터를 삽입하는 'put'메서드인데 SqlSessionDaoSupport에 내장되 있는 getSqlSession()메서드를 활용해 users-sql.xml에 매핑되있는 SQL구문을 불러오고 있다.

원리는 다음과 같다. getSqlSession 메서드는 첫번째 인자로 users-sql.xml의 <mapper>요소 중 namespace속성(이 예제에서는 users)을 통해 어느 <mapper>를 불러올지 판단하고 그 세부 요소의 id를 선택하여 쿼리문장을 불러오는 형태이다. 2번째는 인자값으로 DB에 넘겨줄 파라미터가 된다. 만약에 레퍼런스 문서를 잘 읽었다면 굳이 설명이 필요 없을 정도로 잘 알고 있을테니 이런 부수적인 설명은 생략하기로 하겠다.

이제 만든 MyBatisUserDao를 빈에 등록시킨 다음 잘 동작하는지 JUnit테스트를 해보자. JUnit테스트를 위해서는 작성된 스프링 컨텍스트 XML파일이 target/classes폴더에 들어가야 하는데 이에 대한 자세한 설명은 다음의 포스트를 읽으면 감이 올 것이다.

테스트 결과는 성공이다. 이제 스프링과 MyBatis가 성공적으로 연결되었고 마지막으로 스프링 트랜잭션만 설정하면 끝이다.

<aop:config>
<aop:advisor pointcut="bean(*)" advice-ref="transactionAdvice" />
</aop:config>
<tx:advice id="transactionAdvice">
<tx:attributes>
<tx:method name="*" />
</tx:attributes>
</tx:advice>

위의 코드를 root-context.xml에 넣도록 하자. 그리고 네임스페이스에 'tx'와 'aop'를 추가해주면 트랜잭션에 대한 설정은 모두 끝난다! 정말 이걸로 되나 싶을 정도로 간단한 몇줄의 코드이긴 하지만 트랜잭션을 만만히 봤다간 큰코다칠 것이다. 과거에 트랜잭션은 피땀과 눈물을 흘려가며 구현했어야 마땅한 것이었다. 굉장히 고급기술이었고 일반인은 상상도 할 수 없는 기술이었다. 하지만 지금은 스프링같이 발달한 프레임워크 덕분에 우리는 간단한 설정만으로도 트랜잭션을 이용할 수 있게 되었다 :D

사실 위의 코드 몇줄로 트랜잭션의 설정이 모두 끝났다고는 할 수 없지만 여기서는 디테일한 설명보단 방법론에 대한 이야기만 하고 있으므로 나중에 좀 더 시간을 내어 설명하도록 하겠다. 


이것으로 모든 설정이 끝났고 이제는 개발만 남았다. 사실 중간 과정에서 MyBatisUserDao를 제대로 구현해 보려면 userDao 인터페이스를 구현한 뒤 이를 MyBatisUserDao가 상속받도록 설정하는 것이 마땅하지만 이런 부분은 토비의 스프링에서도 잘 설명해주고 있고 괜히 여기서 중복 설명해봤자 쓸데없는 참견이라 생각되어 생략하였다. 정말 별것도 아닌 걸로 질질 끌어댄 문서를 읽어주느라 수고했고 다음에 더 좋은 주제로 돌아오도록 하겠다.

덧글

  • 겸군 2013/01/21 13:47 # 삭제 답글

    동일하게 구축했는대 에러가 납니다 도와주세요 T.T
    public class HomeController {

    private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
    private MyBatisUserDao test;
    public void setMyBatisUserDao(MyBatisUserDao test){
    this.test = test;

    }
    /**
    * Simply selects the home view to render by returning its name.
    */
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String home(Locale locale, Model model) {
    logger.info("Welcome home! The client locale is {}.", locale);

    Date date = new Date();
    DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);


    User test1=new User(123,"abc","abc");
    boolean abc =test.put(test1);

    String formattedDate = dateFormat.format(date);

    model.addAttribute("serverTime", formattedDate );

    return "home";
    }

    }
    -----------------------------------------------------------------------------
    에러코드
    java.lang.NullPointerException
    at com.test.mybatis.HomeController.home(HomeController.java:42)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:213)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:126)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:96)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:617)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:578)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:923)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:852)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:778)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:936)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1004)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)

    --------------------------------------------------------------------------------------
    보시면 put을 가져다가 바로 insert하는대 널포인트 에러가 나네요 왜그럴까요?
  • jaeokbr 2013/01/28 18:39 # 삭제 답글

    내용 정리가 되게 잘 되어있네요.
    내용 담아 갈게요~
  • 인간병기 2013/04/17 12:33 # 삭제 답글

    안녕하세요. 정리해주신대로 잘 설정해서 사용하고 있습니다.
    그런데 트랜잭션 설정에서 하나 빠진게 있는 것 같습니다.
    root-context.xml 파일에 트랜잭션 설정을 해주면 transactionManager 빈을 찾는데.. 이 설정도 같이 올려 주셨으면 좋겠습니다.
    Referenced bean 'transactionManager' not found [config set: simpleTest/web-context]
댓글 입력 영역