병렬 프로그래밍 - 구성 단위 : http://aroundck.tistory.com/867


스레드에 안전한 Map을 사용하려면 ConcurrentMap 을 사용하고, 스레드에 안전하면서 Iterator 처럼 읽기가 많은 경우 (채팅 Client 목록 등)에는 CopyOnWriteArrayList 를 사용하는게 효과적이다.



이 자료는 "에어콘" 사의 "자바 병렬 프로그래밍" 이라는 도서의 내용을 학습하면서 정리한 내용입니다. 예제로 제시된 소스코드 및 자세한 설명은 책을 참조하세요~

 

05. 구성 단위



5.1. 동기화된 컬랙션 클래스

- 동기화되어 있는 컬렉션 클래스의 대표 주자는 바로 Vector 와 HashTable이다. Collections.synchronizedXxx 메소드를 사용해 이와 비슷하게 동기화되어 있는 몇 가지 클래스가 제공된다. 이 클래스는 모두 public 으로 선언된 모든 메소드를 클래스 내부에 캡슐화해 내부의 값을 한 스레드만 사용할 수 있도록 제어하면서 스레드 안전성을 확보하고 있다.


5.1.1. 동기화된 컬렉션 클래스의 문제점.


- 동기화된 컬렉션 클레스는 스레드 안전성을 확보하고 있기는 하다. 하지만 여러 개의 연산을 묶어 하나의 단일 연산처럼 활용해야 할 필요성이 항상 발생한다. 두 개 이상의 연산을 묶어 사용해야 하는 예는 반복(iteration), 이동(navigation), 없는 경우에만 추가( add if absent ) 등이 있다. 동기화된 컬렉션을 사용하면 따로 락이나 동기화 기법을 사용하지 않는다 해도 이런 대부분의 기능이 모두 스레드 안전하다. 하지만 여러 스레드가 해당 컬렉션 하나를 놓고 동시에 그 내용을 변경하려 한다면 컬렉션 클래스가 상식적으로 올바른 방법으로 동작하지 않을 수도 있다.

- 동기화된 컬렉션 클래스는 대부분 클라이언트 측 락을 사용할 수 있도록 만들어져 있기 때문에, 컬렉션 클래스가 사용하는 락을 함께 사용한다면 새로 추가하는 기능을 컬렉션 클래스에 들어 있는 다른 메소드와 같은 수준으로 동기화시킬 수 있다. 동기화된 컬렉션 클래스는 컬렉션 클래스 자체를 락으로 사용해 내부의 전체 메소드를 동기화시키고 있다.


5.1.2. Iterator 와 ConcurrentModificationException


- Collection 클래스에 들어 있는 값을 차례로 반복시켜 읽어내는 가장 표준적인 방법은 바로 Iterator 를 사용하는 방법이다. ( 이런 방법은 Iterator를 직접 사용하건, 아니면 자바 5.0부터 사용할 수 있는 특별한 문법의 for each 문을 사용하건 동일하다.) Iterator를 사용해 컬렉션 클래스 내부의 값을 차례로 읽어다 사용한다 해도 반복문이 실행되는 동안 다른 스레드가 컬렉션 클래스 내부의 값을 추가하거나 제거하는 등의 변경 작업을 시도할 때 발생할 수 있는 문제를 막아주지는 못한다. 다시 말해 동기화된 컬렉션 클래스에서 만들어낸 Iterator를 사용한다 해도 다른 스레드가 같은 시점에 컬렉션 클래스 내부의 갓을 변경하는 작업을 처리하지는 못하게 만들어져 있고, 대신 즉시 멈춤( fail-fast ) 의 형태로 반응하도록 되어 있다. 즉시 멈춤이란 반복문을 실행하는 도중에 컬렉션 클래스 내부의 값을 변경하는 상황이 포착되면 그 즉시 ConcurrentModificationException 예외를 발생시키고 멈추는 처리 방법이다.

- 컬렉션 클래스는 내부에 값 변경 횟수를 카운트하는 변수를 마련해두고, 반복문이 실행되는 동안 변경 횟수 값이 바뀌면 hasNext 메소드나 next 메소드에서 ConcurrentModificationException을 발생시킨다. 더군다나 변경 횟수를 확인하는 부분이 적절하게 동기화되어 있지 않기 때문에 반복문에서 변경 횟수를 세는 과정에서 스테일 값을 사용하게 될 가능성도 있고, 따라서 변경 작업이 있었다는 것을 모를 수도 있다는 말이다. 이렇게 구현한 모습이 문제가 있기는 하지만 전체적인 성능을 떨어뜨릴 수 있기 때문에 변경 작업이 있었다는 상황을 확인하는 기능에 정확한 동기화 기법을 적용하지 않았다고 볼 수 있다.

- 단일 스레드 환경의 프로그램에서도 ConcurrentModificationException이 발생할 수 있다. 반복문 내부에서 Iterator.remove 등의 메소드를 사용하지 않고 해당하는 컬렉션의 값을 직접 제거하는 등의 작업을 하려 하면 예외 상황이 발생한다.

- for-each 반복문을 사용해 컬렉션 클래스의 값을 차례로 읽어들이는 코드는 컴파일할 때, 자동으로 Iterator를 사용하면서 hasNext 나 ext 메소드를 매번 호출하면서 반복하는 방법으로 변경한다. 따라서 반복문을 실행할 때 ConcurrentModificationException이 발생하지 않도록 미연에 방지하는 방법은 Vector에서 반복문을 사용할 때처럼 반복문 전체를 적절한 락으로 동기화를 시키는 방법밖에 없다.
 
- 반복문을 실행하는 코드 전체를 동시화시키는 방법이 그다지 훌륭한 방법이 아니라고 주장하는 이유는, 컬렉셔넹 엄청나게 많은 수의 값이 들어 있거나 값마다 반복하면서 실행해야 하는 작업이 시간이 많이 소모되는 작업일 수 있는데, 이런 경우에는 컬렉션 클래스 내부의 값을 사용하고자 하는 스레드가 상당히 오랜 시간을 대기 상태에서 기다려야 할 수 있다는 말이다. 또한 반복문에서 락을 잡고 있는 상황에서 또 다른 락을 확보해야 한다면, 데드락(deadlock)이 발생할 가능성도 높아진다. 

- 소모상태(starvation)이나 데드락의 위험이 있는 상태에서 컬렉션 클래스를 오랜 시간 동안 락으로 막아두고 있는 상태라면 전체 애플리케이션의 확장성을 해칠 수도 있다. 반복문에서 락을 오래 잡고 있으면 있을수록, 락을 확보하고자 하는 스레드가 대기 상태에 많이 쌓일 수 있고, 대기 상태에 스레드가 적체되면 될수록 CPU 사용량이 급격하게 증가할 가능성이 높다.

-  반복문을 실행하는 동안 컬렉션 클래스에 들어 있는 내용에 락을 걸어둔 것과 비슷한 효과를 내려면 clone 메소드로 복사본을 만들어 복사본을 대상으로 반복문을 사용할 수 있다. 이렇게 clone 메소드로 복사한 사본은 특정 스레드에 한정되어 있으므로 반복문이 실행되는 동안 다른 스레드에서 컬렉션 사본을 건드리기 어렵기 때문에 ConcurrentModificationException이 발생하지 않는다. ( 물론 최소한 clone 메소드를 실행하는 동안에는 컬렉션의 내용을 변경할 수 없도록 동기화시켜야 한다. ) 

- clone 메소드로 복사본을 만드는 작업에도 시간은 필요하기 마련이다. 따라서 반복문에서 사용할 목적으로 복사본을 만드는 방법도 컬렉션에 들어 있는 항목의 개수, 반복문에서 개별 항목마다 실행해야 할 작업이 얼마나 걸리는지, 컬렉션의 여러 가지 기능에 비해 반복 기능을 얼마나 빈번하게 사용하는지, 그리고 응답성과 실행 속도 등의 여러가지 요구 사항을 충분히 고려해서 적절하게 적용해야 한다.



5.1.3. 숨겨진 Iteraotr


- 컬렉션 클래스의 toString 메소드 소스코드를 들여다 보면 해당 컬렉션 클래스의 tierator 메소드를 호출해 내용으로 보관하고 있는 개별 클래스의 toString 메소드를 호출해 출력할 문자열을 만들어 내도록 되어 있다.

- 개발자는 상태 변수와 상태 변수의 동기화를 맞춰주는 락이 멀리 있을수록 동기화를 맞춰야 한다는 필요성을 잊기 쉽다.

클래스 내부에서 필요한 변수를 모두 캡슐화하면 그 상태를 보존하기가 훨씬 편리한 것처럼, 동기화 기법을 클래스 내부에 캡슐화하면 동기화 정책을 적용하기가 쉽다.


- toString 메소드뿐만 아니라 컬렉션 클래스의 hashCode 메소드나 equals 메소드도 내부적으로 iterator 를 사용한다. containsAll, removeAll, retainAll 등의 메소드, 컬렉션 클래스를 넘겨받는 생성 메소드 등도 모두 내부적으로 iterator 를 사용한다. 이렇게 내부적으로 iterator를 사용하는 모든 메소드에서 ConcurrentModificationException 이 발생할 가능성이 있다.
 




5.2. 병렬 컬렉션.

- 동기화된 컬렉션 클래스는 컬렉션의 내부 변수에 접근하는 통로를 일련화해서 스레드 안전성을 확보했다. 여러 스레드가 한꺼번에 동기화된 컬렉션을 사용하려고 하면 동시 사용성은 상당 부분 손해를 볼 수밖에 없다.

- 병렬 컬렉션은 여러 스레드에서 동시에 사용할 수 있도록 설계되어 있다. HashMap 을 대치하면서 병렬성을 확보한 ConcurrentHashMap 과 List 클래스의 하위 클래스이며 객체 목록을 반복시키며 열람하는 연산의 성능을 최우선으로 구현한  CopyOnWriteArrayList 도 병렬 컬렉션이다. ConcurrentMap 도 병렬 컬렉션인데, 인터페이스를 보면 추가하려는 항목이 기존에 없는 경우에만 새로 추가하는 put-if-absent, replace, conditional remove 연산 등이 정의되어 있다.

기존에 사용하던 동기화 컬렉션 클래스를 병렬 컬렉션으로 교체하는 것만으로도 별다른 위험 요소 없이 전체적인 성능을 상당히 끌어 올릴 수 있다.


- Queue 인터페이스는 작업할 내용을 순서대로 쌓아둘 수 있는 구조이고, ConcurrentLinkedQueue 는 FIFO 방식 Queue 이며, PriorityQueue 는 우선 순위에 따라 큐에 쌓여 있는 항목이 추출되는 순서가 바뀌는 특성을 가지고 있다. Queue 인터페이스에 정의되어 있는 연산은 동기화를 맞추느라 대기 상태에서 기다리는 부분이 없다.
 
- Queue 를 상속받은 BlockingQueue 클래스는 큐에 항목을 추가하거나 뽑아낼 때 상황에 따라 대기할 수 있도록 구현되어 있다. 예를 들어 큐가 비어 있다면 큐에서 항목을 봅아내는 연산은 새로운 항목이 추가될 때까지 대기한다. 반대로 큐에 크기가 지정되어 있는 경우에 큐가 지정한 크기만큼 가득 차 있다면, 큐에 새로운 항목을 추가하는 연산은 큐에 빈 자리가 생길 때까지 대기한다. BlockingQueue 클래스는 프로듀서-컨슈머( producer-consumer ) 패턴을 구현할 때 굉장히 편리하게 사용할 수 잇다.

- ConcurrentSkipListMap  ConcurrentSkipListSet  각각 SortedMap 과 SortedSet 클래스의 병렬성을 높이도록 발전된 형태이다. ( SortedMap 과 SortedSet 은 treeMap 과 treeSet을 synchronizedMap 으로 처리해 동기화시킨 컬렉션과 같다. ) 



5.2.1. ConcurrentHashMap

 
- ConcurrentHashMap 은 HashMap 과 같이 해시를 기반으로 하는 Map이다. 하지만 내부적으로는 이전에 사용하던 것과 전혀 다른 동기화 기법을 채택해 병렬성과 확장성이 훨씬 나아졌다. 이전에는 모든 연산에서 하나의 락을 사용했기 때문에 특정 시점에 하나의 스레드만이 해당 컬렉션을 사용할 수 있었다. 하지만 ConcurrentHashMap 은 락스트라이핑( Lock striping ) 이라 부르는 굉장히 세밀한 동기화 방법을 사용해 여러 스레드에서 공유하는 상태에 훨씬 잘 대응할 수 있다. 

- 값을 읽어가는 연산은 많은 수의 스레드라도 얼마든지 동시에 처리할 수 있고, 읽기 연산과 쓰기 연산도 동시에 처리할 수 있으며, 쓰기 연산은 제한된 개수만큼 동시에 수행할 수 있다. 속도를 보자면 여러 스레드가 동시에 동작하는 환경에서 일반적으로 훨씬 높은 성능 결과를 볼 수 있으며, 이와 함꼐 단일 스레드 환경에서도 성능상의 단점을 찾아볼 수 없다. 

- 다른 병렬 컬렉션 클래스와 비슷하게 ConcurrentHashMap 클래스도 Iterator를 만들어 내는 부분에서 많이 발전했는데, ConcurrentHashMap 이 만들어 낸 iterator 는 ConcurrrentModificationException 을 발생시키지 않는다. 따라서 ConcurrentHashMap의 항목을 대상으로 반복문을 실행하는 경우에는 따로 락을 걸어 동기화해야 할 필요가 없다.

- ConcurrrnetHashMap 에서 만들어 낸 iterator 는 즉시 멈춤(fail-fast) 대신 미약한 일관성 전략을 취한다. 미약한 일관성 전략은 반복문과 동시에 컬렉션의 내용을 변경한다 해도 Iterator 를 만들었던 시점의 상황대로 반복을 계속할 수 있다. 게다가 Iterator를 만든 시점 이후에 변경된 내용을 반영해 동작할 수도 있다.( 이 부분은 반드시 보장되지는 않는다. ) 

- 병렬성 문제때문에 Map의 모든 하위 클래스에서 공통적으로 사용하는 size 메소드나 isEmpty 메소드의 의미가 약간 약해졌다. 예를 들어 size 메소드는 그 결과를 리턴하는 시점에 이미 실제 객체의 수가 바뀌었을 수 있기 때문에 정확히 말하자면 size 메소드의 결과는 정확한 값일 수 없고, 단지 추정 값일 뿐이다.

- 동기화된 Map 에서는 지원하지만 ConcurrentHashMap에서는 지원하지 않는 기능이 있는데, 바로 맵을 독점적으로 사용할 수 있도록 막아버리는 기능이다. HashTable 과 SynchronizedMap 메소드를 사용하면 Map 에 대한 락을 잡아 다른 스레드에서 사용하지 못하도록 막을 수 있다. 

- ConcurrentHashMap 을 사용하면 HashTable 이나 SynchronizedMap 메소드를 사용하는 것에 비해 단점이 있기는 하지만, 훨씬 많은 장점을 얻을 수 있기 때문에 대부분의 경우에는 HashTable 이나 SynchronizedMap 을 사용하던 부분에 ConcurrentHashMap 을 대신 사용하기만 해도 별 문제 없이 많은 장점을 얻을 수 있다. 만약 작업 중인 애플리케이션에서 특정 Map 을 완전히 독점해서 사용하는 경우가 있다면, 그 부분에 ConcurrentHashMap 을 적용할 때는 충분히 신경을 기울여야 한다. 



5.2.2. Map 기반의 또 다른 단일 연산


- ConcurrentHashMap 클래스에는 일반적으로 많이 사용하는 put-if-absent, remove-if-equals, replace-if-equal 과 같이 자주 필요한 몇 가지의 연산이 이미 구현되어 있다.



5.2.3. CopyOnWriteArrayList


- CopyOnWriteArrayList 클래스는 동기화된 List 클래스보다 병렬성을 훨씬 높이고자 만들어졌다. 병렬성이 향상됐고, 특히 List에 들어있는 값을 Iterator로 불러다 사용하려 할 때 List 전체에 락을 걸거나 List 를 복제할 필요가 없다. ( CopyOnWriteArrayList 와 비슷하게 Set인터페이스를 구현하는 CopyOnWriteArraySet 도 있다. )

- '변경할 때마다 복사'하는 컬렉션 클래스는 불변 객체를 외부에 공개하면 여러 스레드가 동시에 사용하려는 환경에서도 별다른 동기화 작업이 필요 없다는 개념을 바탕으로 스레드 안전성을 확보하고 있다. 하지만 컬렉션이라면 항상 내용이 바귀어야 하기 때문에, 컬렉션의 내용이 변경될 때마다 복사본을 새로 만들어 내는 전략을 취한다. 만약 CopyOnWriteArrayList 컬렉션에서 iterator 를 뽑아내 사용한다면 Iterator  를 뽑아내는 시점의 컬렉션 데이터를 기준으로 반복하며, 반복하는 동안 컬렉션에 추가되거나 삭제되는 내용은 반복문과 상관 없는 복사본을 대상으로 반영하기 때문에 동시 사용성에 문제가 없다. 

- 반복문에서 락을 걸어야 할 필요가 있기는 하지만, 반복할 대상 전체를 한번에 거는 대신 개별 항목마다 가시성을 확보하려는 목적으로 잠깐씩 락을 거는 정도면 충분하다.

- 변경할 때마다 복사하는 컬렉션에서 뽑아낸 Iterator를 사용할 때는 ConcurrentModificationException이 발생하지 않으며, 컬렉션에 어떤 변경 작업을 가한다 해도 Iteraotr를 뽑아내던 그 시점에 컬렉션에 들어 있던 데이터를 정확하게 활용할 수 있다.

- 물론 컬렉션의 데이터가 변경될 때마다 복사본을 만들어내기 때문에 성능의 측면에서 손해를 볼 수 있고, 특히나 컬렉션에 많은 양의 자료가 들어 있다면 손실이 클 수 있다. 따라서 변경할 때마다 복사하는 컬렉션은 변경 작업보다 반복문으로 읽어내는 일이 훨씬 빈번한 경우에 효과적이다. 




5.3.. 블로킹 큐와 프로듀서-컨슈머 패턴

- 블로킹 큐(blocking queue)는 put과 take라는 핵심 메소드를 갖고 있고, 더불어 offer 와 poll 이라는 메소드도 갖고 있다. 만약 큐가 가득 차 있다면 put 메소드는 값을 추가할 공간이 생길 때까지 대기한다. 반대로 큐가 비어 있는 상태라면 take 메소드는 뽑아낼 값이 들어올 때까지 대기한다. 큐는 그 크기를 제한할 수도 있고, 제한하지 않을 수도 있다.

- 블로킹 큐는 프로듀서-컨슈머(producer-consumer)패턴을 구현할 때 사용하기에 좋다. 프로듀서-컨슈머 패턴은 '해야 할 일' 목록을 가운데에 두고 작업을 만들어 내는 주체와 작업을 처리하는 주체를 분리시키는 설계 방법이다. 프로듀서-컨슈머 패턴을 사용하면 작업을 만들어 내는 부분과 작업을 처리하는 부분을 완전히 분리할 수 있기 때문에 개발 과정을 좀 더 명확하게 단순화시킬 수 있다.

-  프로듀서-컨슈머 패턴을 적용해 프로그램을 구현할 때 블로킹 큐를 사용하는 경우가 많은데, 예를 들어 프로듀서는 작업을 새로 만들어 큐에 쌓아두고, 컨슈머는 큐에 쌓여 있는 작업을 가져다 처리하는 구조다. 프로듀서는 어떤 컨슈머가 몇 개나 동작하고 있는지에 대해 전혀 신경 쓰지 않을 수 있다. 단지 새로운 작업 내용을 만들어 큐에 쌓아두기만 하면 된다. 반대로 컨슈머 역시 프로듀서에 대해서 뭔가를 알고 있어야 할 필요가 없다. 프로듀서가 몇 개이건, 얼마나 많은 작업을 만들어 내고 있건 상관이 없다. 단지 큐에 쌓여 있는 작업을 가져다 처리하기만 하면 된다. 블로킹 큐를 사용하면 여러 개의 프로듀서와 여러 개의 컨슈머가 작동하는 프로듀서-컨슈머 패턴을 손쉽게 구현할 수 있다. 큐와 함께 스레드 풀을 사용하는 경우가 바로 프로듀서-컨슈머 패턴을 활용하는 가장 흔한 경우이다.

- 프로듀서가 컨슈머가 감당할 수 잇는 것보다 많은 양의 작업을 만들어 내면 해당 애플리케이션의 큐에는 계속해서 작업이 누적되어 결국에는 메모리 오류가 발생하게 된다. 하지만 큐의 크기에 제한을 두면 큐에 빈 공간이 생길 때까지 put 메소드가 대기하기 때문에 프로듀서 코드를 작성하기가 훨씬 간편해진다. 그러면 컨슈머가 작업을 처리하는 속도에 프로듀서가 맞춰야 하며, 컨슈머가 처리하는 양보다 많은 작업을 만들어 낼 수 없다.

-  블로킹 큐에는 offer 메소드가 있는데, offer 메소드는 큐에 값을 넣을 수 없을 때 대기하지 않고 바로 공간이 모자라 추가할 수 없다는 오류를 알려준다. offer 메소드를 잘 활용하면 프로듀서가 작업을 많이 만들어 과부하에 이르는 상태를 좀 더 효과적으로 처리할 수 있다.

블로킹 큐는 애플리케이션이 안정적으로 동작하도록 만들고자 할 때 요긴하게 사용할 수 있는 도구이다. 블로킹 큐를 사용하면 처리할 수 있는 양보다 훨씬 많은 작업이 생겨 부하가 걸리는 상황에서 작업량을 조절해 애플리케이션이 안정적으로 동작하도록 유도할 수 있다.


- 생각하기에는 컨슈머가 항상 밀리지 않고 작업을 마쳐준다고 가정하고, 따라서 작업 큐에 제한을 둘 필요가 없을 것이라고 마음 편하게 넘어갈 수도 있다. 이런 가정을 하는 순간 나중에 프로그램 구조를 뒤집어 엎어야 하는 원인을 하나 남겨두는 것뿐이니 주의하자. 블로킹 큐를 사용해 설계 과정에서부터 프로그램에 자원 관리 기능을 추가하자.

- LinkedBlockingQueue  ArrayBlockingQueue  FIFO 형태의 큐인데, LinkedList 나 ArrayList 에서 동기화된 List 인스턴스를 뽑아 사용하는 것보다 성능이 좋다. PriorityBlockingQueue 클래스는 우선 순위를 기준으로 동작하는 큐이고, FIFO 가 아닌 다른 순서로 큐의 항목을 처리해야 하는 경우에 손쉽게 사용할 수 있다.

- SynchronousQueue 클래스도 BlockingQueue 인터페이스를 구현하는데, 큐에 항목이 쌓이지 않으며, 따라서 큐 내부에 값을 저장할 수 있도록 공간을 할당하지도 않는다. 대신 큐에 값을 추가하려는 스레드나 값을 읽어가려는 스레드의 큐를 관리한다.

- 프로듀서와 컨슈머가 직접 데이터를 주고받을 때까지 대기하기 때문에 프로듀서에서 컨슈머로 데이터가 넘어가는 순간은 굉장히 짧아진다는 특징이 있다. 컨슈머에게 데이터를 직접 넘겨주기 때문에 넘겨준 데이터와 관련되어 컨슈머가 갖고 있는 정보를 프로듀서가 쉽게 넘겨 받을 수도 있다.

- SynchronousQueue는 데이터를 넘겨 받을 수 있는 충분한 개수의 컨슈머가 대기하고 있는 경우에 사용하기 좋다.



5.3.1. 예제 : 데스크탑 검색




5.3.2. 직렬 스레드 한정


- 프로듀서-컨슈머 패턴과 블로킹 큐는 가변 객체(mutable object)를 사용할 때 객체의 소유권을 프로듀서에서 컨슈머로 넘기는 과정에서 직렬 스레드 한정(serial thread confinement)기법을 사용한다. 스레드에 한정된 객체는 특정 스레드 하나만이 소유권을 가질 수 있는데, 객체를 안전한 방법으로 공개하면 객체에 대한 소유권을 이전(transfer)할 수 있다. 이렇게 소유권을 이전하고 나면 이전받은 컨슈머 스레드가 객체에 대한 유일한 소유권을 가지며, 프로듀서 스레드는 이전된 객체에 대한 소유권을 완전히 잃는다. 이렇게 안전한 공개 방법을 사용하면 새로운 소유자로 지정된 스레드는 객체의 상태를 완벽하게 볼 수 있지만 원래 소유권을 갖고 있던 스레드는 전혀 상태를 알 수 없게 되어, 새로운 스레드 내부에 객체가 완전히 한정된다.

- 객체 풀(object pool)은 직렬 스레드 한정 기법을 잘 활용하는 예인데, 풀에서 소유하고 있던 객체를 외부 스레드에게 '빌려주는' 일이 본업이기 때문이다. 풀 내부에 소유하고 있던 객체를 외부에 공개할 떄 적절한 동기화 작업이 되어 있고, 그와 함게 풀에서 객체를 빌려다 사용하는 스레드 역시 빌려온 객체를 외부에 공개하거나 풀에 반납한 이후에 계속해서 사용하는 등의 일을 하지 않는다면 풀 스레드와 사용자 스레드 간에 소유권이 원활하게 이전되는 모습을 볼 수 있다.
 
- 가변 객체의 소유권을 이전해야 할 필요가 있다면, 위에서 설명한 것과 다른 객체 공개 방법을 사용할 수도 있다. 하지만 항상 소유권을 이전받는 스레드는 단 하나여야 한다는 점을 주의해야 한다.



5.3.3. 덱, 작업 가로채기


- Deque(덱) 과 BlockingDeque 은 각각 Queue 와 Blockingqueue 를 상속받은 인터페이스이다. Deque는 앞과 뒤 어느 쪽에도 객체를 쉽게 삽입하거나 제거할 수 있도록 준비된 큐이며, Deque을 상속받은 실제 클래스로는 ArrayDeque LinkedBlockingDeque 이 있다.

- 작업 가로채기(work stealing) 이라는 패턴을 적용할 때에는 덱을 그대로 가져다 사용할 수 있다. 작업 가로채기 패턴에서는 모든 컨슈머가 각자의 덱을 갖는다. 만약 특정 컨슈머가 자신의 덱에 들어 있던 작업을 모두 처리하고 나면 다른 컨슈머의 덱에 쌓여있는 작업 가운데 맨 뒤에 추가된 작업을 가로채 가져올 수 있다. 작업 가로채기 패턴은 그 특성상 컨슈머가 하나의 큐를 바라보면서 서로 작업을 가져가려고 경쟁하지 않기 때문에 일반적인 프로듀서-컨슈머 패턴보다 규모가 큰 시스템을 구현하기에 적당하다. 더군다나 컨슈머가 다른 컨슈머의 큐에서 작업을 가져오려 하는 경우에도 앞이 아닌 맨 뒤의 작업을 가져오기 때문에 맨 앞의 작업을 가져가려는 원래 소유자와 경쟁이 일어나지 않는다.

- 작업 가로채기 패턴은 또한 컨슈머가 프로듀서의 역할도 갖고 있는 경우에 적용하기에 좋은데, 스레드가 작업을 진행하는 도중에 새로 처리해야 할 작업이 생기면 자신의 덱에 새로운 작업을 추가한다. ( 작업을 서로 공유하도록 구성하는 경우에는 다른 작업 스레드의 덱에 추가하기도 한다.) 만약 자신의 덱이 비었다면 다른 작업 스레드의 덱을 살펴보고 밀린 작업이 있다면 가져다 처리해 자신의 덱이 비었다고 쉬는 스레드가 없도록 관리한다. 




5.4. 블로킹 메소드, 인터럽터블 메소드

- 스레드는 여러 가지 원인에 의해 블록 당하거나, 멈춰질 수 있다. 예를 들어 I/O 작업이 끝나기를 기다리는 경우도 있고, 락을 확보하기 위해 기다리는 경우도 있고, Thread.sleep 메소드가 끝나기를 기다리는 경우도 있고, 다른 스레드가 작업 중인 내용의 결과를 확인하기 위해 기다리는 경우도 있다.

- 스레드가 블록되면 동작이 멈춰진 다음 블록된 상태(BLOCKED, WAITING, TIMED_WAITING) 가운데 하나를 갖게 된다. 블로킹 연산은 단순히 실행 시간이 오래 걸리는 일반 연산과는 달리 멈춘 상테에서 특정한 신호를 받아야 계속해서 실행할 수 있는 연산을 말한다.

- 기다리던 외부 신호가 확인되면 스레드의 상태가 다시 RUNNABLE 상태로 넘어가고 다시 시스템 스케줄러를 통해 CPU 를 사용할 수 있게 된다.

- Thread 클래스는 해당 스레드를 중단시킬 수 있도록 interrupt 메소드를 제공하며, 해당 스레드에 인터럽트가 걸려 중단된 상태인지를 확인할 수 있는 메소드도 있다. 모든 스레드에는 인터럽트가 걸린 상태인지를 알려주는 불린 값이 있으며, 외부에서 인터럽트를 걸면 불린 변수에 true 가 설정된다.

-  스레드 A가 스레드 B에 인터럽트를 건다는 것은 스레드 B에게 실행을 멈추라고 '요청'하는 것일 뿐이며, 인터럽트가 걸린 스레드 B는 정상적인 종료 시점 이전에 적절한 때를 잡아 실행 중인 작업을 멈추면 된다.

- 프로그램이 호출하는 메소드 가운데 InterruptedException 이 발생할 수 있는 메소드가 있다면 그 메소드를 호출하는 메소드 역시 블로킹 메소드이다. 따라서  InterruptedException이 발생했을 때 그에 대처할 수 있는 방법을 마련해둬야 한다. 라이브러리 형태의 코드라면 일반적으로 두 가지 방법을 사용할 수 있다.

 1. InterruptedException 을 전달 : 받아낸 InterruptedException 을 그대로 호출한 메소드에게 넘긴다.
 2. 인터럽트를 무시하고 복구 : InterruptedException 을 throw 할 수 없을 수 있는데, 이 경우는 예외를 catch 한 다음, 현재 스레드의 interrupt 메소드를 호출해 인터럽트 상태를 설정해 상위 호출 메소드가 인터럽트 상황이 발생했음을 알 수 있도록 해야 한다.

- InterruptedException을 처리함에 있어서 하지 말아야 할 일이 한 가지 있다. 바로 InterruptedException 을 cath 하고는 무시하고 아무 대응도 하지 않는 일이다. 이렇게 아무런 대응을 하지 않으면 인터럽트가 발생했었다는 증거를 인멸하는 것이며, 호출 스택의 상위 메소드가 인터럽트에 대응해 조치를 취할 수 있는 기회를 주지 않는다.

- 발생한 InterruptedException 을 먹어버리고 더 이상 전파하지 않을 수 있는 경우는 Thread 클래스를 직접 상속하는 경우뿐이며, 이럴 때는 인터럽트에 필요한 대응 조치를 모두 취했다고 간주한다.
 





5.5. 동기화 클래스.

- 상태 정보를 사용해 스레드 간의 작업 흐름을 조절할 수 있도록 만들어진 모든 클래스륻 동기화 클래스( synchronizer ) 라고 한다. 동기화 클래스의 예로는 세마포어(semaphore), 배리어(barrier), 래치(latch) 등이 있다.

- 모든 동기화 클래스는 구조적인 특징을 갖고 있다. 모두 동기화 클래스에 접근하려는 스레드가 어느 경우에 통과하고 어느 경우에는 대기하도록 멈추게 해야 하는지를 결정하는 상태 정보를 갖고 있고, 그 상태를 변경할 수 있는 메소드를 제공하고, 동기화 클래스가 특정 상태에 진입할 때가지 효과적으로 대기할 수 있는 메소드도 제공한다.


5.5.1. 래치


- 래치는 스스로가 터미널(terminal)상태에 이를 때까지의 스레드가 동작하는 과정을 늦출 수 있도록 해주는 동기화 클래스이다. 일종의 관문과 같은 형태로, 래치가 터미널 상태에 이르기 전에는 관문이 닫혀 있고, 어떤 스레드도 통과할 수 없다. 그리고 래치가 터미널 상태에 다다르면 관문이 열리고 모든 스레드가 통과한다. 래치가 한 번 터미널 상태에 다다르면 그 상태를 다시 이전으로 되돌릴 수는 없으며, 따라서 한 번 열린 관문은 계속해서 열린 상태로 유지된다.

- 특정한 단일 동작이 완료되기 이전에는 어떤 기능도 동작하지 않도록 막아야 하는 경우에 요긴하게 사용할 수 있다.
 * 특정 자원을 확보하기 전에는 작업을 시작하지 말아야 하는 경우.
 * 의존성을 갖고 있는 다른 서비스가 시작하기 전에는 특정 서비스가 실행되지 않도록 막아야 하는 경우.
 * 특정 작업에 필요한 모든 객체가 실행할 준비를 갖출 때까지 기다리는 경우.

- CountDownLatch는 하나 또는 둘 이상의 스레드가 여러 개의 이벤트가 일어날 때까지 대기할 수 있도록 되어 있다. 래치의 상태는 양의 정수 값으로 카운터를 초기화하며, 이 값은 대기하는 동안 발생해야 하는 이벤트의 건수를 의미한다.

- CountDownLatch 의 countDown 메소드는 대기하던 이벤트가 발생했을 때 내부에 갖고 있는 이벤트 카운터를 하나 낮춰주고, await 메소드는 래치 내부의 카운터가 0 이 될 때까지 대기하던 이벤트가 모두 발생했을 때까지 대기하도록 하는 메소드이다. 외부 스레드가 awiat 메소드를 호출할 때 래치 내부의 카운터가 0보다 큰 값이었다면, await 메소드는 카운터가 0이 되거나, 대기하던 스레드에 인터럽트가 걸리거나, 대기 시간이 길어 타임아웃이 걸릴 때까지 대기한다.



5.5.2. FutureTask


- FutureTask 가 나타내는 연산 작업은 Callable 인터페이스를 구현하도록 되어 있는데, 시작 전 대기, 시작됨, 종료됨과 같은 세 가지 상태를 가질 수 있다. 종료된 상태는 정상적인 종료, 취소, 예외 상황발생과 같이 연산이 끝나는 모든 종류의 상태를 의미한다. FutureTask 가 한 번 종료됨 상태에 이르고 나면 더 이상 상태가 바뀌는 일은 없다.

- FutureTask 는 Executor 프레임웍에서 비동기적인 작업을 실행하고자 할 때 사용하며, 기타 시간이 많이 필요한 모든 작업이 있을 때 실제 결과가 필요한 시점 이전에 미리 작업을 실행시켜두는 용도로 사용한다. 



5.5.3. 세마포어 ( Semaphore )


- 카운팅 세마포어(counting semaphore)는 특정 자원이나 특정 연산을 동시에 사용하거나 호출할 수 있는 스레드의 수를 제한하고자 할 때 사용한다. 카운팅 세마포어의 이런 기능을 사용하면 자원 풀(pool)이나 컬렉션의 크기에 제한을 두고자 할 때 유용한다.

- Semaphore 클래스는 가상의 퍼밋(permit)을 만들어 내부 상태를 관리하며, Semaphore 를 생성할 때 생성 메소드에 최초로 생성할 퍼밋의 수를 넘겨준다. 외부 스레드는 퍼밋을 요청해 확보( 남은 퍼밋이 있는 경우 )하거나, 이전에 확보한 퍼밋을 반납할 수도 있다. 현재 사용할 수 잇는 남은 퍼밋이 없는 경우, acquire 메소드는 남는 퍼밋이 생기거나, 인터럽트가 걸리거나, 지정한 시간을 넘겨 타임아웃이 걸리기 전까지 대기한다. release 는 확보했던 퍼밋을 다시 세마포어에게 반납하는 기능을 한다.

- 세마포어는 데이터베이스 연결 풀과 같은 자원 풀에서 요긴하게 사용할 수 있다.



5.5.4. 배리어


- 배리어( barrier ) 는 특정 이벤트가 발생할 때까지 여러 개의 스레드를 대기 상태로 잡아둘 수 있다는 측면에서 래치와 비슷하다고 볼 수 있다. 래치와의 차이점은 모든 스레드가 배리어 위치에 동시에 이르러야 관문이 열리고 계속해서 실행할 수 있다는 점이 다르다. 래치는 '이벤트'를 기다리기 위한 동기화 클래스이고, 배리어는 '다른 스레드'를 기다리기 위한 동기화 클래스이다. 

- CyclicBarrier 클래스를 사용하면 여러 스레드가 특정한 배리어 포인트에서 반복적으로 서로 만나는 기능을 모델링할 수 있고, 커다란 문제 하나를 여러 개의 작은 부분 문제로 분리해 반복적으로 병렬 처리하는 알고리즘을 구현하고자 할 때 적용하기 좋다.

- 스레드는 각자 배리어 포인트에 다다르면 await 메소드를 호출하며, await 메소드는 모든 스레드가 배리어 포인트에 도달할 떄까지 대기한다. 모든 스레드가 배리어 포인트에 도달하면 배리어는 모든 스레드를 통과시키며, await 메소드에서 대기하고 있던 스레드는 대기 상태가 모두 풀려 실행되고, 배리어는 다시 초기상태로 돌아가 다음 배리어 포인트를 준비한다. 만약 await 를 호출하고 시간이 너무 오래 지나 타임아웃이 걸리거나 await 메소드에서 대기하던 스레드에 인터럽트가 걸리면 배리어는 깨진 것으로 간주하고, await 에서 대기하던 모든 스레드에 BrokenBarrierException 이 발생한다.

- 배리어가 성공적으로 통과하면 await 메소드는 각 스레드별로 배리어 포인트에 도착한 순서를 알려주며, 다음 배리어 포인트로 반복 작업을 하는 동안 뭔가 특별한 작업을 진행할 일종의 리더를 선출하는 데 이 값을 사용할 수 있다.

- 배리어와 약간 다른 형태로 Exchanger 클래스가 있는데 Exchanger 는 두 개의 스레드가 연결되는 배리어이며, 배리어 포인트에 도달하면 양쪽의 스레드가 서로 갖고 있던 값을 교환한다. Exchanger 클래스는 양쪽 스레드가 서로 대칭되는 작업을 수행할 때 유용하다. 

- Exchanger 객체를 통해 양쪽의 스레드가 각자의 값을 교환하는 과정에서 서로 넘겨지는 객체는 안전한 공개 방법으로 넘겨주기 때문에 동기화 문제를 걱정할 필요가 없다.


=====================================================================================================================================



출처 : http://deepblue28.tistory.com/entry/Java-SynchronizedCollections-vs-ConcurrentCollections
참고 : http://tutorials.jenkov.com/java-util-concurrent/concurrentmap.html
       http://whiteship.me/?p=9191

SynchronizedCollections(동기화된 컬렉션)과 ConcurrentCollections(병렬 컬렉션)

동기화된 컬렉션 클래스는 컬렉션의 내부 변수에 접근하는 통로를 일련화해서 스레드 안전성을 확보했다. 하지만 이렇게 만들다 보니 여러 스레드가 한꺼번에 동기화된 컬렉션을 사용하려고 하면 동시 사용성은 상당 부분 손해를 볼 수 밖에 없다. 하지만 병렬 컬렉션은 여러 스레드에서 동시에 사용할 수 있도록 설계되었다.

ConcurrentMap에는 put-if-absent, replace, condition-remove 등을 정의하고 있다.


기존에 사용하던 동기화 컬렉션 클래스를 병렬 컬렉션으로 교체하는 것만으로도 별다른 위험 요소 없이 전체적인 성능을 상당히 끌어 올릴 수 있다.

<에이콘 - 자바 병렬 프로그래밍(p.137) 발췌>


동기화되지 않은(unsynchronized) 컬렉션

- List: ArrayList, LinkedList

- Map: HashMap

- Set: HashSet

- SortedMap: TreeMap

- SortedSet: TreeSet

- Since JDK 1.2

- 문제점: Thread Safe하지 않다.


동기화된(synchronized) 컬렉션

- Vector, Hashtable, Collections.synchronizedXXX()로 생성된 컬렉션들

- Since JDK 1.2

- 문제점: Thread Safe하나, 두개 이상의 연산을 묶어서 처리해야 할 때 외부에서 동기화 처리를 해줘야 한다. (Iteration, put-if-absent, replace, condition-remove 등)


병렬(concurrent) 컬렉션

- List: CopyOnWriteArrayList

- Map: ConcurrentMap, ConcurrentHashMap

- Set: CopyOnWriteArraySet

- SortedMap: ConcurrentSkipListMap (Since Java 6)

- SortedSet: ConcurrentSkipListSet (Since Java 6)

- Queue 계열:ConcurrentLinkedQueue

- Since Java 5

- 특이사항: Concurrent(병렬/동시성)이란 단어에서 알 수 있듯이 Synchronized 컬렉션과 달리 여러 스레드가 동시에 컬렉션에 접근할 수 있다. ConcurrentHashMap의 경우, lock striping 이라 부르는 세밀한 동기화 기법을 사용하기 때문에 가능하다. 구현 소스를 보면 16개의 락 객체를 배열로 두고 전체 Hash 범위를 1/16로 나누어 락을 담당한다. 최대 16개의 스레드가 경쟁없이 동시에 맵 데이터를 사용할 수 있게 한다. (p.350)

반 대로 단점도 있는데, clear()와 같이 전체 데이터를 독점적으로 사용해야할 경우, 단일 락을 사용할 때보다 동기화 시키기도 어렵고 자원도 많이 소모하게 된다. 또한, size(), isEmpty()같은 연산이 최신값을 반환하지 못할 수도 있다. 하지만 내부 상태를 정확하게 알려주지 못한다는 단점이 그다지 문제되는 경우는 거의 없다.


※ Queue, BlockingQueue 인터페이스는 Java 5에서 추가되었다. (Deque, BlockingDeque는 6에서 추가되었다.)

※ Synchronized 컬렉션은 객체 자체에 락을 걸어 독점하게되고, Concurrent 컬렉션은 객체 자체 독점하기가 쉽지 않은 단점이 있지만, 장점이 훨씬 더 많다. Concurrent 컬렉션은 컬렉션 전체를 독점하기 위해서는 충분히 신경을 기울여야 한다.

※ Hash를 기반으로 하는 컬렉션은 hashCode()의 해시값이 넓고 고르게 분포되지 못하면 한쪽으로 쏠린 해시 테이블을 사용하게 되는데, 최악의 경우는 단순한 Linked List와 거의 동일한 상태가 될 수 있다.


* ConcurrentMap 사용법

ConcurrentMap concurrentMap = new ConcurrentHashMap(); concurrentMap.put("key", "value"); Object value = concurrentMap.get("key");

// key가 존재하지 않으면 map에 저장, return 은 null
// key가 존재하면 map에 저장, return 은 기존에 저장되어 있는 값
concurrentMap.put("key", "value");


// key가 존재하지 않으면 map에 저장, return 은 null
// key가 존재하면 map에 저장하지 않음, return 은 기존에 저장되어 있는 값
concurrentMap.putIfAbsent("key", "value");


* Synchronized Collections 사용법

public class CrunchifySynchronizedListFromArrayList {
    public static void main(String[] args) {
        // ********************** synchronizedList ************************
        ArrayList<String> crunchifyArrayList = new ArrayList<String>();

        // populate the crunchifyArrayList
        crunchifyArrayList.add("eBay");
        crunchifyArrayList.add("Paypal");
        crunchifyArrayList.add("Google");
        crunchifyArrayList.add("Yahoo"); 

        // Returns a synchronized (thread-safe) list backed by the specified
        // list. In order to guarantee serial access, it is critical that all
        // access to the backing list is accomplished through the returned list.
        List<String> synchronizedList = Collections.synchronizedList(crunchifyArrayList);
        System.out.println("synchronizedList conatins : " + synchronizedList); 

        // ********************** synchronizedMap ************************

        Map<String, String> crunchifyMap = new HashMap<String, String>();

        // populate the crunchifyMap
        crunchifyMap.put("1", "eBay");
        crunchifyMap.put("2", "Paypal");
        crunchifyMap.put("3", "Google");
        crunchifyMap.put("4", "Yahoo");
   

        // create a synchronized map
        Map<String, String> synchronizedMap = Collections.synchronizedMap(crunchifyMap);

        System.out.println("synchronizedMap contains : " + synchronizedMap);
    }
}

-------------------------------------------------------------------------------------

import java.util.Collection; 
import java.util.Collections;
import java.util.TreeSet;

public class SynchronizedCollectionEx { 
 //treeSet은 Thread-Safe 하지 않습니다.
 static Collection treeSet = new TreeSet(); 
 //synchronizedSet은 Thread-Safe 합니다. 이 객체를 공유 객체로 사용해야 합니다. 
 static Collection synchronizedSet = Collections.synchronizedCollection(treeSet);
 public static void main(String[] args){
     Thread[] t = new Thread[10000];
     for (int i = 0; i < 10000; i++){
         t[i] = new Thread(new WriterThread());
         t[i].start();
     }
 }
}

class WriterThread implements Runnable {
  public void run() {
    for (int i = 0; i < 100; i++){
      SynchronizedCollectionEx.synchronizedSet.add(new Integer((int)(Math.random() * 10)));
      SynchronizedCollectionEx.synchronizedSet.remove(new Integer((int)(Math.random() * 10)));
    }
  }
}


=====================================================================================================================================

Concurrent List 만들기

출처 : https://okihouse.tistory.com/entry/List-Concurrent-List-%EB%A7%8C%EB%93%A4%EA%B8%B0


보통의 경우 가장 잘 알려진 Concurrent Map 의 경우 ConcurrentHashMap이 있다.

동기화를 가능하게 해주므로 Thread Safe를 지원하게 되는데, 간혹가다 Thread Safe를 지원하는 List를 사용해야 될 경우가 있다.


Thread Safe를 가능하게 하는 여러가지 방법이 아래와 같이 존재한다. 


  • Collections.synchronizedList();
  • CopyOnWriteList
  • concurrent Queue or Deque implementations
  • custom List implementations


보통 가장 많이 쓰는게 아마 CopyOnWriteArrayList 일 것 같다.

하지만 CopyOnWriteArrayList 에는 결점이 하나 있는데 이름 그대로 List를 복사하여 사용한다는 점이다. 

즉, 복사본을 만들어 사용한다는 점인데, 이 경우 Write 시간에 영향을 미칠 수 있다.

결국 CopyOnWriteArrayList 는 읽기속도는 빠를 수 있으나, 쓰기 시간이 지연될 수 있으며 쓰기 속도가 중요한 코드에서는 권장하지 않는다.


몇가지 테스트와 검색을 통해서 알아낸 점은 List를 사용자의 환경에 맞게 구현하여 사용하는게 좋다고 판단하였다.


몇몇 테스트를 직접 하지는 않았고, 실제 테스트한 사람의 결과를 보니 놀라운 점을 발견하였다.



5000 번의 읽기와 5000번의 쓰기를 완료한 시간 (읽기 쓰기 비율은 1:1이며 10개의 Thread로 테스팅 함)

  • ArrayList - 16450 ns( not thread safe)
  • ConcurrentList - 20999 ns <- 사용자가 직접 구현한 Concurrent List
  • Vector -35696 ns
  • CopyOnWriteArrayList - 197032 ns


위 결과로도 알 수 있듯이 CopyOnWriteArrayList 는 효율면에서 떨어지는 게 나타난다.


생각보다 직접 구현하는 편이 효율적인 것 같아서 직접 구현을 해보기로 하였다.

위와같이 List<T> Interface를 상속받아 구현하면 된다. List 에는 꼭 구현해야 되는 Method 들이 있는데 일단 전부 @Override 받아야 한다.

  1. public class ConcurrentList<T> implements List<T>  

전부 구현할 필요는 없을 것 같아서 일부만 구현하였다.

  1. private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();  
  2. private final List<T> list;  
  3.   
  4. public ConcurrentList(List<T> list) {  
  5.     this.list = list;  
  6. }  
  7.   
  8. @Override  
  9. public boolean add(T t) {  
  10.     readWriteLock.writeLock().lock();  
  11.     boolean sucsses;  
  12.     try {  
  13.         sucsses = list.add(t);  
  14.     } finally {  
  15.         readWriteLock.writeLock().unlock();  
  16.     }  
  17.     return sucsses;  
  18. }  
  19.   
  20. @Override  
  21. public T get(int index) {  
  22.     readWriteLock.readLock().lock();  
  23.     try {  
  24.         return list.get(index);  
  25.     } finally {  
  26.         readWriteLock.readLock().unlock();  
  27.     }  
  28. }  
  29.   
  30. @Override  
  31. public int size() {  
  32.     readWriteLock.readLock().lock();  
  33.     try {  
  34.         return list.size();  
  35.     } finally {  
  36.         readWriteLock.readLock().unlock();  
  37.     }  
  38. }  

위와 같이 add, get size 만 구현하였고 나머지는 본인의 취향에 따라서 구현하면 된다. 

구동 방법은 생각외로 간단하다. write lock 으로 Thread Safe 환경을 만들어주고 해당 명령어를 실행시킨 뒤 unlock 한다.


보기에는 마치 transaction 방식처럼 느껴지면서 성능에 영향을 미칠 것 같지만 생각외로 효율이 좋다.


실제 List 코드에서는 아래와 같이 작성해서 사용하면 된다. 

단!! 구현하지 않은 코드를 사용하였다간.... 책임지지 못할 일이 발생한다... 

  1. private List<JedisPool> slaveJedisList = new ConcurrentList<JedisPool>(new ArrayList<JedisPool>());  

Redis 에서 사용할 Jedis Pool 에서 사용할 것이기 때문에 위와 같이 선언하였고 사용자의 Object Type 에 맞게 작성하면 된다.

저렇게 사용하면 문제 없이 사용이 가능하다. 


특히, List 의 기능중에 iterator에서는 아래와 같이 객체를 만들어 사용하라고 권장한다.

이유는 ConcurrentModificationException 를 회피하며, 실제 Origin 객체를 수정하지 않고 복사본으로 사용하기 위함이다. 

  1. public Iterator<T> iterator()  
  2.     {  
  3.         readWriteLock.readLock().lock();  
  4.         try  
  5.         {  
  6.             return new ArrayList<T>( list ).iterator();  
  7.         }  
  8.         finally  
  9.         {  
  10.             readWriteLock.readLock().unlock();  
  11.         }  
  12.     }  


실제 Project 에서 테스트 중인데 현재까지는 만족스러운 결과를 나타내었다.






출처 : http://blog.outsider.ne.kr/870
참고 : http://whiteship.me/?tag=%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98&paged=2

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

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

<?xml version="1.0" encoding="UTF-8"?>
  xsi:schemaLocation="
 
  <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>

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

<?xml version="1.0" encoding="UTF-8"?>
  xsi:schemaLocation="
 
  <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/> 설정

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
















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

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

// 트랜잭션을 적용하고자 하는 서비스 클래스
@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 설정을 추가해서 빈 인스턴스에 트랜잭션을 적용할 수 있다.

<!-- 'context.xml' 파일에서 -->
<?xml version="1.0" encoding="UTF-8"?>
     xsi:schemaLocation="
   
  <!-- 트랜잭션을 적용하고자 하는 서비스 객체다 -->
  <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-manager transactionManager 사용할 트랜잭션 관리자의 이름. 위의 예제처럼 트랜잭션 관리자의 이름이 transactionManager이 아닌 경우에만 필요하다.
mode proxy 기 본 모드인 "proxy"가 스프링의 AOP 프레임워크를 사용해서 프록시되는 어노테이션이 붙은 빈을 처리한다.(위에서 얘기한 다음의 프록시 의미는 프록시를 통한 메서드 호출에만 적용된다.) 다른 모드인 "aspectj"는 스프링의 AspectJ 트랜잭션 관점으로 영향받은 클래스를 대신 위빙해서 모든 종류의 메서드 호출에 적용하기 위해 대상객체의 바이트코드를 수정한다. AspectJ 위빙은 활성화된 로드타임 위빙(또는 컴파일타임 위빙)과 마찬가지로 클래스패스에 spring-aspects.jar를 필요로 한다.(로드타임 위빙을 설정하는 방법은 Section 8.8.4.5, “스프링 설정”를 참고해라.)
proxy-target-class false proxy 모드에만 적용된다. @Transactional 어노테이션이 붙은 클래스에 어떤 타입의 트랜잭션 프록시를 생성할 것인지 제어한다. proxy-target-class 속성을 true로 설정했다면 클래스기반의 프록시가 생성된다. proxy-target-class가 false이거나 이 속성을 생략하면 표준 JDK 인터페이스 기반 프록시가 생성된다. (다른 프록시 타입의 자세한 설명은 Section 8.6, “프록싱 메카니즘”를 참고해라.)
order Ordered.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 어노테이션이 클래스수준에 정의된 트랜잭션 설정보다 앞선다.


@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 프로퍼티

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




















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


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

 public class TransactionalService {

   
  @Transactional("order")
  public void setSomething(String name) { ... }
 
  @Transactional("account")
  public void doSomething() { ... }
}

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

<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에 같은 속성을 반복적으로 사용하고 있다면 스프링 메타어노테이션 지원을 이용해서 커스텀 단축 어노테이션을 정의할 수 있다. 예를 들면 다음과 같은 어노테이션을 정의해서

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(value="order", propagation=Propagation.REQUIRED_NEW, rollbackFor=Exception.class)
public @interface OrderTx {
}
 
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
public @interface AccountTx {
}


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

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, “어드바이스 순서”를 참고해라.


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 version="1.0" encoding="UTF-8"?>
     xsi:schemaLocation="
 
  <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의 선언적인 접근을 사용한다.

<?xml version="1.0" encoding="UTF-8"?>
     xsi:schemaLocation="
 
  <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, 스프링의 관점 지향 프로그래밍를 읽으면 도움이 될 것이다.


// 적절한 트랜잭션 관리자를 생성한다
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(..) 메서드에 전달한다.


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 클래스를 사용해라.

transactionTemplate.execute(new TransactionCallbackWithoutResult() {
 
  protected void doInTransactionWithoutResult(TransactionStatus status) {
    updateOperation1();
    updateOperation2();
  }
});

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

 transactionTemplate.execute(new TransactionCallbackWithoutResult() {

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


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

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를 필요한 만큼의 서비스에 주입할 수 있다.

<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를 사용해서 트랜잭션을 시작하고 롤백하고 커밋할 수 있다.

 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 모두에서 트랜잭션을 설정하고 사용하는 방법에 대한 예제들도 포함되어 있다.


출처 : http://opennaru.tistory.com/44


레드햇 JBoss EAP (Enterprise Application Platform) 6 제품 관련 비교 설명

 


JBoss의 의미는 무엇인가요?
JBoss 는 1999년에 Mark Fleury가  EJB컨테이너를 오픈소스로 개발하기 위해 EJBOSS(Enterprise Java Beans Open Source Software)라는 이름으로 시작한 프로젝트였지만 SUN과의 상표권 문제 때문에 앞의 E를 빼고 현재의 JBoss라는 이름이 되었습니다.


JBoss EAP6는 무엇인가요? 
JBoss EAP 6는 개방형 표준을 기반으로 개발된  오픈소스 미들웨어 플랫폼으로 Java EE 6 인증을 받은 웹 애플리케이션 서버 제품입니다. 엔터프라이즈 환경에서 미들웨어가 갖추어야 하는 대용량 트랜잭션(Transaction), 고가용성을 위한 클러스터링, 메시징, 분산 캐싱, 고성능 보장 등의 기술들을 제공합니다.

JBoss EAP 6 버전은 오픈소스 커뮤니티 프로젝트의 결과물을 이용하여 제품화 됩니다. 다음 URL에서 JBoss EAP 6가 커뮤니티의 어떤 버전을 사용하고 있는지 확인할 수 있다. 다음의 URL ( https://access.redhat.com/site/articles/112673 ) 에서 JBoss EAP 버전 별 구성 컴포넌트에 대한 상세 내용을 확인할 수 있습니다. 


JBoss AS 제품을 사용 중인데 레드햇이 직접 기술 지원해 주시나요?
! 아니오. 레드햇은 JBoss EAP 제품에 대해서 만 기술 지원이 가능합니다. 무료로 사용하고 계신 JBoss AS ( 최신의 Wildfly) 제품에 대해서는 기술 지원을 받으실 수 없습니다.  기술 지원 문제로 JBoss AS 제품에서 JBoss EAP 제품으로 전환을 고려 하신 다면 오픈나루 ( service@opennaru.com ) 나 레드햇 ( customerservice-kr@redhat.com ) 에 문의해 주세요.


JBoss EAP6와 JBoss AS7  제품의 차이점은 무엇인가요?
JBoss EAP6 은 유상 제품으로 JBoss AS7 이라는 오픈소스 커뮤니티 제품을 
Red Hat 에서 추가 테스트와 검증 등을 통하여 안정성, 성능 그리고 보안 수준을 높인 기업용 오픈소스 애플리케이션 서버입니다.
즉 JBoss EAP6 와 JBoss AS7은 소스 코드 수준에서 거의 동일한 제품으로 벤더에 의한 유료 기술 지원을 받을 것인지 아니면 무료로 사용자 스스로 제품 문제들을 해결할 것 인지가 가장 큰 차이점입니다.  ( ※ JBoss.org 에서 다운로드 할 수 있는 JBoss AS 나 Wildfly 등의 제품은 레드햇에서 기술 지원되지 않으니 주의해 주십시오. )

JBoss AS7 제품은 커뮤니티에서 주로 기능 개발을 목적으로 하는 제품으로 기능 개발 중에 발생된 버그는 수정되어 릴리즈에 포함될 수 있지만, 버그 수정이나 지원 환경에 대한 검증을 목적으로 한 유지 보수와 보안 업데이트는 없습니다. 즉 주요 기능을 포함한 메이저 버전 업그레이드는 있지만 보안이나 버그 패치 등의 유지보수 차원의 릴리즈는 없기 때문에 커뮤니티 버전에서 버그 수정이 필요하다면 직접 코드 수정, 포팅 그리고 빌드해야 합니다.

JBoss EAP6 제품은 상용 서비스 제품으로 기능 개발 중심의 커뮤니티 버전과는 달리 고객이 요청한 버그 수정 또는 보안 업데이트 등의 안정성과 품질 그리고 기술 지원 환경에 대한 호환성 테스트를 거쳐 출시하는 엔터프라이즈 제품입니다. 

 
JBoss 커뮤니티 제품
JBoss 엔터프라이즈 제품
사이트
AS7(jboss.org)/Wildfly8 (wildfly.org)
redhat.com/jboss.com
라이선스 비용
없음
없음
기술지원
사용자 스스로 기술 지원
벤더 기술 지원
소스 코드 접근
완전한 접근 가능
완전한 접근 가능
대상
누구나
레드햇 서브스크립션 고객
개발
오픈 소스 프로젝트 커뮤니티
오픈 소스 프로젝트 커뮤니티 및 JBoss EAP 제품화 팀
개선 및 향상
커뮤니티의 요구에 따른 임시 또는 실험적인 소프트웨어 변경
주요 릴리스, 소규모 릴리스, 패치 및 마이그레이션 도구를 포함한 구조화된 릴리스 과정
매뉴얼
프로젝트 컴포넌트에 따라 다양
전문적인 소프트웨어 설명서
품질
기능 검증을 위한 테스팅 위주
매우 다양한 테스트 등이 통합된 우수한 품질
  JBoss 커뮤니티 제품과 엔터프라이즈 제품의 차이점


JBoss 와 Wildfly 의 차이점은 무엇인가요?
! JBoss AS 라는 의미가 JBoss Application Server 의 약자이며 많은 경우 JBoss 로 표기하여 Red Hat 의 상용 오픈소스 제품인 JBoss EAP 와 혼돈을 야기 했습니다. 이러한 혼란을 해소하기 위하여 JBoss AS8 버전부터는 기존의 ‘JBoss’라는 이름을 ‘Wildfly’ 라는 이름으로 변경하기로 결정했으며, 현재는 jboss.org 가 아닌 wildfly.org 커뮤니티에서 ‘wildfly8’이라는 이름으로 오픈소스 프로젝트를 진행하고 있습니다.


JBoss EAP6 제품은 레드햇 리눅스만 지원하나요?
아 니오, JBoss EAP6 는 Pure Java 로 개발된 애플리케이션 서버 제품으로 대부분의 Java 런타임을 지원하고 있으며, 레드햇 리눅스 이외에도 마이크로소스트 윈도우즈 서버와 유닉스 서버를 지원합니다. JBoss EAP6 버전에서 지원 환경은 아래의 URL에서 확인해 주세요. 

 


References




lombok : http://www.projectlombok.org/

출처 : http://gitblog.ihoney.pe.kr/blog/2013/01/23/use-lombok-annotation-in-java-project/



Lombok을 사용해봅시다.

자바에서 DTO, VO, Domain Object 만들다보면, 멤버필드에 대한 Getter/Setter 메소드, Equals, hashCode, ToString과 멤버필드에 주입하는 생성자를 만드는 코드 등으로 불필요하게 코드가 길어지는 경우를 볼 수가 있다. 불필요하지만 생성해야 하는 코드들을 줄일 수 있는 방법이 있다면, 얼마나 좋을까?

Project Lombok 소개

적용사례

Lombok annotation을 적용하지 않은 현재 스터디에서 사용하는 User domain 코드

package net.slipp.domain.user;

public class User {
  private String userId;
  private String password;
  private String name;
  private String email;

  public User(String userId, String password, String name, String email) {
      this.userId = userId;
      this.password = password;
      this.name = name;
      this.email = email;
  }

  public String getUserId() {
      return userId;
  }

  public String getPassword() {
      return password;
  }

  public String getName() {
      return name;
  }

  public String getEmail() {
      return email;
  }
  
  public boolean matchPassword(String loginPassword) {
      if (loginPassword == null) {
          return false;
      }
      
      return loginPassword.equals(password);
  }
  
  public void update(User user) {
      this.userId = user.getUserId();
      this.password = user.getPassword();
      this.name = user.getName();
      this.email = user.getEmail();
  }
  
  @Override
  public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((email == null) ? 0 : email.hashCode());
      result = prime * result + ((name == null) ? 0 : name.hashCode());
      result = prime * result + ((password == null) ? 0 : password.hashCode());
      result = prime * result + ((userId == null) ? 0 : userId.hashCode());
      return result;
  }

  @Override
  public boolean equals(Object obj) {
      if (this == obj)
          return true;
      if (obj == null)
          return false;
      if (getClass() != obj.getClass())
          return false;
      User other = (User) obj;
      if (email == null) {
          if (other.email != null)
              return false;
      } else if (!email.equals(other.email))
          return false;
      if (name == null) {
          if (other.name != null)
              return false;
      } else if (!name.equals(other.name))
          return false;
      if (password == null) {
          if (other.password != null)
              return false;
      } else if (!password.equals(other.password))
          return false;
      if (userId == null) {
          if (other.userId != null)
              return false;
      } else if (!userId.equals(other.userId))
          return false;
      return true;
  }

  @Override
  public String toString() {
      return "User [userId=" + userId + ", password=" + password + ", name=" + name + ", email=" + email + "]";
  }
}


Lombok Annotation 적용코드

package net.slipp.domain.user;

import lombok.*;

@AllArgsConstructor
@EqualsAndHashCode
@ToString
public class User {
  
  @Getter
  private String userId;
  @Getter
  private String password;
  @Getter
  private String name;
  @Getter
  private String email;

  public boolean matchPassword(String loginPassword) {
      if (loginPassword == null) {
          return false;
      }
      
      return loginPassword.equals(password);
  }
  
  public void update(User user) {
      this.userId = user.getUserId();
      this.password = user.getPassword();
      this.name = user.getName();
      this.email = user.getEmail();
  }
} 

90라인이 넘는 코드를 30여줄로 1/3 줄일 수가 있다.

설치방법

1. lombok.jar 다운로드
    a.lombok.jar 직접 다운로드
        - http://projectlombok.org/download.html

    b. pom.xml dependency 추가   

<dependencies>
  ...

  <!-- Project lombok -->
  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>0.11.6</version>
  </dependency>
  ...
</dependencies> 


2. lombok  설치하기
    a. lombok.jar 직접 다운로드의 경우 :
        - 방법1. 다운로드 경로 이동 후 lombok.jar 더블클릭으로 실행
        - 방법2. cmd 창에서 다운로드 경로 이동 후 java -jar lombok.jar 실행

    b. Maven pom.xml에 추가한 경우 : 
        - 설치 경로 이동 : cd ~/.m2/repository/org/projectlombok/lombok/
        - 위(a) 방법1 또는 방법2로 실행

    c. IDE 설치 위치[Specify locaion…]를 검색해서 경로에 추가한다.
   
    d. eclipse.ini or sts.ini 파일 변경되고 동일한 경로에 lombok.jar 추가됨

    e. 별도로 프로젝트 내에 라이브러리를 추가해서 사용할 수도 있겠다.

소스코드에서 Lombok Annotation을 추가해보자

lombok features : http://www.projectlombok.org/features/index.html

  • @Data
  • @Getter/@Setter
    • 접근제어 : AccessLevel[PUBLIC, PROTECTED, PACKAGE, PRIVATE]을 통해서 접근레벨을 제한할 수 있다.
      • 예 : @Getter(AccessLevel.PACKAGE), @Setter(AccessLevel.PRIVATE)
    • getter/setter 관례에 따라서 get필드명, set필드명 메소드가 생성됨
    • Getter
    • Setter
  • @EqualsAndHashCode
    • 코드에서 객체의 비교 등의 용도로 사용되는 equals(), hashCode() 메소드의 코드를 절감할 수가 있다.
    • @EqualsAndHashCode(exclude={“field1”, “field2”}) 처럼 필요에 따라서 특정 필드를 제외할 수가 있다.
  • @ToString
    • 로그Log에서 객체의 내용을 확인하는 등의 용도로 쓰이는 toString() 메소드를 대신할 수 있다.
    • @ToString(exclude=”field1”) 처럼 필요에 따라서 특정 필드를 제외할 수 있다.
  • @Log
    • 최근에 알게된 기선님의 글() 녀석인데, Logger와 관련된 코드들을 줄일 수 있다.
    • 추가하면 자동으로 필드에 private static final Logger log 가 추가된다. 이후 로그를 찍으려는 곳에서는 log.error(), log.warn(), log.debug(), log.info() 형태로 사용하면 된다.


============================================================================================================

※ lombok이 이클립스에 설치가 안되는 경우

방법1.
1. lombok.jar를 eclipse 폴더에 복사한다.
2. eclipse.ini 맨 아래에 아래 붉은 코드를 넣는다.

-javaagent:C:\dev\eclipses\eclipse-jee-2018-09-win32-x86_64\lombok.jar 



방법. 이클립스 이전 버전
1. lombok.jar를 eclipse 폴더에 복사한다.
2. eclipse.ini 맨 아래에 아래 붉은 코드를 넣는다.

-vmargs
-Dfile.encoding=UTF-8
-Dosgi.requiredJavaVersion=1.6
-Xms2048m
-Xmx2048m
-javaagent:lombok.jar
-Xbootclasspath/a:lombok.jar



============================================================================================================

Gradle 로 build 시

build.gradle

// plugins 선언은 build.gradle 최상단에 해야 한다. (apply 보다도 먼저)
plugins {
    id 'net.ltgt.apt' version '0.19'
}

dependencies {
    // compileOnly 만 있으면 gradle build 시 문제가 없다.
    compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.2'

    apt group: 'org.projectlombok', name: 'lombok', version: '1.18.2'
}






============================================================================================================


Lombok 사용시 컴파일 경고 없애는 방법


Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type.


import lombok.EqualsAndHashCode;



@Data /* 아래 코드 추가 */
@EqualsAndHashCode(callSuper=false)

public class Program extends CommonModel {
    private String id;
}


좀더 자세한 사용법은 아래 링크에 코드를 참조

http://projectlombok.org/features/EqualsAndHashCode.html

간단히 이야기 하면 별도로 구현하는 Value Object 가 없다면 위에 callSuper=false 있다면 위 링크를 보고 참조해서 구현하면 됩니다.


eclipse에서 maven project를 생성하면 아래처럼 버전이 낮은 프로젝트가 생성된다.
    - JRE 버전 : 1.5
    - Dynamic Web Module (web.xml) 버전 : 2.3

프로젝트 Properties > Project Facets 에서 Dynamic Web Module를 3.0으로 변경하면
"Cannot change version of project facet Danymic Web Module to 3.0" 이란 메시지가 나오면서 변경이 안된다.

1. web.xml 변경 (Dynamic Web Module 2.3 -> 3.0)

Servlet 4.0
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
    <display-name>Archetype Created Web Application</display-name>
</web-app>

Servlet 3.1
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
    <display-name>Archetype Created Web Application</display-name>
</web-app>

Servlet 3.0
<?xml version="1.0" encoding="UTF-8"?> 
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
    <display-name>Servlet 3.0 Web Application</display-name>
</web-app>

Servlet 2.5
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
    <display-name>Archetype Created Web Application</display-name>
</web-app>

Servlet 2.4
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" id="servlet-2_4" version="2.4">
    <display-name>Servlet 2.4 Web Application</display-name>
</web-app>

Servlet 2.3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
    <display-name>Servlet 2.3 Web Application</display-name>
</web-app>

Servlet 2.2
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>
    <display-name>Servlet 2.2 Web Application</display-name>
</web-app>


2. pom.xml에 build 추가

<properties>
    <jdk.version>1.7</jdk.version>
</properties>

<build>
        <finalName>${project.name}</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>${jdk.version}</source>
                    <target>${jdk.version}</target>
                      <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
</build> 


3. 프로젝트 우클릭 > Maven > Update Project 클릭

4. 프로젝트 Facet 변경
    - 프로젝트 Properties > Project Facets 에서 Dynamic Web Module을 3.0으로, Java 1.7 변경
    위 방법으로 할 때
    "Cannot change version of project facet Danymic Web Module to 3.0"이란 메시지가 나오는 경우
    ==> Navigator에서 프로젝트/.settings/org.eclipse.wst.common.project.facet.core.xml 파일 직접 수정

<?xml version="1.0" encoding="UTF-8"?>
<faceted-project>
  <fixed facet="wst.jsdt.web"/>
  <installed facet="java" version="1.7"/>
  <installed facet="jst.web" version="3.0"/>
  <installed facet="wst.jsdt.web" version="1.0"/>
</faceted-project> 


5. Maven Dependencies를 Deployment에 추가
프로젝트 Properties > Deployment Assembly 에서 Add 클릭 - Java Build Path Entries 선택 - Maven Dependencies 선택

6. 프로젝트 우클릭 > Maven > Update Project 클릭


※ 참고 (Tomcat / Servlet / Java Specifications)
http://tomcat.apache.org/whichversion.html

Servlet Spec 

 JSP Spec

 EL Spec

 WebSocket Spec

 Apache Tomcat version

 Actual release revision

 Support JavaEE Versions

 Support JavaSE Versions

 Memo

 4.0

 TBD (2.4?)

 TBD (3.1?)

 TBD (1.2?)

 9.0.x

 None

  8 and later

 


 3.1

 2.3

 3.0

 1.1

 8.0.x

 8.0.18

  7 and later

 

Non-blocking I/O, HTTP protocol upgrade mechanism


 3.0

 2.2

 2.2

 1.1

 7.0.x

 7.0.57

 6 and later
(WebSocket 1.1 requires 7 or later)

 JavaSE 6

JSR 315, Ajax 대응 비동기 지원, 어노테이션에 의한 개발편의성, web.xml 없는 개발 및 배포

 2.5

 2.1

 2.1

 N/A

 6.0.x

 6.0.43

 5 and later

 J2SE 5.0

  JSR 154, Requires J2SE 5.0, supports annotations

 2.4

 2.0

 N/A

 N/A

 5.5.x (archived)

 5.5.36 (archived)

  1.4 and later

 J2SE 1.3

 JSR 154, web.xml uses XML Schema

 2.3

 1.2

 N/A

 N/A

 4.1.x (archived)

 4.1.40 (archived)

 1.3 and later

 J2SE 1.2

 Addition of Filters

 2.2

 1.1

 N/A

 N/A

 3.3.x (archived)

 3.3.2 (archived)

 1.1 and later

 J2SE 1.2

 Becomes part of J2EE, introduced independent web applications in .war files





Eclipse에서 javascript validation 끄기


※ 이클립스 Preferences - Validation에서 체크를 해제해도 javascript의 validation을 체크할 때 끄는 방법

1. 프로젝트 우클릭 - Properties > Clientside JavaScript > Include Path
1. 프로젝트 우클릭 - Properties - 왼쪽 메뉴 JavaScript - Include Path 클릭

2. Source 탭 - 프로젝트 확장 - Excluded 선택 - Edit 버튼 클릭

3. Exclusion patterns 에 패턴 등록
    *.js


출처 : http://ilikesura.tistory.com/entry/%EC%A4%91%EB%B3%B5%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%84%B8%EC%85%98-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0-%EC%84%A4%EC%A0%95%EB%B2%95



세션 관리

타임아웃 감지하기

스프링 시큐리티에서 유효하지 않은 세션 ID를 감지하고 적절한 URL로 리다이렉트 시키도록 설정할 수 있다. 이것은 session-management 엘리먼트를 사용한다.

 
<http> ... <session-management invalid-session-url="/sessionTimeout.htm" /> </http>

동시 세션 제어

사용자가 동시에 한번만 로그인할 수 있도록 제한하고 싶으면, 스프링 시큐리티는 다음과 같이 간단하게 추가할 수 있도록 지원한다. 세션 생명주기와 관련된 이벤트를 스프링 시큐리티가 받을 수 있도록 하기 위해, 우선 다음의 리스너를 web.xml 파일에 추가할 필요가 있다.


  <listener>
    <listener-class>
      org.springframework.security.web.session.HttpSessionEventPublisher
    </listener-class>
  </listener>

그리고 애플리케이션 컨텍스트에 다음의 코드를 추가한다.

  <http>
    ...
    <session-management>
        <concurrency-control max-sessions="1" />
    </session-management>
  </http>

이것은 한 사용자가 동시에 두번 로그인 하는것을 방지한다. (두번째 로그인으로 인해 첫번째 로그인은 무효화된다) 종종 두번째 로그인을 방지할 필요가 있는데, 그런 경우에는 다음과 같이 할 수 있다.

  <http>
    ...
    <session-management>
        <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
    </session-management>
  </http>

그러면 두번째 로그인은 거부될 것이다. "거부"될 경우는, 폼-기반 로그인이 사용되는 환경에서는 authentication-failure-url 로 보내진다. 만약 두번째 로그인이 "remember-me"와 같은 비-상호적 메커니즘에 의해 수행되었다면, "unauthorized(불허가)"(402) 에러가 발생할 것이다. 대신에 에러페이지를 사용하려면 session-management 엘리먼트의 session-authentication-error-url 속성을 추가할 수 있다.

만약 폼-기반 로그인에 직접 작성한 인증 필터를 사용한다면, 동시 세션 제어 기능을 명시적으로 설장할 수 있다. 더 자세한 사항은 세션관리 장에서 찾을 수 있다.

Session Fixation Attack 방지

악의적인 사용자가 사이트에 접근하기 위한 세션을 만들고, 그 세션을 통해 다른 사용자로 로그인 하려고 하는 경우(예를 들어, 세션에 ID를 파라미터로 포함하여 전송하는 경우) Session fixation attack의 잠재적인 위험이 존재하게 된다. 스프링 시큐리티는 이러한 공격을 자동으로 막기 위하여 사용자 로그인 때마다 새로운 세션을 생성한다. 이러한 방지 기능이 필요하지 않거나, 다른 기능들과 충돌이 발생할 경우에는, <session-management>의 session-fixation-protection 속성값으로 동작을 제어할 수 있다. 속성은 다음과 같은 세가지 옵션값들을 가진다.

  • migrateSession - 새로운 세션을 생성하고 기존의 세션 값들을 새 세션에 복사해준다. 기본값으로 설정되어 있다.
  • none - 아무것도 수행하지 않는다. 원래의 세션이 유지된다.
  • newSession - "깨끗한" 새로운 세션을 생성한다. 기존의 세션데이터는 복사하지 않는다.


출처 : http://gnujava.com:8000/board/article_view.jsp?article_no=1003&board_no=14&table_cd=EPAR05&table_no=05


2003서버에 계정을 하나 만드시고, 원격에서 접속하려는 컴퓨터에서 그 계정으로 로그온 하도록 설정해야 됩니다.

 방법은 시작 -> 프로그램 -> 관리도구 -> 로컬 보안 정책 을 실행합니다.

 

 그 후, 로컬 정책 -> 보안 옵션 -> 네트워크 액세스 : 로컬 계정에 대한 공유 및 보안 탭을 선택합니다. 여기에 값을 일반 -> 로컬 사용자를 그대로 인증 으로 변경한 후, 재부팅 합니다.

 

위와 같이 설정 후

  1. 로컬 및 사용자 그룹에서 Guest계정을 사용안함으로 설정
  2. 새 사용자를 선택한 후, 사용자 등록
  3. 폴더 생성
  4. 공유 설정(NTFS 파일 시스템일 경우)
    • 공유 탭에서 폴더 공유 지정 사용 권한에 EVERYONE 권한을 모두 줌.
    • 보안탭으로 가서 2번에서 만든 계정을 등록 후, 모든 권한을 줌
    • 다시 EVERYONE 을 선택한 후, 제거
  5. 확인을 누릅니다.

그 후, XP 홈에서 접속을 시도하면, 로그온창이 나타나게 됩니다. 그럼 좀전에 만든 계정으로 로그온을 하시면, 접속이 가능해 집니다.


서비스 항목에서 아래의 서비스들이 실행되어 있어야 합니다.

Server

Workstation

Computer Browser

위 서비스가 구동중인지 확인 바랍니다. 물론 2003서버에서 말이죠.. 이서비스들이 사용안함으로 되어 있으면, 네트워크 공유가 되지 않습니다.

그리고, 이벤트 로그를 보시고, 이벤트 ID 2011이 발생한다면, 아래 사이트를 방문하여 레지스트리를 수정합니다.

 http://support.microsoft.com/default.aspx?scid=kb;ko;177078



참고 : http://blog.naver.com/whdals0/220107223824


1. 시작 -> cmd

2. c:\> diskpart

3. DISKPART> list disk
    - 디스크 목록 확인

4. DISKPART> select disk 1
    - select disk 번호 : 번호는 list disk 목록에 나온 번호

5. attributes disk clear readonly
    - 읽기만 하는 속성 지움

6. exit


Report4FCR_Domain.erp


1. erwin에서 Tools -> Data Browser 선택

2. Data Browser에서 Reports -> Open Reports 클릭 후 첨부파일 (Report4FCR_Domain.erp) 선택
* 새로운 Report 만들 경우
    - File -> New Report
    - Name에 report 이름을 적고 Physical 선택 후 Category는 Column 선택
    - 왼쪽 트리에서 Column -> Table -> Name 선택, Column -> Name, Column -> Datatype 선택

3. 왼쪽 트리에서 Table Reports -> reportTableDef2 더블 클릭 또는 마우스 우클릭 후 excute.. 클릭

4. 하위 트리 메뉴가 생성되는데 마우스 우클릭 후 Export... 클릭하면 export 메뉴가 나옴
    - Export : csv로 선택
    - Presentation : Tabuilar - 중복되는 결과값은 제외
                           Tabular with duplicates - 중복과 상관없이 모두 출력

+ Recent posts