[Info]Tags categorized posts and contents patterns..

[AJAX] Ajax Code E xamples.. [Book] About the book.. [CSS] CSS Code E xamples.. [DB] Sql Code E xamples.. [DEV] All development stor...

2016년 5월 27일 금요일

[JAVA] 12장 DAO 지원..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.

12. DAO 지원
12.1 소개
스프링의 Data Access Object (DAO) 지원은 JDBC, Hibernate, JPA, JDO같은 데이터 접근 기술과 관련된 작업을 일관된 방법으로 쉽게 할 수 있게 도와준다. DAO 지원은 앞서 언급한 퍼시스턴스 기술을 꽤 쉽게 해주고 각 기술에 특화된 익셉션 처리에 대해 걱정하지 않고 코드를 짤 수 있게 해준다.

12.2 일관된 예외 계층
스프링은 SQLException처럼 기술에 특화된 예외를 루트 익셉션처럼 DataAccessException의 스프링 예외계층으로 편리하게 변환한다. 이러한 예외는 원래의 예외를 감싸므로 잘못되거나 정보를 잃어버릴 위험이 절대 없다.

JDBC 예외에 추가적으로 스프링은 소유자로부터 변환해서 하이버네이트에 특화된 예외와 체크드 익셉션(하이버네이트 3.0 이전의 버전인 경우)을 관심있는(focused) 런타임 예외(JDO와 JPA 예외에도 마찬가지다.)의 세트로 감쌀 수 있다. 이는 복구할 수 없고 적합한 계층에서만 존재하고 보일러플레이트 catch-throw 블락을 귀찮음없으면서 DAO에 선언된 예외인 대부분의 퍼시스턴스 예외를 처리하게 해준다. (여전히 필요한 곳에서는 예외를 처리할 수 있다.) 앞에서 얘기했듯이 JDBC 예외(데이터베이스에 특화된 방언을 포함해서)도 같은 계층 즉 일관된 프로그래밍 모델에서 JDBC로 작업을 수행할 수 있는 계층으로 변환할 수 있다.

스프링의 다양한 템플릿 클래스는 다양한 ORM 프레임워크를 지원한다. 인터셉터 기반의 클래스를 사용한다면 어플리케이션은 되도록이면 SessionFactoryUtils의 convertHibernateAccessException(..)나 convertJdoAccessException()에 각각 위임해서 HibernateExceptions와 JDOExceptions 자체를 다루는 데 신경써야 한다. 이 메서드들은 예외를 org.springframework.dao 예외 계층의 예외와 호환되도록 변환한다. JDOExceptions가 언체크드면 예외와 관련된 용어로 제너릭 DAO 추상화를 희생시켜서 그냥 던질 수 있다.

스프링이 제공하는 예외 계층은 아래에서 볼 수 있다. (이미지의 클래스 계층은 전체 DataAccessException 계층의 서브세트만 보여준다.)

사용자 삽입 이미지


12.3 DAO나 리파지토리 클래스 설정에 사용하는 어노테이션
데이터 접근 객체(DAO)나 리파지토리가 예외 변환을 제공한다는 것을 보장하는 가장 좋은 방법은 @Repository 어노테이션을 사용하는 것이다. 이 어노테이션은 컨포넌트 스캔이 XML 설정없이도 DAO와 리파지토리를 찾고 설정하도록 해준다.


1
2
3
4
5
6
Java

@Repository
public class SomeMovieFinder implements MovieFinder {
  // ...
}

모든 DAO와 리파지토리 구현체는 사용하는 퍼시스턴스 기술에 따라 퍼시스턴스 리소스에 접근해야할 필요가 있을 것이다. 예를 들어 JDBC 기반 리파지토리는 JDBC DataSource에 접근할 것이고 JPA 기반 리파지토리는 EntityManager에 접근해야 할 것이다. 이렇게 하는 가장 쉬운 방법은 @Autowired,, @Inject, @Resource, @PersistenceContext 어노테이션 중에 하나를 사용해서 해당 리소스를 의존성 주입하는 것이다. 다음은 JPA 리파지토리의 예제이다.

1
2
3
4
5
6
7
8
9
Java

@Repository
public class JpaMovieFinder implements MovieFinder {
  @PersistenceContext
  private EntityManager entityManager;

  // ...
}

전형적인 하이버네이트 API를 사용한다면 SessionFactory를 주입할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Java

@Repository
public class HibernateMovieFinder implements MovieFinder {

  private SessionFactory sessionFactory;
  
  @Autowired
  public void setSessionFactory(SessionFactory sessionFactory) {
      this.sessionFactory = sessionFactory;
  }
  
  // ...
}

이번 장에서 보여줄 마지막 예제는 대표적인 JDBC 지원이다. DataSource를 사용하는 SimpleJdbcCall 등과 같은 데이터 접근 지원 클래스나 JdbcTemplate를 생성하하는 곳에 초기화 메서드에 이 DataSource를 주입할 것이다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Java

@Repository
public class JdbcMovieFinder implements MovieFinder {

  private JdbcTemplate jdbcTemplate;
  
  @Autowired
  public void init(DataSource dataSource) {
      this.jdbcTemplate = new JdbcTemplate(dataSource);
  }
  
  // ...
}



Note
이러한 어노테이션의 이점을 취하기 위해 어플리케이션 컨텍스트를 설정하는 방법은 각 퍼시스턴스 기술의 해당하는 부분을 참고하길 바란다.

[UFC] 미오치치, 1차 방어전 상대는 오브레임 UFC 203..

일전에 UFC 헤비급 타이틀을 획득한 미오치치에 대한 포스팅을 하고서 또 미오치치 소식의 글을 올리게 됬다..

미오치치가 1차 방어전을 한다는 소식이다.. 그의 상대는 다소 예상을 했던 알리스타 오브레임이다.. 예상을 한 이유는 오브레임의 이전 경기들이 다 화끈하게 KO 승을 해왔기 때문이다.. 경기 장소 및 이벤트 넘버링은 9월 11일 클리블랜드 퀵큰 론즈 아레나에서 열리는 UFC 203 메인이벤트다..



조심스럽게 이번 경기를 예상해보자면 나는 미오치치에게 손을 들어주고 싶다.. 개인적으로 흥행성?? 선수의 상품성?? 등으로 보자면 오브레임을 더 좋아한다.. 과거 프라이드[일본 격투 단체였던 PRIDE 지금은 UFC에 인수합병되고 사라짐] 시절부터 말이지..

하지만 미오치치의 산토스 그리고 직전 경기인 베우둠과의 경기를 보면 타격이 상당하다.. 무엇보다 그라운드에서도 띄어난 실력을 갖고 있기에 모든 방면에서 균형이 맞춰진 선수라고 볼 수 있다.. 지난 챔피언이었던 케인 벨라스케즈도 그런 선수중에 하나이긴 하지만 미오치치를 조금 더 높게 보는건 케인은 타격과 그라운드가 5:5 가 아니다..

비율적으로 구태여 따지자면 타격이 3이고 결국에는 그라운드로 끌고 가기에 7이라고 볼 수 있을 듯 하다.. 이부분은 지극히 개인적인 생각이다.. 그렇지만 미오치치는 타격으로 풀어도 무방하고 그라운드로 풀어도 무방할 5:5의 선수이면서 가장 결정적으로 피지컬이 좋다.. 그 부분은 산토스와의 경기에서도 증명하듯 그 발빠르고 타격이 좋은 산토스가 뒤로 빠져도 미오치치는 리치가 길기 때문에 다 맞춰버렸다.. 그 경기의 승자는 미오치치라고 생각했는데 산토스가 저버리긴 했지만 말이다.. ㅡㅡ..

무튼!!! 그런면에서 미오치치의 승이라고 생각한다.. 오브레임도 물론 과거에 입식타격기를 해왔기에 타격감이 좋긴하지만 결정적으로 유리턱이다.. 그리고 약물파동 이후에 그의 강인함은 없어진듯한 느낌이다.. 반면 미오치치는 맷집도 상당하단 말이지.. 의외로 타격전이 아닌 그라운드로 끌고가는 경기가 나올수도 있을듯하다.. 9월 경기여서 아직 멀긴 했지만 상당히 기대가 되는 매치업이다.. 부디 둘중 누구하나 부상없이 무사히 이벤트에 출전하길 빈다..

다음은 미오치치가 타이틀을 획득한 후 한 말이다.. 미오치치의 바람이 이뤄지길 빈다..

"이 벨트를 클리블랜드로 가져간다. 타이틀을 오래 지킬 것을 약속한다"

[NEWS] '오라클 대 구글' 평결서 구글 승소..

요즘 새롭게 추가적으로 해보는 것이 있는데 얼마전에도 글을 올렸던 것처럼 IT 뉴스를 좀 보고 있다.. 햄처럼 거창하게 이곳 저곳을 다 볼만한 능력은 안되고.. 솔직히 그런 곳을 모르기도 한다 ㅋㅋㅋ.. 무튼 내가 웹 뉴스에서 가쉽거리를 많이 보기 때문에 그 한 귀퉁이에 있는 IT 뉴스에서 좀 볼만한 것들을 챙겨보고 블로그에 포스팅 하려고 한다..

그것의 첫 발걸음이 지난 NEWS 카테고리로 올린 글이었고 이번이 두번째가 되는 셈이다.. 점점 무엇인가 많아지는 듯 하긴한데.. 너무 욕심내지는 말고 작은것부터 시작해서 꾸준히 하도록 하자..

이번 뉴스를 보고 알게 된 것인데 오라클과 구글에 지적재산권에 대해서 분쟁이 있었다고 한다.. 아마 IT 분야에 관심이 있는 분들은 알았을 듯 하다.. 나는 관심 자체가 없었기에 상당히 오랜 시간[6년] 분쟁을 일으킨듯 한데 전혀 몰랐단 말이지..ㅜㅠ 아래는 관련 뉴스 전문이다..

출처 : 연합뉴스

자바 프로그래밍 언어의 지적재산권을 보유한 오라클이 안드로이드 운영체제(OS)를 만든 구글을 상대로 낸 '오라클 대 구글' 소송에서 배심원단이 피고 구글의 손을 들어 줬다. 미국 샌프란시스코 소재 캘리포니아 북부 연방지방법원의 배심원단은 26일(현지시간) 이런 평결을 내렸다. 배심원단은 구글이 안드로이드를 개발하면서 자바 코드 중 일부를 이용한 것은 미국 저작권법상의 '공정 이용'(fair use)에 해당하므로, 구글이 오라클로부터 라이선스를 받을 필요가 없다고 판단했다.
오라클은 구글에 손해배상액으로 88억 달러(10조4천억 원)를, 받지 못한 라이선스 수익으로 4억7천500만 달러(5천600억 원)를 각각 요구했으나, 만약 이번 평결 내용이 최종판결로 확정될 경우 한 푼도 받지 못하게 된다.

다만 패소한 오라클이 항소할 것이 확실하므로, 6년간 진행돼 온 이 소송의 결론이 나려면 앞으로도 몇 년이 더 걸릴 것으로 보인다. 오라클은 자바를 개발한 썬마이크로시스템스를 2010년 인수한 뒤 "구글이 자바의 응용프로그램인터페이스(API) 37종의 구조와 순서, 조직을 베끼는 등 부적절한 방법으로 안드로이드를 설계했다"면서 소송을 제기했다.

당초 캘리포니아 북부 연방지방법원은 2012년 자바 API가 저작권의 대상이 될 수 없다고 보고 구글의 손을 들어 줬으나, 2014년 워싱턴 소재 연방구역 연방항소법원은 오라클의 저작권을 인정하는 판단을 내렸고 이 판결이 연방대법원에서 작년에 확정됐다. 이에 따라 오라클과 구글 양측은 자바 API 코드에 대한 오라클의 저작권을 인정하는 전제로 "구글이 라이선스 계약 없이 이를 사용하는 것이 '공정 이용'의 범위에 포함되는가"라는 좁혀진 쟁점을 놓고 1심 재판을 다시 해 왔다.

2016년 5월 26일 목요일

[Book] 읽기 좋은 코드가 좋은 코드다..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

읽기 좋은 코드가 좋은 코드다 - 8점
더스틴 보즈웰 & 트레버 파우커 지음
임백준 옮김
한빛미디어

제목에서 느껴지는 대로 클린 코드처럼 좋은 코드를 작성하는 법을 설명하는 책이며 특정 언어에 한정되지 않고 어떤 프로그래밍에도 적용할 수 있는 내용을 다루고 있다. 실제로 책의 예제들도 자바스크립트, 파이썬, C++ 등 다양한 언어를 사용해서 설명하고 있으며 딱히 해당 언어의 문법을 몰라도 문맥을 이용하는데는 무리가 없다. 내가 생각하기에 이 책의 가장 좋은 장점은 무겁지 않다는 것이다. 약간은 딱딱하고 지루한 내용일 수도 있지만 간결하게 핵심만 설명하고 있고 분량도 200여페이지정도만 되기 때문에 부담없이 읽을 수 있다는 장점이 있다. 내용이 가볍다는 얘기는 아니다.

처음에는 좋은 코드란 무엇을 의미하는 가를 먼저 설명한다. 코드의 분량등도 한 부분이 될 수가 있지만 최종 목표는 다른 사람이 보았을 때 최소한의 시간으로 코드를 이해할 수 있도록 한다는 것을 최종 목표로 하고 있다는 것을 뚜렷히 밝히고 있다. 이어서 어떤 변수명을 사용하는 것이 좋고 어떤 이름을 피하는 것이 좋은 가를 설명하고 코드의 배열을 어떻게 하면 이해하기 쉽게 작성할 수 있는지 설명한다. 코드에서 빠질 수 없는 주석에 대해서도 어떤 주석이 좋은지 명확히 설명해 주고 있다. 1부가 제법 간단한 내용이었다고 한다면 2부부터는 리팩토링에 대한 주제를 다루고 있으며 어떻게 하면 복잡한 로직의 코드를 이해하기 쉽게 리팩토링할 수 있는지를 설명하고 있다. 아무래도 리팩토링을 다루므로 1부보다는 코드도 좀 길고 약간 복잡해진다.

각 장에서 설명하는 내용의 핵심을 팁처럼 정리해놓았기 때문에 이러한 내용을 정리해두거나 코드를 작성할 때 지속적으로 참고하면 도움이 될만한 내용들이 많다. 그리고 단순히 읽기 좋은 코드에 대한 팁만을 제공하는 것이 아니라 접근하는 방법이랄까 생각하는 방법에 대해서도 설명하고 있기 때문에 이 책에 나온 기본적인 팁들을 바탕으로 직접 생각하려고 노력한다면 코드를 개선하는데 많은 도움이 되리라고 생각한다. 예를 들어 작성한 로직을 리팩토링하기 전에 한국말로 구현내용을 정리해서 접근한다거나 이해하기 쉬운지 타인에게 직접 의견을 구해보라는 조언들은 중요하다고 본다.

저자가 이 책을 위해서 많은 코드들을 찾아보았다고 하는데 그래서인지 너무 이론적인 부분에만 치우치지 않고 실용적인 위치를 잘 지킨 느낌이다. "일관성있는 스타일은 '올바른' 스타일보다 더 중요하다"라든가 이해하기 좋은 코드를 작성하려는 노력이 아키텍처설계나 효율성, 테스트 용이성 등의 목적과 충돌하는 가에 대한 견해를 밝힌 것도 저자가 많은 노력과 고민을 했다는 것을 느낄 수 있다. 어떤 면에서는 기초적인 부분이라고 생각할 수도 있겠지만 핵심을 많이 담고 있다. 


My Comment..
해당 책을 읽지는 않았지만 상당히 공감되는 내용에 대한 책이라고 본다.. 우선 나부터도 코드에 대한 나만의 생각을 갖고 있긴 하다.. 그렇기 때문에 업무를 하다보면 당연히 다른 사람이 선행한 코드를 봐야되는 경우가 빈번한데 너무 본인만 알아보게끔 코딩한 사람들이 있다..

예를 들어서 한줄에 주욱 붙여서 코딩을 했거나 변수명을 정의해두었는데 이건 머 암호도 아니고 당췌 알아먹질 못하게 한 경우가 있다.. 또한, 주석을 모든 소스에 다 적을 필요는 없지만 그래도 좀 복잡한 로직이거나 어떠한 특이 케이스에 대해서는 적어줘야되는데 그렇지 않은 경우가 상당하다..

이 책의 내용을 다는 모르더라도 기본적으로 내가 짠 소스코드가 나만 알아봐서는 안된다고 본다.. 어떤 누가보더라도 간결하고 금방 파악이 될 수 있도록 코딩하는게 좋은 코드라고 본다.. 정말 오랫만에 캐!!! 공감하는 내용의 책인듯하다.. 문제는 책을 읽은게 아니라는 것이.. ㅠㅜ 그래도 햄 덕분에 많은 패턴의 책들이 있구나 라고 파악을 하게 되서 그나마 다행이다..;;; ㅎㅎㅎ



[JAVA] 11장 트랜잭션 관리 #2..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.

11.5.4 다른 빈에 다른 트랜잭션의 의미를 설정하기
다수의 서비스계층 객체가 있고 각각에 완전히 다른 트랜잭션 설정을 적용하고자 하는 시나리오를 생각해 보자. 이러한 경우 다른 pointcut과 advice-ref 속성값을 가진 별개의 <aop:advisor/> 요소를 정의할 수 있다.

약 간의 차이점있지만 우선 모든 서비스 계층의 클래스는 x.y.service 패키지 루트에 정의되어 있다고 가정한다. 모든 빈을 이 패키지(혹은 그 하위 팩키지)에 정의된 클래스의 인스턴스로 만들고 Service로 이름이 끝나는 모든 빈이 기본 트랜잭션 설정을 가지도록 하려면 다음과 같이 작성해야 한다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="
  http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/tx 
  http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
  http://www.springframework.org/schema/aop 
  http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

  <aop:config>

    <aop:pointcut id="serviceOperation"
          expression="execution(* x.y.service..*Service.*(..))"/>

    <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>

  </aop:config>

  <!--   빈은 트랜잭션이 적용될 것이다... -->
  <bean id="fooService" class="x.y.service.DefaultFooService"/>
  <bean id="barService" class="x.y.service.extras.SimpleBarService"/>

  <!-- ... 그리고   빈은 트랜잭션이 적용되지 않는다 -->
  <bean id="anotherService" class="org.xyz.SomeService"/> <!-- (적합한 패키지에 있지 않다) -->
  <bean id="barManager" class="x.y.service.SimpleBarManager"/> <!-- (이름이 'Service' 끝나지 않는다) -->

  <tx:advice id="txAdvice">
    <tx:attributes>
      <tx:method name="get*" read-only="true"/>
      <tx:method name="*"/>
    </tx:attributes>
  </tx:advice>

  <!-- PlatformTransactionManager와 같은 다른 트랜잭션 인프라스트럭처 빈은 생략했다... -->

</beans>

다음 예제는 환전히 다른 트랜잭션 설정으로 별도의 두 빈을 설정하는 방법을 보여준다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="
  http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/tx 
  http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
  http://www.springframework.org/schema/aop 
  http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

  <aop:config>

    <aop:pointcut id="defaultServiceOperation"
          expression="execution(* x.y.service.*Service.*(..))"/>

    <aop:pointcut id="noTxServiceOperation"
          expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>

    <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>

    <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>

  </aop:config>

  <!--  빈은 트랜잭션이 적용된다 ('defaultServiceOperation' 포인트컷을 참고해라) -->
  <bean id="fooService" class="x.y.service.DefaultFooService"/>

  <!--  빈도 트랜잭션이 적용되지만 완전히 다른 트랜잭션 설정을 가진다 -->
  <bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>

  <tx:advice id="defaultTxAdvice">
    <tx:attributes>
      <tx:method name="get*" read-only="true"/>
      <tx:method name="*"/>
    </tx:attributes>
  </tx:advice>

  <tx:advice id="noTxAdvice">
    <tx:attributes>
      <tx:method name="*" propagation="NEVER"/>
    </tx:attributes>
  </tx:advice>

  <!-- PlatformTransactionManager와 같은 다른 트랜잭션 인프라스트럭처 빈은 생략했다... -->
</beans>

11.5.5 <tx:advice/> 설정
이번 색션에서는 <tx:advice/> 태그를 사용해서 지정할 수 있는 여러가지 트랜잭션 설정을 간략히 설명한다. 기본 <tx:advice/> 설정은 다음과 같다.

  • 전파(Propagation) 설정은 REQUIRED다.
  • 격리 수준(Isolation level)은 DEFAULT이다.
  • 트랜잭션은 읽기/쓰기이다.
  • 트랜잭션 타입아웃 기본값은 의존하는 트랜잭션 시스템의 기본 타입아웃이거나 타임아웃을 지원하지 않는다면 존재하지 않는다.
  • 모든 RuntimeException은 롤백을 발생시키고 모든 체크드 Exception은 롤백을 발생시키지 않는다.
이 기본 설정을 변경할 수 있다. <tx:advice/>와 <tx:attributes/>내에 중첩된 <tx:method/> 태그의 여러가지 속성은 아래에 정리되어 있다.

Table 11.1. <tx:method/> 설정

속성필수여부?기본값설명
nameYes연결된 트랜잭션 속성의 메서드 이름. 와일드카드 (*) 문자를 다수의 메서드를 가진 같은 트랜잭션 속성 설정과 연결하는데 사용할 수 있다. 예를 들어, get*, handle*, on*Event 등등 이다.
propagationNoREQUIRED트랜잭션 전파 동작.
isolationNoDEFAULT트랜잭션 격리 수준.
timeoutNo-1트랜잭션 타임아웃 값 (초단위).
read-onlyNofalse해당 트랜잭션이 읽기 전용인가?
rollback-forNo롤백을 일으키는 Exception(s). 콤마로 구분한다. 예를 들면 com.foo.MyBusinessException,ServletException.
no-rollback-forNo롤백을 일으키지 않는 Exception(s). 콤마로 구분한다. 예를 들어 com.foo.MyBusinessException,ServletException.












11.5.6 @Transactional 사용하기
트 랜잭션 설정에 대한 XML에 기반한 선언적인 접근에 추가적으로 어노테이션 기반의 접근을 사용할 수 있다. 자바 소스코드에 직접 트랜잭션을 선언하는 것은 영향받는 코드에 훨씬 가깝게 선언을 둘 수 있다. 어쨌든 트랜잭션을 사용하는 코드는 거의 항상 이러한 방법으로 배포되기 때문에 과도한 커플링으로 인한 큰 위험은 없다.

@Transactional 어노테이션의 쉬운 사용방법은 예제로 설명하는 것이 가장 쉽고 예제후에 좀더 자세히 설명한다. 다음의 클래스 정의를 보자.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Java

// 트랜잭션을 적용하고자 하는 서비스 클래스
@Transactional
public class DefaultFooService implements FooService {

  Foo getFoo(String fooName);

  Foo getFoo(String fooName, String barName);

  void insertFoo(Foo foo);

  void updateFoo(Foo foo);
}

위의 POJO가 스프링 IoC 컨테이너의 빈처럼 정의되었을 때 딱 한 줄의 XML 설정을 추가해서 빈 인스턴스에 트랜잭션을 적용할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Xml

<!-- 'context.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:aop="http://www.springframework.org/schema/aop"
     xmlns:tx="http://www.springframework.org/schema/tx"
     xsi:schemaLocation="
     http://www.springframework.org/schema/beans 
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
     http://www.springframework.org/schema/tx 
     http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
     http://www.springframework.org/schema/aop 
     http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
  
  <!-- 트랜잭션을 적용하고자 하는 서비스 객체다 -->
  <bean id="fooService" class="x.y.service.DefaultFooService"/>

  <!-- 어노테이션에 기반한 트랜잭션 동작의 설정을 활성화한다. -->
  <tx:annotation-driven transaction-manager="txManager"/>

  <!-- a PlatformTransactionManager는 여전히 필요하다 -->
  <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <!-- (이 의존성은 어딘가에 정의되어 있다) -->
  <property name="dataSource" ref="dataSource"/>
  </bean>
  
  <!-- 다른 <bean/> 정의는 여기에 한다 -->
</beans>



Tip
연 결하려는 PlatformTransactionManager의 빈 이름이 transactionManager인 경우에는 <tx:annotation-driven/> 태그에서 transaction-manager 속성을 생략할 수 있다. 의존성 주입하려는 PlatformTransactionManager 빈이 다른 이름을 가지고 있다면 앞의 예제처럼 transaction-manager 속성을 명시적으로 사용해야 한다.

인터페이스 정의, 인터페이스의 메서드, 클래스 정의, 클래스의 퍼블릭 메서드 앞에 @Transactional 어노테이션을 둘 수 있다. 하지만 단지 @Transactional 어노테이션의 존재만으로는 트랜잭션 동작을 활성화하기에 충분하지 않다. @Transactional 어노테이션은 단순히 @Transactional을 인지하는 몇몇 런타임 인프라스트럭처가 소비할 수 있는 메타데이터이고 적절한 빈에 트랜잭션 동작을 설정하는데 메타데이터를 사용할 수 있다. 앞의 예제에서 <tx:annotation-driven/> 요소가 트랜잭션 동작을 활성화한다.



메서드 가시성과 @Transactional
프 록시를 사용할 때는 public 가시성을 가진 메서드에만 @Transactional 어노테이션을 적용해야 한다. protected나 private, package-visible 메서드에 @Transactional 어노테이션을 붙히면 오류가 발생하지는 않지만 어노테이션이 붙은 메서드는 설정된 트랜잭션 설정에 나타나지 않는다. 퍼블릭이 아닌 메서드에 어노테이션을 붙혀야 한다면 AspectJ의 사용을 고려해봐라(아래 참고).


Tip
스 프링은 인터페이스에 어노테이션을 붙히는 것과는 반대로 구현(concrete) 클래스(그리고 구현 클래스의 메서드들)에만 @Transactional 어노테이션을 붙히기를 권장한다. 확실히 인터페이스(또는 인터페이스의 메서드)에도 @Transactional 어노테이션을 붙힐 수 있지만 인터페이스 기반의 프록시를 사용하는 경우에만 제대로 동작한다. 자바의 어노테이션이 인터페이스를 상속받지 않는다는 점은 클래스 기반의 프록시 (proxy-target-class="true")를 사용하거나 위빙기반의 관점 (mode="aspectj")을 사용할 때 프록시와 위빙 인프라스트럭처가 트랜잭션 설정을 인지하지 못하고 해당 객체가 트랜잭션이 적용된 프록시로 감싸지지 않는다는 것(아주 안좋다)을 의미한다.


Note
프 록시 모드에서(기본값이다) 프록시를 통한 외부 메서드 호출만을 가로챈다. 즉, 호출된 메서드가 @Transactional로 표시되어 있더라도 자기호출(self-invocation, 대상 객체의 다른 메서드를 호출하는 대상 객체내의 메서드)은 런타임시에 실제로 트랜잭션이 되지 않을 것이다.

자기호출이 트랜잭션으로 잘 감싸지길 원한다면 AspectJ 모드의 사용을 고려해 봐라.(아래 표의 mode 속성을 참고해라.) 우선 이 경우에 프록시가 없다. 대신, 모든 종류의 메서드에서 @Transactional을 런타임동작으로 바꾸기 위해 대상 객체는 위빙된 것이다.(즉 대상객체의 바이트코드가 수정될 것이다.)

Table 11.2. <tx:annotation-driven/> 설정

속성기본값설명
transaction-managertransactionManager사용할 트랜잭션 관리자의 이름. 위의 예제처럼 트랜잭션 관리자의 이름이 transactionManager이 아닌 경우에만 필요하다.
modeproxy기 본 모드인 "proxy"가 스프링의 AOP 프레임워크를 사용해서 프록시되는 어노테이션이 붙은 빈을 처리한다.(위에서 얘기한 다음의 프록시 의미는 프록시를 통한 메서드 호출에만 적용된다.) 다른 모드인 "aspectj"는 스프링의 AspectJ 트랜잭션 관점으로 영향받은 클래스를 대신 위빙해서 모든 종류의 메서드 호출에 적용하기 위해 대상객체의 바이트코드를 수정한다. AspectJ 위빙은 활성화된 로드타임 위빙(또는 컴파일타임 위빙)과 마찬가지로 클래스패스에 spring-aspects.jar를 필요로 한다.(로드타임 위빙을 설정하는 방법은 Section 8.8.4.5, “스프링 설정”를 참고해라.)
proxy-target-classfalseproxy 모드에만 적용된다. @Transactional 어노테이션이 붙은 클래스에 어떤 타입의 트랜잭션 프록시를 생성할 것인지 제어한다. proxy-target-class 속성을 true로 설정했다면 클래스기반의 프록시가 생성된다. proxy-target-class가 false이거나 이 속성을 생략하면 표준 JDK 인터페이스 기반 프록시가 생성된다. (다른 프록시 타입의 자세한 설명은 Section 8.6, “프록싱 메카니즘”를 참고해라.)
orderOrdered.LOWEST_PRECEDENCE@Transactional 어노테이션이 붙은 빈에 적용되는 트랜잭션 어드바이스의 순서를 정의한다. (AOP 어드바이스의 순서와 관계된 규칙에 대한 내용은 Section 8.2.4.7, “어드바이스 순서”를 참고해라.) 순서를 지정하지 않으면 AOP 서브시스템이 어드바이스의 순서를 결정한다.






















Note
@Transactional 어노테이션이 붙은 클래스에 어떤 타입의 트랜잭션이 적용된 프록시를 생성할 것인 지를 <tx:annotation-driven/> 요소의 proxy-target-class 속성이 제어한다. proxy-target-class 속성을 true로 설정했으면 클래스기반의 프록시가 생성된다. proxy-target-class가 false이거나 이 속성을 생략하면 표준 JDK 인터페이스기반의 프록시가 생성된다. (다른 프록시 타입에 대한 내용은 Section 8.6, “프록싱 메카니즘”를 참고해라.)


Note
<tx:annotation- driven/>는 같은 어플리케이션 컨텍스트에 정의된 @Transactional가 붙은 빈만을 찾는다. 즉, DispatcherServlet을 위해서 WebApplicationContext에 <tx:annotation-driven/>를 두면 서비스가 아니라 컨트롤러에서만 @Transactional 빈을 확인한다. 자세한 내용은 Section 16.2, “The DispatcherServlet”를 참고해라.

메 서드에 트랜잭션 설정을 평가할 때 가장 깊은 위치(most derived location)의 설정을 우선시한다. 다음 예제에서 DefaultFooService 클래스는 읽기전용 트랜잭션 설정으로 클래스수준에 어노테이션을 붙혔지만 같은 클래스의 updateFoo(Foo) 메서드의 @Transactional 어노테이션이 클래스수준에 정의된 트랜잭션 설정보다 앞선다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Java

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

  public Foo getFoo(String fooName) {
    // 어떤 작업을 한다
  }

  // 이 메서드에는 이 설정이 우선시된다
  @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  public void updateFoo(Foo foo) {
    // 어떤 작업을 한다
  }
}

11.5.6.1 @Transactional 설정
@Transactional 어노테이션은 인터페이스, 클래스, 메서드가 트랜잭션이 되어야 한다는 것을 지정하는 메타데이터이다. 예를 들어 “해당 메서드가 호출될 때 기존의 존재하는 트랜잭션은 중지시키고 읽기 전용의 새로운 트랜잭션을 시작해야 한다는 식이다.” 기본 @Transactional 설정은 다음과 같다.

  • 전파 설정은 PROPAGATION_REQUIRED 이다.
  • 격리 수준은 ISOLATION_DEFAULT 이다.
  • 트랜잭션은 읽기/쓰기가 가능하다.
  • 트랜잭션 타임아웃의 기본값은 의존하는 트랜잭션 시스템의 기본 타임아웃값이거나 타임아웃이 지원되지 않는다면 타임아웃이 없다.
  • 모든 RuntimeException은 롤백을 실행하고 체크드 Exception은 롤백하지 않는다.
이 기본설정은 변경할 수 있다. @Transactional 어노테이션의 다양한 프로퍼티는 다음 표에 정리해 놨다.

Table 11.3. @Transactional 프로퍼티

프로퍼티타입설명
valueString사용할 트랜잭션 관리자를 지정하는 선택적인 제한자(qualifier)
propagationenum: Propagation선택적인 전파 설정.
isolationenum: Isolation선택적인 격리수준.
readOnlyboolean읽기/쓰기 트랜잭션인가? 읽기전용 트랜잭션인가?
timeoutint (in seconds granularity)트랜잭션 타임아웃.
rollbackForThrowable에서 얻어져야하는 Class 객체들의 배열.반드시 롤백해야 하는 예외 클래스의 선택적인 배열.
rollbackForClassname클래스 이름의 배열. Throwable에서 얻어져야 하는 클래스들.반드시 롤백해야 하는 예외 클래스 이름의 선택적인 배열.
noRollbackForThrowable에서 얻어져야 하는 Class 객체들의 배열.반드시 롤백하지 않아야 하는 예외 클래스의 선택적인 배열.
noRollbackForClassnameThrowable에서 얻어져야 하는 클래스 이름 String의 배열.반드시 롤백하지 않아야 하는 예외 클래스 이름의 선택적인 배열.












지금은 트랜잭션의 이름으로 명시적인 제어를 할 수 없다. 여기서 '이름'은 가능한 경우 트랜잭션 모니터(예를 들면 웹로직의 트랜잭션 모니터)와 로깅 출력에 나올 트랜잭션의 이름을 의미한다. 선언적인 트랜잭션에서 트랜잭션 이름은 항상 정규화된 클래스명 + "." + 트랜잭션하게 어드바이즈된 클래스의 메서드명이다. 예를 들어 BusinessService 클래스의 handlePayment(..) 메서드가 트랜잭션을 시작하면 트랜잭션의 이름은 com.foo.BusinessService.handlePayment가 된다.

11.5.6.2 @Transactional과 여러 트랜잭션 관리자
대 부분의 스프링 어플리케이션은 딱 하나의 트랜잭션 관리자만 필요로 하지만 하나의 어플리케이션에서 여러 개의 독립적인 트랜잭션 관리자가 필요한 상황이 있을 것이다. @Transactional 어노테이션의 value 속성은 사용할 PlatformTransactionManager의 식별자를 선택적으로 지정하는데 사용할 수 있다. 이는 트랜잭션 관리자 빈의 이름이나 제한자(qualifier) 값이 될 수 있다. 예를 들어 제한자(qualifier) 표기법을 사용한 다음의 자바코드는


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Java

public class TransactionalService {
  
  @Transactional("order")
  public void setSomething(String name) { ... }

  @Transactional("account")
  public void doSomething() { ... }
}

어플리케이션 컨텍스트에서 다음의 트랜잭션 관리자 빈 선언과 섞을 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Xml

<tx:annotation-driven/>

  <bean id="transactionManager1" class="org.springframework.jdbc.DataSourceTransactionManager">
    ...
    <qualifier value="order"/>
  </bean>

  <bean id="transactionManager2" class="org.springframework.jdbc.DataSourceTransactionManager">
    ...
    <qualifier value="account"/>
</bean>

이 경우에 TransactionalService의 두 메서드는 "order"와 "account" 제한자로 구분된 별도의 트랜잭션 관리자에서 실행될 것이다. 적합한 PlatformTransactionManager 빈을 특별히 찾아내지 못한다면 transactionManager 빈 이름을 가리키는 기본 <tx:annotation-driven>를 여전히 사용할 것이다.

11.5.6.3 커스텀 단축 어노테이션
여러 메서드의 @Transactional에 같은 속성을 반복적으로 사용하고 있다면 스프링 메타어노테이션 지원을 이용해서 커스텀 단축 어노테이션을 정의할 수 있다. 예를 들면 다음과 같은 어노테이션을 정의해서


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Java

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("order")
public @interface OrderTx {
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
public @interface AccountTx {
}

다음과 같이 이전 섹션의 예제를 작성할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Java

public class TransactionalService {
  
  @OrderTx
  public void setSomething(String name) { ... }

  @AccountTx
  public void doSomething() { ... }
}

여기서는 트랜잭션 관리자 제한자를 정의하는 문법을 사용했지만 전파 동작, 롤백 규칙, 타임아웃 등도 사용할 수 있다.

11.5.7 트랜잭션 전파
이번 섹션에서는 스프링에서 트랜잭션 전파의 의미를 설명한다. 이번 섹션이 트랜잭션 전파의 적절한 소개가 아니라는 것을 유념해야 한다. 오히려 스프링에서 트랜잭션 전파에 관련된 몇 가지 의미를 설명한다.

스프링이 관리하는 트랜잭션에서sms 물리적인 트랜잭션과 논리적인 트랜잭션간의 차이점과 이 차이점에 전파설정을 적용하는 방법을 알아야 한다.

11.5.7.1 Required

PROPAGATION_REQUIRED
PROPAGATION_REQUIRED

전파 설정이 PROPAGATION_REQUIRED인 경우 설정이 적용된 각 메서드마다 논리적인 트랜잭션의 범위가 생성된다. 이러한 각각의 논리적인 트랜잭션 범위는 외부 트랜잭션 범위는 내부 트랜잭션 범위와 논리적으로 독립되어 개별적인 롤백전용(rollback-only) 상태를 결정한다. 물론 표준 PROPAGATION_REQUIRED 동작의 경우 이러한 모든 범위는 같은 물리적인 트랜잭션에 매핑될 것이다. 그래서 내부 트랜잭션 범위에 설정된 롤백전용 표시(marker)는 외부 트랜잭션이 실제로 커밋할 기회에 영향을 준다.(기대대로)

하지만 내부 트랜잭션 범위가 롤백전용으로 설정된 경우에 외부 트랜잭션 스스로 롤백을 결정하지 않으므로 롤백을(내부 트랜잭션 범위가 조용히 실행한다.) 예상하지 못한다. 이 때 대응되는 UnexpectedRollbackException이 던져진다. 이는 트랜잭션의 호출자가 커밋되지 않아야 하는 경우 커밋되었다고 가정하지 않도록 할 수 있는 예상된 동작이다. 그래서 내부 트랜잭션(외부 호출자가 알지 못하는)은 트랜잭션을 조용히 롤백전용으로 표시하고 외부 호출자는 여전히 커밋을 호출한다. 외부 호출자는 커밋이 아니라 롤백이 수행되었다는 것을 명확히 알도록 UnexpectedRollbackException를 받야야 한다.

11.5.7.2 RequiresNew

PROPAGATION_REQUIRES_NEW
PROPAGATION_REQUIRES_NEW

PROPAGATION_REQUIRED 와는 반대로 PROPAGATION_REQUIRES_NEW는 영향받은 각각의 트랜잭션 범위에 완전히 독릭적인 트랜잭션을 사용한다. 이러한 경우 의존하는 물리적인 트랜잭션이 다르므로 외부 트랜잭션이 내부 트랜잭션의 롤백상태에 영향을 받지 않고 독립적으로 커밋하거나 롤백할 수 있다.

11.5.7.3 Nested
PROPAGATION_NESTED 는 롤백할 수 있는 여러 세이브포인트(savepoint)를 가진 하나의 물리적인 트랜잭션을 사용한다. 이러한 부분 롤백으로 어떤 작업이 롤백되었더라도 외부 트랜잭션은 계속해서 물리적인 트랜잭션을 진행하면서 내부 트랜잭션 범위가 자신의 범위에서 롤백을 실행할 수 있게 한다. 이 설정은 보통 JDBC 세이브포인트에 매핑되므로 JDBC 리소스 트랜잭션에서만 동작할 것이다. 스프링의 DataSourceTransactionManager를 참고해라.

11.5.8 트랜잭션 작업 어드바이징하기
트랜잭션 작업과 몇가지 기본적인 프로파일링 어드바이스를 둘 다 실행한다고 생각해보자. <tx:annotation-driven/> 컨텍스트에서 이렇게 하려면 어떻게 해야하는가?

updateFoo(Foo) 메서드를 호출할 때 다음의 동작을 보기 원할 것이다.

  1. 설정된 프로파일링 관점 시작.
  2. 트랙잭션이 적용된 어드바이스 실행.
  3. 어드바이즈된 객체의 메서드 실행.
  4. 트랜잭션 커밋.
  5. 프로파일링 관점이 전체 트랜잭션 메서드 호출의 정확한 실행시간을 리포팅함.
Note
이번 장에서는 AOP의 자세한 내용은 설명하지 않는다.(트랜잭션을 적용하는 것과 관련된 것은 제외하고) 일반적인 다음의 AOP와 AOP의 설정에 대한 자세한 내용은 Chapter 8, 스프링의 관점 지향 프로그래밍를 참고해라.

다음은 앞에서 얘기한 간단한 프로파일링 관점에 대한 코드이다. 어드바이스의 순서는 Ordered 인터페이스로 제어한다. 어드바이스 순서에 대한 자세한 내용은 Section 8.2.4.7, “어드바이스 순서”를 참고해라.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
Java

package x.y;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
import org.springframework.core.Ordered;

public class SimpleProfiler implements Ordered {

  private int order;

  // 어드바이스의 순서를 제어할 수 있게 한다
  public int getOrder() {
    return this.order;
  }

  public void setOrder(int order) {
    this.order = order;
  }

  // 이 메서드는 around advice 이다
  public Object profile(ProceedingJoinPoint call) throws Throwable {
    Object returnValue;
    StopWatch clock = new StopWatch(getClass().getName());
    try {
      clock.start(call.toShortString());
      returnValue = call.proceed();
    } finally {
      clock.stop();
      System.out.println(clock.prettyPrint());
    }
    return returnValue;
  }
}

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:aop="http://www.springframework.org/schema/aop"
     xmlns:tx="http://www.springframework.org/schema/tx"
     xsi:schemaLocation="
   http://www.springframework.org/schema/beans 
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/tx 
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/aop 
   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

  <bean id="fooService" class="x.y.service.DefaultFooService"/>

  <!-- 관점이다 -->
  <bean id="profiler" class="x.y.SimpleProfiler">
    <!-- 트랜잭션이 적용된 어드바이스 이전에 실행한다 (그러므로 순서(order) 번호가 작다) -->
    <property name="order" value="1"/>
  </bean>

  <tx:annotation-driven transaction-manager="txManager" order="200"/>

  <aop:config>
    <!--  어드바시스는 트랜잭션이 적용된 어드바이스 주위에서(around) 실행될 것이다 -->
    <aop:aspect id="profilingAspect" ref="profiler">
      <aop:pointcut id="serviceMethodWithReturnValue"
              expression="execution(!void x.y..*Service.*(..))"/>
      <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
    </aop:aspect>
  </aop:config>

  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
    <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
    <property name="username" value="scott"/>
    <property name="password" value="tiger"/>
  </bean>

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

위의 설정으로 원하는 순서에 따라 fooService 빈에 프로파일링과 트랜잭셔널 관점을 적용한다. 유사한 방법으로 다수의 추가 관점을 설정한다.

다음 예제는 위의 설정과 같은 효과가 있지만 순수하게 XML의 선언적인 접근을 사용한다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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:aop="http://www.springframework.org/schema/aop"
     xmlns:tx="http://www.springframework.org/schema/tx"
     xsi:schemaLocation="
   http://www.springframework.org/schema/beans 
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/tx 
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/aop 
   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

  <bean id="fooService" class="x.y.service.DefaultFooService"/>

  <!-- 프로파일링 어드바이스 -->
  <bean id="profiler" class="x.y.SimpleProfiler">
    <!-- 트랜잭션이 적용된 어드바이스 이전에 실행된다. (그러므로 순서(order) 번호가 낮다) -->
    <property name="order" value="1"/>
  </bean>

  <aop:config>

    <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>

    <!-- 프로파일링 어드바이스 이후에 실행될 것이다. (order 속성을 비교해봐라) -->
    <aop:advisor
        advice-ref="txAdvice"
        pointcut-ref="entryPointMethod"
        order="2"/> <!-- order 값이 프로파일링 관점보다 크다 -->

    <aop:aspect id="profilingAspect" ref="profiler">
      <aop:pointcut id="serviceMethodWithReturnValue"
              expression="execution(!void x.y..*Service.*(..))"/>
      <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
    </aop:aspect>

  </aop:config>

  <tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
      <tx:method name="get*" read-only="true"/>
      <tx:method name="*"/>
    </tx:attributes>
  </tx:advice>

  <!-- DataSource와 PlatformTransactionManager같은 다른 <bean/> 정의는 여기에 한다 -->
</beans>

앞 의 설정으로 순서에 따라 프로파일링 관점과 트랜잭셔널 관점이 fooService 빈에 적용될 것이다. 트랜잭셔널 어드바이스에 진입한 이후와 트랜잭셔널 어드바이스가 끝나기 전에 프로파일링 어드바이스를 실행하려고 하면 그냥 프로파일링 관점 빈의 order 프로퍼티 값을 트랜잭셔널 어드바이스의 order 값보다 높게 바꿔주면 된다.

추가적인 관점도 비슷한 방법으로 설정한다.

11.5.9 AspectJ로 @Transactional 사용하기
스 프링 컨테이너 외부에서 AspectJ 관점으로 스프링 프레임워크의 @Transactional 지원을 사용할 수도 있다. 이렇게 하려면 일단 클래스(선택적으로 클래스의 메서드에)에 @Transactional 어노테이션을 붙히고 그 다음 spring-aspects.jar에 정의된 org.springframework.transaction.aspectj.AnnotationTransactionAspect와 어플리케이션은 연결(위브, weave)한다. 관점도 반드시 트랜잭션 관리자로 설정해야 한다. 당연히 관점을 의존성 주입하는데 스프링 프레임워크의 IoC 컨테이너를 사용할 수 있다. 트랜잭션 관리 관점을 설정하는 가장 간단한 방법은 <tx:annotation-driven/> 요소를 사용하고 Section 11.5.6, “@Transactional 사용하기”에서 설명한 것처럼 aspectj에 mode 속성을 지정하는 것이다. 여기서는 스프링 컨테이너 외부에서 동작하는 어플리케이션에 대해서 얘기하고 있으므로 프로그래밍적으로 설정하는 방법을 설명할 것이다.

Note
계속 읽기 전에 Section 11.5.6, “@Transactional 사용하기”와 Chapter 8, 스프링의 관점 지향 프로그래밍를 읽으면 도움이 될 것이다.


1
2
3
4
5
6
7
Java

// 적절한 트랜잭션 관리자를 생성한다 
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());

// 사용할 AnnotationTransactionAspect를 설정한다. 이는 반드시 트랜잭션이 적용된 어떤 메서드라도 실행하기 전에 이뤄져야 한다.
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager); 



Note
이 관점을 사용할 때는 클래스가 구현한 인터페이스(존재한다면)가 아니라 구현 클래스(또는 구현클래스의 메서드)에 어노테이션을 붙혀야 한다. AspectJ는 인터페이스에 붙은 어노테이션은 상속받지 않는다는 자바의 규칙을 따른다.

클래스에 붙은 @Transactional 어노테이션은 클래스의 모든 메서드 실행에 대한 기본 트랜잭션 동작을 지정한다.

클래스내의 메서드에 붙은 @Transactional 어노테이션은 클래스 어노테이션(존재한다면)이 지정한 기본 트랜잭션 동작을 덮어쓴다. 가시성에 상관없이 모든 메서드는 어노테이션이 붙을 수 있다.

AnnotationTransactionAspect으로 어플리케이션을 위빙하려면 어플리케이션은 AspectJ로 구성하거나(AspectJ 개발 가이드 참고) 로드타입 위빙을 사용해야 한다. AspectJ를 사용하는 로드타임 위빙에 대한 내용은 Section 8.8.4, “스프링 프레임워크에서 AspectJ를 사용한 로드타임 위빙(Load-time weaving)”를 참고해라.

11.6 프로그래밍적인 트랜잭션 관리
스프링 프레임워크는 프로그래밍적인 트랜잭션 관리의 두가지 수단을 제공한다.

  • TransactionTemplate 사용하기.
  • PlatformTransactionManager 구현체를 직접 사용하기.
스프링 팀은 프로그래밍적인 트랜잭션 관리에 보통 TransactionTemplate를 추천한다. 두번째 접근은 예외 처리에 대한 부단이 적기는 하지만 JTA UserTransaction API를 사용하는 것과 유사하다.

11.6.1 TransactionTemplate 사용하기
TransactionTemplate 은 JdbcTemplate같은 다른 스프링 템플릿과 같은 접근을 채택했다. TransactionTemplate는 어플리케이션 코드가 보일러플레이트 획득과 트랜잭셔널 리소스의 해지에서 자유롭도록 콜백 접근을 사용한다. 그래서 코드는 의도지향적(intention driven)이 되고 작성한 코드는 개발자가 하고자 한것에만 오로지 집중할 수 있다.



Note
다 음에 예제에서 보듯이 TransactionTemplate를 사용하면 스프링의 트랜잭션 인프라스트럭처 API에 완전히 커플링된다. 개발요구사항에 프로그래밍적인 트랜잭션 관리가 적합하든지 적합하지 않든지간에 결정은 스스로 하는 것이다.

트랜잭션 컨텍스트에서 실행되어야 하고 명시적으로 TransactionTemplate를 사용할 어플리케이션 코드는 다음과 같다. 어플리케이션 개발자는 트랜잭션 컨텍스트에서 실행해야하는 코드를 담고 있는 TransactionCallback 구현체를 작성한다. 그 다음 작성한 커스텀 TransactionCallback의 인스턴스를 TransactionTemplate에 노출된 execute(..) 메서드에 전달한다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Java

public class SimpleService implements Service {

  // 이 인스턴스의 모든 메서드에서 공유되는 하나의 TransactionTemplate
  private final TransactionTemplate transactionTemplate;

  // PlatformTransactionManager를 제공하기 위해 생성자 주입을 사용한다
  public SimpleService(PlatformTransactionManager transactionManager) {
    Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
    this.transactionTemplate = new TransactionTemplate(transactionManager);
  }

  public Object someServiceMethod() {
    return transactionTemplate.execute(new TransactionCallback() {

      // 이 메서드의 코드는 트랜잭션 컨텍스트에서 실행된다
      public Object doInTransaction(TransactionStatus status) {
        updateOperation1();
        return resultOfUpdateOperation2();
      }
    });
  }
}

반환값이 없으면 다음과 같이 익명 클래스로 간편한 TransactionCallbackWithoutResult 클래스를 사용해라.

1
2
3
4
5
6
7
8
9
Java

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

  protected void doInTransactionWithoutResult(TransactionStatus status) {
    updateOperation1();
    updateOperation2();
  }
});

콜백내의 코드는 제공된 TransactionStatus 객체의 setRollbackOnly() 메서드를 호출해서 트랜잭션을 롤백할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Java

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

  protected void doInTransactionWithoutResult(TransactionStatus status) {
    try {
      updateOperation1();
      updateOperation2();
    } catch (SomeBusinessExeption ex) {
      status.setRollbackOnly();
    }
  }
});

11.6.1.1 트랜잭션 설정 지정하기
프 로그래밍적으로나 설정에서 TransactionTemplate에 전파모드, 격리수준, 타임아웃 등의 트랜잭션 설정을 지정할 수 있다. 기본적으로 TransactionTemplate 인스턴스는 기본 트랜잭션 설정을 가진다. 다음 예제는 특정 TransactionTemplate의 트랜잭션 설정을 프로그래밍적으로 커스터마이징한 것을 보여준다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Java

public class SimpleService implements Service {

  private final TransactionTemplate transactionTemplate;

  public SimpleService(PlatformTransactionManager transactionManager) {
    Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
    this.transactionTemplate = new TransactionTemplate(transactionManager);

    // 원한다면 트랜잭션 설정은 여기서 명시적으로 설정할 수 있다
    this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
    this.transactionTemplate.setTimeout(30); // 30 초
    // 등등...
  }
}

음 예제는 스프링 XML 설정을 사용해서 몇가지 커스텀 트랜잭션 설정으로 TransactionTemplate를 정의한다. sharedTransactionTemplate를 필요한 만큼의 서비스에 주입할 수 있다.

1
2
3
4
5
6
Xml

<bean id="sharedTransactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
  <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
  <property name="timeout" value="30"/>
</bean>

마지막으로 TransactionTemplate 클래스의 인스턴스들은 스레드세이프하므로 어떤 대화식(conversational) 상태도 유지하지 않는다. 하지만 TransactionTemplate 인스턴스는 설정(configuration) 상태를 유지한다. 그러므로 다수의 클래스가 하나의 TransactionTemplate 인스턴스를 공유할 것이므로 클래스가 다른 설정(예를 들어 다른 격리수준)의 TransactionTemplate를 사용해야 한다면 두 가지의 다른 TransactionTemplate 인스턴스를 생성해야 한다.

11.6.2  PlatformTransactionManager 사용하기
트 랜잭션을 관리하는데 org.springframework.transaction.PlatformTransactionManager를 직접 사용할 수도 있다. 단순히 사용하는 PlatformTransactionManager의 구현체를 빈 레퍼런스로 빈에 전달해라. 그 다음 TransactionDefinition와 TransactionStatus를 사용해서 트랜잭션을 시작하고 롤백하고 커밋할 수 있다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Java

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 트랜잭션 이름만이 프로그래밍적으로 명시적으로 설정할 수 있다
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);
try {
  // 여기의 비즈니스 로직을 실행한다
}
catch (MyException ex) {
  txManager.rollback(status);
  throw ex;
}
txManager.commit(status);

11.7 프로그래밍적인 트랜잭션 관리와 선언적인 트랜잭션 중에서 선택하기
프 로그래밍적인 트랜잭션 관리는 적은 수의 트랜잭션 작업이 있을 때만 보통 좋은 생각이다. 예를 들어 특정 업데이트 작업에만 트랜잭션이 필요한 웹어플리케이션인 경우 스프링이나 다른 기술을 사용해서 트랜잭션이 적용된 프록시를 설정하기 원치 않을 것이다. 이러한 경우 TransactionTemplate을 사용하는 것이 좋은 접근이 될 수 있다. 명시적으로 트랜잭션 이름을 설정할 수 있는 것도 트랜잭션 관리에 프로그래밍적인 접근을 사용해서만 할 수 있는 것이다.

반면에 어플리케이션에 매우 많은 트랜잭션 작업이 있다면 선언적인 트랜잭션 관리가 일반적으로 휼륭하다. 선언적인 트랜잭션 관리는 비즈니스 로직 외부에서 트랜잭션 관리를 하고 설정하기가 어렵지 않다. EJB CMT가 아니라 스프링 프레임워크를 사용할 때 선언적인 트랜잭션 관리의 설정 비용은 엄청나게 줄어든다.

11.8 어플리케이션 서버에 특화된 통합
스 프링의 트랜잭션 추상화는 보통 어플리케이션 서버와 관계가 없다. 게다가 JTA UserTransaction와 TransactionManager 객체들을 검색하는 JNDI를 선택적으로 수행하는 스프링의 JtaTransactionManager 클래스는 어플리케이션 서버마다 다른 TransactionManager의 위치를 자동으로 탐지한다. JTA TransactionManager로의 접근해서 특히 트랜잭션 중지 지원같은 향상된 트랜잭션을 사용할 수 있다. 자세한 내용은 JtaTransactionManager Javadoc을 참고해라.

스프링의 JtaTransactionManager는 Java EE 어플리케이션 서버에서 실행할 때의 일반적인 선택이고 별도의 설정을 하지 않고도 일반적인 모든 서버에서 동작한다고 알려져있다. 트랜잭션 중지같은 향샹된 기능은 다수의 서버에서 잘 동작한다.(GlassFish, JBoss, Geronimo, Oracle OC4J를 포함해서) 하지만 완전한 트랜잭션 중지 지원과 더 향상된 통합을 위해서 스프링은 IBM WebSphere, BEA WebLogic 서버, Oracle OC4J에 대한 전용 아답터를 제공한다. 이러한 아답터들은 다음 섹션에서 설명한다.

WebLogic 서버, WebSphere, OC4J를 포함한 일반적인 시나리오에서는 간편한 <tx:jta-transaction-manager/> 설정요소의 사용을 고려해 봐라. 이 요소를 설정하면 의존하는 서버를 자동으로 탐지하고 플랫폼에서 사용할 수 있는 가장 좋은 트랜잭션 관리자를 선택한다. 즉, 서버에 특화된 아답터 클래스(앞의 섹션에서 설명했듯이)를 명시적으로 설정하지 않아도 된다. 반대로 아답터 클래스들은 자동으로 선택하고 기본 폴백(fallback)으로 표준 JtaTransactionManager를 사용한다.

11.8.1 IBM WebSphere
WebSphere 6.1.0.9 이상의 버전에서 사용하길 추천하는 스프링 JTA 트랜잭션 관리자는 WebSphereUowTransactionManager이다. 이 전용 아답터는 웹스피어 어플리케이션 서버 6.0.2.19 이상의 버전과 6.1.0.9 이상의 버전에서 사용할 수 있는 IBM의 UOWManager API를 사용한다. 이 아답터를 사용하면 스프링 주도의 트랜잭션 중지(PROPAGATION_REQUIRES_NEW으로 시작된 것처럼 중지/복귀)를 IBM이 공식적으로 지원한다!

11.8.2 BEA WebLogic 서버
웹 로직 서버 9.0 이상에서는 JtaTransactionManager 클래스 대신 WebLogicJtaTransactionManager를 보통 사용할 것이다. 이 클래스는 웹로직에 특화된 클래스로 일반적인 JtaTransactionManager의 하위클래스로 표준 JTA 의미를 넘어 웹로직이 관리하는 트랜잭션 환경에서 스프링의 트랜잭션 정의를 완전히 지원한다. 트랜잭션 이름, 트랜잭션당 격리 수준, 모든 경우에서 트랜잭션을 적절히 복귀하는 등의 기능을 포함한다.

11.8.3 Oracle OC4J
스프링은 OC4J 10.1.3 이상의 버전을 위해서 전용 아답터 클래스인 OC4JJtaTransactionManager를 제공한다. 이 클래스는 앞에서 설명한 WebLogicJtaTransactionManager 클래스와 유사해서 OC4J에 트랜잭션 이름, 트랜잭션당 격리 수준같은 부가가치를 제공한다.

트랜잭션 중지를 포함한 전체 JTA 기능은 OC4J상에서도 스프링의 JtaTransactionManager로 잘 동작한다. 전용 OC4JJtaTransactionManager 아답터는 표준 JTA 이상의 부가가치를 제공할 뿐이다.

11.9 일반적인 문제에 대한 해결책
11.9.1 지정한 DataSource에 잘못된 트랜잭션 관리자의 사용
트 랜잭션 기술과 요구사항의 선택에 기반해서 올바른 PlatformTransactionManager 구현체를 사용해라. 적절히 사용하면 스프링 프레임워크는 단지 직관적이고 이식성있는 추상화를 제공할 뿐이다. 전역 트랜잭션을 사용한다면 모든 트랜잭션 작업에 org.springframework.transaction.jta.JtaTransactionManager 클래스(또는 이 클래스의 하위클래스이면서 어플리케이션에 특화된 클래스)를 반드시 사용해야 한다. 그렇지 않으면 트랜잭션 인프라스트럭처가 컨테이너 DataSource 인스턴스같은 리소스에 지역 트랜잭션을 수행하려고 한다. 이러한 지역 트랜잭션은 적합하지 않으며 좋은 어플리케이션 서버라면 에러로 취급한다.

11.10 관련 자료
스프링 프레임워크의 트랜잭션 지원에 대한 더 자세한 정보는 다음을 참고해라.

  • XA를 사용하든지 안하든간에 스프링에서 분산 트랜잭션은 스프링소스의 David Syer가 스프링 어플리케이션에서 분산 트랜잭션의 7가지 패턴을 설명하는 JavaWorld의 발표자료이다. 7가지 패턴 중 3가지는 XA를 사용하고 4가지는 사용하지 않는다.
  • 자바 트랜잭션 디자인 전략InfoQ의 책으로 자바에서 트랜잭션에 대한 좋은 소개를 제공한다. 그리고 이 책은 스프링 프레임워크와 EJB3 모두에서 트랜잭션을 설정하고 사용하는 방법에 대한 예제들도 포함되어 있다.