Spring MVC

springmvc.egloos.com


포토로그


2012/02/22 23:58

스프링만의 독특한 트랜잭션 비법! 1장 Spring MVC

평소에 "다나까"체로 글을 썼는데 이런 글이 가독성에 참 좋지 않은데다가 쥐뿔도 모르면서 남한테 설교하는 느낌이 너무 나가지고 문체를 "~습니다요"체로 바꾸도록 하겠습니다 ^^;



일전에 썼던 트랜잭션에 대한 원리글을 읽으셨다면 앞으로 우리가 사용할 트랜잭션이 무엇인지 대충 감 잡으셨다고 생각합니다 ^^; 제대로 읽었음에도 잘 이해가 가지 않으시다면 제 글이 부족한 탓이니… 토비의 스프링의 5.2 트랜잭션 서비스 추상화 부분을 정독해보세요. 제 블로그 글보다 더 자세한 디테일한 설명이 담겨져 있답니다.

오늘은 본격적으로 스프링의 트랜잭션 적용법에 대해 이야기 하고자 합니다. 스프링 트랜잭션 적용법은 정말 독특한데다 적용법만 하더라도 애노테이션 적용법AOP적용법 2가지나 존재합니다. 물론 두가지를 한번에 사용하는 것도 가능하구요. 게다가 몇가지 간단한 설정만으로도 메서드 단위까지 세세한 트랜잭션 적용이 가능하니 그야말로 jQuery의 모토처럼 "The Write Less, Do More"의 원칙을 준수하였다고 보면 되겠습니다.

먼저 간단한 원리부터 설명하고자 합니다. 과거 스프링같이 트랜잭션을 위한 프레임워크가 없었던 시절 우리가 트랜잭션을 적용하기 위해선 다음과 같은 원리로 작성해야만 했습니다.

단 2개의 쿼리문을 실행시키기 위해 거의 100줄에 가까운 코드를 작성해야만 합니다. 스프링 트랜잭션과 MyBatis를 이용하면 10줄이나 될까말까한 코드를 말입니다! 게다가 이런식으로 코드를 짜간다면 가독성은 둘째치고 유지보수부터 큰 문제가 생기는데다 중간에 에러라도 한줄 발생한다면 개발자는 처음부터 끝까지 코드를 재검색해야만 합니다.

개발자들이 트래잭션 한번 적용해보겠다고 이렇게까지 코드를 쳐대야 했으니 회사에서 업무효율 상 트랜잭션보단 프로시져를 편들어 주었던 과거 개발환경도 어느 정도 이해는 갑니다. 프로시져는 DB에 로직을 등록하고 java에서는 "call ~procedure~"한줄이면 만사 오케이였으니까요.

그렇다면 이번엔 스프링 트랜잭션으로 확바뀐 트랜잭션 적용법을 알아보겠습니다.

<tx:annotation-driven />

<aop:config>
<aop:advisor pointcut="bean(*Service)" advice-ref="transactionAdvice" />
</aop:config>

<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="exceptionPut" propagation="REQUIRES_NEW" />
<tx:method name="get*" propagation="REQUIRED" read-only="true" />
<tx:method name="*" />
</tx:attributes>
</tx:advice>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

위의 예제는 필자의 소스에서 스프링의 트랜잭션 적용한 부분만 도려낸 것입니다. 소스만 봐서 이해가 가지 않는다고 걱정하지 마세요. 스프링은 트랜잭션이란 개념을 완전히 들어엎은 신개념 트랜잭션인데다가 어떻게 이런 설정이 가능했는지 쉽게 감이 오지 않는 기술이니까요.(물론 사용법은 쉽습니다.) 간단히 설명을 하자면 스프링은 트랜잭션 경계범위를 정규표현식과 애노테이션으로 설정하기를 권장하고 있습니다. 물론 반드시 그렇게 해야 한다는 것은 아니지만 굳이 넣지 않아도 될 트랜잭션 선언 코드를 무리하게 자신의 클래스에 꾸역꾸역 넣고 싶은 사람은 아무도 없겠지요.

일단 스프링의 트랜잭션을 이해하기 위해서는 AOP개념과 더불어 몇가지 알아야 하는 용어가 있는데요. 우선적으로 트랜잭션을 이해하기 위해 필요한 것만 뽑자면 다음의 4가지 용어를 알아야 할 것 같습니다.


포인트 컷과 어드바이스

<aop:advisor pointcut="bean(*Service)" advice-ref="transactionAdvice" />


먼저 스프링 트랜잭션의 가장 기초가 되는 포인트 컷과 어드바이스에 대해 알아봅시다. 포인트컷은 자동 프록시 생성기가 검색할 클래스(또는 메서드)를 선별하는 역할을 담당합니다. 위의 예제 중 pointcut = "bean(*Service)"처럼 설정한다면 빈 중에서 Service로 끝나는 모든 빈을 선택하게 됩니다.

포인트컷은 학습테스트나 특별한 기능을 위해서라면 org.springframework.aop.Pointcut 인터페이스를 구현하여 따로 클래스로 만들 수도 있지만 보통은 위의 예제처럼 간편하게 <aop:advisor> 내에 위치시키는 방법이 일반적입니다. 다만 하나 이상의 포인트컷을 입력하고 싶다면 아래 이미지와 같이 구성할 수도 있습니다.

AOP에서 포인트 컷을 설정하기 위한 표현식(expression)은 매우 다양하므로 그 방법은 차후에 다른 포스트를 통해 올리도록 하겠습니다. 좀 더 유익하게 트랜잭션 기술을 이용하기 위해서라면 표현식은 필수적으로 연마해야할 스킬입니다.

다음으로 우리가 알아볼 것은 바로 어드바이스입니다. 어드바이스는 포인트컷으로 선택한 빈에 적용될 부가기능을 뜻합니다. AOP는 트랜잭션만을 위한 기술이 아니기 때문에 필요하다면 어드바이스를 통해 포인트컷으로 선별될 빈(또는 클래스, 메서드)의 적용 기술을 자유롭게 변경할 수 있습니다. 물론 여기서 우리가 적용할 부가기능은 바로 트랜잭션입니다.

이런 어드바이스는 org.aopalliance.intercept.MethodInterceptor를 구현하여 만들어집니다. 사실 좀 기나긴 이야기가 되겠지만 이런 어드바이스의 개념을 이해하기 위해서는 프록시와 다이나믹 프록시, 그리고 팩토리빈으로 이어지는 프록시의 발전사를 어느 정도 이해해야만 합니다. 특히 다이나믹 프록시는 그 개념이 상당히 복잡하여 몇줄로 설명할 수 없는 패턴이지만 그래도 나름 간략하게 설명해본다면 기존의 프록시 패턴에서 발전하여 코드의 재사용성을 높인 새로운 프록시 기술이라 할 수 있습니다. (팩토리빈은 그런 다이나믹 프록시를 한단계 더 끌어올린 기술입니다.)

생각해보니 프록시 패턴을 모르시는 분을 위해 프록시 패턴에 대해서도 설명이 필요하겠군요. 프록시 패턴이란 일종의 래퍼개념입니다. 예를 들어 인터넷을 자주 이용해본 독자라면 프록시 서버라는 말을 종종 들어보셨을 겁니다. 이 프록시 서버는 기존의 핵심기능이 수행되는 순간 그 기능을 가로채어 앞뒤로 미리 설정된 프록시 기능을 수행해주는 역할을 합니다. 이 기능은 접속 시에 개인정보를 요구하는 것이 될 수도 있고 접속 시 필요한 서버 정보가 덧붙여진 것일 수도 있습니다.

우리가 이런 프록시 서버처럼 기존 기능을 가로채는 프록시 패턴을 완벽하게 구현하기 위해서는 프록시 클래스에서 본래의 클래스를 주입받고 또 주입받은 클래스의 모든 기능을 전부 대신 작동하게끔 대체 메서드를 작성해야 되는데 이게 여간 쉬운 작업이 아닙니다!

프록시 패턴의 예제

위의 예제는 간단하게 AutoWriter라는 클래스에 프록시 패턴을 적용한 Proxy란 클래스를 만든 예제인데, 이것만으로 자바의 프록시 패턴이 무엇이냐 정의해 본다면 새로운 프록시 클래스의 기존의 클래스의 기능을 모두 재정의한 대체 클래스라 정의할 수 있겠군요. 근데 이런 프록시 패턴에는 한가지 중대한 문제점 존재합니다.

일단 한번 상상해봅시다. 지금은 AutoWriter란 클래스가 단 2개의 메서드밖에 없지만 만약 메서드가 한 10개에서 20개 정도로 늘어났다고 말입니다. 게다가 클라이언트가 취향이 매우 까다로와 기존의 메서드에서 종류별, 분야별, 느낌별, 감성별로 기능을 나눠달라고 생때를 부린 통에 어쩔 수 없이 프록시 클래스를 한 20개 정도나 만들어야 된다면 작업 분량이 얼마나 될까요?

벌써부터 최소 200~400개의 메서드를 일일이 타이핑 해줘야 한다는 계산이 나오는군요. 세상에… 정말로 이렇게 클래스를 만들어야 한다면 프록시 클래스만 만들면서 한 일주일은 보내야 할판입니다. 근데 다행히도 자바에는 이런 문제를 해결할 수 있는 방법을 내장해 두었습니다. 그것은 바로 다이나믹 프록시입니다.


위의 이미지는 프록시의 핵심개념이므로 잘 이해하셔야 됩니다. 프록시 패턴의 핵심원리는 기존의 기능의 앞, 뒤로 프록시 기능을수행만 하면 된다는 것입니다. 그러므로 기존 기능만 알아서 반복동작한다면 프로그래머는 앞뒤로 덧붙일 기능만 작성하면 됩니다. 다이나믹 프록시는 이런 원리에 입각하여 기존의 메서드를 전부 재작성할 필요 없이 하나의 메서드에 덧붙일 새로운 프록시 기능만 완성시키면 자동으로 모든 메서드에 동일한 프록시 기능을 생성시켜줍니다.

이런 원리의 다이나믹 프록시를 업무에 적용한다면 기존의 400개의 메서드는 20개의 메서드로 분량이 획기적으로 줄어들게 됩니다. 그렇다면 어드바이스와 지금 설명하고 잇는 다이나믹 프록시와는 도대체 무슨 상관관계가 있는 것일까요? 그것은 어드바이스는 자체가 바로 이 다이나믹 프록시이기 때문입니다. (정확히 다이나믹 프록시의 구현부라고 합니다.)

MethodInterceptor 인터페이스는 스프링의 어드바이스를 작성하기 위해 구현해야 할 (스프링에서 사용되는) 다이나믹 프록시 인터페이스입니다. 우리가 MethodInterceptor를 구현하게 되면 반드시 invoke란 메서드를 구현해야 합니다. 바로 이 invoke메서드가 대체할 클래스의 모든 메서드를 실행하게 됩니다. 그러므로 우리는 이 invoke메서드에 기존의 기능에 앞뒤로 덧붙이기만 하면 다이나믹 프록시가 완성됩니다. 그리고 이것을 포인트컷과 함께 어드바이저에 넘겨주면 끝이죠.

드리고 싶은 말은 다이나믹 프록시 기술은 굳이 트랜잭션 뿐만 아니라 여러분의 코드 전반에 걸쳐 사용될 수 있는 기막힌 고급기술입니다. 어드바이스는 한번 배워두면 두고두고 써먹을 소중한 기술이므로 자세한 어드바이스 작성법을 배우기 위해서 토비의 스프링 책의 ProxyFactoryBean 작성 부분을 유심히 읽어보시기 바랍니다. 그리고 자기 나름대로의 어드바이스를 한번 만들어 보세요.


어드바이저

이제 포인트컷과 어드바이스에 대한 이해가 끝나셨다면 어드바이저를 이해하실 차례입니다. 어드바이저는 이렇게 모인 어드바이스와 포인트컷의 정보를 참조하는 기능을 수행합니다. AOP에서 제공하는 기본 어드바이저인 org.springframework.aop.support.DefaultPointcutAdvisor를 이용하신다면 특별히 새로운 어드바이저 클래스를 만들 일은 없습니다. 그래도 자신만의 어드바이저 클래스를 구현하고 싶으시다면 어드바이저는 org.springframework.aop.Advisor 인터페이스를 구현해서 만들어야 한다는 사실을 기억하세요.

자동프록시생성기

자동프록시생성기는 기본적으로 org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator 클래스를 뜻합니다. 이 클래스가 빈으로 등록되어 있다면 자동으로 Advisor 인터페이스를 구현한 빈이 컨텍스트에 등록되었는지를 확인해주고 해당 어드바이저들에 정의되있는 포인트컷과 어드바이스를 해석하여 해당 요청이 왔을 때 자동으로 이 요청을 가로채어 프록시를 생성해줍니다.

그야말로 스프링 기술의 정점이라고 할 수 있는데요. 사용법은 컨텍스트에 해당 빈을 생성해주기만 하는 것으로 끝이 납니다.

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />

자동프록시생성기는 어디에 참조되지도 않고 무엇을 참조하지도 않으므로 id도 필요없습니다. 등록된 순간 마치 인공지능처럼 알아서 모든 요청을 처리해주는 기특한 녀석입니다. 근데 여기까지 자동프록시생성기에 대한 이야기를 읽다보면 조금 이상한 점이 눈에 띕니다. 뭐냐하면 상단에 필자의 트랜잭션 적용 예제에선 자동프록시생성기를 등록하는 부분이 어디에도 보이지 않으니 말입니다. 제가 빼먹거나 일부러 넣지 않은 것은 아닙니다. 게다가 위의 예제만 등록하더라도 트랜잭션의 기능이 훌륭하게 동작하니 말입니다. 그렇다면 도대체 이 자동프록시생성기는 어떻게 된걸까요?

먼저 우리는 스프링에서 제공하는 AOP기술에 대해 바로 알아야 합니다. 필자가 잘못 이해하지 않았다면 스프링의 AOP기술은 바로 자동프록시생성기 그 자체입니다. <aop> 요소로 이용할 수 있는 기능이 무엇이 있나 한번 살펴보죠. <aop:config />, <aop:aspectj-autoproxy />, <aop:scoped-proxy />가 전부입니다. 즉 AOP로 우리가 할 수 있는 것은 자동프록시생성기의 생성주기를 설정하고 자동프록시생성기를 설정하고 또 어드바이저를 등록하는 것이 전부입니다. 이렇게 본다면 스프링 AOP가 자동프록시생성기 그 자체라는 말도 그렇게 허황된 말은 아닙니다. 우리가 AOP를 이용한다는 것은 다이나믹 프록시 기술을 이용한다는 것이고 스프링 원칙에 따라 모든 설정의 기본값으로 적용된 자동프록시생성기가 컨텍스트에 등록되게 됩니다.

그러므로 스프링은 자동프록시생성기를 <aop:config> 요소를 선언함과 동시에 자동으로 등록되도록 하였습니다. 다만 등록되는 자동프록시생성기는 DefaultAdvisorAutoProxyCreator 클래스가 아닌 AspectJAwareAdvisorAutoProxyCreator 클래스이긴 하지만 여하튼 어느 자동프록기생성기를 등록하건 우리는 딱 한가지 약속을 보장 받을 수 있습니다. <aop:config>에서 등록된 어드바이저들은 모두 자동프록시생성기를 통해 해당 포인트컷으로 클래스를 인터셉터하고 어드바이스에 등록된 기능을 수행한다는 보장 말입니다.

스크롤이 길어지는 관계로 다음 이야기는 2장에서 계속 하도록 하겠습니다 ^^;


덧글

  • ..ㅇ 2012/05/23 12:57 # 삭제 답글

    글에서 내공이 느껴지네요..
  • narise 2012/10/31 00:57 # 삭제 답글

    웹에서 aop로 트랜젝션관리는 장점이 별로 없는듯합니다.

    웹에서는 Open Session In View 방식이 제일 심플하고 성능도 뛰어납니다.

    AOP를 웹이 아닌 일반어플, 또는 로그출력등에 쓸만한 것같습니다.

    퍼모먼트영역 메모리리크 하는 것같아서, 이젠 거의 쓰지않고 다른 방식으로 하고 있습니다.
  • 하하하하 2013/04/16 16:46 # 삭제 답글

    잘 배우고 있습니다요~ ㅎ
  • 신입신입 2017/02/07 15:50 # 삭제 답글

    글들이 너무 유용하네요 ㅠㅠ 잘 배우고 있습니다.
댓글 입력 영역