요즘엔 보통 Tomcat 5~6버전 혹은 Tomcat 7 버전을 WAS로 사용하는 경우가 있을 겁니다. 제가 일하는 곳의

환경은 꽤 구형 시스템을 사용하고 있고 프로그램에서 DB 커넥션 시 JDBC로 바로 연결하고 있어 부하가 상당히 걸리는

편이라 개선을 해보고자 DBCP를 사용하여 커넥션을 얻어오도록 테스트를 해봤습니다. 요즘엔 각종 프레임워크에서

손쉽게 설정 가능 하지만 이곳 상황이 그렇지 못하기 때문에 좀 삽질을 했습니다.

먼저 DBCP를 사용하기 위해서는 자카르타 DBCP API인 Commons-dbcp-1.2.1.jar, Commons-collections-3.1.jar,

Commons-pool-1.2,jar 가 반드시 필요합니다.(꼭 버전이 맞아야 하는것은 아닙니다.)

그리고 중요한 것은 이 세개의 라이브러리가 톰캣 설치 디렉토리의 commons/lib 디렉토리에 위치해야 하며 어플리케이션이

위치하는 컨텍스트에 이 라이브러리들이 중복해서 등록됐을경우 오류를 발생 시킵니다.

1. Tomcat - Server.xml 수정

  - 톰캣이 설치된 디렉토리의 conf/server.xml을 수정합니다. 여기서 Resource의 name속성에 지정된 명칭은

  나중에 JNDI를 통해 가져올 이름으로 이후에 수정할 컨텍스트 내의 web.xml의 명칭과 동일 해야 합니다.

  혹은 전역적으로 설정하기 위해 <GlobalNamingResources> 내부에서 <Context>를 제외하고 아래 내용을 사용해도 됩니다.

<Context path="" docBase="ROOT" debug="0">
   <Resource name="jdbc/erpdb" auth="Container" type="javax.sql.DataSource"/>
   <ResourceParams name="jdbc/erpdb">
     <parameter>
          <name>username</name>
          <value>test</value>
     </parameter>
     <parameter>
          <name>password</name>
          <value>test</value>
     </parameter>
     <parameter>
          <name>driverClassName</name>
          <value>com.sybase.jdbc2.jdbc.SybDriver</value>
     </parameter>
     <parameter>
          <name>url</name>
          <value>jdbc:sybase:Tds:111.222.222.111:4444/erpdb?charset=eucksc</value>
     </parameter>
     <parameter>
          <name>removeAbandoned</name>
          <value>true</value>
     </parameter>
     <parameter>
          <name>removeAbandonedTimeout</name>
          <value>60</value>
     </parameter>
  </ResourceParams> 
 </Context>

설정후 이 부분을 보면 jdbc:sybase:Tds:111.222.222.111:4444/erpdb?charset=eucksc 뒤에 파라미터로 캐릭터셋을

지정해 줬습니다. 여기서는 DB의 캐릭터셋을 지정해 주면 됩니다. Sybase에서 혹 안되면 eucksc 대신 euc-kr 로 해보면 될 것

같습니다. 여기서 좀 삽질을 했는데 DB의 캐릭터셋이 euc-kr이라 위에서 설정시 <parameter>에 넣고 설정해 봤는데 아래와

같으 오류가 발생했습니다. JNDI를 통해서 커넥션을 가져오면서 나오는 에러였는데 위와 같이 설정해 줌으로 해결됐습니다.

org.apache.tomcat.dbcp.dbcp.SQLNestedException: Cannot create PoolableConnectionFactory (JZ004: DriverManager.getConnection(..., Properties)에 사용자 이름 속성이 없습니다.)

2. web.xml 수정(컨텍스트/WEB-INF/web.xml)

프로그램에서 JNDI를 통해 커넥션을 가져오기 위해 위에서 서버쪽의 server.xml에서 설정한 Resource name 과 동일하게

설정 해줍니다. 동일하지 않을경우 커넥션을 받을 수 없으므로 오류가 발생하게 됩니다.

 <resource-ref>
      <description>ERP DB</description>
      <res-ref-name>jdbc/erpdb</res-ref-name>
      <res-type>javax.sql.DataSource</res-type>
      <res-auth>Container</res-auth>
 </resource-ref>

2. 프로그램에서의 테스트

  Connection conn = null;
  InitialContext initContext = null;
  Context ctx = null;
  DataSource dataSource = null;

  try{
   initContext = new InitialContext();
   //ctx = (Context) initContext.lookup("java:comp/env");
   //dataSource = (DataSource) ctx.lookup("jdbc/erpdb");
   dataSource = (DataSource) initContext.lookup("java:comp/env/jdbc/erpdb");
  }catch(Exception e){
   System.out.println(e);
  }

  conn = dataSource.getConnection();

여기서 한가지 주의할 점은 경우에 따라 JDBC API도 톰캣 설치디렉토리의 common/lib 에만 위치 시키고 컨텍스트의

lib 디렉토리에서 중복될 경우 아래와 같은 오류가 발생할 수 있습니다.

org.apache.tomcat.dbcp.dbcp.SQLNestedException: Cannot load JDBC driver class 'com.sybase.jdbc2.jdbc.SybDriver'

여기까지 설정후에 확인해보시면 아마 제대로 되지 않을까 싶습니다. 그나저나 기존 소스에 JDBC 커넥션 맺는 부분을

어떻게 효과적으로 바꿀지가 더 고민이 되는군요. 파일이 수만개가 넘고 거의 모든곳에서 커넥션을 맺는 행위를 서블릿에서

init() 호출시 하도록 되어 있습니다. 노가다의 끝판왕을 보게 될 듯 싶네요 ㅎㅎ. 어쨋든 톰캣 구 버전을 사용할때 위 내용을

참고하면 될 듯 싶습니다.

 

 

보통 우리가 인터넷을 사용하면서 주소창에 흔히 볼수있는 주소 표현으로 다음과 같이..

 

http://xens.tistory.com/content/list.jsp?user=park&page=1 과 같은형태를 많이 보게 됩니다. 이를 RESTful 하게 표현하면

 

http://xens.tistory.com/content/list/user/park/page/1 과 같이 표현할 수 있게됩니다. 개념상으로는 단순히 이렇게 주소

 

표기를 위해서 뜬금없이 나타난 것은 아니고 'URI로 자원대상을 명시하고 Method로 자원에대한 행위를 정의한다'

 

를 베이스로 가지고 있는 아키텍쳐 이며 기본적으로 CRUD(Create, Read, Update, Delete)와 매치될수 있는 형태의 서비스

 

에서 쓰이고 있습니다. 저도 여기저기서 글만 봤지 최근에서야 공부겸 예제를 작성해 봤는데 글 제목에서 썼듯 URL Mapping

 

문제로 굉장한 삽질을 했습니다. 분명 시간이 지나면 잊어버릴게 뻔하니 간단히 정리를 해봅니다.

 

예제를 만들때는 Spring Framework 3.0.5와 Tomcat 7, jQuery를 사용했습니다. 이클립스에서 프로젝트 구조는 다음과

 

같다고 보시면 됩니다.

 

 

 

 

※JSP 호출 오류 해결방법

  REST형식으로 호출하면서 처음부터 진입을 가로막는것이 JSP 입니다. 처음 시작페이지가 content.jsp라고 하겠습니다.

 

 요즘에는 보통 서비스(URL)호출시 AJAX를 사용하여 콜백으로 온 데이터를 통해 결과를 출력하거나 하는 개발 방식을 많이

 

 사용하고 있는데 예를 들어서 content.jsp 에서 아래와 같이 호출한다고 가정합니다(처음 실행페이지가 content.jsp).

 

 $.ajax({

   ...

   url : "/person/parkdi"

   ...

 });

 

 그리고 스프링 컨트롤러는 다음과 같이 작성 했다고 가정합니다.

 

 @RequestMapping(value="/person/{id}", method=RequestMethod.GET)
 public ModelAndView getDataRestfulUrlPattern(@PathVariable("id") String id){...}

 

 이렇게 작성하고 바로 호출을 해보면 호출하는 content.jsp 자체가 에러가 납니다. 일단 이상현상을 보게된 후

 

 제일 먼저 봐야할것은 WAS의 web.xml 파일입니다. 우리는 Spring Framework를 사용했기 때문에 DispatcherServlet를

 

 다음과 비슷하게 매핑시켜줬을 겁니다.

 

  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>ContextConfigLocation</param-name>
      <param-value>/WEB-INF/webmvc_config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>

 

실행할때의 톰캣의 Console로그를 보시면 content.jsp파일도 DispatcherServlet에서 처리하려고 하는것을 볼수 있습니다.

 

그러나 위의 web.xml 설정과 같이 *.do에 대한 패턴을 매핑하도록 했기 때문에 content.jsp에 대한 매핑 URL을 찾을수가

 

없어서 에러가 발생하게 됩니다. 우선 문제에 대한 해결책은.. web.xml을 수정해야 합니다.

 

  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>ContextConfigLocation</param-name>
      <param-value>/WEB-INF/webmvc_config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <servlet-mapping> 
    <servlet-name>default</servlet-name>
    <url-pattern>*.js</url-pattern>
    <url-pattern>*.css</url-pattern>
    <url-pattern>*.jpg</url-pattern>
    <url-pattern>*.gif</url-pattern>
    <url-pattern>*.png</url-pattern>
    <url-pattern>*.ico</url-pattern>
    <url-pattern>*.swf</url-pattern>
 </servlet-mapping>       

 

우선 문제에 대한 해결책은 위의 붉은 글씨처럼 Dispatcher서블릿의 url-pattern을 '/' 로 변경하고 deault servlet의 url-pattern

 

을 추가해줬습니다. 호출하는 페이지인 content.jsp에서는..

 

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

 $.ajax({

   ...

   url : "<c:url value='/person/parkdi' />"    //컨텍스트Path(spring_webmvc_restful_ex1/person/parkdi)

   ...

 });

 

컨트롤러에서는 위에 작성된 예와 같이 그대로 두고 호출하면 됩니다. 일단 문제는 해결됐지만 이유는 알아야 되겠죠.

 

※오류 원인

 

JSP는 WAS(톰캣 등)의 서블릿 컨테이너에서 처리를 하게 됩니다. 톰캣을 예로 보면 이클립스 프로젝트에서의 web.xml이 아닌

 

%CATALINA_HOME%(톰캣 설치 디렉토리)의 conf/web.xml을 열어보면

 

 <sevlet-mapping>

  <servlet-name>jsp</servlet-name>

  <url-pattern>*.jsp</url-pattern>

 </sevlet-mapping>

 <servlet>
    <servlet-name>jsp</servlet-name>
    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
    <init-param>
       <param-name>fork</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
        <param-name>xpoweredBy</param-name>
        <param-value>false</param-value>
     </init-param>
    <load-on-startup>3</load-on-startup>
  </servlet>

 

위와같이 org.apache.jasper.servlet.JspServlet 에서 jsp를 처리한다는 것을 볼 수 있습니다. JSP 인식오류 해결을 위해

 

위에서 추가했던 <servlet-name>default</servlet> 의 경우 서블릿 컨테이너의 DefaultServlet을 의미하는데 

 

url-pattern을 '/'로 정의한 경우에 디폴트 서블릿을 의미하며 이 디폴트 서블릿은 서블릿 매핑 URL에 걸리지 않는 요청을

 

처리한다고(톰캣 설정파일의 주석) 되어있습니다. 그래서 이제 DispatcherServlet의 url-pattern을 '/'로 설정함으로서

 

JSPServlet의 JSP처리와 DispatcherServlet의 처리를 분리 시켰습니다. 이렇게 분리 할경우 JSP와 스프링 컨트롤러 매핑은

 

잘 처리되지만 css, js등의 정적 컨텐츠들이 나오지 않는것을 볼수 있습니다. 이는 DispatcherServlet의 url-pattern을 '/'로

 

설정하면서 톰캣의 server.xml에 정의되있는 DefaultServlet의 url-pattern을 무시하게 되기 때문에 DefaultServlet이 작동하지

 

않게 되기 때문입니다. 따라서 호출시 정적 컨텐츠들도 DispatcherServlet이 처리하게 되니 표현되지 않게 됩니다.

 

<servlet-mapping> 
  <servlet-name>default</servlet-name>
  <url-pattern>*.js</url-pattern>
  <url-pattern>*.css</url-pattern>
  <url-pattern>*.jpg</url-pattern>
  <url-pattern>*.gif</url-pattern>
  <url-pattern>*.png</url-pattern>
  <url-pattern>*.ico</url-pattern>
  <url-pattern>*.swf</url-pattern>

</servlet-mapping> 

그래서 DefaultServlet이 처리할 url-pattern을 재정의 해준것이 윗 부분입니다. 사실 위와 같이 정의해줄경우 정적 컨텐츠에

 

대한 url-pattern을 전부다 정의해줘야 줘야하므로 지저분하기도 합니다. 그래서 스프링 3.0에서는 이 부분을

 

<mvc:default-servlet-handler /> 를 선언해줘서 해결을 한다고 합니다. 여기에는 주요 WAS의 DefaultServlet 명에

 

대한 내용을 처리하도록 되있다고 하네요. 저도 위의 DefaultServlet의 매핑을 재정의 해주기 전에 발견해서 적용해봤는데

 

xmlns:mvc="http://www.springframework.org/schema/mvc ,

xsi:schemaLocation=http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd

 

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" p:alwaysUseFullPath="true" />

 

위 내용이 분명히 선언되있는데도 아래와 같이

 

org.xml.sax.SAXParseException: cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'mvc:default-servlet-handler'.

 

찾을수 없다고 계속 떠서 포기했습니다. 찾아봐도 위 내용의 XML확인이나 <mvc:annotation-driven /> 추가 여부만

 

확인하라는 답변밖에 없더군요.. 혹시 비슷한 경험 있으신 분은 답변 주시면 감사하겠습니다. 어쨋든 그냥 REST의 개념만

 

정리하고 CRUD에 해당하는 스프링의 RequestMethod(POST,GET,PUT,DELETE)를 좀 더 알아보려고 했던 것인데

 

하루종일 삽질만 한 것 같습니다;;

지금 회사에는 쓰는 ERP시스템을 좀더 편하게 보기위해서 MIS비스무레한 걸 만들다가 하도 오랜만에 날짜관련된걸

만드려니 한참 해매게 된..ㅜ Calendar API는 정말 편리하긴 하지만 사용법을 잘 모르면 API 뒤지느라 좀 짜증나기도 하네요.

역시 반복학습과 경험이 중요한것 같습니다.

사용법 :

   -  첫번째 예제 : 파라미터로 기준 날짜(ex)201304, 가감할 숫자를 넘깁니다.

   -  두번째 예제 : 파라미터로 가감할 숫자를 넘깁니다.

   -  세번째 예제 : 파라미터로 기준 날짜(ex)201304를 넘깁니다.

 //파라미터의 해당하는 년월의 전달을 구한다.
 public String getBeforeYearMonthByYM(String yearMonth, int minVal){
  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMM");
  Calendar cal = Calendar.getInstance();
  int year = Integer.parseInt(yearMonth.substring(0,4));
  int month = Integer.parseInt(yearMonth.substring(4,6));

  cal.set(year, month-minVal, 0);

  String beforeYear = dateFormat.format(cal.getTime()).substring(0,4);
  String beforeMonth = dateFormat.format(cal.getTime()).substring(4,6);
  String retStr = beforeYear + beforeMonth;

  System.out.println("retStr : "  + retStr);
  return retStr;
 }

 //현재 년월의 전달을 구한다.
 //param : minVal - ex)0,1,2,.....
 public String getBeforeYearMonthByYM(int minVal){
  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMM");
  Calendar cal = Calendar.getInstance();
  cal.add(cal.MONTH, -minVal);  
 
  String beforeYear = dateFormat.format(cal.getTime()).substring(0,4);
  String beforeMonth = dateFormat.format(cal.getTime()).substring(4,6);

  String retStr = beforeYear + beforeMonth;
  System.out.println("retStr : " + retStr);
  return retStr;
 }

 //해당년월의 마지막 날짜를 구한다.
 public String getLastDayOfMonth(String yearMonth){  
  String year = yearMonth.substring(0,4);
  String month = yearMonth.substring(4,6);
  
  int _year = Integer.parseInt(year);
  int _month = Integer.parseInt(month);
  
  Calendar calendar = Calendar.getInstance();
  calendar.set(_year, (_month-1), 1); //월은 0부터 시작  
  String lastDay = String.valueOf(calendar.getActualMaximum(Calendar.DATE));
  System.out.println("lastDay of present month  : " + lastDay);
  
  return lastDay;
 }