<tx:annotation-driven />
<aop:config>
<aop:advisor advice-ref="transactionAdvice" pointcut="bean(*Service)" />
</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>까지 했으니 이제 <tx> 요소에 대해 설명할 차례네요. <tx>는 스프링 트랜잭션 기술의 집약체입니다. 스프링의 트랜잭션 기술은 전장에서도 말했듯이 어노테이션 적용법과 AOP 적용법 2가지가 있으며 위의 예제는 당연히 AOP 적용법입니다.

먼저 <tx:advice>로 트랜잭션 어드바이스를 하나 만듭니다. 여기에 적용한 트랜잭션을 선택하는데 만약 트랜잭션 빈의 이름이 'transactionManager'이라면 위의 'transaction-manager' 속성은 생략될 수 있습니다. 위의 예제에서는 다만 원리를 설명하기 위해 속성을 넣어둔 것 뿐이므로 현재 'transaction-manager'은 생략이 가능하다 할 수 있겠네요.

그다음 <tx:attributes>의 <tx:method>로 세밀하게 메서드별 속성을 조절해주도록 합시다. 우리는 이 부분에서 어느 메서드에 트랜잭션을 적용할 것인지 선택할 수 있으며 속성에는 다음과 같은 값들이 존재합니다.

name - 메서드명
메서드의 이름이며 와일드 문자(*)를 사용할 수 있습니다. 예로 'get*'은 get으로 시작하는 모든 메서드를 가리키며'*get'은 get으로 끝나는 모든 메서드를 가리킵니다.

timeout - 제한시간 (기본값 : -1)
트랜잭션의 제한시간을 설정합니다. DB가 해당기능을 지원해야 하며 기본값으로는 -1인 제한시간 없음이 설정됩니다.

propagation - 전파옵션 (기본값 : REQUIRED)
REQUIRED : 부모 트랜잭션 내에서 실행하며 부모 트랜잭션이 없을 경우 새로운 트랜잭션을 생성합니다.
REQUIRES_NEW : 부모 트랜잭션을 무시하고 무조건 새로운 트랜잭션이 생성되도록 합니다.
SUPPORT : 부모 트랜잭션 내에서 실행하며 부모 트랜잭션이 없을 경우 nontransactionally로 실행됩니다.
MANDATORY : 부모 트랜잭션 내에서 실행되며 부모 트랜잭션이 없을 경우 예외가 발생됩니다.
NOT_SUPPORT : nontransactionally로 실행하며 부모 트랜잭션 내에서 실행될 경우 일시 정지 됩니다.
NEVER : nontransactionally로 실행되며 부모 트랜잭션이 존재한다면 예외가 발생합니다.
NESTED : 해당 메서드가 부모 트랜잭션에서 진행될 경우 별개로 커밋되거나 롤백될 수 있습니다. 둘러싼 트랜잭션이 없을 경우 REQUIRED와 동일하게 작동합니다.

isolation - 격리수준 (기본값 : DEFAULT)

DEFAULT : DB에서 설정된 기본 격리 수준을 따릅니다.
SERIALIZABLE : 가장 높은 격리수준을 가지며 사용시 성능 저하가 있을 수 있습니다.
READ_UNCOMMITTED : 커밋되지 않은 데이터에 대한 읽기를 허용합니다.
READ_COMMITTED : 커밋된 트랜잭션에 대해 읽기를 허용합니다.
REPEATABLE_READ : 동일한 필드에 대한 다중 접근 시 동일한 결과를 얻을 수 잇는 것을 보장합니다.

read-only - 읽기전용 (기본값 : false)

해당 메서드는 오로지 읽기에만 사용됩니다. INSERT나 UPDATE, DELETE문은 허용되지 않습니다. 만약 쓰기나 삭제가 실행될 경우 에러를 발생시킵니다.

rollback-for - 예외처리 (기본값 :  RuntimeException)

특정 예외가 발생했을 경우에 롤백되도록 설정합니다. 설정하지 않을 경우 오로지 RuntimeException을 상속받은 예외에만 롤백처리를 해줍니다.

no-rollback-for - 예외처리 (기본값 : 없음)

특정 예외가 발생하더라도 롤백되지 않도록 설정합니다.

위의 속성과 값들이 <tx:method>에서 설정할 수 있는 전부입니다. 잘 알아본 다음 자신에게 맞는 속성을 설정한 후에 완성된 <tx:advice>를 <aop:config>에 주입하기만 하면 자동프록시검색기가 알아서 <tx>어드바이스와 pointcut을 해석하여 해당 요청을 대신 처리해 줄 것입니다.

이제 거의 막바지에 왔네요. 다음은 <tx:annotation-driven />에 대한 설명입니다. 이 요소가 설정되있다면 트랜잭션을 어노테이션으로 설정 가능하게 합니다. 설정방법은 트랜잭션을 원하는 메서드에 @Transactional이란 어노테이션을 붙이면 그만이며 <aop>설정은 어노테이션과 전혀 무관합니다. 어노테이션 트랜잭션은 오로지 <tx:annotation-driven /> 요소만을 요구합니다.

@Transactional(isolation=Isolation.DEFAULT, propagation=Propagation.REQUIRES_NEW)

어노테이션으로 트랜잭션으로 설정하는 방법은 직관적이고 알기 쉽지만 수정이 필요할 때에는 코드 자체를 수정해야 한다는 단점이 있습니다. 위의 예시처럼 어노테이션도 <tx:method>와 같이 똑같은 옵션설정이 가능합니다. 설정방법은 이클립스의 자동완성기능(CTRL + F1)으로 알아보시면 편리하니 따로 설명하지는 않겠습니다 :D

일단 어노테이션으로 트랜잭션을 설정할 시 주의할 점은 가장 우선적으로 고려되는 순서가 있다는 것입니다. 만약 인터페이스 - 클래스 - 메서드 모두에 트랜잭션 어노테이션이 설정되있다면 스프링은 메서드 어노테이션을 제일 먼저 고려하고 그 다음이 클래스, 인터페이스 순서으로 적용합니다. 개인적으로는 가급적 어드바이스를 활용해 트랜잭션 경계를 설정하는 게 좋다고 생각하는데 어노테이션 트랜잭션을 남발할 경우 작성된 프레임워크의 잦은 수정이 불가피해지기 때문입니다.

트랜잭션도 상황에 따라 자주 설정이 변경되어줘야 하는데 어노테이션으로 설정을 고정해버리고 매번 수정이 필요할 때마다 소스자체를 바꾸다보면 다른 코드에 영향을 줄 수도 있고 이용하는 사람 입장에서도 상당히 불편할 수 있습니다. 가급적 어드바이스로 트랜잭션 경계를 설정하고 정말 불변하는 트랜잭션에 대해서만 어노테이션을 이용하길 권장합니다.


이제 마지막으로 트랜잭션 매니저 설정을 위한 PlatformTransactionManager을 설명하고 끝마치려 합니다 ^^. 스프링은 다양한 플랫폼과 환경에서도 완벽하게 트랜잭션 클래스들의 DI가 가능 하도록 PlatformTransactionManager 인터페이스를 갖고 있다는 사실을 알고 계신가요?

우리가 구현했던 소스에서는 볼 수 없었지만 분명 <tx:advice>를 구성하는 소스에는 PlatformTransactionManager 인터페이스를 통해 구현 클래스의 주입을 요구하고 있습니다. 현재의 소스는 제가 JDBC/MyBatis를 이용하고 있으므로 위의 예제와 같이 DataSrouceTransactionManager 클래스를 이용해 트랜잭션 매니저를 구현했지만 만약 하이버네트나 JTA같은 다른 플랫폼을 이용할 경우에도 소스에 변경 일절 없이 오로지 트랜잭션 매니저만 변경해주면 간편하게 모든 설정을 끝마칠 수 있습니다.

이렇게 트랜잭션에 대한 모든 설명이 끝났네요. 사실 부족한 부분이 많지만 이정도면 스프링에서 어떻게 트랜잭션을 설정하였고 활용방안에 대한 감을 잡을 수 있다는 생각이 듭니다. 부족한 부분이나 이해가 안가시는 부분은 피드백 주시구요. 다음에 좀 더 좋은 포스트로 찾아올게요 ^^

 

출처 : http://springmvc.egloos.com/499291

 

'Spring' 카테고리의 다른 글

Spring Transaction #2  (0) 2014.10.01
Spring Transaction #1  (0) 2014.10.01
VO, DTO, DAO  (0) 2013.07.08
domain object에 대한...  (0) 2013.07.08
Spring Java Mail  (0) 2013.07.05
Spring MultipartResolver  (0) 2013.07.02

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

springmvc.egloos.com/498979

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



일전에 썼던 트랜잭션에 대한 원리글을 읽으셨다면 앞으로 우리가 사용할 트랜잭션이 무엇인지 대충 감 잡으셨다고 생각합니다 ^^; 제대로 읽었음에도 잘 이해가 가지 않으시다면 제 글이 부족한 탓이니… 토비의 스프링의 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장에서 계속 하도록 하겠습니다 ^^;

'Spring' 카테고리의 다른 글

Spring Transaction #2  (0) 2014.10.01
Spring Transaction #1  (0) 2014.10.01
VO, DTO, DAO  (0) 2013.07.08
domain object에 대한...  (0) 2013.07.08
Spring Java Mail  (0) 2013.07.05
Spring MultipartResolver  (0) 2013.07.02

VO, DTO, DAO 클래스 설명

[출처] http://choijaehyuk.com/128



VO

개념

Value Object DTO와 동일한 개념이나 차이 점은 read only 속성을 갖습니다.

Value Object는 관계데이터베이스의 레코드에 대응되는 자바클래스입니다형태는 db레코드를 구성하는 필드들을 Value Object Attribute로 하고 해당 변수에 접근 할 수 있는 Getter Setter 메소드의 조합으로 클래스를 형성되어진 클래스입니다특성은 대체로 불변성이고equals()로 비교할 때 객체의 모든 값을 비교해야 합니다.

필요성

Network traffic을 줄임으로 인해서 효과적입니다.

기대효과

Network traffic이 줄어듭니다.

 

장 단점

장점으로는 비 서버 측 클라이언트도 네트워크 오버헤드 없이 영속성 데이터에 액세스 할 수 있다는 점입니다데이터 전달을 위해 가장 효율적인 방법이지만클래스의 선언을 위해 많은 코드가 필요합니다즉 파일수가 많아지게 되고 관리도 힘들어지게 됩니다.

예제 소스코드

DTO

개념

데이터가 포함된 객체를 한 시스템에서 다른 시스템으로 전달하는 작업을 처리하는 객체입니다. Vo dto의 차이점은 vo는 특정한 비즈니스 값을 담는 객체를 vo라 하고 dto는 레이어간의 통신용도로 오가는 객체를 dto라고 합니다.

Vo DTO의 비교

DTO의 나머지 속성은 vo와 똑같다고 생각하여서 차라리 차이점을 비교하려고 합니다.

Core J2EE Patterns 라는 책에서는... Value Object Transfer Object를 동일한 뜻으로 사용합니다만 반대로 Martin Fowler는 저서 Patterns of Enterprise Application Architecture에서 약간 다른 의미로 이야기 합니다. DTO는 메소드 호출 횟수를 줄이기 위해 데이터를 담고 있는 녀석으로, VO는 값이 같으면 동일 오브젝트라고 볼 수 있는 녀석으로 표현을 하고 있습니다.

 

쉽게

DTO a = new DTO(1);

DTO b = new DTO(1);

이라고 했을 때 a != b 이지만,

 

VO a = VO(1);

VO b = VO(1); 이라고 했을때는 a == b라고 정의하는 형태입니다.

사실 이러한 내용도 헷갈리는 부분이 있는게 대부분의 검색에서 사람들은 vo dto를 같은 개념으로 이야기 하고 있어서 아직도 vo dto가 이런거다라기 보다 거의 똑 같은 개념으로 생각하고 있습니다.

 

DAO

개념

데이터 접근을 목적하는 객체입니다커넥션 같은 것을 하나만 두고 여러 사용자가 DAO의 인터페이스를 사용하여 필요한 자료에 접근 하도록 하는 것이 DAO의 개념입니다.

필요성

모든 데이터베이스에 공통적으로 접속 할 수 있는 ODBC가 나왔지만 완벽하진 못했습니다여전히 로우 레벨의 API를 포함하고 있었기 때문에 개발 장벽이 여전히 높았습니다이런 이유 때문에 개발자들은 정작 데이터베이스에 들어 있는 데이터를 어떻게 이용할지에 초점을 맞추기 보다어떻게 데이터베이스에 접속해서 데이터베이스와 교류하는지에 더 초점을 기울였습니다즉 데이터를 활용하는 논리적 고민보다 기술적 고민에 더 많은 신경을 썻었습니다이런 이유로 DAO란 대안이 나왔습니다.

기대효과

사용자는 자신이 필요한 Interface DAO에게 던지고 DAO는 이 인터페이스를 구현한 객체를 사용자에게 편리하게 사용 할수 있도록 반환해줍니다.

장 단점

DB에 대한 접근을 DAO가 담당하도록 하여 데이터베이스 엑세스를 DAO에서만 하게 되면 다수의 원격호출을 통한 오버헤드를 VO DTO를 통해 줄일수 있고 다수의 DB 호출문제를 해결할 수 있습니다또한 단순히 읽기만 하는 연산이므로 트랜잭션 간의 오버헤드를 감소할 수 있습니다.

 그러나 Persistent Storage를 너무 밀접하게 결합해서 작성을 하게 되면 Persistent Stroage를 다시 작성할 경우가 생기는데 이러한 경우 유지 보수의 문제가 생길수도 있습니다.

'Spring' 카테고리의 다른 글

Spring Transaction #2  (0) 2014.10.01
Spring Transaction #1  (0) 2014.10.01
VO, DTO, DAO  (0) 2013.07.08
domain object에 대한...  (0) 2013.07.08
Spring Java Mail  (0) 2013.07.05
Spring MultipartResolver  (0) 2013.07.02

먼저 도메인 객체에 대해 잘 모르는 사람들을 위해 이 오브젝트를 조금 자세히 설명할 필요가 있다.과거 자바가 웹의 영역에 드러서기 시작하면서 다른 플랫폼들과 소통하기 위한 장치가 필요했는데 그것이 바로 JSR-000220, 자바빈즈였다. 자바빈은 본래 다양한 목적으로 설계되었으나 지금은 대부분이 외부 리소스를 담는 그릇같은 용도로 사용되고 있으며, 빈 생성자를 가지며 내부에 private로 설정된 프로퍼티에 get…, set…과 같은 명명규칙을 갖고 있는 클래스를 일컫는다.


이렇듯 도메인 오브젝트의 첫 시작이었던 자바빈즈는 클래스 내부에 연산이나 다양한 기능을 넣기엔 기술적 한계가 많았으므로 대부분 해당 데이터를 처리하기 전에 임시로 담아두는 정도로 밖에 사용하지 않았다. 임시적으로 발생하는 데이터를 처리하기 전까지 담아두는 도메인 객체를 일컫어 빈약한 도메인 오브젝트 방식이라도 한다.

빈약한 도메인 오브젝트를 보다 잘 이해할 수 있도록 한가지 예를 들어보자. @Controller에서 파라미터로 넘어온 값을 DB에 넣기 위해 다음과 비슷한 도메인 오브젝트를 만들어 본 기억이 있을 것이다.

public class User {
private String id;
private String password;
private String name;

public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}

…중략…

}

이런 단순한 도메인 오브젝트는 테이블의 컬럼 또는 뷰에서 넘어오는 파라미터를 기본적으로 완벽하게 이해하게끔 설계하고 있으므로 해당 요청을 처리하는 계층에서 로직이 시작되기 전에 미리 User란 그릇에 테이블의 컬럼값과 파라미터 값을 저장시켜놓고 사용할 수 있었다.

근데 이런 도메인 오브젝트를 계속 사용하다보니 조금 헷갈리기 시작한다. 왜냐하면 따지고 보면 도메인 오브젝트가 가장 객체의 본질을 잘 표현해주는 주된 형태인데 이걸 User 오브젝트 자신보다 연관성이 떨어지는 UserService나 UserDao, UserController같은 곳에서 대신 컨트롤 하고 있으니 말이다.

혹시나 이해가 잘 안된 사람을 위해서 좀 더 자세히 설명하고자 한다. 객체지향적인 관점에서 말해보자면 도메인 오브젝트는 객체 자신에 그 무엇보다 가깝다. 현재의 자바기술로 도메인 오브젝트는 바로 객체 자신이라고까지 할 수 있으며 비록 잠시 태어나 소멸할지언정 가장 명확하게 객체 자신을 표현하고 있다는 오브젝트였다는 말이다.

너무 중요한 단락이기에 더욱 쉽게 설명해보겠다. 예를 들어 나는 초밥을 좋아하고 키가 178cm에 단정한 머리 스타일이고 …블라블라… 여하튼 나 자신을 너무 잘 알고 있다. 근데 나를 만든 개발자 때문에 나는 어디든 내 마음대로 움직일 수 없다. 이 개발자는 내가 스스로 못 움직이게끔 바닥에 나를 고정시켜놓고는 대신 나와 비슷한 녀석들을 대량으로 처리하는 이상한 기계에 맡겨 나를 조종하고 있다. 나는 저 기계보다 내 자신이 누군지 잘 이해하고 행동할 자신이 있는데 개발자 때문에 내가 누군지도 잘 모르는 기계가 나를 다스리도록 하고 있는 셈이다.

이제 이해가 조금 됬는지 모르겠다. 우리가 객체지향 프로그래밍이라 부르는 것은 바로 사물을 객체지향적인 관점으로 바라보고 그대로 해석할 수 있는 언어이기 때문에 객체지향적이라고 일컫는 것이다. 근데 빈약한 오브젝트 방식은 이런 객체지향적인 관점을 어느 정도 훼손하고 있다. 물론 보다 능률적인 대량처리를 위해 빈약한 오브젝트 방식이 결코 나쁘다고는 할 수는 없지만 한편으로는 결코 좋은 방식이라고도 할 수 없는 것이다.

그렇다면 이 빈약한 오브젝트를 조금 풍성하게 해보면 어떨까? 다음과 같은 소스처럼 말이다.

public class User {
private String id;
private String password;
private String name;

public int getToken() {
return getId() + getPassword();
}

… 중략…
}

다시 말하지만 도메인 오브젝트는 자신을 가장 잘 이해하고 있으므로 getToken() 메서드를 효율적으로 처리할 수 있는 로직을 가장 직관적으로 구현할 수 있다. 바로 아래와 같이 말이다.

public class User {
private String id;
private String password;
private String name;

private UserDao userDao;

@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public int getToken() {
return getId() + getPassword();
}
public String getId() {
Assert.hasText(id);
return id;
}
public String getPassword() {
if(password==null) return userDao.getPassword(id);
else return password;
}

… 중략 …
}

위의 코드는 여러모로 부족한 점이 많지만 중요한 것은 getToken()의 무결성을 보장해준다는 것이다. 만약 이 로직을 도메인 오브젝트 외의 Service 계층이나 Dao계층에서 처리 해야했다면 굉장히 이해가 불편함과 동시에 무결성을 보장하기가 까다로웠을 것이다. 하지만 우리는 위와 같이 도메인 오브젝트가 자기 자신과 관련한 비지니스 로직은 스스로 처리하게 하고 그 무결성을 스스로 보장하게끔 한 덕분에 매우 직관적이고 깔끔한 코드를 얻을 수 있게 되었다.

결론적으로 DDD는 도메인 자체에 더 많은 권한과 기능을 부여함으로써 도메인이 가질 수 있는 능력을 극대화 시키고 주도해 나가는 개발방식이며 더 나아가 도메인을 하나의 계층으로 인정하고 다른 영역과 분리해내는 4Tier 아키텍쳐인 것이다.

물론 위의 코드는 여러모로 문제점이 많다. 우선 도메인 객체에 적용될 수 없는 @Autowired와 같은 DI기술이 적용됬다는 점이며 지금은 보이지 않지만 해당 도메인이 DAO 계층을 흡수한 만큼 DB에 접근할 수 있게된 권한으로 자칫 엄청난 불상사를 초래할 수 있다는 점이다.

하지만 이런 문제점은 스프링의 @Configurable과 AOP 기술, 그리고 DTO 모델을 도입함으로써 해결할 수 있는 문제점들이기 때문에 DDD 주도의 개발을 막지는 못한다. 되려 DDD는 개발을 보다 쉽고 빠르게 도와주며 코드 자체를 직관적으로 바꿔주어 이용자의 부담과 불필요한 이해를 막아준다.


글을 마치며...

도메인 오브젝트는 이렇듯 단순한 자바빈에서 시작하여 조금씩 기능을 더해오더니 이제 개발의 주도적인 역할까지 수행하는 단계에 이르게 되었다. 이렇듯 도메인 오브젝트를 적극 활용하여 개발을 하게 되면 소스의 테스트가 더욱 쉬워지고 활용도 또한 극대화 된다. 그렇다고 무작정 도메인 오브젝트에 모든 기능을 씌우려고 해서는 절대로 안된다. 왜냐하면 DDD를 적용하는 것은 어디까지나 직관적이고 객체지향적인 설계를 위한 것이지 무분별한 남용을 위한 것은 아니기 때문이다.

만약 도메인 오브젝트에 존재하지 않는 외부값의 첨가가 필요하다면 이런 로직은 필히 해당 도메인의 Service 계층, 또는 Support 계층에서 전담하여 처리하고 도메인 오브젝트는 오로지 본인 내부의 리소스만을 조합하여 처리할 수 있는 로직만을 담당하여야 한다.

이렇게 도메인 오브젝트의 발전사를 마쳤다. 필자는 멋도 모르고 코딩을 해대던 과거 시절, 이미 자바빈 객체를 내멋대로 확장하여 사용한 적이 있었는데 그 당시 MVC를 알게 되고 부터는 자바빈에 기능을 부여하는 것이 잘못된 것으로만 알고 있었다. 헌데 이젠 DDD란 이름으로 예전의 나의 코딩방식이 또다시 새로운 패러다임이라고 조명받고 있으니 조금 우습기도 했다.

사람이란게 말을 함부로 바꿔서는 안되는 모양인가 보다.



'Spring' 카테고리의 다른 글

Spring Transaction #2  (0) 2014.10.01
Spring Transaction #1  (0) 2014.10.01
VO, DTO, DAO  (0) 2013.07.08
domain object에 대한...  (0) 2013.07.08
Spring Java Mail  (0) 2013.07.05
Spring MultipartResolver  (0) 2013.07.02

java mail API를 이용하여 메일 발송하는 자료를 모으고자 한다.

내용은 추가적으로 계속 업데이트 할 예정임 .. !!!!



Spring 3 환경에서 메일을 발송하는 방법을 조사해봤다. 간단하게 Oracle에서 제공하는JavaMail API를 사용하면 된다. 현재 최신 버전은 1.4.7이며 maven javamail로 구글링하면 mail-1.4.7.jar 파일을 다운로드받을 수 있다.


* 다운로드받은 mail-1.4.7.jar 파일을 프로젝트의 CLASSPATH에 추가한다. 웹 애플리케이션의 경우 /WebContent/WEB-INF/lib 디렉토리에 복사하면 간단하게 끝난다.


* 메일 발송 정보를 저장할 mail.xml 파일을 작성한다.

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bean="http://www.springframework.org/schema/bean"

xsi:schemaLocation="

        http://www.springframework.org/schema/beans 

        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">

<bean id="mailSender"

class="org.springframework.mail.javamail.JavaMailSenderImpl">

<property name="host" value="smtp.somemailhost.com" />

<property name="port" value="25" />

<property name="username" value="someuser" />

<property name="password" value="somepassword" />

<prop key="mail.transport.protocol">smtp</prop>

<prop key="mail.smtp.auth">true</prop>

<prop key="mail.smtp.starttls.enable">true</prop>

<prop key="mail.debug">true</prop>

</property>

</bean>

</beans>


* DispatcherServlet 역할을 하는 XML 파일에 아래 내용을 추가한다.

<import resource="mail.xml" />


* 애플리케이션의 @Service 오브젝트에서 사용될 MailService 클래스를 작성한다. 아래는 간단한 예로 첨부 파일 발송, HTML 템플릿 등을 적용하려면 살을 더 붙여야 한다.

@Service("mailService")

public class MailService {

  @Autowired

  private MailSender mailSender;

  

  public void sendMail(String from, String to, String subject, String text) {

    SimpleMailMessage message = new SimpleMailMessage();

    message.setFrom(from);

    message.setTo(to);

    messate.setSubject(subject);

    message.setText(text);

    mailSender.send(message);

  }  

}


* 메일 발송 비즈니스 로직을 수행할 @Service 클래스에서는 아래와 같이 작성한다.

@Service("someService")

public class someService {

  @Autowired

  private MailService mailService;

  

  public void sendMail() {

    mailService.sendMail("from@MailAddress.com", "to@MailAddress.com", "someSubject", "someText");

  }

}


<참고자료>

Send Mail with Spring : JavaMailSenderImpl Example (by Lokesh Gupta)

http://howtodoinjava.com/2013/05/27/send-email-with-spring-javamailsenderimpl-example/


Spring Framework 3.2 Reference Documentation: 26. Email

http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/mail.html


'Spring' 카테고리의 다른 글

Spring Transaction #2  (0) 2014.10.01
Spring Transaction #1  (0) 2014.10.01
VO, DTO, DAO  (0) 2013.07.08
domain object에 대한...  (0) 2013.07.08
Spring Java Mail  (0) 2013.07.05
Spring MultipartResolver  (0) 2013.07.02
Spring MVC는 파일 업로드 기능을 지원하기 위하여 
Commons 파일 업로드(http://jakarta.apache.org/commons/fileupload)와 
COS 파일 업로드(http://www.servlets.com/cos) 라이브러리를 지원하고 있다.
(Spring API의 org.springframework.web.multipart 패키지를 보면 두 개의 라이브러리를 지원하는 패키지가 따로 있음을 확인할 수 있다. )
 
Spring 프레임워크에서는 파일업로드에 대한 구현을 이미 다 만들어 놓은 MultipartResolver란 클래스가 있는데 여기에는 재미있는 property가 있다. uploadTempDir 이란 넘인데 이 속성명에서 풍기는 의미에서 처럼 Spring 프레임워크는 업로드 받을 임시저장소를 가지고 있다. 예를 들어 100명의 접속자가 500MB의 파일을 동시에 올린다고 생각해 보자. 서버는 이를 처리하기 위해 50GB의 메모리를 사용하게 되는데 이것은 서버에 과부하를 가져다 주게 된다. Spring 프레임워크는 이를 방지하기 위해 임시저장소를 두어 업로드된 파일에 대한 버퍼를 마련해 둔 것이다.
 
책에서는 빈 설정 파일을 다음과 같이 설정하였다.
 
 <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize">
       <value>10000000</value>
    </property>
    <property name="uploadTempDir" ref="uploadDirResource" />
 </bean>
 
 <bean id="uploadDirResource" class="org.springframework.core.io.FileSystemResource">
    <constructor-arg>
       <value>D:/Temp/fileupload/temp/</value>
    </constructor-arg>
 </bean>
 
친절하게 업로드될 max사이즈도 정할 수 있음을 알 수 있다. 참고로 Resolver란 이름이 붙은 클래스에 대해서 우리가 직접 IoC를 적용할 필요가 없다. viewResolver에서도 확인할 수 있는데 우리는 어디에서도 직접적으로 viewResolver를 사용하고 있지 않다. 이것은 Spring 프레임워크가 처리하는 부분으로 우리가 감히 손대기에는 큰 일이 아닐까 한다.
 
Spring 프레임워크 워크북 에서는 업로드 기능에 대해서 FileUploadUtil 이란 클래스를 사용하고 있는데 사실 이 클래스는 불필요한 클래스다. 이미 Spring 프레임워크에서 기능을 제공해 주고 있는데 똑같은 기능을 만들 필요는 없을 듯하다. 그럼 그 기능을 해주고 있는 넘은 누구며 어떤 메소드냐?
 
아래와 같이 첨부된 파일을 정의하는 모델이 있다고 하자.
 
public class AttachFile {
        Integer fileId;              // 파일ID
        String  userFileName;  // 실제 파일명
        String  saveFileName;  // 경로포함된 파일명 
        String  contentType;    // content-type
        Long    fileSize;            // 파일 사이즈
 
        .......... getter/setter 생략
}
 
그리고 첨부된 파일을 속성으로 가지는 모델이 있다.
 
public class FileUploadBean {
       Integer fileUploadId;
       String  name;
       AttachFile file1;    // 파일 1
       AttachFile file2;    // 파일 2
       AttachFile file3;    // 파일 3
 
       ............ getter/setter 생략
}
 
 
MultiPartFile을 AttachFile로 변경해 주는 FilePropertyEditor이 있다.
 

public class FilePropertyEditor extends PropertyEditorSupport {
  String uploadRepository ;
  
  @Override
  public void setValue(Object value) {
    if (value instanceof MultipartFile) {
      MultipartFile multipartFile = (MultipartFile) value;

      if (multipartFile.isEmpty()) {
        super.setValue(null);
        return ;
      }

      String uploadTempDir = makeUploadTempDir() + File.separator;
      try {
        new File(uploadRepository + uploadTempDir ).mkdir();
        multipartFile.transferTo(new File(uploadRepository + uploadTempDir + multipartFile.getOriginalFilename()));
      }
      catch(IOException e) {
        throw new RuntimeException("Can't create item attach file : " + uploadRepository + " : " + e.getMessage());
      }
         
      AttachFile attachFile = new AttachFile();
      attachFile.setUserFileName(multipartFile.getOriginalFilename());
      attachFile.setSaveFileName(uploadTempDir + multipartFile.getOriginalFilename());
      attachFile.setContentType(multipartFile.getContentType());
      attachFile.setFileSize(multipartFile.getSize());
      
      super.setValue(attachFile);
    } else {
      super.setValue(value != null? value.toString().getBytes(): null);
    }
  }
  
  private String makeUploadTempDir() {
    UUID uuid = UUID.randomUUID();
    return uuid.toString();
  }

  public String getUploadRepository() {
    return uploadRepository;
  }

  public void setUploadRepository(String uploadRepository) {
    this.uploadRepository = uploadRepository;
  }
}

makeUploadTempDir() 메소드는 UUID를 이용하여 랜덤하게 폴더명을 만드는 것으로 중복된 파일명 처리를 위해 멋(?)을 부렸다.  우째튼 결론은 굵은 글씨체로 되어 있는 transferTo(String path) 메소드인데 API문서의 설명을 보면 감이 올 것이다.
 
마지막으로 살펴볼 부분이 빈 설정 파일일 것이다.
 
 <bean id="filePropertyEditor" class="caps.support.propertyeditor.FilePropertyEditor" singleton="false" >
    <property name="uploadRepository">
       <value>C:\temp\fileupload\upload\</value>
     </property>
 </bean>
 
 
책의 예제를 뒤져보면 Control 쪽에서 실제 저장소를 정의해 두었는데 FilePropertyEditor를 이해했다면 실제 저장소를 PropertyEditor에 정의하는 게 옳을 것이다. 그리고 MultipartResolver에서 정의한 임시 저장소와 실제 저장소는 다르다는 것을 다시 한번 더 확인해 두는 바이다


'Spring' 카테고리의 다른 글

Spring Transaction #2  (0) 2014.10.01
Spring Transaction #1  (0) 2014.10.01
VO, DTO, DAO  (0) 2013.07.08
domain object에 대한...  (0) 2013.07.08
Spring Java Mail  (0) 2013.07.05
Spring MultipartResolver  (0) 2013.07.02

+ Recent posts