Whiteship's Note

'모하니?/Coding'에 해당되는 글 299건

  1. 2010.08.11 템플릿-콜백 패턴으로 Try-Catch-Finally 블럭을 무찌르자. (6)
  2. 2010.08.11 [BlackBerry] 헬로 Whiteship (4)
  3. 2010.08.04 [GAE 시리즈] 7. 스프링 @MVC
  4. 2010.08.03 [GAE 시리즈] 6. 배포하기
  5. 2010.08.03 [GAE 시리즈] 5. 정적 자원
  6. 2010.08.03 [GAE 시리즈] 4. JDO
  7. 2010.08.03 [GAE 시리즈] 3. JSP와 로깅
  8. 2010.08.03 [GAE 시리즈] 2. 구글 로그인
  9. 2010.08.03 [GAE 시리즈] 1. 구글 앱 엔진 + 메이븐 + IntelliJ 프로젝트 세팅
  10. 2010.07.07 [이전글에 이어지는 이야기] 숫자에서 객체로...
  11. 2010.07.07 복잡한 로직은 복잡한 DAO로 직결되는가? (6)
  12. 2010.07.02 스프링 DAO 3파전 (6)
  13. 2010.06.25 전자정부 프레임워크 공통 컴포넌트 실행 성공(?) (2)
  14. 2010.05.27 [하이버네이트 Criteria] 목록 사이즈 구하기 (4)
  15. 2010.05.25 JUnit 4.8 카테고리 기능
  16. 2010.05.11 인텔리J에서 스칼라 시작하기 (7)
  17. 2010.04.20 서비스 계층의 비즈니스 로직을 도메인으로 옮기자
  18. 2010.04.04 [주말 코딩] 봄싹 위키 (10)
  19. 2010.04.01 [JSON in Java] JSON에서 필요한 데이터 뽑기
  20. 2010.03.26 [Spring 3.0 & Atlassian] RestTemplate으로 Confluence의 Space 목록 가져오기
  21. 2010.03.19 [Spring Security] Method Security Test
  22. 2010.03.17 봄싹 개발자 모집합니다. (완료됨) (4)
  23. 2010.03.11 [구글 Visualization API] Fluent Interface 적용하기
  24. 2010.03.07 [Google Visualization API] 캬.. 멋지구나~ (6)
  25. 2010.03.05 톰캣 메일링 리스트 가입하기
  26. 2010.03.03 우편번호 검색&입력기 만들기
  27. 2010.02.17 [스프링 3.0 @MVC] 컨트롤러에 스프링 AOP가 적용되지 않는다는건 이제 거짓말
  28. 2010.02.11 [SVN] Spring Framework 3.0.0.RELEASE
  29. 2010.01.21 [봄싹 DevTerms] 활성화 맞춤형 개발
  30. 2010.01.20 2010 대한민국 매쉬업 참가했습니다. (10)

템플릿-콜백 패턴으로 Try-Catch-Finally 블럭을 무찌르자.

모하니?/Coding : 2010.08.11 18:40


어제부터 계속 정리하고 싶었는데 어젠 이상하게 시간을 쏟아 버리는 바람에.. 이제 정리한다. 자세한 내용은 토비의 스프링 3에 나오는데 이번에 구글 캘린더 API 코딩하다가 써먹을 기회가 와서.. 적용해 봤다. 문제는 내 강의를 들었던 학생은 그걸 하지 못했다는게 아쉽다. 어디 한술에 배부르랴.. 그래도 내가 코딩한 걸 보고 그게 그건지 알아차렸으니.. 그걸로 만족한다.


구글 캘린더 API를 사용하려면 위에 있는 문서만 보면 된다. 친절하게 안내해주고 있어서 쓰기 편하다. 그런데 문제는 예외처리다. 


    public List<CalendarEntry> getMyCalendarList(){
        URL feedUrl = makeUrl(OWN_CALENDAR_URL);
        try {
            CalendarFeed resultFeed = calendarService.getFeed(feedUrl, CalendarFeed.class);
            return resultFeed.getEntries();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ServiceException e) {
            throw new RuntimeException(e);
        }
    }


저기서 호출하고 있는 calendarService는 구글에서 제공해주는 API로 IOException과 ServiceException을 던지는 메서드들을 왕창 가지고 있다. 또 그녀석들을 자주 쓰게 된다.

따라서 어떤 호출을 하더라도 위와같은 예외 처리 코드가 생기기 마련이다. 이걸 어떻게 하면 좋을까? 스프링 개발자라면 이런 코드를 스프링과 잘 어울리는 형태로 처리할 수 있어야 한다.

어떻게 할것인가? 어떻게 하면 저 지져분한 Try-Catch(-Filnally) 문에서 벗어날 것인가? 한번 도전 해보자...

결과는..

public List<CalendarEntry> getMyCalendarList(){
        return calendarServiceTemplate(new CalendarServiceCallBack<List<CalendarEntry>> () {
            public List<CalendarEntry> queryForObject() throws IOException, ServiceException {
                return calendarService.getFeed(makeUrl(OWN_CALENDAR_URL), CalendarFeed.class).getEntries();
            }
        });
    }

이것과 비슷한 형태가 될 것이다. 물론 중요한건 결과가 아니라 저렇게 바꾸는 프로세스.. 그걸 이해하고 자신의 스킬로 만드는 것이 중요하겠다. 토비의 스프링 3에서 아주 잘 배울 수 있으니 꼭.. 3장 템플릿을 정독하도록 하자. 연습하고. 또 연습하고. 또 연습하고. try-catch 문이 반복해서 나올 떄 마다 적용해 보도록 하자.

저작자 표시
신고
top


[BlackBerry] 헬로 Whiteship

모하니?/Coding : 2010.08.11 13:55



아이콘은 무료 아이콘 사이트에서 하나 다운 받았고.. 초간단 앱은 역시 헬로 월드.. 

public class HelloWorld extends UiApplication {
final class HelloWorldScreen extends MainScreen {
public HelloWorldScreen() {
super();
LabelField title = new LabelField("Hello App", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH);
setTitle(title);
add(new RichTextField("Hello Whiteship!!"));
}
public boolean onClose() {
Dialog.alert("바이바이!");
System.exit(0);
return true;
}
}

public static void main(String[] args) {
HelloWorld app = new HelloWorld();
app.enterEventDispatcher();
}
public HelloWorld() {
pushScreen(new HelloWorldScreen());
}

}

일단 앱은 UiApplication을 상속 받아서 만들고 우선.. 맨 아래에 있는 메서드 pushScreen을 사용해서 MainScreen을 상속해서 만든 화면을 뿌려주고,, enterEventDispatcher 메서드를 호출해서 이 앱에서 발생하는 이벤트들을 감지하도록 설정하는 것 같네요.

흠냐.. 뭘 만들어 볼까나..
저작자 표시
신고
top


[GAE 시리즈] 7. 스프링 @MVC

모하니?/Coding : 2010.08.04 04:42


GAE 시작하기 메뉴얼을 따라하면서 들었던 생각은 스프링을 어서 도입해봐야겠다는 것이었다. 특히 PMF 라는 클래스를 만들때 간절했다. JDO의 PersistenceManagerFacotry를 싱글톤으로 사용하려고 만든 클래스인데.. 전혀 좋은 코드가 아니었다. 그뿐아니라 자바 코드와 HTML이 섞여있는 guestbook.jsp도 마찬가지이고, HttpServlet을 직접 상속해서 구현한 GurestbookServlet과 SignGuestbookServlet도 스프링 @MVC 컨트롤러로 고치고 싶었다.

그래서 일단해야 할 일은 스프링의 초간단 @MVC 컨트롤러를 추가하고 그게 동작하는지 확인하는 일이었다. 이전에 라이브러리는 넣어둔 상태라 간단하게 설정만 조금 추가하면 됐다.

web.xml

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext*.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

spring-servlet.xml

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

applicationContext.xml

<context:component-scan base-package="whiteship">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>

그리고 컨트롤러

@Controller
@RequestMapping("/hello")
public class GreetingController {

    @RequestMapping("/{name}")
    public String hello(@PathVariable String name, Model model){
        model.addAttribute("name", name);
        return "/WEB-INF/views/hello.jsp";
    }

}

그리고 뷰

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page isELIgnored="false" %>
<html>
  <head><title>Simple jsp page</title></head>
  <body>잘잤니~ ${name}</body>
</html>

끝이다. 잘 돌아간다.



저작자 표시
신고
top


[GAE 시리즈] 6. 배포하기

모하니?/Coding : 2010.08.03 14:51


http://code.google.com/intl/ko-KR/appengine/docs/java/gettingstarted/uploading.html

드디어 GAE 시작하기 매뉴얼로는 마지막이군요. 하지만 아직 스프링 도입하기가 남았으니 GAE 시리즈는 계속 이어지겠지만 오늘은 오후에 일이 있어서 오늘로는 마지막 포스팅이 될듯 하군요.

https://appengine.google.com/

일단 저기에 접속해서 계정을 만들고 애플리케이션 ID를 생성합니다. 해당 ID를 웹 프로젝트의 appengine-web.xml의 <application> 엘리먼트에 설정해 줍니다.

그리고 이클립스나 인텔리J 같은 IDE에서 직접 업로드 하거나.. 콘솔에서 업로드 할 수도 있습니다. 저는 콘솔에서 해봤습니다. 

http://whiteshipgb.appspot.com



저작자 표시
신고
top

TAG GAE, 배포

[GAE 시리즈] 5. 정적 자원

모하니?/Coding : 2010.08.03 14:23


CSS 파일, JS 파일, 이미지, 플래시, 음악, 무비 등 정적 자원은 서블릿 컨테이너가 아닌 별도의 웹서버로 서비스를 사용해서 서비스 해준다. 

이때 WAR로 패키징되는 애플리케이션의 모든 자원 중 /WEB-INF 밑에 있는 JSP 파일들을 제외하고는 모두 정적 자원으로 처리해준다. 따라서 별도의 설정을 할 필요 없지만.. 만약에 별도로 정적 파일을 지정하고 싶다면 web.xml 근처에 있는 appengine-web.xml 파일에 설정해주면 된다.

appengine-web.xml에 설정파일에 대한 자세한 내용은 http://code.google.com/intl/ko-KR/appengine/docs/java/config/appconfig.html


저작자 표시
신고
top


[GAE 시리즈] 4. JDO

모하니?/Coding : 2010.08.03 13:15


http://code.google.com/intl/ko-KR/appengine/docs/java/gettingstarted/usingdatastore.html

 분산 웹 애플리케이션을 만들려면 고민할께 많은데 GAE를 사용하면 그럴 걱정은 할 필요 없단다. 매력적이다. 하지만 손수 분산 웹서버, 분산 DB 환경을 구축해보고 싶긴하다. 머 어쨋건;; 지금은 GAE 공부 중이니깐;; GAE가 알아서 분산, 복제, 로드 밸런싱을 해주기 때문에 개발자는 그냥 심플한 API만 사용해서 개발하면 분산 환경은 알아서 사용하게 된다.

GAE의 datastore도 그러한 서비스 중 하나인데 두 종류 API를 지원한다. 하나는 JDO 하나는 JPA. JDO는 오래전부터 표준이었고 JPA는 하이버네이트 영향으로 생긴 EJB 3의 표준인데... 어째 둘이 생긴게 비슷하다. JDO가 JPA를 따라서 변형된 것 같은 모습인데 실제로 그런건진 모르겠다. 그러거나 말거나.. 쓰기 편하고 기능이 좋으면 그만이다.

JPA를 쓸때 persistence.xml 설정 파일 만들듯이 JDO를 쓸땐 jdoconfig.xml 파일을 만든다. 이 파일에 대한 자세한 설명은 생략;

http://code.google.com/intl/ko-KR/appengine/docs/java/datastore/usingjdo.html

난 JPA를 쓰고 싶은데, 아니 사실은 하이버네이트를 직접 사용하고 싶은데;; 예제가 JDO라 어쩔 수 없이 일단은 따라해 보기로 했다. 

다음은 도메인 클래스를 만들고 매핑 정보를 애노테이션으로 설정한다. JPA랑 똑같다.  그 다음도 사실 똑같다. PersistenceManagerFactory에서 PersistenceMaanger를 가져가다 사용하면 된다. 하이버네이트로 치자면 SessionFactory에서 Session 가져다 쓰는거랑 비슷하다. JDO에도 HQL 같은 JDOQL이라는 쿼리 언어가 있다. 도무지;; JDO랑 JPA랑 구분이 되지 않는다. 똑같아 보인다. 이럴바엔 하나로 합치는 표준을 하나 만들고 그 API를 쓰게 하는게 좋치 않을까.. 



저작자 표시
신고
top

TAG GAE, JDO

[GAE 시리즈] 3. JSP와 로깅

모하니?/Coding : 2010.08.03 06:53


http://code.google.com/intl/ko-KR/appengine/docs/java/gettingstarted/usingjsps.html

GAE라고 해서 JSP에 특별한 머시기를 해주는건 아니고 그냥 일반적인 Servlet&JSP 코딩하듯이 코딩하면 된다.

JSP는 web 폴더 밑에 두면 자동으로 매핑 되니까 guestbook.jsp 같은걸 만들고, 그 안에서 직접 자바 코드 호출해서 사용하고 있다. (빨리 스프링 MVC 적용해서 걷어내고 싶다.)  그 다음엔 폼을 추가하고 그 폼 서브밋을 처리할 서블릿을 하나 만들고 (그안에서 로그인 확인 중복 코드 발생한다.) 로그인 여부에따라 java.util.logging.Logger를 사용해서 로그 메시지를 남기고 있다. 그 서블릿 매핑 정보를 web.xml에 추가한다. (이부분도 역시 어서 스프링 MVC를 도입하고 싶게 해주는 부분이다. 귀찮게 맨날 web.xml에 대여섯줄 씩 매핑 정보를;; @_@;)

자.. 이제 끝이 아니라. 로깅을 설정하는게 이게 조금 재밌다.

web/WEB-INF 폴더 밑에 보면 appengine-web.xml이 있는데 드디어 이녀석이 하는 일 중 하나가 밝혀진다. 바로 환경 변수 설정이다. 

    <system-properties>
        <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
    </system-properties>

이런식으로 로깅 파일 위치를 설정해주면 GAE에서 해당 위치에 로깅 파일을 사용하여 어드민 콘솔 이라는 앱으로 GAE에 배포한 앱의 로그 메시지를 기록하고 간편하게 찾아볼 수 있는 서비스를 제공해준다. log4j 설정만 써보고 자바 Logger 설정은 안써봤는데; 의외로 간편하다.

.level = WARNING
whiteship.level = INFO

달랑 요거;


 


저작자 표시
신고
top


[GAE 시리즈] 2. 구글 로그인

모하니?/Coding : 2010.08.03 06:22


http://code.google.com/intl/ko-KR/appengine/docs/java/gettingstarted/usingusers.html

        UserService userService = UserServiceFactory.getUserService();
        User user = userService.getCurrentUser();
        if(user != null) {
            resp.setContentType("text/plain");
            resp.getWriter().println("Hello, " + user.getNickname());
        } else {
            resp.sendRedirect(userService.createLoginURL(req.getRequestURI()));
        }

이 코드가 핵심인데.. 많은 것들을 해준다.

1. 우선 무척이나 간단하다. 이렇게 쉽게 구글 로그인을 연동할 수 있다니... API도 직관적이라 어떤 일을 하는지 쉽게 알 수 있다. 마지막 줄의 코드가 로그인 한 다음 바로 로그인을 요청한 페이지로 이동하기 위한 API 인데 흠.. 괜찮은 것 같다. 권한 관리는 어떻게 하는지..  실제론 스프링 시큐리티랑 연동해서 쓰는지 아님 구글에서 제공하는 인증/권한 관리 API가 있는 것인지.. 있다면 권한 관리 설정은 편리한지 궁금하다.

2. 로그인 화면 자동생성. 로그인이 필요할 때 guestbook 예제에서 봤던 로그인 화면이 등장하는데 나는 그런 JSP 파일을 만든적도 없는데 알아서 만들어줬다. 편리하다.

3. 간편할 뿐 아니라 이 코드는 개발 환경과 배포 환경에 따라 동작 방식이 바뀐다. 오.. 놀라워라; 즉 개발환경에서는 아무런 아이디만 넣어도 로그인이 되고, 관리로 로그인하기 체크 박스가 존재한다. 하지만 GAE에 배포하는 순간 실제 구글 계정으로 로그인 한단다. 아직 GAE에 배포는 안해봤다. 그런데.. 개발할 때 실제 구글 계정으로 테스트해보고 싶으면 어쩌지?? 

4. 로그아웃 URL로 제공한다. userService.createLogoutURL()을 사용하면 된다.


저작자 표시
신고
top

TAG GAE, Java

[GAE 시리즈] 1. 구글 앱 엔진 + 메이븐 + IntelliJ 프로젝트 세팅

모하니?/Coding : 2010.08.03 05:21


구글 앱 엔진 시작하기 메뉴얼을 보며 예제를 실습해봤다. 그러나.. 내 입맛에 맞지 않는 구석이 몇개 있었다.

1. 라이브러리를 맘대로 추가할 수 있으니 코딩하는데는 문제가 안되는데 빌드가 보통 앤트를 사용하는 듯하다. 이건 불편하다. 그래서 메이븐을 썼다. GAE가 원하는 프로젝트 구조를 건드리지 않으면서도 필요한 라이브러리는 쉽게 가져다 쓸 수 있도록 메이븐을 설정했다. 이전에도 계속 써오던 형태라 대충 복사해서 붙여넣기고 당장 필요없는 라이브러리는 뺐다. (사실 스프링은 남겨뒀다;;)

2. 이클립스 종속적인 가이드였다. 인텔리J도 GAE 플러긴이 있으며 업데이트 사이트를 추가하는 귀찮은 작업 없이도 간편하게 플러그인을 찾아서 설치할 수 있었다.

3. 배포 설정

웹 서버에 배포할 때 프로젝트 이름/out 폴더 밑으로 웹 컨텐츠를 전부 복사해서 배포하게 되어 있는데 난 프로젝트/web에 배포하게 설정했다. 이래야 재배포 하지 않고도 JSP를 수정할 때 마다 바로바로 적용된다. 


저작자 표시
신고
top

TAG GAE, IntelliJ, Java

[이전글에 이어지는 이야기] 숫자에서 객체로...

모하니?/Coding : 2010.07.07 13:21


... 윗부분 생략 
               double qtyIn = invInDao.getQtyOf(today, item, location);
                // 금일 출고량 조사
                double qtyOut = invOutDao.getQtyOf(today, item, location);
                // 금일 재고량 계산
                double qtyEnd = qtyStart + qtyIn - qtyOut;

                if(isEmpty(qtyStart, qtyIn)){
                    continue;
                }

                InvDailyClosing invDailyClosing = new InvDailyClosing();
                invDailyClosing.setDate(today);
                invDailyClosing.setLocation(location);
                invDailyClosing.setItem(item);
                invDailyClosing.setQtyStart(qtyStart);
                invDailyClosing.setQtyIn(qtyIn);
                invDailyClosing.setQtyOut(qtyOut);
                invDailyClosing.setQtyEnd(qtyEnd);
...아래도 생략

쓰고나서 생각하니 약간 위험한 글을 썼다는 생각에 괜히 불안했는데..  아니나 다를까... 대용량 데이터베이스를 공부하고, 자바 코드로 치우져서 보는거 아니냐, 벽을 세워놓고 한쪽만 보는건 좋치 않다. 라는 글이 달렸었는데 사라졌다.

하아.. 본문에도 적었지만 사실 내가 대용량 DB를 다루는 SQL을 잘 못하는건 사실이다. 근데 어쩌겠는가.. SQL을 하고 싶지 않아서 안하는게 아니라 SQL을 작성할 일이 별로 없는데다, 대용량 데이터는 내가 만지고 싶다고 만질 수 있는 것도 아니다. 난 이럴 때 마다 떠올리며 나를 위로하는 문구가 있는데.. 켄트벡이 쓴 "테스트 주도 개발" 뒷부분 어딘가 보면 그런 말이 있다. 아.. 이런.. 문제의 크기와 해결방법에 관한 거였는데.. 까먹었다. 암튼 0과 1을 떠올리게 하는거였는데 ㅠ.ㅠ

생각해보니, 재고 마감 데이터가 입고와 출고 수량만 들고 있는게 아니라, 해당 마감 데이터에 해당하는 입고 목록과 출고 목록을 들고 있는게 더 유용하겠다는 생각이 들었다. 그렇게 해야 "재고 마감 뷰"에서 해당 마감에 대한 입고 내역과 출고 내역을 보여주기 편할 것 같다. 아니면 마감 데이터를 가지고 해당 마감에 해당하는 입고목록과 출고 목록을 가져오는 기능을 입고DAO와 출고DAO에 추가해줘야 하는데... 글쎄... 흠냐.. 그거 만들기 귀찮아서 기능을 고치고 있는 기분도 들지만, 왠지.. 설계상으론 더 객체 지향적이고 멋진 것 같다.

이런 로직을 반영하려면 숫자보다 객체를 들고 있게 하는게 편하고.. SQL 보다.. 위와 같은 자바 코드를 수정하는게 더 편한데...그건 내가 그렇다는 것이고...  사람에 따라, 상황에 따라, 대용량인지 아닌지에 따라.. 달라질 수 있겠다..

                List<InvIn> invInList = invInDao.listOf(today, item, location);
             List<InvOut> invOutList = invOutDao.listOf(today, item. location);

                if(isEmpty(qtyStart, invInList)){
                    continue;
                }

                InvDailyClosing invDailyClosing = new InvDailyClosing();
                invDailyClosing.setDate(today);
                invDailyClosing.setLocation(location);
                invDailyClosing.setItem(item);
                invDailyClosing.setQtyStart(qtyStart);
                invDailyClosing.setInvInList(invInList);
                invDailyClosing.setInvOutList(invOutList);
                invDailyClosing.closing();

                dao.add(invDailyClosing);

결국은 이렇게 바뀔 것 같다. InvDailyClosing 도메인 객체의 closing() 메소드에서 입고 목록, 출고 목록, 전날 재고를 가지고 금일 재고를 계산하도록...말이다. 물론.. 이정도 쯤이야. SQL로도 간단하게 할 수 있겠다(?). 난 잘 모르겠지만..

저작자 표시
신고
top


복잡한 로직은 복잡한 DAO로 직결되는가?

모하니?/Coding : 2010.07.07 10:14


아니지 않을까... 비즈니스 로직이 복잡하다고 해서 SQL이 복잡해 질 필요는 없다.

재고 마감 로직을 생각해보자. 모든 창고에 들어있는 모든 상품들의 전일 재고량, 금일 입고량, 금일 출고량을 가져와서 금일 재고량을 계산한다고 치자. 이걸 가지도 또 상품 카테고리 별로 묶고 창고 별로 묶어서 집계를 내야 하는데.. 일단은 그 전 단계까지만 해보자.

Location - Inventory
Item - Inventory
Item - InvIn
Item - InvOut
Item - InvDailyClosing

이렇게 관계가 맺어져 있으니 SQL로 join을 하던 sub select를 하던 어떻게든 SQL 한방으로 어떤 창고에 있는 어떤 상품의 '전일 재고량', '금일 입고량', '금일 출고량'을 한방에 가져올 수 있을 것 같다.

근데 난 그렇게 하지 않는다. 사실 그렇게 복잡한걸 만들지도 못한다. 만들라면 공부해가면서 만들겠지만 무지 오래 걸릴 것 같다. 그래서 난 그냥 자바 코드로 SQL을 대신한다.

        dao.delelteAllAt(today);

        for(Location location : locationDao.getAll()) {
            // 모든 상품 마다
            for(Item item : itemDao.getAll()){
                // 전일 재고량 조사
                double qtyStart = inventoryDao.getQtyOf(yesterday(today), item, location);
                // 금일 입고량 조사
                double qtyIn = invInDao.getQtyOf(today, item, location);
                // 금일 출고량 조사
                double qtyOut = invOutDao.getQtyOf(today, item, location);
                // 금일 재고량 계산
                double qtyEnd = qtyStart + qtyIn - qtyOut;

               나머진 생략...
            }
        }

즉 이런식으로 짠다. 굵은 글씨 부분은 DAO test를 해서 해당 기능이 내가 원한대로 동작하는지 일일히 확인해다. DBUnit을 사용해서 테스트용 데이터를 넣고 저 기능을 실행해보는 방식이다. 이렇게 구현하니까 상당히 마음이 놓인다. 저걸 만약 한방에 SQL로 구현했다던지.. 테스트 없이 구현했다면.. 글쎄.... 그렇겐 못했을 것 같다.

암튼 이래서.. 복잡한 로직을 처리하는 DAO 코드라 할지라도 단순해진다. 이런걸 디바이드 앤 퀀쿼 라고 하던가.. 몰겠다. 머라하든.

    public double getQtyOf(Date date, Item item, Location location) {
        Object result = getSession().createQuery("select sum(qty) from InvIn where date = :date and item = :item and location = :location group by item")
                .setDate("date", DateUtils.getDateOnly(date))
                .setEntity("item", item)
                .setEntity("location", location)
                .uniqueResult();
        if(result == null)
            return 0.0;
        return (Double) result;
    }

대충 이정도 코드가 생기는데.. 이정도야 뭐.. 초간단 SQL 수준 아닌가..

이렇게 짜면 걱정되는게.. 성능인데... DB에 자주 다녀올수록 더 많은 부하가 생기는건 사실이지만 어차피 복잡한 쿼리 자체도 join 여러번 하면서 부하가 생길테니.. SQL 도사가 아닌 이상 차라리 성능을 조금 포기하고 유지 보수 가능하고 알아보기 쉬운 코드로 나눠서 작성하는게 더 좋치 않을까 싶다.





저작자 표시
신고
top


스프링 DAO 3파전

모하니?/Coding : 2010.07.02 13:00


JDBC를 사용하는 부류가 있고, iBatis를 사용하는 부류가 있고, 하이버네이트를 사용하는 부류가 있다. 사실 이보다 더 다양한 영속화 기술들이 있지만.. 이 세 부류로 간추려 보려고 한다. 자 이들은 각각 어떻게 코딩을 하고 있을까.

public interface MemberDao {
void add(Member member);
void update(Member member);
Member get(int id);
List<Member> list();
void delete(int id);

}

이런 인터페이스를 각 부류에서 구현한다고 생각해보자.

아참, 전제는 스프링을 사용한다는 것이다.

1. JDBC

@Repository
public class MemberDaoJdbc implements MemberDao{
@Autowired SimpleJdbcTemplate jdbcTemplate;
public void setMemberMapper(RowMapper<Member> memberMapper) {
this.memberMapper = memberMapper;
}

RowMapper<Member> memberMapper = new RowMapper<Member>(){
public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
Member member = new Member();
member.setId(rs.getInt("id"));
member.setName(rs.getString("name"));
member.setJoined(rs.getDate("joined"));
return member;
}};
public void add(Member member) {
jdbcTemplate.update("insert into member(id, name, joined) values (?, ?, ?)", 
member.getId(), member.getName(), member.getJoined());
}

public void delete(int id) {
jdbcTemplate.update("delete from member where id = ?", id);
}

public Member get(int id) {
return jdbcTemplate.queryForObject("select * from member where id = ?", memberMapper, id);
}

public List<Member> list() {
return jdbcTemplate.query("select * from member", memberMapper);
}

public void update(Member member) {
jdbcTemplate.update(
"update member set name = :name, joined = :joined where id = :id", 
new BeanPropertySqlParameterSource(member));
}

}

대충 이런 코드가 된다. 스프링이 제공해주는 SimplJdbcTemplate은 멀티 쓰레드 환경에서 공유하면서 사용해도 안전한 객체이다. 따라서 빈으로 등록해놓고 주입받아서 쓴다고 한들 문제될 것이 없다. 

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/testContext.xml")
@Transactional
public class MemberDaoJdbcTest {
@Autowired MemberDaoJdbc memberDao;
@Test
public void di(){
assertThat(memberDao, is(notNullValue()));
}
@Test
public void crud(){
Member member = new Member();
member.setId(1);
member.setName("whiteship");
member.setJoined(new Date());
memberDao.add(member);
assertThat(memberDao.list().size(), is(1));
member.setName("기선");
memberDao.update(member);
assertThat(memberDao.get(1).getName(), is("기선"));
memberDao.delete(1);
assertThat(memberDao.list().size(), is(0));
}
}

테스트는 대충 만들었기 때문에 너그럽게 봐주시길...^_^;;;

2. iBatis

스프링에 iBatis의 SqlMapClient를 만들어 주는 팩토리빈을 등록해야 한다.
   
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="SqlMapConfig.xml" />
</bean>

아이바티스 설정 파일도 추가한다. 저 설정에 보이는 SqlMapConfig.xml이 필요하다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMapConfig      
    PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"      
    "http://ibatis.apache.org/dtd/sql-map-config-2.dtd">

<sqlMapConfig>
<sqlMap resource="sample/ibatis/Member.xml" />
</sqlMapConfig>

그 다음 Member와 관련있는 SQL을 모아둔 Member.xml 파일을 만든다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap      
    PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"      
    "http://ibatis.apache.org/dtd/sql-map-2.dtd">

<sqlMap namespace="Member">
<typeAlias alias="Member" type="sample.domain.Member" />

<delete id="delete" parameterClass="int">
delete from member where id = #id#
</delete>

<insert id="add" parameterClass="Member">
insert into member (id, name, joined) values(#id#, #name#, #joined#)
  </insert>
 
<update id="update" parameterClass="Member">
update member set name = #name#, joined = #joined# where id = #id#
</update>
 
<select id="get" parameterClass="int" resultClass="Member">
select * from member where id = #id#
  </select>

<select id="list" resultClass="Member">
select * from member order by id
  </select>
</sqlMap>

XML 엘리먼트와 어트리뷰트는 별도의 학습이 필요하지 않을만큼 직관적이다.. 이제 DAO 코드를 작성하자.

@Repository
public class MemberDaoIbatis implements MemberDao {
@Autowired SqlMapClientTemplate sqlMapClientTemplate;
public void add(Member member) {
sqlMapClientTemplate.insert("add", member);
}

public void delete(int id) {
sqlMapClientTemplate.delete("delete", id);
}

public Member get(int id) {
return (Member) sqlMapClientTemplate.queryForObject("get", id);
}

@SuppressWarnings("unchecked")
public List<Member> list() {
return sqlMapClientTemplate.queryForList("list");
}

public void update(Member member) {
sqlMapClientTemplate.update("update", member);
}

}

흠 많이 짧아졌다. 그런데.. 전체 코드량은 더 많이 늘은것 같지 않은가? 코드량이 척도의 전부는 아니지만.. 왠지 작업이 줄었다가 보다는 DAO에서 할일을 설정파일로 미뤘다는 느낌이 강하다. 아참 여기서 사용한 SqlMapClientTemplate도 SimleJdbcTemplate과 마찬가지로 빈으로 등록해서 사용해도 별 지장이 없는 쓰레드-안전한 클래스기 때문에 빈으로 등록해놓고 사용했다.

3. Hibernate

이것도 역시 빈을 하나 설정해줘야한다.
 
   <!-- ============================================================= -->
    <!--  Hibernate                                                    -->
    <!-- ============================================================= -->
    <bean id="transactionManager"
          class="org.springframework.orm.hibernate3.HibernateTransactionManager"
          p:sessionFactory-ref="sessionFactory"/>

    <bean id="sessionFactory"
          class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="packagesToScan" value="sample.domain" />
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>
    </bean>

이번건 설정이 제법 길다. 그리고 빈이 하나가 아니라 두갠데.. 그건 나중에 TransactionManager에 대한 글을 올릴 때 설명해야겠다. 아니면 그냥 토비님 책을 보기 바란다. 11장에 자세히 설명해주시고 있다. TransactionManager와 기반 기술의 관계(?)랄까나.. 암튼.. 이제 바로 DAO를 만들 수 있다. 아 아니다 일단 Member 클래스에 애노테이션 부터 추가하자.

@Entity
public class Member {

@Id
int id;

...
}

이렇게 두 군데에 각각 하나씩만 붙여주면 된다. 이제 DAO를 만들자.

@Repository
public class MemberDaoHibernate implements MemberDao{
@Autowired SessionFactory sessionFactory;

public void add(Member member) {
getSession().save(member);
}

public void delete(int id) {
getSession().createQuery("delete from Member where id = ?")
.setInteger(0, id)
.executeUpdate();
}

public Member get(int id) {
return (Member) getCriteria()
.add(Restrictions.eq("id", id))
.uniqueResult();
}

@SuppressWarnings("unchecked")
public List<Member> list() {
return getCriteria().list();
}

public void update(Member member) {
getSession().update(member);
}

private Criteria getCriteria() {
return getSession().createCriteria(Member.class);
}

private Session getSession() {
return sessionFactory.getCurrentSession();
}
}

SQL이 없다. SQL처럼 보이는 HQL(굵은 글씨)만 보인다. 이렇게만 하면 정말 DAO가 구현되는지 의심스럽겠지만 정말 구현이 끝난다. 하지만 하이버네이트 Session과 Criteria API는 약간 학습이 필요하다. 자주 쓰는 API는 몇개 안되기 떄문에 대략 1시간 정도면 익힐 수 있다.

셋 개 다 해본결과.. 난 역시 하이버네이트가 편하다.
저작자 표시
신고
top


전자정부 프레임워크 공통 컴포넌트 실행 성공(?)

모하니?/Coding : 2010.06.25 15:13



이 화면 보기가 너무 힘들다;

OSAF 예제 애플리케이션 띄우는거 보다 100배는 올래거리는것 같다. 

그나마 이 화면에서 다음으로 전개도 안된다. 왜냐면 DB가 세팅되지 않았기 떄문에;;

전자 정부 사이트에서 받은 mysql용 sql 파일들을 돌리다 보면 계속 에러가 난다. 자주 보이는 건 주키가 너무 길다는 에러인데 이건 주키 컬럼 사이즈(보통 메서드 이름이 200으로 잡혀 있었다.)를 줄여가면서 지나갔다. 그런데 두번째 sql 파일의 에러는 도무지 모르겠다; MySQL을 설치한지 오래되서 새 버전으로 올린 담에 해봐야 하는건지.. 그냥 오라클로 해야하는건지... 아흑..

맞다. DB만 만들다고 돌아가는것도 아니다. globals.properties 파일을 C:\Documents and Settings\MyHome\egovProps 이 위치에 넣어줘야 하고 이걸 제대로 쓰려면 그 파일에 있는 각종 설정 내용을 다 손봐줘야 한다.

이거.. 아무래도 내가 너무 무모만 것에 손을 댄것 같다. 덕분에 전자 정부 프레임워크 구조가 어떤지 파악할 수 있었다.

개발 환경쪽에서 '구현도구'라고 되어있는 이클립스를 받아서 전자정부 웹 프로젝트를 만들어야지 필요한 pom.xml이 나오지만 사실상 이건 제대로 된 pom.xml파일이 아닌것 같다. 그래도 일단 이렇게 받는다.

http://www.egovframe.org/wiki/doku.php?id=egovframework:배포_패키지_구성안

여기에서 공통여부 Y로 체크되어 있는 것은 무엇을 클릭하던 똑같이 egovframework-common-1.0.0를 받게 된다. 저 안에 수많은 공통 기능이 들어가 있다. 즉 저것을 넣어서 앱을 실행하면 전자정부 프레임워크 도입을 완수한거나 마찬가지다. 거기에 부가적으로 게시판이나 통계등을 넣고 싶다면 해당 메뉴를 클릭해서 소스를 받은 다음 프로젝트에 추가해줘야 한다. 

이 "공통 컴포넌트"라고 하는 서비스들은 "실행 환경"을 기반으로 하고 있다. 실행 환경 코드는 다시 네가지로 나뉜다. 공통기반, 데이터처리, 연계통합, 화면처리.. 이 중에서 웹 관련 기능이 궁금하다면 "화면 처리" 코드를 받아서 보면 된다.

하지만 안타깝게도 이 "실행 환경"에서 받는 코드들은 pom.xml이 없다. 빌드가 되지 않는다. 아참;; 위에서 설명하려다 까먹었는데.. "개발 환경"에서 자동으로 만들어준 pom.xml은 버리고 "공통 컴포넌트" 중에서도 "공통"에 해당하는 egov~~-common-1.0.0을 받으면 들어있는 pom.xml이 있다. 그걸 쓰면 "공통 컴포넌트"에 필요한 의존성까지 설정되어 있으니 무난히 빌드할 수 있다. 어쨋거나 이 코드들은 그냥 쌩으로 봐야한다. 빌드가 안되니.. 별도의 jar 파일을 만들수도 없다. (원래 안만들어도 된다. jar 파일만 따로 다운 받을 수도 있고, 공통 컴포넌트의 pom.xml에서 의존성으로 알아서 가져오게 되어 있다.) 정... 하고 싶으면 pom.xml 만들고 필요한 의존성 다 붙여주면 되는데.. 글쎄;; 별로..

소스 중에 유일하게 살펴본 코드로는 "화면처리(ptl)"에 들어있는 SimpleUrlAnnotationHandlerMapping이 있는데 이름에서는 추측하기 어려운 일을 해준다. 핸들러 매핑 마다 일괄적용되는 인터셉터가 부담스러워서 그걸 url 리스트를 사용해서 걸러주는 작업이다. 그런데 토비님 책을 보면 스프링 3.0의 <mvc:inteceptor>로 해결이 가능하다. 글로벌한 인터셉터 설정이기 때문에 핸들러 매핑을 기준으로 생각할 필요가 없어졌다. 아마도 저 클래스는 주석에도 적혀있듯이 3.0 가면서 deprecated 될 것 같다.
저작자 표시
신고
top


[하이버네이트 Criteria] 목록 사이즈 구하기

모하니?/Coding : 2010.05.27 10:47


난 편의상 이런 메서드들을 만들어 놓고 쓴다.

    private Session getSession() {
        return sessionFactory.getCurrentSession();
    }

    private Criteria getCriteriaOf(Class clazz){
        return getSession().createCriteria(clazz);
    }

이 두개가 있다고 했을 때.. Criteria API를 이용해서 목록 사이즈를 구하는 코드를 작성해보자.

    public int totalSize() {
        return getCriteriaOf(Code.class).list().size();
    }

캬.. 얼마나 명시적인가. 하지만 이러면 안된다. ㅠ.ㅠ.. 난 하이버가 좀 더 똑똑해져서 저렇게 짜더라도 알아서 count 쿼리를 만들어 주면 좋겠지만 그러지 않는다. 전부다 select 해온다. 로그를 보면..

Hibernate: select this_.id as id0_0_, this_.code as code0_0_, this_.descr as descr0_0_, this_.name as name0_0_ from Code this_

이렇다. 필요한건 사이즈 뿐인데 Code 테이블에 있는걸 전부다 가져온 셈이다. 크헉.. 따라서 목록 사이즈를 구할 때는 이런 쿼리를 쓰면 안된다.. 절대로; 그럼 어떻게 할까나..

    public int totalSize() {
        return (Integer)getCriteriaOf(Code.class)
            .setProjection(Projections.count("id"))
            .uniqueResult();
    }

이렇게 하면된다. Projections를 이용하면 count 말고도 max, min등을 사용할 수 있다. 코드가 좀 길어지고 Projection API에 적응해야 한다는 단점이 있지만 쿼리는 깔끔해진다.

Hibernate: select count(this_.id) as y0_ from Code this_

딱 내가 원하던 쿼리다. 하이버네이트를 쓸땐 이렇게 Criteria나 HQL이 생성해주는 SQL도 일일히 확인하는것이 좋다. 사실 일일히 확인하는 작업을 DBA가 해주면 좋겠지만 하이버네이트랑 친한 DBA가 있을 때의 이야기이고 그렇지 않다면 본인이 해야겠다. 아니면 하이버네이트를 마스터해서 어떤 쿼리가 생성되는지 달달달 꽤고 있다면 시간을 좀 단축 시킬 수 있을 것 같기도 하다.

하이버네이트를 마스터 하기 위해서는 조마간 나올 하이버네이트 번역서가 필수라는....앗..  광고를 하려고 시작한 글은 아니었는데;; ㅋㅋㅋ

ps1: Criteria를 사용해서 좀 더 간결한 코딩으로 가져오는 방법이 있으면 알고 싶다.
ps2: 반드시 Criteria를 써야 한다. 나중에 저 쿼리에 검색 조건이 임의로 추가될텐데 그럴때 Criteria가 빛을 발하기 때문이다. 난 동적쿼리를 SQL이나 HQL로 짜지 못하겠다. 짜증난다.
저작자 표시
신고
top


JUnit 4.8 카테고리 기능

모하니?/Coding : 2010.05.25 16:00


http://kentbeck.github.com/junit/doc/ReleaseNotes4.8.html

public interface FastTests { /* category marker */ }
public interface SlowTests { /* category marker */ }

public class A {
    @Test
    public void a() {
        fail();
    }

    @Category(SlowTests.class)
    @Test
    public void b() {
    }
}

@Category({SlowTests.class, FastTests.class})
public class B {
    @Test
    public void c() {

    }
}

@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@SuiteClasses( { A.class, B.class }) // Note that Categories is a kind of Suite
public class SlowTestSuite {
  // Will run A.b and B.c, but not A.a
}

@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@ExcludeCategory(FastTests.class)
@SuiteClasses( { A.class, B.class }) // Note that Categories is a kind of Suite
public class SlowTestSuite {
  // Will run A.b, but not A.a or B.c
}

1. A.a() 테스트: 아무것도 설정안함.
2. A.b() 테스트: SlowTest.class 카테고리로 지정함(애노테이션 참조)
3. B.c() 테스트: SlowTest.class과 FastTest.class 카테고리에 모두 해당함.( c() 메소드를 가지고 있는 클래스에 붙인 애노테이션 참조)

첫번째 SlowTestSuite 실행할 때는 SlowTest.class 카테고리를 포함시킴. 테스트 대상으로 A.class와 B.class에 있는 모든 테스트를 지정해줌.
=> 따라서 A와 B 클래스에 들어있는 테스트 1, 2, 3이 모두 테스트 대상이 되지만 그 중에서 SlowTest 카테고리에 해당하는 1번(A.b())과 3번(B.c())만 실행됨.

두번째 SlowTestSuite 실행할 때는 SlowTest.class 카테고리를 포함시키고 FastTests.class는 제외시킴.
=> 따라서 위 결과와 비슷하지만 FastTests.class 카테고리에도 들어갈 수 있는 3번이 제외됨.

벤댕이 그림을 그려서 보면 좋겠지만 귀찮아서..패스
저작자 표시
신고
top

TAG JUnit 4.8

인텔리J에서 스칼라 시작하기

모하니?/Coding : 2010.05.11 16:18


1. 플러그인 설치

생략

2. 자바 플젝 + scala 만들기

일반 자바 플젝 만들다 다음 화면에서 scala 체크! 하면 라이브러리 다운받음.


3. HelloWorld

http://www.scala-lang.org/node/166

new -> Scala Class

object HelloWorld {
  def main(args: Array[String]) {
    println("Hello World")
  }
}

4. 스칼라 콘솔 띄우기


+ -> Scala console 선택, 이름 적당히 변경 후 실행.


저작자 표시
신고
top


서비스 계층의 비즈니스 로직을 도메인으로 옮기자

모하니?/Coding : 2010.04.20 14:06


@RunWith(MockitoJUnitRunner.class) public class StudyServiceImplTest { StudyServiceImpl service; @Mock SecurityService securityService; @Mock StudyRepositoryImpl repository; @Mock UnifiedNotificationService notiService; @Mock MemberRepository memberRepository; @Before public void setUp() throws Exception { service = new StudyServiceImpl(); service.securityService = securityService; service.repository = repository; service.notiService = notiService; service.memberRepository = memberRepository; }

....

@Test
public void addCurrentMember() throws Exception {
Study study = new Study();
study.setMaximum(2);
Member currentMember = new Member();

// check add 1
when(securityService.getPersistentMember()).thenReturn(currentMember);
service.addCurrentMember(study);
checkStudyAndMemberSize(study, 1, currentMember, 1);
// check duplicated member add
service.addCurrentMember(study);
checkStudyAndMemberSize(study, 1, currentMember, 1);
// check add 2
currentMember = new Member("keesun@email.com");
when(securityService.getPersistentMember()).thenReturn(currentMember);
service.addCurrentMember(study);
checkStudyAndMemberSize(study, 2, currentMember, 1);

// check add over limit error!!
        try {
            service.addCurrentMember(study);
            fail();
        } catch(StudyMaximumOverException e) {

        }
checkStudyAndMemberSize(study, 2, currentMember, 1);

        //TODO NO_LIMIT_MEMBER_COUNT test
}

봄싹에 있는 코드입니다. 코드가 좀.. 거시기 합니다. mockito를 사용해서 테스트 했는데 사실 mockito를 잘 모르고 mock 테스트에 대해서도 잘 모르는 상태에서 저런 테스트를 이해하기는 힘듭니다.

문제의 원인은 뭘까요?

1. 테스트 코드가 지저분해서..
2. 로직이 서비스에 있느니까..

1번이라고 생각하셔도 별로 할 말은 없습니다. ㅋㅋㅋ 저는 2번이라고 주장하고 싶습니다. 서비스 계층에 비즈니스 로직이 들어가면 어쩔 수 없어요. 목킹을 해서 저렇게 단위테스트를 하던, 느려터진 통합 테스트를 하면 되는데.. 사실 해결책이 있는 경우가 많습니다.

비즈니스 로직을 도메인 클래스로 옮겨주는거죠.

public void addCurrentMember(Study study) {
study.addMember(securityService.getCurrentUser());
}

StudyService에 있던 코드입니다. 이렇게 서비스 계층에서 도메인 계층으로 로직을 모두 옮겼는데 서비스 계층의 테스트가 필요할까요?? 무엇을 테스트할까요? study의 addMember를 호출하는지? seurityService. .getCurrentUser()를 호출하는지? 그 결과는 잘 오는지? 글쎄요.. 호출하면 당연히 호출하겠죠; 머하러 그런걸 테스트할까요. 화이트박스 테스트인지, 행위 기반 테스트인지를 할 때는 필요할지도 모르겠지만 저는 별로 그러고 싶지 않습니다. (구현 세부 사항을 일일히 확인하느니 그냥 그 시간에 코딩을 하는게;;)

그래서 제 나름대로 내린 결론은 저런 코드는 테스트할 필요가 없다는 겁니다. 당연히 잘 호출하겠죠. 물론 예외는 있습니다.

- 트랜잭션 테스트
- 메서드 보안 테스트

이 녀석들은 서비스 계층의 역할로 할당했는데 이런것들은 스터디 통합 테스트에서 하는게 적절합니다. 이번 글에서 다루고자 하는 주제와는 거리가 있으니 다음으로 미루겠습니다.

그럼 위의 테스트를 지우면 끝일까요? 

아니죠. 테스트 위치가 바뀌어야 합니다. 도메인으로 비즈니스 로직이 옮겨갔으니 도메인 클래스를 테스트해야죠.

    @Test
    public void addMember(){
        Study study = new Study();
        Member member = new Member();

        study.addMember(member);

        assertThat(study.getCurrentMembers().size(), is(1));
        assertThat(member.getStudies().size(), is(1));
    }


    @Test(expected = StudyMaximumOverException.class)
    public void addMemberException(){
        Study study = new Study();
        study.setMaximum(1);

        Member member = new Member();

        study.addMember(member);

        assertThat(study.getCurrentMembers().size(), is(1));
        assertThat(member.getStudies().size(), is(1));
        
        study.addMember(new Member());
    }

그렇게 옮겨간 테스트가 이렇습니다. 모든 경우를 다 옮기진 않았지만 요렇습니다. 서비스에서 테스트 할 때 보다 훨씬 간결하기 때문에 테스트 작성도 쉽고 이해하기도 쉽습니다. 그쵸?? 아님 말구요.ㅋㅋ

사실 이 부분을 KSUG 발표 때 보여드렸어야 하는데;; 소녀시대 때문에 깜빡했어요.
저작자 표시
신고
top


[주말 코딩] 봄싹 위키

모하니?/Coding : 2010.04.04 22:51



왼쪽에는 모든 위키 목록, 오른쪽에는 최근 작업한 페이지 목록.. 각 목록의 이미지를 클릭하면 하단에 설명 부분이 열립니다. 페이지 목록에서는 미리 보기 형태로 페이지를 보여주는데;; 디자인이 좀 깨지기 때문에 직접 가서 보는게 좋겠습니다. 해당 위키나 페이지로 이동하려면 이미지가 아니라 제목 주변을 클릭하시면 됩니다.

자바 코딩하는 데는 1시간도 안걸렸는데 화면 설계하고 코딩하는데 3시간 걸리네요. @_@;; 이렇게 보여줄까 저렇게 보여줄까? 이게 보기 편할까? 저게 보기 편할까? JQuery에 toggle을 어떻게 쓰더라. 옵션이 머지. accodoan을 써볼까 어쩔까.. 덩덩덩...

위키 목록은 Confluence REST API를 호출하여 가져오고, 최근 글 목록은 Confluence에서 최근 30일 내에 글 10개를 가져오도록 만들어 둔 Atom Feed를 사용해서 가져옵니다. 매번 요청할 때 마다 두 URL로 요청/파싱을 해서 그런지 느립니다. @_@;

내부 구현을 DAO를 사용하도록 바꾸는 작업은 KSUG에서 발표 할 때 해볼까 합니다.
1. 각 목록에 새로고침 버튼 추가.
2. 해당 버튼은 "관리자" 권한이 있는 사용자만 보이고 사용할 수 있음.
3. 새로고침을 누르면 WikiSpace와 WikiPage DB를 모두 비우고 새 데이터를 추가.
4. 위키 페이지 보여줌.
5. '일반' 사용자가 위키 페이지를 클릭하면 DB에 저장되어 있는 데이터를 가져옴.

이렇게 하면 아마 스프링 시큐리티, 스프링 MVC, 하이버네이트 코딩까지도 보여드릴 수 있을것 같습니다.
Comming Soon!
저작자 표시
신고
top


[JSON in Java] JSON에서 필요한 데이터 뽑기

모하니?/Coding : 2010.04.01 15:26


참조: 
http://www.json.org/java/
http://decoder.tistory.com/38

예전에는 JSON.simple 이라는 프로젝트였나본데, 소스 코드를 묶어서 주네요. 의존성도 없고 깔끔하군요. 최근까지도 계속 업데이트를 하고 있는 것 같으니 믿고 써볼만 해 보입니다.

몇일 전 Confluence 위키 내용을 RestTemplate을 이용해서 가져온 적이 있는데, 그 데이터 타입을 XML로 받을까 JSON으로 받을까 고민했었는데 그냥 JSON으로 받기로 했습니다. XML 받아서 OXM으로 파싱해도 되는데 일단 전송되는 텍스트량이 XML은 너무 많고 JSON은 꽤 간결하기 떄문입니다. 그 뒤에 객체로 변환하는 과정이 둘 중에 어떤게 편하냐를 가지고도 판단해보려고 했는데 OXM 써보기도 전에 JSON으로 기울태세 입니다.

        RequestCallback callback = new SpringSproutDefaultRestRequestCallBack();
        ResponseExtractor<String> extractor = new SpringSproutJsonRestResponseExtractor();

        RestTemplate restTemplate = new RestTemplate();
        String result = restTemplate.execute(SPACE_LIST_URL, HttpMethod.GET, callback, extractor);
        System.out.println(result);

        // ref: http://decoder.tistory.com/38
        JSONObject spaceListJSON = new JSONObject(result);
        JSONArray spaceJSONArray = spaceListJSON.getJSONArray("space");
        for(int i = 0 ; i < spaceJSONArray.length() ; i++){
            JSONObject spaceJSON = spaceJSONArray.getJSONObject(i);
            System.out.println(spaceJSON.getString("name"));
            System.out.println(spaceJSON.getString("key"));
            System.out.println(spaceJSON.getString("description"));
        }

캬.... 간단하군요. 이제 저 코드를 Extractor 쪽으로 옮겨서 RestTemplate 호출할 때 자동으로 ConfluenceSpace 컬렉션 객체가 튀어나오도록 해봐야겠습니다.

저작자 표시
신고
top


[Spring 3.0 & Atlassian] RestTemplate으로 Confluence의 Space 목록 가져오기

모하니?/Coding : 2010.03.26 00:51


public class ConfluenceRestTest {

    public static final String SPACE_LIST_URL = "http://dev.springsprout.org/wiki/rest/prototype/1/space?os_authType=basic";

    /**
     */
    @Test
    public void testGetWikiSpaceList() {
        RestTemplate restTemplate = new RestTemplate();
        String result = restTemplate.execute(SPACE_LIST_URL, HttpMethod.GET,
                new JsonRestRequestCallBack(),
                new BodyToStringRestResponseExtractor());
        System.out.println(result);
    }

    static class JsonRestRequestCallBack implements RequestCallback {
        public void doWithRequest(ClientHttpRequest clientHttpRequest) throws IOException {
            HttpHeaders headers = clientHttpRequest.getHeaders();
            headers.add("Authorization", "Basic XXXXXXXX(base64 encoded username:passwd)");
            headers.add("Accept", "application/json");
        }
    }

    static class BodyToStringRestResponseExtractor implements ResponseExtractor<String> {

        public String extractData(ClientHttpResponse clientHttpResponse) throws IOException {
            return convertStreamToString(clientHttpResponse.getBody());
        }
        ....
    }

}

봄싹 위키를 컨플루언스로 바꾸었기 때문에 기존 메뉴를 컨플루언스에 있는 정보를 끌어다 보여주는 것으로 바꾸려고 합니다. 그래서 파일럿(?) 겸사해서 코딩을 해봤는데 잘 됐네요.

{"expand":"space","space":[{"name":"개발툴","key":"DTL","link":{"rel":"self","href":"http://dev.springsprout.org/wiki/rest/prototype/1/space/DTL"},"description":"개발툴 관련 내용을 정리하는 공간입니다."},{"name":"베타리딩","key":"BTR","link":{"rel":"self","href":"http://dev.springsprout.org/wiki/rest/prototype/1/space/BTR"},"description":"봄싹 베타리딩 관련 공간입니다."},{"name":"봄싹 Modules","key":"BSM","link":{"rel":"self","href":"http://dev.springsprout.org/wiki/rest/prototype/1/space/BSM"},"description":"봄싹 Module과 관련된 정보를 정리하는 공간입니다."},{"name":"봄싹 개발","key":"SSD","link":{"rel":"self","href":"http://dev.springsprout.org/wiki/rest/prototype/1/space/SSD"},"description":"봄싹에서 진행하는 개발과 관련된 정보를 정리하는 공간입니다."},{"name":"스프링 3.0 레퍼런스 번역","key":"SRK","link":{"rel":"self","href":"http://dev.springsprout.org/wiki/rest/prototype/1/space/SRK"},"description":"스프링 3.0 레퍼런스를 번역하는 공간입니다."},{"name":"스프링 시큐리티 3.0 레퍼런스 번역","key":"springsecurity","link":{"rel":"self","href":"http://dev.springsprout.org/wiki/rest/prototype/1/space/springsecurity"},"description":""},{"name":"스프링소스 블로그 번역","key":"SBK","link":{"rel":"self","href":"http://dev.springsprout.org/wiki/rest/prototype/1/space/SBK"},"description":"스프링소스 블로그를 번역하는 공간입니다."},{"name":"연습장","key":"ds","link":{"rel":"self","href":"http://dev.springsprout.org/wiki/rest/prototype/1/space/ds"},"description":"봄싹 위키 사용법을 익히는 공간입니다."},{"name":"자바","key":"JAVA","link":{"rel":"self","href":"http://dev.springsprout.org/wiki/rest/prototype/1/space/JAVA"},"description":"자바 학습 공간입니다."}]}

막상 해보고 나니 고민이 생기네요. "저 작업을 뷰에서 할 것이냐 서버단에서 할 것이냐?"

뷰에서 하면 저 페이지로 이동 할 때 마다 요청을 보낼테니 Confluence 응답이 늦어지면 문제고 Confluence 입장에서 부담이 될 수도 있고, Confluence가 죽었을땐 위키 페이지까지 마비가 되지만 구현하는 입장에선 뷰 코드만 지지고 볶으면 되니까 간단할 것 같은데..

서버단에서 하면 DB에 저장해두거나 캐싱을 해둘 수 있으니 Confluence의 부담도 덜고 Confluence가 죽어도 위키 정보는 보여줄 수 있지만 구현하면서 이것 저것 신경쓸게 많아지네;; 정보를 읽어올 주기, 배치로 돌릴 것이냐? JSON말고 XML로 받아온다음 OXM을 이용해서 XML->Object로 바로 변환해버리는 ResponseExtracter를 만들까나.. 캐싱은 이미 Ehcache를 하이버 땜시 쓰고 있으니 캐싱도 해보고?

흠.. 서버단에서 할까나 어쩔까나.. Confluence가 죽어있으면 어차피 봄싹 Wiki에서 목록 본다음에 링크 클릭해도 이동을 못하니까 그냥.. 뷰에서 읽을까나.. +_+ 그럼 나 머하러 RestTemplate 사용한거지.. @_@.. 기왕에 썼으니까 OXM도 적용해서 서버 단에서 해버렷?ㅋ 
저작자 표시
신고
top


[Spring Security] Method Security Test

모하니?/Coding : 2010.03.19 21:15


    @PreAuthorize("hasRole('ROLE_MEMBER')")
    public boolean write(String contents) {
        while (graffitiRepository.getTotalRowCount() >= GRAFFITI_LIMIT_COUNT) {
            graffitiRepository.deleteFirstGraffiti();
        }
        Graffiti graffiti = new Graffiti(contents, securityService.getCurrentMember());
        graffitiRepository.add(graffiti);
        return true;
    }

위 코드는 봄싹 낙서장 서비스의 코드이다. 봄싹 메인 화면에 있는 낙서장은 최대 100개를 유지하며 ROLE_MEMBER 권한을 가지고 있는 사용자(이메일 인증 절차를 거친 기본 회원)라면 누구나 낙서를 추가할 수 있다.

이 코드의 내용 대부분은 단위 테스트로 커버가 가능하다. 하지만 스프링 시큐리티 애노테이션이 제대로 동작하는지는 어떻게 테스트 할 것인가?

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/testContext.xml", "/testContext-security.xml"})
@Transactional
public class MethodSecurityTest extends DBUnitSupport{

    @Autowired GraffitiService graffitiService;
    @Autowired GraffitiRepository graffitiRepository;

    @Before
    public void login() throws Exception {
        insertXmlData("testData.xml");
        SecurityContext securityContext = new SecurityContextImpl();
        Authentication authentication = new UsernamePasswordAuthenticationToken("whiteship@email.com", "passwd");
        securityContext.setAuthentication(authentication);
        SecurityContextHolder.setContext(securityContext);
    }

    @Test
    public void methodAuth(){
        assertThat(graffitiRepository.getAll().size(), is(0));
        graffitiService.write("hi");
        assertThat(graffitiRepository.getAll().size(), is(1));
    }

    @After
    public void after(){
        SecurityContextHolder.clearContext();
    }

}


이런식으로 테스트 할 수 있다.

1. 시큐리티 설정 파일을 테스트에서 만들 AC(ApplicationContext)용 설정에 추가한다.
2. DB에 xml로 작성한 가짜 사용자, 권한 정보를 넣어야 하니까 DBUnit을 편하게 사용할 수 있게 해주는 클래스를 이용한다.
3. 테스트 실행하기 전에 로그인 한다. (SecurityContextHolder에 SecurityContext를 넣는다.)
4. 테스트 실행한 뒤에 로그오프 한다. (SecurityContextHolder를 비워버린다.)


저작자 표시
신고
top


봄싹 개발자 모집합니다. (완료됨)

모하니?/Coding : 2010.03.17 11:00


모집이 완료되었습니다.
참가 신청 해주셔서 감사합니다.
- updated March, 22th. 2010
================================
봄싹 공통 개발팀: http://dev.springsprout.org/wiki/pages/viewpage.action?pageId=1114148
봄싹 세미나 개발팀: http://dev.springsprout.org/wiki/pages/viewpage.action?pageId=1114158

두 팀에서 개발자를 모집하고 있습니다. 이건 일자리가 아니라 취미 개발입니다. 개발 기간은 4~5주 정도로 잡고 있습니다. 자세한건 팀을 모집한 뒤에 논의하겠습니다.

가급적이면 서울 중심지에 사시는 대학생 또는 스프링과 하이버네이트에 관심있는 개발자 분들이 참여해주시면 좋겠습니다.1주일에 한번 정도는 오프라인으로 만나서 할거라서 서울 중심지에 사시는 분들이 이동하기 편할 것 같구요. 프로젝트가 스프링, 하이버네이트로 되어있어서 경험이 없지만 이 둘을 사용하는 프로젝트를 하고 싶으신 분들이 참여해주시기 바랍니다. 물론 경험자여도 상관없습니다. 같이 플젝을 해보고 싶으신 분들이라면 누구나 참여 가능합니다.

한 프로젝트당 최대 수용 인원은 4명 ~ 5명이구요. 제 메일(이 블로그 제일 하단에 있습니다.)로 신청해주시기 바랍니다. 메일을 보내주실 때 다음 유의사항을 지켜주세요.

- 제목 머릿말에 [봄싹]을 넣어주세요. 예) [봄싹] 개발 참가 신청합니다.
- 이름 또는 아이디 (뭐라고 불러드릴까요??)
현재 하시는 일 (무슨 플젝에 참여했었고 어디서 몇년째 근무중인게 궁금한게 아니랍니다. 학생일 경우에는어느 대학 무슨 학부 몇 학년인지 궁금한게 아니랍니다. 그런 피상적인 것들이 아니라 실제로 하고 계신 공부나 일을 적어주세요.)
- 참여하고 싶은 팀 (둘 중에 하나만 선택해 주세요.)
- 토요일 오전 모임 참석 가능 여부(중요합니다. 주로 신촌에서 토요일 오전에 모입니다.)
- 기타 (블로그, 하고싶은 이야기, 기타 덩덩덩)

기다리겠습니다.
저작자 표시
신고
top


[구글 Visualization API] Fluent Interface 적용하기

모하니?/Coding : 2010.03.11 14:18


http://martinfowler.com/bliki/FluentInterface.html

이름은 좀 멋지지만 별거 아니라는...

        Statistics statistics = new Statistics(StatisticsChartType.MOTION, "스터디 참석자 통계 그래프");
        statistics.addColumn(StatisticsColumnType.STRING, "스터디 이름");
        statistics.addColumn(StatisticsColumnType.DATE, "모임 날짜");
        statistics.addColumn(StatisticsColumnType.NUMBER, "참석자 수");
        statistics.addColumn(StatisticsColumnType.NUMBER, "모임 횟수");
        statistics.addColumn(StatisticsColumnType.NUMBER, "참석율");
        statistics.addColumn(StatisticsColumnType.STRING, "스터디 상태");
        statistics.addColumn(StatisticsColumnType.STRING, "모임명 이름");

이 코드는 얼마전에 봄싹 사이트에 Statistics 메뉴를 추가하면서 코딩한 것인데 오늘 보다가 갑자기 Fluent Interface가 떠올랐습니다. 그래서 적용해봤지요.

        Statistics statistics = new Statistics(StatisticsChartType.MOTION, "스터디 참석자 통계 그래프")
            .withColumn(StatisticsColumnType.STRING, "스터디 이름")
            .withColumn(StatisticsColumnType.DATE, "모임 날짜")
            .withColumn(StatisticsColumnType.NUMBER, "참석자 수")
            .withColumn(StatisticsColumnType.NUMBER, "모임 횟수")
            .withColumn(StatisticsColumnType.NUMBER, "참석율")
            .withColumn(StatisticsColumnType.STRING, "스터디 상태")
            .withColumn(StatisticsColumnType.STRING, "모임명 이름");

조금 fluent 하게 보이나요??ㅋ 객체를 다 만든 다음에 추가하는게 아니라 객체 만들 때 메서드를 연달아 호출하는 모양이여서 메서드 이름을 add -> with로 변경했습니다. 메서드 리턴 타입도 당연히 바꿨지요.

장점은 저렇게 조금 API가 이쁘고 사용하기 편해진다는 것이고 단점은 디버깅할 때 조금 불편할 수 있다는데.. 머.. 글쎄요. 그다지...
저작자 표시
신고
top


[Google Visualization API] 캬.. 멋지구나~

모하니?/Coding : 2010.03.07 23:12


http://code.google.com/intl/ko-KR/apis/visualization/interactive_charts.html
http://code.google.com/intl/ko-KR/apis/visualization/documentation/index.html
http://code.google.com/intl/ko-KR/apis/visualization/documentation/using_overview.html
http://code.google.com/intl/ko-KR/apis/visualization/documentation/reference.html
http://code.google.com/apis/ajax/playground/?type=visualization#motion_chart


09-10 시즌 보딩을 저번 주를 끝으로 접었더니 주말에 갑자기 시간이 좀 생겼네요.
심심해서 낮에는 본가에 다녀오고 저녁에는 와이프가 공부하느라 흑흑.. 저는 코딩이나...

http://springsprout.org/statistics/index.do
저작자 표시
신고
top


톰캣 메일링 리스트 가입하기

모하니?/Coding : 2010.03.05 20:34


http://tomcat.apache.org/lists.html#tomcat-dev

1. 메일 보내기

위 페이지에서 원하는 메일링 리스트로 메일을 보낸다. 제목도 없고 본문도 없이 그냥 빈 메일..

2. 답장하기

그럼 조금 뒤에 메일이 한통오는데 잘 읽어보면 결국 확인할겸 해당 주소로 답장을 보내달라는 것이다.
그냥 답장 버튼을 눌러서 간단하게 적어서 전송하면 된다.

3. 확인하기

그럼 확인 메일이 또 한통 온다.

끝.
저작자 표시
신고
top


우편번호 검색&입력기 만들기

모하니?/Coding : 2010.03.03 16:30


1. 우편번호 DB 다운받기

http://www.zipfinder.co.kr/zipcode/index.html

우정국에서 제공하는 zipcode 데이터를 받습니다.
여러가지 형태가 있는데 저는 가장 컬럼 갯수가 적은 걸 받았습니다.

2. 도메인 만들기

@Entity
@DomainInfo("우편번호")
public class Zipcode {

    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    int id;
    @Column(length=7)
    @DomainInfo("zipcode")
    String zipcode;
    @Column(length=4)
    String sido;
    @Column(length=15)
    String gugun;
    @Column(length=52)
    String dong;
    @Column(length=17)
    String bunji;

위에서 받은 필드에 맞게 정의해줍니다.
hbm2ddl을 auto 모드로 놓고 개발중이라 하이버네이트가 돌다가 테이블이 없어서 만들어 줍니다.
제품 단계에서 hbm2ddl을 auto로 설정해두는 것은 매우 위험하오니 꼭 개발중에만 사용하시길..(저는 배포 중에도 쓰지만 ㅋㅋㅋ)

3. zipcode 데이터 넣기

1번에서 받은 데이터를 엑셀로 변환하고 만개 단위로 나눠서 저장했습니다. DBUnit으로 넣었는데 5만개가 넘는걸 한 방에 넣으려고 했더니 POI 쪽에서 에러가 나더군요. 스크롤 기능이 있는지 찾아보려다 귀찮아서 그냥 만개 단위로 잘라서 넣었습니다.

        dbUnitSupport.insertXlsData("zipcode1.xls");
        dbUnitSupport.insertXlsData("zipcode2.xls");
        dbUnitSupport.insertXlsData("zipcode3.xls");
        dbUnitSupport.insertXlsData("zipcode4.xls");
        dbUnitSupport.insertXlsData("zipcode5.xls");

대충 요런식으로..

4. 사용자 추가 화면에 우편번호 자동완성 필드 추가

<k:fzipcode />

이 태그 파일을 만들고 저거 한줄을 사용자를 추가하는 jsp에 추가해줍니다. 저 태그 파일은 대충..

...
<c:set var="pathNS" value="${path != null ? path : 'zipcode'}" />
<c:set var="labelNS" value="${label != null ? label : '우편번호'}" />
<c:set var="popupUrlNS" value="${popupUrl != null ? popupUrl : '/system/zipcode/popupsearch.do'}" />

<o:fSearchPopup label="${labelNS}" url="/ajax/zipcode/dong.do" path="${pathNS}"
    model="zipcodes" size="20"  maxlength="20" />

<script type="text/javascript">
function formatItem(row, i, total){
    return row[1] + " " + row[2] + " " + row[3] + " " + row[4] + " " + row[5];
}
function formatResult(row){
    return row[1];
}
function result(event, row, formatted){
    if (row){
        $("#addr").attr("value", row[2] + " " + row[3] + " " + row[4]);
    }
}
$("#${pathNS}row img").click(function(e) {
    popup("<c:url value='${popupUrlNS}'/>", "itemPopup", 900, 900, "yes", "yes");
});
</script>
...

이런식으로 생겼습니다. 여기서 또 다른 태그 파일을 사용하고 있는데 그녀석이 예전에 만들어둔 Ajax 자동완성 태그 파일입니다.

5. Ajax 요청처리를 구현합니다.

    @RequestMapping("/ajax/zipcode/dong.do")
    public ModelAndView zipcodes(ModelMap map, AjaxParams ajaxParams) {
        map.addAttribute("zipcodes", zipcodeService.findZipcodesByDong(ajaxParams));
        return new ModelAndView("jsonView", map);
    }

Ajax 요청이 왔을 때 우편번호에 필요한 것들을 가져다 주도록 Service -> DAO로 전달 전달. DAO가 가져와서 Service -> Controller 전달 전달.. 뷰까지 ㄱㄱㄱ

6. 검색 버튼 구현

자동완성 오른쪽에는 항상 수동으로 검색할 수 있도록 검색 버튼이 달려있습니다. 팝업이 떠서 검색하고 팝업이 닫힐 때 사용자 추가 화면에 자동으로 값을 넣어 주도록 만드는게 관건.

버튼 눌렀을 때 요청할 URL은 4번 단계에서 태그 파일에 설정해 두었으니 이제 해당 요청을 처리할 컨트롤러와 뷰 만들고 서비스 구현하면 끝.

<o:searchpage title="우편번호 검색">
    <o:buttons>
        <o:searchbtn />
    </o:buttons>
    <o:searchform action="selectgrid.do">
        <o:searchrow>
            <o:text label="읍, 면, 동, 리, 건물명" id="dong" size="30" maxlength="30" />
        </o:searchrow>
    </o:searchform>
</o:searchpage>
<%@ include file="/include/searchfooter.jsp" %>

이게 검색화면 끝.

<o:gridpage>
    <o:zipcodeselectgrid list="${list}">
        <o:col header="id" path="id" hidden="true" type="integer" pattern="#########"/>
        <o:col header="우편번호" path="zipcode" width="80"/>
        <o:col header="시, 도" path="sido" width="50"/>
        <o:col header="구, 군" path="gugun" width="160" />
        <o:col header="동, 면, 건물" path="dong" width="250" />
        <o:col header="번지" path="bunji" width="180" />
    </o:zipcodeselectgrid>
</o:gridpage>

이게 그리드 화면 끝.

    @RequestMapping
    public void popupsearch() {
    }

    @RequestMapping
    public ModelAndView selectgrid(HttpServletRequest req, HttpServletResponse res,
            ModelMap model, ZipcodeParams params, OrderPage orderPage, String view) throws Exception {
        ModelAndView mav = super.grid(req, res, model, params, orderPage, view);
        mav.setViewName("system/zipcode/selectgird");
        return mav;
    }

이게 요청 처리 및 서비스 구현 끝.

7. 완성



팝업으로 검색해서 선택하면 우편번호와 주소에 팍팍 입력되고 팝업 닫히는 것도 되지만 스샷은 생략~
저작자 표시
신고
top


[스프링 3.0 @MVC] 컨트롤러에 스프링 AOP가 적용되지 않는다는건 이제 거짓말

모하니?/Coding : 2010.02.17 14:49


@MVC를 사용하면 좋은 점 중 하나가 스프링 AOP 적용이 쉽다는 겁니다. 아직도 여러 이유로 이전의 Controller 계층 구조를 이용해서 개발하시는 분들이 많겠지만 @MVC를 사용하시는 분들은 이점을 꼭 알고 계셔야 합니다. 스프링 @MVC도 이제 스프링 AOP가 아주 잘 먹힙니다.

@Controller
public class TestController {

    @RequestMapping("/test")
    @Transactional
    public void test() {
        System.out.println("hi!!!!!!!!!!!!!!!!!!!!!!");
    }

}

하지만 그렇다고 해서 이런 코드를 작성해 달라는 것은 아닙니다. 절대로.. 네버..

어찌됐든 위와 같은 코드도 동작하게 되어있는데 경우에 따라서는 안 될 수도 있습니다. 그런 경우는 스프링 트랜잭션이나 스프링 시큐리티 애노테이션에 문제가 있는것이 아니라 빈설정과 관련이 있을 수가 있으니 빈 설정을 잘 보셔야 합니다.

만약 위와 같은 경우라면.. @Controller 빈이 만들어지는 ApplicationContext와 tx:annotation 머시기가 등록되는 ApplicationContext가 같은지 확인해봐야 합니다.

정말,, 굳이.. 컨트롤러에 AOP를 적용해야 한다면 http://toby.epril.com/?p=934 이 글따라서 DS가 만드는 WebAC 하나로 모두 통합하는 것도 좋겠습니다.
신고
top


[SVN] Spring Framework 3.0.0.RELEASE

모하니?/Coding : 2010.02.11 16:07


스프링 프레임워크 SVN: https://src.springframework.org/svn/spring-framework
스프링 프레임워크 3.0.0.RELEASE: tags/spring-framework-3.0.0.RELEASE


좋아 좋아. 인텔리J구나 역시...
신고
top


[봄싹 DevTerms] 활성화 맞춤형 개발

모하니?/Coding : 2010.01.21 10:15



현재 봄싹 DevTerms는 오픈을 한지 얼마 안되서 인지 사용자가 몇 안되고 쌓인 데이터가 별로 없지만 장차 사용자도 늘어나고 데이터가 많아지면 다음과 같은 기능을 추가할 계획입니다. (어제 오늘 오픈 하고 난 뒤 생간난 아이디어들입니다.)

봄싹 DevTerms 트위터가 활성화 되면..
- 개발 용어 및 한글 용어가 올라갈 떄마다 트위터에 등록되는데, 만약 봄싹 DevTerms 트위터가 활성화 되어 올라온 메시지에 누군가 reply나 retweet등을 달면 해당 메시지들을 읽어와서 봄싹 DevTerms에서 보여줄 수 있겠습니다.

개발 용어가 많이 등록되면..
- 오늘 용어, 이번주 용어 탭을 추가하여 오늘 하루에 몇 개나 등록됐으며 어떤 단어들이 등록됐는지 볼 수 있게 하겠습니다. 그러나.. 아직은 뭐..

등록된 개발 용어가 2000개가 넘으면...
- 아이폰과 안드로이드폰 용 애플리케이션을 개발하겠습니다.

개발 용어가 빈번히 등록 또는 수정 된다면..
- 코멧을 적용하여 트위터처럼 실시간으로 새로 등록된 용어가 있음을 알려주고 페이지 업데이트를 하라는 메시지를 전달하거나 자동으로 페이지를 업데이트 해주겠습니다. 리버스 Ajax를 적용해야되서 기술적으로도 재밌을 것 같네요.

등록자 외에도 개발 용어를 편집하고 싶은 분들이 있다면..
- 위키 형태로 히스토리를 저장하고 누가 언제 수정했는지 기록을 남기겠습니다. 현재는 관리자 권한을 가진 사용자는 모든 용어를 편집할 수 있지만 히스토리를 남기고 있지 않아서 위키라고 볼 수는 없는데 아직 이 기능이 필요한 시점은 아닌 것 같습니다. 활성화가 된다면 모를까..

뷰 카운트 및 추천 카운트가 1000이 넘는다면..
- 현재 봄싹 막내(스루)가 미래를 위한 대비 작업으로 천천히 진행 중이긴 한데, StackOverFlow 처럼 카운트 단위를 도입해서 1000건이 넘는 조회수에는 k를 붙여서 1000v -> 1kv로 카운트 단위를 적용하겠습니다.

The more you use it, the more you will get.



신고
top


2010 대한민국 매쉬업 참가했습니다.

모하니?/Coding : 2010.01.20 16:30


"개발 용어 한글화 프로젝트 DevTerms"로 참가했습니다. 이 녀석을 어제 배포하고 밤새 나머지 주요 기능 중 하나였던 '관심 용어'까지 구현한 뒤 배포하고 참가 신청까지 완료했습니다. 2010 매쉬업 참가 목록에서 보실 수 있습니다.


위에 보시면 아시겠지만 작년 11월부터 이미 DevTerms를 만들고 싶다는 이야기를 봄싹 그룹스에서 했었습니다. 그러던 중 매쉬업 소식을 듣게 되었고 DevTerms에 오픈 API를 적용해서 내보내게 된 겁니다. 1석 2조가 됐지요. 어차피 봄싹에 새 기능으로 추가할 계획이었고 그 기능에 필요한 오픈 API를 적용해서 매쉬업까지 나갔으니 말이죠.

그래서 그런지 제가 제출한 제품은 매쉬업이 메인이 아니라 '개발 용어 한글화' 서비스가 메인입니다. (벌써 43개나 제출됐네요.. 캬오.)그걸 보조하는 수단으로 사전 API와 트위터 API 사용하고 있죠. 사전 API는 기대 했던 것 보다 효과가 좋다고 생각하고 있습니다. 개발 용어는 영어 사전으로 검색한 결과를 보여주고 한글 용어는 한글 사전으로 검색한 결과를 보여주고 있습니다. 트위터 API를 이용하여 등록되는 용어들을 트위터 메시지로 등록해주고 링크를 타고 봄싹 DevTerms로 올 수 있게 해뒀습니다. 트위터 서비스만 해두면 사용자들이 트위터 RSS 피드나 노티 애플리케이션을 이용해서 등록되는 용어들을 실시간으로 받아 볼 수도 있지요.

http://twitter.com/devterms

사실 중간에 매쉬업 참가를 포기할까도 고민했습니다. 혼자서 디자인까지 신경쓰면서 기능 구현을 하자니 중간에 탁탁 끊기는 느낌이고 누군가 제가 집중해서 기능을 구현하고 있으면 그 동안 만든 페이지들 디자인을 점검하고 개선해주면 좋겠다 싶어서 봄싹 팀원에게 도움을 요청했습니다. 다행히 그 친구가 흔쾌히 승낙하고 휴가까지 반납하고 우리 회사로 찾아와 개발을 해주었기 때문에 일정과 품질을 다 기대했던 것 만큼 맞출 수 있었다고 생각합니다. 회사나 계약 등으로 묶여서 같이 일하는 것이 아니라 순수한 열정으로 묶여서 '함께 일한다'는 것이 무엇인지 조금은 알 수 있을 것 같은 기분입니다.

개발에 투자한 일수는 얼마 되지 않습니다. 1월 초까지도 번역을 하고 있었고 JSF를 공부하던 중에 갑자기 매쉬업이 생각나서 저번주부터 오늘까지 한 10일 정도 달린 것 같습니다. 하지만 투자한 시간은 거의 한달 정도 회사일에 투자한는 시간이었던 것 같습니다. 아침 10시부터 새벽 5시까지 전철 타고 밥먹는 시간 뺴고 코딩만 해본건 처음이었습니다. @_@ (어제와 오늘은 출근 길에도 전철에서 코딩을 했습니다. 아흑.. '쟤 모야... '이런 사람들의 시선도 무시한 채 꾿꾿히..) 이렇게 빠져버린 날 이해해준 아내에게 그저 고맙고 미안할 뿐입니다.

원래 계획은 1월 20일. 오늘부터 다시 번역에 집중해서 2월 달 안에 번역을 마무리하는게 계획이었습니다. 그런데 왠걸.. 24일까지 수정할 수 있게 해준다는군요. 뭐 기존에 생각했던 기능들은 다 만들었지만 막상 써보니까 생기는 요구사항들을 무시할 순 없을 것 같습니다. 24일까지만 손을 대고 후딱 번역을 마무리 한 다음 3월부터 진정한 2010년을 맞이해야겠습니다. 후아...

결과가 어찌될지 몰겠지만 수고했다. 기선아. 조금만 더 하고 마무리하자.





신고
top




: 1 : 2 : 3 : 4 : ··· : 10 :





티스토리 툴바