Spring MVC 에서는 어떻게 하나 봤더니 Controller 에 Interceptor 를 등록하면 되더군요.
단계를 요약하자면...
- org.springframework.web.servlet.HandlerInterceptor 또는 org.springframework.web.servlet.handler.HandlerInterceptorAdapter 를 상속받아 preHandler, postHandler, afterComletion 등에 원하는 작업을 구현합니다.
- servlet.xml 에 위에서 작성한 Interceptor 를 등록합니다.
아주 쉽죠?
그럼 실제 예제와 함께 보시죠. 예제는 Spring Framework 3.2.1.RELEASE 와 Hibernate 4.1.9 Final 로 제작했습니다.
UnitOfWorkInterceptor 는 사용자 요청이 있으면 Start 하고, 요청 작업이 완료되면 Close 하도록 합니다. 이는 Hibernate 를 이용하여 Unit Of Work 패턴을 구현하여, 하나의 요청 중에 모든 작업을 하나의 Transaction으로 묶을 수 있고, 웹 개발자에게는 Unit Of Work 자체를 사용하기만 하면 되고, 실제 Lifecycle 은 Spring MVC 에서 관리하도록 하기 위해서입니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kr.nsoft.data.hibernate.springmvc; | |
import kr.nsoft.data.hibernate.unitofwork.IUnitOfWork; | |
import kr.nsoft.data.hibernate.unitofwork.UnitOfWorks; | |
import lombok.extern.slf4j.Slf4j; | |
import org.springframework.web.servlet.HandlerInterceptor; | |
import org.springframework.web.servlet.ModelAndView; | |
import javax.servlet.http.HttpServletRequest; | |
import javax.servlet.http.HttpServletResponse; | |
/** | |
* Spring MVC 에서 servlet 시작과 완료 시에 UnitOfWork를 시작하고, 완료하도록 합니다. | |
* User: sunghyouk.bae@gmail.com | |
* Date: 13. 2. 15. | |
*/ | |
@Slf4j | |
public class UnitOfWorkInterceptor implements HandlerInterceptor { | |
/** | |
* Controller 가 수행되기 전에 호출됩니다. | |
*/ | |
@Override | |
public boolean preHandle(HttpServletRequest request, | |
HttpServletResponse response, | |
Object handler) throws Exception { | |
UnitOfWorks.start(); | |
return true; | |
} | |
/** | |
* Controller의 메소드가 수행이 완료되고, View 를 호출하기 전에 호출됩니다. | |
*/ | |
@Override | |
public void postHandle(HttpServletRequest request, | |
HttpServletResponse response, | |
Object handler, | |
ModelAndView modelAndView) throws Exception { | |
// Nothing to do | |
} | |
/** | |
* View 작업까지 완료된 후 Client에 응답하기 바로 전에 호출됩니다. | |
*/ | |
@Override | |
public void afterCompletion(HttpServletRequest request, | |
HttpServletResponse response, | |
Object handler, | |
Exception ex) throws Exception { | |
IUnitOfWork unitOfWork = UnitOfWorks.getCurrent(); | |
if (unitOfWork != null) { | |
UnitOfWorks.closeUnitOfWork(unitOfWork); | |
if(log.isDebugEnabled()) | |
log.debug("Client 요청 처리를 완료하였으므로, UnitOfWork를 종료합니다."); | |
} | |
} | |
} |
postHandle 에서는 아무 작업도 하지 않는데, 실제 View 에서 ViewModel 를 binding 할 시에, lazy initialized 되는 associated entity를 후에 가져 올 수 있기 때문입니다. 여기서 unit of work 를 끝내버리면, session을 닫아버려, lazy initialize 작업에서 예외가 발생하게 됩니다.
afterCompletetion 에서는 View 작업까지 완료되고, Client 에 Reponse 를 보내기 바로 직전에 호출됩니다. 여기서 unit of work 를 close 합니다.
다음으로는 웹 어플리케이션에 Spring MVC Context 를 설정 시 interceptor를 등록하기만 하면 됩니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<context:annotation-config/> | |
<context:component-scan base-package="kr.nsoft.contact"/> | |
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> | |
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> | |
<property name="prefix" value="/WEB-INF/view/"/> | |
<property name="suffix" value=".jsp"/> | |
<property name="contentType" value="text/html;charset=UTF-8"/> | |
</bean> | |
<!-- UnitOfWorks.start(), close() 를 servlet의 시작과 완료에 처리하도록 한다 --> | |
<mvc:interceptors> | |
<bean class="kr.nsoft.data.hibernate.springmvc.UnitOfWorkInterceptor"/> | |
</mvc:interceptors> |
Spring AOP 를 사용해도 되지만, 요 방법이 가장 간단하고, 쉽게 이해할 수 있을 것 같습니다.
다른 블로거의 글에는 logging 으로 예제를 많이 많들었더군요...
asp.net 으로는 제가 간단하게 성능 측정하는 코드를 예제로 만들어보긴 했지만, 쓸모는 없지요^^
위의 unit of work관련 코드는 asp.net 으로 구현한 것은 상용 제품의 코드로 쓰이고, 아직도 많은 회사에서 구동되고 있습니다...
이를 spring mvc, hibernate 로 포팅한 작업입니다.