Spring Data Sqlmap 을 보완하다 런타임에서 메소드에다 애노테이션을 붙이거나 수정해야하는 이슈가 생겨서 한번 만들어 보았습니다.

소스사용법은 Github 에 있습니다. 
Posted by Arawn Trackback 0 : Comment 1
꽤 오래(?)전부터 SpringSource 에서 Spring Data 라는 프로젝트가 진행 중 입니다.
처음에는 NoSql DB 를 추상화해 사용하기 위해 시작된 프로젝트이었다고 기억하고 있는데 얼마전 fupfin 님께서 Spring Data JPA 가 나왔다고 하시면서 간단하게 소개를 해주셨습니다.

persistence framework 를 사용해서 DAO(Repository)를 구현하다보면 CRUD 는 대부분 persistence framework 의 누군가(sessionfactory, entitymanager, sqlmapclient 등)에게 위임할뿐 특별히 어떤 로직을 수행하는 경우는 드뭅니다.
계층을 구분하기 위해 DAO 가 필요하지만 구현 작업은 번거롭다는 문제점이 있는것이죠.

Spring Data JPA 사용해서 진행하는 개발을 간략하게 살펴보면 다음과 같습니다.
1. Entity 작성
2. JpaRepository 상속해 Repository interface 작성
3. Spring 설정에 Spring Data JPA 전용 네임스페이스로 몇줄 작성
4. Repository를 주입받아 사용

JpaRepository 인터페이스를 상속받아서 적절한 interface 만 만들어두면 구현체는 runtime 에 Spring Data JPA 에서 만들어 넣어줍니다.
개발이 매우 간결하게 진행된다는걸 느낄 수 있습니다.

그래서 국내에서 가장 많이 사용하는 persistence framework 인 iBatis 를 Spring Data 에 붙여보았습니다.

prototype 수준정도로 구현해보았지만 기본적인 기능은 대부분 동작합니다.
소스 및 예제는 여기서 볼 수 있습니다.
Posted by Arawn Trackback 0 : Comment 6
ROO 는 Maven 으로 프로젝트가 관리되고 있습니다. 저장소에서 소스를 받아 /pom.xml 을 열어보시면 modules 엘리먼트에 루가 관리하는 모든 모듈이 등록되어 있습니다.


    annotations
    support
    model
    shell
    shell-jline
    metadata
    file-undo
    file-monitor
    file-monitor-polling
    project
    process-manager
    classpath
    classpath-javaparser
    addon-maven
    addon-plural
    addon-propfiles
    addon-beaninfo
    addon-configurable
    addon-email
    addon-javabean
    addon-jpa
    addon-jms
    addon-finder
    addon-logging
    addon-entity
    addon-property-editor
    addon-security
    addon-tostring
    addon-web-flow
    addon-web-mvc-controller
    addon-web-mvc-jsp
    addon-web-menu
    addon-web-selenium
    addon-dod
    addon-test
    addon-backup

    bootstrap


parent 에서는 무언가를 구현하고 있지 않고 하위 모듈에 루의 모든것이 구현되어있습니다. 크게보면 bootstrap 을 선두로 shell, metadata, classpath 등의 루의 몸체(?)를 구성하는 모듈들과 addon-[...] 으로 시작되는 팔다리(?) 모듈로 볼 수 있는것 같습니다. addon-[...] 모듈은 몸체 모듈에서 필요한 몇가지를 의존성으로 가지고 구현되어있습니다.
Posted by Arawn Trackback 0 : Comment 0

Roo 는 JDK 1.5 에 Maven 2.0.9 이상을 필요로 합니다!

여기에 적당히 Eclipse + m2eclipse 만 준비하시면 됩니다.
(전 SpringSource Tool Suite 2.3.0을 썼습니다.)

1. Roo 소스 구하기
    svn co
https://anonsvn.springframework.org/svn/spring-roo/trunk/ spring-roo
    (체크아웃 받아보시면 spring-roo 폴더에 readme.txt 가 있습니다. 더 자세히 나옵니다 ^^)

2. 컴파일 및 이클립스 프로젝트로 변환
    mvn clean eclipse:clean eclipse:m2eclipse compile

3. 환경변수 등록(총 두곳에다가 셋팅을 했는데 안해되 되는 녀석이 하나... 해야지 되는 녀석이 하나 더군요;;)
    ROO_CLASSPATH_FILE roo_source_dir/bootstrap/target/roo_classpath.txt
    * 붉은색부분은 source 를 check out 받은 폴더 path 를 넣어야합니다.

4. Eclipse File > Import > Existing Projects into Workspace 로 roo_source_dir 를 지정하면 37개 프로젝트가 들어옵니다. Maven Module 화가 정말 잘 되어있네요;;

5. 프로젝트 중 org.springframework.roo.bootstrap 를 찾아가서 다음 클래스를 실행합니다.
    org.springframework.roo.bootstrap.Bootstrap.java
    main(...) 메소드로 되어있으니 Java Application 으로 구동하시면 Console 창에서 Roo Shell 을 보실 수 있을겁니다. ^^ 쉘은 나왔지만 아직 커맨드 입력이 되지 않습니다.

6. Run Configurations 창을 열고 Bootstrap 어플에 VM 옵션을 넣어주세요. (ctrl + 3(단축키) > run configurations)
    -Djline.WindowsTerminal.directConsole=false
    -Djline.terminal=jline.UnsupportedTerminal

   그리고, Working directory 에 roo 가 구동될 빈 디렉토리를 잡아주세요.
   roo가 생성하는 파일들이 이 디렉토리에 생성됩니다.




    



       
  

Posted by Arawn Trackback 0 : Comment 0

OS 로 Windows 7 64bit 를 사용하고 있습니다.

32bit JDK 먼저 설치하고 64bit 를 설치한 상황에서 roo 를 실행하니 다음과 같은 오류가 발생는군요.

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependen
cyException: Error creating bean with name 'bootstrapCommands' defined in URL .... 생략!!
Caused by: java.lang.UnsatisfiedLinkError: C:\Users\izwork\AppData\Local\Temp\jl
ine_.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform ... 생략!

현재 시스템변수상에는 32bit 를 JAVA_HOME 으로 설정했고 path 를 봐도 32bit 를 보고있지만 console 에서 java -version 을 치면 64bit 가 실행이 되는군요. -_- 

윈도우즈가 어디에선가 잡고있나봅니다;;

roo.bat 를 찾아가서 다음 라인을 수정해서 처리했습니다.

before

java -Djline.nobell=true -Djava.ext.dirs="%ROO_HOME%\dist;%ROO_HOME%\lib;%ROO_HOME%\work;%ROO_JRE%\lib\ext" %ROO_OPTS% -Droo.home="%ROO_HOME%" org.springframework.roo.bootstrap.Bootstrap "classpath:roo-bootstrap.xml" %*


after

D:\izwork\common_tools\java\jdk\bin\java -Djline.nobell=true -Djava.ext.dirs="%ROO_HOME%\dist;%ROO_HOME%\lib;%ROO_HOME%\work;%ROO_JRE%\lib\ext" %ROO_OPTS% -Droo.home="%ROO_HOME%" org.springframework.roo.bootstrap.Bootstrap "classpath:roo-bootstrap.xml" %*
Posted by Arawn Trackback 0 : Comment 0
1. http://www.springsource.org/roo 최신 버전이나 마음(?)에 드는 버전을 다운로드 받습니다.
    아침에 1.0.1 을 받았는데... 지금 들어가보니 1.0.2 가 올라와있네요. :)

2. 하드디스크의 적당한 곳에 압축을 풀어줍니다.

3. 환경변수에 잡아줍니다.
   


4. console 창을 꺼내서 roo 를 실행해봅니다!
Posted by Arawn Trackback 0 : Comment 0
오전에 업무로 부랴부랴 시간을 보내고 오후부터 저번 스터디에서 발표했던 타칭 ORM 으로 불리우는 GenericDao에 주석을 정리하면서 시간을 보내던 중 HelloIS님으로부터 메시지가 왔다.

간단히 줄이자면 @Controller 에 대한 테스트를 방법을 찾아보고 계신다는 것이다.

얼마전에 whiteship님의 블로그에 포팅된 스프링 2.5 @MVC 컨트롤러 테스트에 관한 글도 비슷한 맥락에서 방법을 찾아보고 계신것 같았다. 덧글 중 토비님께서는 프레임워크의 테스트를 믿고 나가는게 맞지 않느냐를 말씀도 있으셨다.

테스트가 필요할까? 필요하지 않을까? 내 생각도 아직까지 정리가 되지 않고 있다. 애초에 프레임워크를 쓰는 이유 중 하나인 높은 생산성도 있을텐데 믿지 못하고 일일히 테스트를 한다면 어느 세월에 다 할것인가 싶기도 하지만 은근히 궁금하다. ^^;;

그리고 개발자도 사람인 이상 오타는 분명이 있을테니 @RequestMapping 어노테이션에 엉뚱한 URL 을 적어두고 딴데서 삽질하는 일이 있을수도 있지 않은가! -0-

그럼 어떻게 테스트를 할 수 있을까?

집에오면서 생각한게 가장 단순하게 생각하면 스프링 @MVC 는 DispatcherServlet 에서부터 시작된다는 것이다.

그럼 테스트 코드에서 DispatcherServlet 을 생성해서 사용하면 되지 않을까?

그래서 우선 다음과 같은 @Controller 을 만들었다.

@Controller
@RequestMapping("/hello/hello.htm")
public class HelloController {
   
    @RequestMapping(method=RequestMethod.GET)
    public String helloGet(HttpServletRequest req, HttpServletResponse res, ModelMap modelMap){
        modelMap.addAttribute("hello", "hi~ my name is GET");
        return "hello/get";
    }
   
    @RequestMapping(method=RequestMethod.POST)
    public String helloPost(HttpServletRequest req, HttpServletResponse res, ModelMap modelMap){
        modelMap.addAttribute("hello", "hi~ my name is POST");
        return "hello/post";
    }
}

그리고 설정 파일을 다음과 같이 만들었다.

<context:annotation-config />
<context:component-scan base-package="controllertest.controller" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"
          p:alwaysUseFullPath="true" />
         
<bean id="viewResolver"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:prefix="/WEB-INF/view/jsp/"
          p:suffix=".jsp" />

마지막으로 테스트 코드를 다음과 같이 작성했다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:/**/ApplicationContext.xml"})
public class HelloControllerTest {
   
    @Autowired
    ApplicationContext applicationContext;
   
    DispatcherServlet dispatcher;
   
    @SuppressWarnings("serial")
    public HelloControllerTest(){
        this.dispatcher = new DispatcherServlet() {
            protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
                GenericWebApplicationContext wac = new GenericWebApplicationContext();
                wac.setParent(applicationContext);
                wac.refresh();
                return wac;
            }
        };
        try {
            this.dispatcher.init(new MockServletConfig());
        } catch (ServletException e) {
            e.printStackTrace();
        }

    }
   
    @Test
    public void helloGet() throws Exception {     
       
        MockHttpServletRequest req = new MockHttpServletRequest("GET", "/hello/hello.htm");
        MockHttpServletResponse res = new MockHttpServletResponse();
       
        dispatcher.service(req, res);
       
        // HTTP 상태 검사
        Assert.assertEquals(200, res.getStatus());
       
        // Attribute 검사
        Object obj = req.getAttribute("hello");
        Assert.assertNotNull(obj);
       
        System.out.println(String.format("AttributeName : hello, Value : %s", obj));
       
        // 포워드 주소...
        System.out.println(res.getForwardedUrl());
    }
   
    @Test
    public void helloPost() throws Exception {

        MockHttpServletRequest req = new MockHttpServletRequest("POST", "/hello/hello.htm");
        MockHttpServletResponse res = new MockHttpServletResponse();
       
        dispatcher.service(req, res);
       
        // HTTP 상태 검사
        Assert.assertEquals(200, res.getStatus());
        // Attribute 검사
        Object obj = req.getAttribute("hello");
        Assert.assertNotNull(obj);
        System.out.println(String.format("AttributeName : hello, Value : %s", obj));
        // 포워드 주소...
        System.out.println(res.getForwardedUrl());
    }
   
    @Test
    public void hello() throws Exception {
       
        MockHttpServletRequest req = new MockHttpServletRequest("POST", "/hello/hello.ht");
        MockHttpServletResponse res = new MockHttpServletResponse();
       
        dispatcher.service(req, res);
       
        // HTTP 상태 검사
        Assert.assertEquals(404, res.getStatus());
    }   
}

위와 같이 작성 후 테스트를 실행하면 다음과 같은 결과를 얻을 수 있었다.

AttributeName : hello, Value : hi~ my name is GET
/WEB-INF/view/jsp/hello/get.jsp

AttributeName : hello, Value : hi~ my name is POST
/WEB-INF/view/jsp/hello/post.jsp

마지막 테스트 함수의 경우 올바른 경로를 입력하지 않았기 때문에 HTTP 상태는 404(파일없음)이 나오는 것이다.

이것이 올바른 방법으로 테스트가 되었고 결과값이 나온건이 대해서는 아직 확신이 없다.
Posted by Arawn Trackback 1 : Comment 3
스터디 발표를 준비를 위해 예제를 작성하던 중 다음과 같은 오류를 만났다.

must override a superclass method

오버라이드를 하기 위해서는 슈퍼클래스의 메소드가 필요하다?

예제 소스를 살펴보니 상속받아서 슈퍼클래스가 존재하는 메소드의 경우에는 @Override 어노테이션이 오류가 나지 않지만 인터페이스 기반에서는 오류가 난다.

구글링을 통해서 알아보니 다음과 같은 글을 발견했다.


Annotation은 Java code만으로 전달할 수 없는 부가적인 정보를 컴파일러나 개발툴로 전달할 수 있다. @Override annotationsuper classinterface의method를 override하고 있음을 명시한다. 하지만, 사소한 syntax 실수 등으로 orverride가 구현되지 않는경우가 있는데, 이는 runtime 레벨에서 오작동 등으로 드러나게 되므로 그 원인을 찾기가 쉽지 않다. 여기에@Override annotation을 사용하면 compile 레벨에서 상위 메소드를 정확히 구현하고 있는지를 체크할 수 있다.@Override annotation이 컴파일러에게 Override method임을 알려주기 때문이다.

하지만, @Override annotation을 사용했을 때, must override a superclass method라는 에러가 발생한다면, 컴파일러의 버전을 의심할 필요가 있다. J2Se 5.0, 그러니까 JDK 1.5는 @Overrider annotation을 지원하지만, super class에 대해서 지원할뿐, Interface에 대해서는 지원하지 않는다. Interface에 대한 지원은 JDK 1.6을 사용할 때 가능하다.


예제 소스는 1.5 기반이었다. -0-;;

Posted by Arawn Trackback 0 : Comment 1
Annotation 기반 Controller 에서는 HTTP 요청 파라미터를 @RequestParam 을 사용해서 메소드의 파라미터로 바로 전달 할 수 있다.

@RequestParam 은 Key=Value 형태의 HTTP 요청 파라미터를 메소드의 파라미터에 전달해준다.





getBoard 메소드 호출시 Request 파라미터에서 "board_seq" 를 찾아 int board_seq 에 넣어둔다.

만약 HTTP 요청 중에 "board_seq" 가 없다면 Exception 이 발생한다.

org.springframework.web.bind.MissingServletRequestParameterException: Required int parameter 'board_seq' is not present

파라미터의 값이 필수가 아니라면 required 속성의 값을 설정해주면 된다. 기본값은 true 이다.



전달받는 파라미터 타입 또한 아주 민감하다. int 형으로 선언된 타입에 숫자가 아닌 다른값이 들어가면 유연한 행동을 보이지 않고 바로 Exception 을 발생시킨다. 그외에도 원시 유형의 변수에 null 값이 들어가도 마찬가지로 Exception 이 발생한다. ( int 보다는 Integer 래퍼(warpper) 를 사용하는게 안전할듯... )

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.beans.TypeMismatchException: Failed to convert value of type [java.lang.String] to required type [int]; nested exception is java.lang.NumberFormatException: For input string: ""
Posted by Arawn Trackback 0 : Comment 3
보통 Spring Web MVC 에서 log4j 의 사용을 위해 web.xml 에 다음과 같이 설정파일을 불러올 위치를 지정해준다.

<context-param>
    <param-name>log4jConfigLocation</param-name>
    <param-value>classpath:/resources/properties/log4j.properties</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>

WAS 가 가동되면 web.xml 설정에 따라서 log4j 의 설정값을 읽어서 정상적으로 로깅을 시작한다.

하지만 다음과 같이 Junit 를 통한 직접적인 테스트에서는 log4j가 올바르게 작동하지 않는다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:/resources/configs/DataBaseContext.xml",
           "classpath:/resources/configs/OzNoteContext.xml"})
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional
public class SqlMapNoteDaoImplTest {

    @Test
    public void testGetBoardList(){
        // 테스트 코드
    }

}

web.xml 을 읽어서 환경을 구성해줄 컨테이너가 없기 때문이다. 특별히 설정이 없는 경우 Spring 은 log4j 의 설정파일을 classpath root에서 파일을 찾아보고 없다면 경고 메시지만 보여주고 로깅은 하지 않는듯 싶다.

log4j:WARN No appenders could be found for logger(org.springframework.test.context.junit4.SpringJUnit4ClassRunner).
log4j:WARN Please initialize the log4j system properly.

그럴 경우에는 대비해서 Spring 은 ApplicationContext 에서 직접 log4j 의 설정파일을 위치를 받을 수 있는 클래스가 만들어져 있다.

org.springframework.util.Log4jConfigurer

위 클래스를 import 시킨 후 다음과 같이 사용 할 수 있다.

Log4jConfigurer.initLogging("classpath:resources/properties/log4j.properties");

Resource에서 쓰던 prefix들 classpath: 와 file:을 사용해서 프로퍼티 파일 위치를 알려주면 된다. 설정파일이 찾지 못한다면 FileNotFoundException 을 던지니 예외처리를 해주어야한다.




이 정보는 스프링 포럼을 통해서 알 수 있었습니다.
Posted by Arawn Trackback 0 : Comment 3