드디어 마지막 수업이다. 이제 파이널 프로젝트만 하면 국비 학원 이야기도 엔딩을 본다.
사실 이제 현 과정 내에서 배울만한 건 거의 다 배워서...오늘은 2가지 처리만 알아보자.
트랜잭션이 무엇인가? 알다시피 DB에 입력되는 정보는 바로 처리되지 않는다. DB 내부에 들어가기 전, 트랜잭션이라는 일종의 ‘중간 저장소’이다.
즉, 데이터의 안정적 처리를 위해 DB 접근을 한 번 막아주는 것이다. 그래서 커밋을 통해서 우리가 진행한 작업을 DB에 한 번에 반영한다.
이때, 백엔드에 서비스 계층이 없다면 Transaction 처리를 컨트롤러에서 해야 한다. 근데 DB 작업을 컨트롤러에서 하는 건, 패턴을 벗어나고, DAO는 동시에 2개의 작업을 진행 할 수 없다.
보다 직관적으로 보기 위해 Transaction 처리를 만들어보자.
글쓰기 기능에서 사용자는 글 하나이지만 실제 백에서는 글 입력과 파일 정보 입력이 따로 들어간다. 이때 발생하는 게 “원자성 이슈”이다.
public void insert(MessagesDTO dto) throws Exception {
dao.insert(dto);
fdao.insert(new FilesDTO(0, "ori", "sys", 0));
}
무슨 일이 있든 사용자는 하나의 작업으로 보기에, 위 두 메서드는 함께 실패하거나 함께 성공해야만 한다.
만약 두 작업 중 앞쪽이 실패하면 자동으로 예외 처리되어 원자성을 지키는데, 앞에는 성공하고, 뒤에서 예외가 나면 이미 앞의 작업은 처리되었기에 원자성 문제가 발생한다.
결국, 경우의 수는 둘 다 성공하던가, 하나가 실패하면 성공한 것은 트랜잭션이 롤백 돼야 한다. 이걸 MVC 2은 Connection 객체를 직접 만져서 rollback()을 호출해서 둘의 상태를 맞췄지만, 스프링에서는 트랙잭션 처리 인스턴스를 제공한다.
트랜잭션 처리를 스프링이 해주기 위해선, 해당 처리를 담당하는 인스턴스를 만들어 줘야 한다. 생성 방식은 생성자로 하며, 인자값으로 DBCP 인스턴스를 DI해준다.
[ root-context.xml ]
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg name="dataSource" ref="dataSource"/>
</bean>
이 역시, 어노테이션으로 처리한다. 그래서 트랜잭션 처리가 필요한 곳을 어노테이션으로 표기해주면 해당 메서드의 Connection 객체는 트랜잭션 처리가 가능하다.
@Transactional
public void insert(MessagesDTO dto) throws Exception {
dao.insert(dto);
fdao.insert(new FilesDTO(0, "ori", "sys", 0));
}
아쉽게도 @Transactional 은 컴포넌트 스캔이 안되서, 직접 올리도록 추가해줘야 한다.
이를 위해서 이클립스에선 하단 콘솔 부분에서 Namespace 이동해, tx를 체크해서, 문법 정보를 추가해준다.
이제, xml이 분석되면서 트랙잭션 어노테이션으로 표기된 것을 메모리로 올려 스프링 풀에 넣어줘야 한다.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<tx:annotation-driven/>
</beans:beans>
이제 우리 프로그램에서 SQL Exception이 발생하면 스프링이 트랜잭션 처리를 진행해준다.
여태 우리는 JSTL/EL로 세션의 로그인 키의 유무에 따라 UI 보이고, 안보이고 하는 걸 로그인 인증인 줄 알고 있었지만, 그건 로그인 인증이 아니다.
그래서 가장 간단하게 로그인 인증을 하는 법은 컨트롤러에서 로그인 시, 접근 가능한 페이지로 가는 모든 맵핑에서 그 유무를 검사하면 된다.
근데 이게 처리는 쉬운데 생각해보면 그런 맵핑이 한 두 개일까? 가능한 얘기지만 매우 비효율적인 방식이다.
결국 우리가 택할 방법은 이 로그인 인증을 한번에 해주는 ‘계층’ 곧, 레이어를 만들어 주는 것이다. 쉽게 보면 우리가 인코딩 방식을 맞추기 위해서 건드린 servlet filter을 구현해주는 것이다.
일단, 클라이언트의 요청이 컨트롤러에 도달할 때까지, Tom-DS(DispatcherServlet)를 거치고, 이 사이의 필터를 servlet filter (톰캣 주관)라 한다. 우리가 만들 계층은 DS – Controller(handler) 사이에 위치하는데, 여기 오는 계층들을 interceptor라고 한다.
이 인터셉터 클래스는 스프링 예하에 있어 스프링 요소를 사용하기 쉽다는 이점이 있다.
돌아보면 모든 클라이언트의 요청은 DS를 거쳐서 컨트롤러에게 간다. 따라서 그 사이에 있는 인터셉터는 모든 request를 받게되는데, 여기에 로그인 아이디가 없으면 돌려보내면 되는 것이다.
일단 어디서나 패키지 만들고, 클래스 만드는 건 기본이다. 그리고 인터셉터는 컨트롤러 하나 당 한 개씩 만들어지기 때문에 웬만하면 패키지로 관리하는게 편하다.
인터셉터 클래스는 HandlerInterceptor 인터페이스를 상속받아, 오버라이딩한다.
public class LoginValidator implements HandlerInterceptor{
@Autowired
private HttpSession session;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) // DS -> controller
throws Exception {
String loginId = (String)session.getAttribute("loginID");
if(loginId != null) {
return true; // 통과
}
response.sendRedirect("/error"); // error요청으로 재요청하여 페이지 전환
return false; // 요청 취소
}
}
일단 다루는 대상이 session 정보이기 때문에 DI를 기본으로 해준다. 그 다음 오버라이딩할 메서드에 다음 로직을 세워준다.
interceptor은 따로 어노테이션을 하지 않기 때문에, 우리가 servlet-context.xml
에서 직접 생성하도록 세팅해줘야 한다.
<interceptors>
<interceptor>
<mapping path="/**"/> <!--resource 맵핑이랑 비슷한 개념 -->
<beans:bean class="kh.spring.interceptor.LoginValidator"/>
</interceptor>
</interceptors>
보다시피 인터셉터는 <interceptors>
라는 큰 태그 안으로 묶인다. 이를 보면 <interceptor>
태그로 여러 개의 인터셉터를 추가할 수 있다는 걸 알 수 있다.
그리고 <mapping path=“/**”>
태그를 통해 어떤 요청으로 들어올 경우, 인터셉터를 거치게 할지에 대해서 설정해준다. 물론 로그인 상태를 알아보기 위해서 사용하는 것이니 모든 경우를 대상으로 해주면 된다.
마지막으로 내부에 우리가 만든 LoginValidator 클래스를 생성해준다.
이렇게 하면 다 된 것 같지만, 막상 돌리면 “리디렉션한 횟수가 너무 많습니다.” 라는 에러 메시지가 브라우저에 나올 것이다.
이는 쉽게 설명하면 모든 요청에 대해서 우리가 설정해놨기 때문에 모든 요청이 인터셉터를 거치기 때문이다. 즉, 로그인 요청마저 인터셉터를 거쳐 에러가 나고, 재요청인 에러 페이지도 로그인 정보가 없기에 다시 반려된다.
이게 무한 반복되니, 당연히 저 문구가 뜨는 것도 당연하다. 그래서 검사 제외 대상을 설정해줘야 한다.
<interceptors>
<interceptor>
<mapping path="/**"/>
<!-- 검사 제외 대상 -->
<exclude-mapping path="/"/>
<exclude-mapping path="/error"/>
<exclude-mapping path="/member/loginProc"/>
<exclude-mapping path="/withoutLogin"/>
<beans:bean class="kh.spring.interceptor.LoginValidator"/>
</interceptor>
</interceptors>
Servlet-context.xml에 우리가 세팅한 인터셉터 태그 내부에 <exclude-mapping path=""/>
로 검사를 제외할 요청을 적어주면 쉽게 해결된다.
드디어 수업은 끝! 이제는 파이널이다!
도움이 되셨다면, 좋아요. 댓글 남겨주세요 :)