레이블이 JAVA인 게시물을 표시합니다. 모든 게시물 표시
레이블이 JAVA인 게시물을 표시합니다. 모든 게시물 표시

Java 8 In Action - Design Pattern

Java 8 In Action - Design Pattern

Lambda를 이용하여 기존 디자인패턴을 변경해보기

from Java 8 In Action

1. 전략패턴 


1.1 일반적인 전략패턴 개발방식 

public interface ValidationStrategy {
boolean execute(String s);
}

public class IsAllowerCase implements ValidationStrategy {
public boolean execute(String s) {
return s.match("[a-z]+");
}
}

public class IsNumeric implements ValidationStrategy {
public boolean execute(String s) {
return s.matches("\\d+");
}
}

public class Validator {
private final ValidationStrategy strategy;

public Validator(ValidationStrategy v) {
this.strategy = v;
}

public boolean validate(String s) {
return strategy.execute(s);
}
}

Validator numericValidator = new Validator(new IsNumeric());
boolean b1 = numericValidator.validate("aaa");

Validator lowerCaseValidator = new Validator(new IsAllLowerCase());
boolean b2 = lowerCaseValidator.validate("bbb");


1.2 람다를 이용한 개발 방식

Validator numericValidator = new Validator((String s) -> s.matches("[a-z]+"));
boolean b1 = numericValidator.validate("aaa");

Validator lowerCaseValidator = new Validator((String a) -> s.match("\\d+"));
boolean b2 = lowerCaseValidator.validate("bbb");

2. Template Method


2.1 일반적인 템플릿 메소드 방식
abstract class OnlineBanking {
public void processCustomer(int id) {
Customer c = Database.getCustomerWithId(id);
makeCustomerHappy(c);
}

abstract void makeCustomerHappy(Customer c);
}

2.2 람다를 이용한 개발 방식

public void processCustomer(int id, Consumer<Customer> makeCustomerHappy) {
Customer c = Database.getCustomerWithId(id);
makeCustomerHappy.accept(c);
}

new OnlineBankingLambda().processCustomer(1234, (Customer c) -> System.out.println("Hello " + c.getName()));

3. Observer


3.1 일반적인 옵저버 패턴

interface Observer {
void notify(String tweet);
}

class NYTimes implements Observer {
public void notify(String tweet) {
if (tweet != null && tweet.contains("money")) {
System.out.println("Breaking news in NY! " + tweet);
}
}
}

class Guardian implements Observer {
public void notify(String tweet) {
if (tweet != null && tweet.contain("queen")) {
System.out.println("Yet another news in London... " + tweet);
}
}
}

class LeMonde implements Observer {
public void notify(String tweet) {
if (tweet != null && tweet.contains("wine")) {
System.out.println("Today cheese, wine and news! " + tweet);
}
}
}

interface Subject {
void registerObserver(Observer o);
void notifyObservers(String tweet);
}

class Feed implements Subject {
private final List<Observer> observers = new ArrayList<>();

public void registerObserver(Observer o) {
this.observers.add(o);
}

public void notifyObservers(String tweet) {
observers.forEach(o -> o.notify(tweet));
}
}

Feed f = new Feed();
f.registerObserver(new NYTimes());
f.registerObserver(new Guardian());
f.registerObserver(new LeMonde());
f.notifyObservers("The queen said her favorite book is Java 8 in Action");


3.2 람다 방식의 개발

f.registerObserver((String tweet) -> {
if (tweet != null && tweet.contains("money")) {
System.out.println("Breaking news in NY! " + tweet);
}
});

f.registerObserver((String tweet) -> {
if (tweet != null && tweet.contains("queen")) {
System.out.println("Yet another news in London... " + tweet);
}
});


4. Chain of Responsibility


4.1 일반적인 방식의 COR방식

public abstract class ProcessingObject<T> {

protected ProcessingObject<T> successor;

public void setSucessor(ProcessingObject<T> successor) {
this.successor = successor;
}

public T handle(T input) {
T r = handleWork(input);
if (successor != null) {
return successor.handle(r);
}
return r;
}

abstract protected T handleWork(T input);
}

public class HeaderTextProcessing extends ProcessingObject<String> {
public String handleWork(String text) {
return "From Raoul, Mario and Alan: " + text;
}
}

public class SpellCheckerProcessing extends ProcessingObject<String> {
public String handleWork(String text) {
return text.replaceAll("labda", "lambda");
}
}

ProcessingObject<String> p1 = new HeaderTextProcessing();
ProcessingObject<String> p2 = new SpellCheckerProcessing();

p1.setSuccessor(p2);

String result = p1.handle("Aren't labdas really sexy?!!");
System.out.println(result);

4.2 람다 방식

UnaryOperator<String> headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text;
UnaryOperator<String> spellCheckerProcessing = (String text) -> text.replaceAll("labda", "lambda");
Function<String, String> pipeline = handleProcessing.andThen(spellCheckerProcessing);

String result = pipeline.apply("Aren't labdas really sexy?!!");

5. Factory


5.1 일반적인 팩토리

public class ProductFactory{
public static Product createProduct(String name) {
switch(name) {
case "loan":
return new Loan();
case "stock":
return new Stock();
case "bond":
return new Bond();
default:
throws new RuntimeException("No such product " + name);
}
}
}

Product p = ProductFactory.createProduct("loan");

5.2 람다 방식

Supplier<Product> loanSupplier = Lona::new;
Loan loan = loanSupplier.get();

final static Map<String, Supplier<Product>> map = new HashMap<>();
static {
map.put("loan", Loan::new);
map.put("stock", Stock::new);
map.put("bond", Bond::new);
}

public static Product createProduct(String name) {
Suppler<Product> p = map.get(name);
if (p != null) return p.get();
throw new IllegalArgumentException("No Such product " + name);
}

ForkJoinPool 예제

ForkJoinPool 예제

ForkJoinPool

ForkJoinPool 은 Java7부터 사용가능한 Java Concurrency 툴이며, 동일한 작업을 여러개의 Sub Task로 분리(Fork)하여 각각 처리하고, 이를 최종적으로 합쳐서(Join) 결과를 만들어내는 방식이다.

ForkJoinPool에는 2가지 인터페이스를 제공한다. 

- RecursiveAction : 

> RecursiveAction은 결과를 반환하지 않는 태스크를 말한다. 특정 작업을 분할하고, 처리를 수행하면서 해당 작업을 내부에서 처리한다. 

예제 : 
package com.company.api_simulation;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveAction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class APICallRecursiveAction extends RecursiveAction {

    public static final int DIVIDE_COUNT = 5;

    List<RequestObject> requestObjects = null;

    public APICallRecursiveAction(List<RequestObject> requestObjects) {
        this.requestObjects = requestObjects;
    }

    @Override
    protected void compute() {
        if (requestObjects.size() > DIVIDE_COUNT) {
            ForkJoinTask.invokeAll(divideSubActions());
        }
        else {
            processAPICall(requestObjects);
        }
    }

    private void processAPICall(List<RequestObject> requestObjects) {
        requestObjects.forEach(System.out::println);
    }

    private List<APICallRecursiveAction> divideSubActions() {

        List<RequestObject> preList = requestObjects.subList(0, requestObjects.size() / 2);
        List<RequestObject> postList = requestObjects.subList(requestObjects.size() / 2, requestObjects.size());

        List<APICallRecursiveAction> list = new ArrayList<>();
        list.add(new APICallRecursiveAction(preList));
        list.add(new APICallRecursiveAction(postList));

        return list;

    }

    public static void main(String[] args) {
        List<RequestObject> lists = new ArrayList<>();
        lists.add(new RequestObject(1L, 1L, "First1"));
        lists.add(new RequestObject(2L, 1L, "First2"));
        lists.add(new RequestObject(10L, 5L, "First10"));
        lists.add(new RequestObject(6L, 3L, "First6"));
        lists.add(new RequestObject(3L, 2L, "First3"));
        lists.add(new RequestObject(4L, 2L, "First4"));
        lists.add(new RequestObject(11L, 6L, "First11"));
        lists.add(new RequestObject(8L, 4L, "First8"));
        lists.add(new RequestObject(5L, 3L, "First5"));
        lists.add(new RequestObject(7L, 4L, "First7"));
        lists.add(new RequestObject(9L, 5L, "First9"));
        lists.add(new RequestObject(12L, 6L, "First12"));

        APICallRecursiveAction action = new APICallRecursiveAction(lists);
        ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();
        forkJoinPool.invoke(action);
    }
}

- RecursiveTask : 

> 기대하는 바와 같이 이 작업은 결과를 반환하는 태스크를 Fork와 Join으로 처리한다. 

예제 : 
package com.company;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

public class CustomRecursiveTask extends RecursiveTask<Integer> {

    private int[] arr;

    private static final int THRESHOLD = 4;

    public CustomRecursiveTask(int[] arr) {
        this.arr = arr;
    }

    @Override
    protected Integer compute() {
        if (arr.length > THRESHOLD) {
            return ForkJoinTask.invokeAll(createSubtasks())
                    .stream()
                    .mapToInt(ForkJoinTask::join)
                    .sum();
        } else {
            return processing(arr);
        }
    }

    private Collection<CustomRecursiveTask> createSubtasks() {
        List<CustomRecursiveTask> dividedTasks = new ArrayList<>();
        dividedTasks.add(new CustomRecursiveTask(
                Arrays.copyOfRange(arr, 0, arr.length / 2)));
        dividedTasks.add(new CustomRecursiveTask(
                Arrays.copyOfRange(arr, arr.length / 2, arr.length)));
        return dividedTasks;
    }

    private Integer processing(int[] arr) {
        return Arrays.stream(arr)
//                .filter(a -> a > 10 && a < 27)
                .map(a -> a * 10)
                .sum();
    }

    public static void main(String[] args) {
        ForkJoinPool commonPool = ForkJoinPool.commonPool();

        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        CustomRecursiveTask task = new CustomRecursiveTask(arr);

//        commonPool.execute(task);
//        Integer result = task.join();
//        System.out.println(result);

//        System.out.println(commonPool.invoke(task));

        System.out.println(task.compute());
    }
}

결론적으로

Fork Join Pool은 우리가 원하는 작업을 Divided and Conquer 방식으로 처리할 수 있도록 해준다. 

실제 프로그램에서 사용할때에는 외부 API 를 여러번 호출해야하는 상황에서 작업을 분리하여 호출할 수도 있을 것이다. 

[Java] Static Import 즉, 정적 import를 왜 하는가?

[Java] Static Import 즉, 정적 import를 왜 하는가?

Static Import

from : http://docs.oracle.com/javase/1.5.0/docs/guide/language/static-import.html

static 멤버에 접근하기 위해서 우리는 Qualify 하게 레퍼런스를 참조해야한다. 이 멤버가 어디에서 왔는지 지정하기 위해서 필요하다.
예를 들면 다음과 같다.
double r = Math.cos(Math.PI * theta);
이를 위해서 사람들은 정적 멤버를 인터페이스에 기술하고, 이 인터페이스를 상속 받는다. 그러나 이것은 좋은 방법이 아니다. 사실 이런 나쁜 방법들에는 그것을 위한 이름이 있다. Constant Interface Antipattern( Effective Java Item 17참조). 이 문제가 되는 것은 다른 클래스의 정적 멤버를 사용하는 클래스는 상세 구현의 일부라는 것이다. 클래스가 인터페이스를 구현할때 이것은 클래스의 public API의 부분이 된다. 구현의 상세는 공개 API에 노출 되어서는 안된다.

static import 생성자는 정적 멤버에 대해서 qualified 하지 않아도 되며, 정적 멤버를 포함하고 있는 타입으로 부터 상속을 받지 않아도 된다. 대신에 프로그램은 멤버를 임포트 할 수 있다.

import static java.lang.Math.PI;
혹은
import static java.lang.Math.*;
를 이용할 수 있다.
그리고 정적 임포트를 통해서 정적 멤버에는 다음과 같이 qualification을 사용하지 않고 이용가능하다.
double r = cos(PI * theta);
정적 import 선언은 일반적인 import와 유사하다.
normal import 선언은 클래스를 package로 부터 임포트한다. 이는 패키지의  qualification없이 사용가능하도록 해준다.

정적 import선언은 클래스로 부터 정적 멤버를 임포트 한다. 이는 클래스의 qualification없이 사용 가능하도록 해준다.

언제 static import를 이용할까? 매우 드물게 (Very sparingly!). 오직 상수의 로컬 복사를 시도하는 경우 혹은 상속을 통해서 어뷰징 하고 있을때 (the Constant Interface Antipattern) 사용한다. 다른  말로 하나 혹은 두개의 클래스에서 정적 멤버를 빈번하게 접근해야할때 이용한다. 만약 static import 기능을 남용하면 코드를 매우 읽기 어렵게 하고, 유지보수 하기 어렵게 한다. namespace를 오염 시켜버리게 된다.
코드를 읽는 독자들은 정적 멤버들이 어디서 왔는지 알지 못하게 될 것이다. 클래스로 부터 정적 멤버 전체를 임포트 하는 것은 읽기를 어렵게 할 수 있다. 만약 하나 혹은 2개의 멤버를 이용하고자 한다면 그것들을 각각 이용하기 바란다.

적절하게만 사용한다면 static import는 당신을 코드를 더 읽기 쉽게 할 수 있으며, 클래스 이름을 중복으로 쓰는 boilerplate한 코드를 제거할 수 있다.

















Java Collection

Java Collection

Java Collection

Collections는 복수개의 엘리먼트를 그룹화 하고 저장하고, 조회하고, 오퍼레이션을 하는 객체이다. 

Collection 인터페이스는 컬렉션 하위 인터페이스들의 최상위 루트이다. 

하위 인터페이스들은 다음과 같다. 

Collection Interface : 

List :
- Ordered : Yes
- Dups : Yes
- 특징 : 특정 위치에 엘리먼트를 입력할 수 있다. 

Map : 
- Ordered : 가능
- Dups : No
- 특징 : 유니크 키, 키에 매핑된 값이 저장된다. 

Queue : 
- Ordered : Yes
- Dups : Yes
- 특징 : FIFO에 의한 오퍼레이션

Set : 
- Ordered : 가능 
- Dups : No
- 특징 : 유니크한 엘리먼트만 받는다. (유니크 성을 위한 구분 필요, 동일성, 동등성)

Collection 구현체 : 

ArrayList : 
- 인터페이스 : List
- Ordered : Index
- Sorted : No
- Dups : Yes
- 특징 : 빠르게 크기를 조정할 수 있는 배열
- BigO :
-- O(1) : get, set
-- O(n) : add, remove, contains, indexOf

LinkedList : 
- 인터페이스 : List
- Ordered : Index
- Sorted : No
- Dups : Yes
- 특징 : 이중 연결 리스트로 구성됨
- BigO :
-- O(1) : get, set, add, remove(from either end)
-- O(n) : get, set, add, remove(from index), contains, indexOf

Vector : 
- 인터페이스 : List
- Ordered : Index
- Sorted : No
- Dups : Yes
- 특징 : 기존 컬렉션, 동기화된 컬렉션임

HashMap : 
- 인터페이스 : Map
- Ordered : No
- sorted : No
- Dups : No
- 특징 : 키/값의 쌍으로 저장
- BigO :
-- O(1) : get, put, remove, containsKey

Hashtable : 
- 인터페이스 : Map
- Ordered : No
- sorted : No
- Dups : No
- 특징 : 기존 컬렉션, 동기화된 컬렉션

LinkedHashMap : 
- 인터페이스 : Map
- Ordered : Insertion, last access
- sorted : No
- Dups : No
- 특징 : 링크드 리스트 and Hash table

TreeMap :
- 인터페이스 : Map
- Ordered : Balanced
- sorted : Yes
- Dups : No
- 특징 : Red-black tree map
- BigO :
-- O(log n) : remove, get, put, containKey

PriorityQueue :
- 인터페이스 : Queue
- Ordered : Priority
- sorted : Yes
- Dups : Yes
- 특징 : 힙 구현체
- BigO :
-- O(1) : peek
-- O(log n) : add, remove

HashSet : 
- 인터페이스 : Set
- Ordered : No
- sorted : No
- Dups : No
- 특징 : 셋에 빠른 접근 가능
- BigO :
-- O(1) : add, remove, contains

LinkedHashSet : 
- 인터페이스 : Set
- Ordered : Insertion
- sorted : No
- Dups : No
- 특징 : Linked list and Hash set
- BigO :
-- O(1) : add, remove, contains

TreeSet :
- 인터페이스 : Set
- Ordered : Sorted
- sorted : Yes
- Dups : No
- 특징 : Red-black tree set
- BigO :
-- O(log n) : add, remove, contains


# 참고 BigO notations : 
---------------------------------------------------------------
O(1) : 엘리먼트의 개수에 상관없이, 변화되지 않는 상수 시간
O(n) : 엘리먼트의 개수에 따라, 선형적으로 증가되는 시간
O(log n) : 엘리먼트 개수에 대해서 로그 로 증가되는 시간
O(n log n) : 엘리먼트 개수에 대해서 선형 로그로 정가되는 시간.
---------------------------------------------------------------


from : Java8 Cookbook 


Java Concurrency

Java Concurrency

Java Concurrency 

Thread 생성
- 자바에서 스레드를 생성하는 방법은 2가지 이다. java.lang.Thread와 java.lang.Runnable 2가지가 있다.

Thread를 상속받는 방법. 
class Comet extends Thread {
    public void run() {
        System.out.println("Orbiting");
        orbit();
    }
}
Comet halley = new Comet();
hally.run();

Runnable를 구현하는 방법.
class Asteroid implements Runnable {
    plublic void run() {
        System.out.println("Orbiting");
        orbit();
    }
}
Asteroid majaAsteroid = new Asteroid();
Thread majaThread = new Thread(majaAsteroid);
majaThread.run();
Lambda 표현식을 이용하는 방법. 
Runnable asteroid = () -> {
    System.out.println("Orbiting");
    orbit();
}
Thread asteroidThread1 = new Thread(asteroid);
Thread asteroidThread2 = new Thread(asteroid);
asteroidThread1.run();
asteroidThread2.run();

Thread 상태 

NEW : 스레드는 생성되었으나 시작되지 않은 상태.
RUNNABLE : 스레드가 실행 가능한 상태가 되었다.
BLOCKED : "alive" 스레드로 모니터 락을 기다리는 블록된 상태.
WAITING : "alive" 스레드로 다른 스레드를 기다리는 동안 wait()혹은 join()을 호출한다.
TIMED_WAITING : "alive"스레드로 특정 시간동안 다른 스레드를 기다린다. sleeping
TERMINATED : 스레드가 완료된 경우

스레드 우선순위

스레드 우선순위는 1 ~ 10단계로 둘수 있다. 기본은 5이다.
MIN_PRIORITY, NORM_PRIORITY,  MAX_PRIORITY 를 이용하여 우선순위를 탐색 할 수 있다.

System.out.println(Thread.MAX_PRIORITY);
낮은 우선순위는 높은 우선순위의 스레드에 양보하게 된다.

공통 메소드 

getPriority() : 스레드의 우선순위를 반환한다.
getState() : 스레드의 상태를 반환한다.
interrupt() : 스레드에 인터럽트를 건다.
isAlive() : 스레드가 얼라이브 상태인지 검사한다.
isInterrupted() : 스레드가 인터럽트 되었는지 검사한다.
join() : 이 메소드를 호출하여 스레드를 기다린다. 이는 객체가 종료 되었음을 알려준다.
setPriority(int) : 스레드의 우선순위를 설정한다.
start() : 스레드를 실행 가능 상태의 위치로 옮긴다.


notify() : 스레드에게 깨어나고, 실행하라고 해준다.
notifyAll() : 모든 기다리는 스레드나 리소스에게 깨어나게 한다. 그리고 스레드 중의 하나를 선택해서 실행한다.
wait() : 다른 스레드가 notify를 호출하거나 notifyAll()을 호출하기 전까지 대기 하게 한다.

# 중요 :
만약 wait() 와 notify()를 호출할때 스레드의 상태가 interrupted flag가 true로 설정된 경우 InterruptedException이 발생된다.

정적 스레드 메소드 : 

activeCount() : 현재 스레드 그룹에서 스레드의 수를 반환한다.
currentThread() : 현재 실행중인 스레드의 레퍼런스를 반환한다.
interrupted() : 현재 실행중인 스레드의 인터럽트 여부를 체크한다.
sleep(long) : 지정된 밀리 세컨동안 실행되는 스레드를 블록한다.
yield() : 현재 스레에게 다른 스레드가 수행할 수 있도록 알려준다.

Synchronization

synchronized 키워드는 특정 블록이나 메소드에게 락을 수행하도록 해준다.
Locks는 블록이나 메소드에 걸리며 이는 매우 중요하게 공유되어야할 리소스에 접근할때 이용하게 된다. 이 모니터 락은 브레이스를 열고 닫는 것으로 시작과 종료가 된다.

객체 인스턴스 t에 동기화 락을 걸어준다.
synchronized (t) {
    // Block body
}
객체 인스턴스 this 에 동기화 락을 걸어준다.
synchronized (this) {
    // Block body
}
raise() 메소드에 동기화 락을 걸어준다.
synchronized void raise() {
    // Method body
}
void raise() {
    synchronized (this) {
        // Method body
    }
}
상기 두 처리는 동일하다.

다음은 정적 메소드에 동기화 락을 걸어 준 예이다.
class Telescope {
    synchronized static void calivrate() {
        // Method body
    }
}

동시성 유틸리티

java에서는 java.util.concurrent 패키지를 제공하여 동시성을 지원하고 있다. 이는 executors와 concurrent collections, synchronizers, timing 유틸리티를 포함하고 있다.

Executor 

ThreadPoolExecutor과 같이 ScheduledThreadPoolExecutor 은 Executor을 구현한 인터페이스이다. 이는 설정 가능하고, 유동적인 스레드 풀을 제공한다.
스레드 풀은 스레드의 재사용 가능하도록 해준다.

Executors는 팩토리를 제공한다.  다음은 스레드 풀을 생성하기 위한 내용을 보여준다.

newCachedThreadPool()
    - unbounded 스레드 풀을 생성하며, 자동적으로 재 사용 가능한 스레드이다.

newFixedThreadPool(int nThreads)
    - 고정 크기의 스레드 풀을 생성한다. 이것은 공유된 unbounded 큐를 끈 스레드를 재사용 하도록 한다.

newScheduledThreadPool(int corePoolSize)
    - 스레드 풀을 생성하며, 스케줄 된 커맨드를 가진 주기적인 실행 혹은 지정된 딜레이를 가진 스레드 풀을 지원한다.

newSingleThreadExecutor()
    - 단일 스레드 executor을 생성하고, unbounded 큐를 끈 처리를 수행한다.

newSingleThreadScheduledExecutor()
    - 단일 스레드 executor을 생성하고 지정된 기간 혹은 특정 딜레이에 의한 스케줄된 커맨드를 가질 수 있다.

다음은 newFixedThreadPool 팩토리 메소드를 사용한 예제이다.

import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
public class ThreadPoolExecutor {
    public static void main() {
        // 태스크를 생성한다.
        RTask t1 = new RTask("thread1");
        RTask t2 = new RTask("thread2");
        // 스레드 매니저를 생성한다.
        ExecutorService threadExecutor = Executors.newFixedThreadPool(2);
        // 스레드를 실행 가능 상태로 만든다.
        threadExecutor.execute(t1);
        threadExecutor.execute(t2);
        // 스레드를 셧다운 한다.
        threadExecutor.shutdown();
    }
}

Concurrent Collections

컬렉션 타입을 동기화된 방식으로 제공한다. 이는 thread-safe 한 처리를 지원한다.

HashMap <--> ConcurrentHashMap
TreeMap <--> ConcurrentSkipListMap
TreeSet   <--> ConcurrentSkipListSet
Map subtypes <--> ConcurrentMap
List subtypes <--> CopyOnWriteArrayList
Set subtypes  <--> CopyOnWriteArraySet
PriorityQueue <--> PriorityBlockingQueue
Deque            <--> BlockingDequeue
Queue            <--> BlockingQueue


Synchronizers

Synchronizers는 특별한 목적을 위해 만들어진 동기화 툴이다.
Semaphore : 허가들의 집합을 관리한다.
CountDownLatch : 막 수행된 오퍼레이션의 집합에 대한 waits를 구현한다.
CyclicBarrer : 공통 장벽 지점에 대한 waits를 구현한다.
Exchanger : 동기화 포인트를 구현하며, 스레드들이 변화시킬 수 있는 엘리먼트에 구현한다.


Timing Utility

TimeUnit은 enumeration으로 시간 기반의 메소드에 공통으로 사용된다. 이것은 시간 파라미터가 어떤 단위로 진행될지 지정하며, 다음과 같다.

if (lock.tryLock(15L, TimeUnit.DAYS)) {...} // 15일 락을 시도한다.

NANOSECONDS : 1/1000us 나노초
MICROSECONDS : 1/1000ms 마이크로초
MILLISECONDS : 1/1000sec 밀리초
SECONDS : sec : 초
MINUTES : 60sec : 60초
HOURS : 60min : 3600초
DAYS : 24hr : 86400초


from : java 8 pocket book







Simple JPA Test

Simple JPA Test

Simple JPA Test

단순한 JPA의 OneToMany, ManyToOne 관계에 대한 테스트를 해보자.

1. JPA테스트 :

- JPA에서 OneToMany와 ManyToOne을 양방향 연결 관계로 설정하고, 이를 운용하는 테스트를 진행해보았다.
- 여기서는 CascadeType.ALL의 설정과 orphanRemove = true에 대해서 알아보고자 한다. 
- 또한 Unique 설정에서 해당 처리가 어떻게 수행되는지 알아 볼 것이다. 

2. Teacher Entity 객체 생성하기.

package kr.co.unclebae.jpa.domain;

import com.google.common.collect.Lists;
import org.springframework.util.CollectionUtils;

import javax.persistence.*;
import java.util.List;

/** * Created by UncleBae on 15. 9. 14.. */@Entity@Table(name = "TEACHER")
public class Teacher {
    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "TEACHER_ID")
    private Long id;

    private String name;

    private String subject;

    @OneToMany(mappedBy = "teacher" ,fetch = FetchType.LAZY, 
cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Student> students = Lists.newArrayList();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public List<Student> getStudents() {
        return students;
    }

    public void setStudents(List<Student> students) {
        if (students == null) {
            return;
        }

        for(Student student : students) {
            addStudent(student);
        }
    }

    public void addStudent(Student student) {
        if (students.contains(student)) {
            return;
        }
        this.students.add(student);
        student.setTeacher(this);
    }

    public void removeStudent(Student student) {
        if (student == null) {
            return;
        }

        students.remove(student);
        student.setTeacher(null);
    }

    public void removeAllStudents() {
        if (CollectionUtils.isEmpty(students)) {
            return;
        }

        for(Student student : students) {
            removeStudent(student);
        }
    }

    @Override    public String toString() {
        return String.format("Teacher ID %s, name %s, 
subject %s, students %s", id, name, subject, 
createStudentsString());
    }

    private String createStudentsString() {
        StringBuffer sb = new StringBuffer();
        sb.append("[");
        if (!CollectionUtils.isEmpty(students)) {
            for (Student student : students) {
                sb.append("{");
                sb.append(student.toString());
                sb.append("},");
            }
        }
        sb.append("]");
        return sb.toString();
    }

    @Override    public boolean equals(Object obj) {
        return super.equals(obj);
    }
}


- Teacher : Student = 1 : N관계 즉 OneToMany관계이다.

- 여기서 mappedBy를 이용하여 연관관계의 주인이 아님을 설정한다. (연관관계의 주인은 보통 Many쪽에 지정한다. )

- cascade = CascadeType.ALL 을 지정하였다.
    이로 인해 Teacher의 영속성 변화에 대해서 자식인 Student에게 전이를 시켜준다. (즉, 부모가 영속화 되면 자식도 영속화 되며, 부모에서 제거되면 자식도 제거되는 처리를 수행한다.)

- orphanRemove = true 를 지정하였다.
    이렇게 지정하면 Teacher객체를 제거하면, 자식인 Student의 객체들도 함께 제거된다.
    또한 Student에 대해서 student.setTeacher(null); 이라고 지정하면 해당 자식은 제거 된다.

- addStudent, removeStudent, removeAllStudents와 같은 편의 메소드를 부모쪽에 지정하여 자식 객체에 대한 연관관계를 함께 추가 및 제거해주도록 처리한다.

3. Student Entity 객체 생성하기.

package kr.co.unclebae.jpa.domain;

import javax.persistence.*;

/** * Created by UncleBae on 15. 9. 14..
 */@Entity@Table(name = "STUDENT")
public class Student {

    @Id    
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true)
    private String name;

    private String grade;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TEACHER_ID")
    private Teacher teacher;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    public String getGrade() {
        return grade;
    }

    public void setGrade(String grade) {
        this.grade = grade;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;

        if (teacher == null) {
            return;
        }

        if ( !teacher.getStudents().contains(this)) {
            teacher.getStudents().add(this);
        }
    }

    @Override    public String toString() {
        return String.format("Student id %s, name %s, 
grade %s", id, name, grade );
    }
}

- ManyToOne 를 지정하여 자식 객체에서 부모와의 연관을 맺어준다.

- ManyToOne에서는 cascade를 사용하지 말것. - 만약 cascade = CascadeType.ALL을 지정했을때 테스트를 해보면 자식객체에서 부모 객체를 제거하면, Teacher객체 역시 함께 제거 되어 버린다. (주의)

4. 리포지토리 생성하기 

TeacherRepository.java

package kr.co.unclebae.jpa.repository;

import kr.co.unclebae.jpa.domain.Teacher;
import org.springframework.data.jpa.repository.JpaRepository;

/** * Created by Uncle Bae on 15. 9. 14.. */
public interface TeacherRepository 
    extends JpaRepository<Teacher, Long> {
}
StudentRepository.java
package kr.co.unclebae.jpa.repository;

import kr.co.unclebae.jpa.domain.Student;
import org.springframework.data.jpa.repository.JpaRepository;

/** * Created by naver on 15. 9. 14.. */
public interface StudentRepository 
    extends JpaRepository<Student, Long> {
}


- SpringData JPA를 이용하였다. 

- JpaRepository<T, ID>를 상속받은 인터페이스를 생성하면, 실행시점에 해당 구현체를 만들어준다. 

5. 테스트 케이스 작성하기.

package kr.co.unclebae.jpa.service;

import com.google.common.collect.Lists;
import kr.co.unclebae.jpa.domain.Student;
import kr.co.unclebae.jpa.domain.Teacher;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

import java.util.List;

/** * Created by Uncle Bae on 15. 9. 14.. */
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration@ContextConfiguration( 
{"classpath:hibernateConfig.xml"})
public class TeacherTest {

    @Autowired    
    private TeacherService teacherService;

    @Test    
    public void TeacherInsertTest() {
        Teacher teacher = createTeacherFixture();

        Teacher savedTeacher = teacherService.save(teacher);

        System.out.println(String.format("Saved Val : %s", savedTeacher));
    }

    @Test    public void TeacherInsertAndSutdentUpdate() {

        // 일반적인 CascadeType.ALL 테스트        
        Teacher teacherFixture = createTeacherFixture();

        Student student1 = createStudentFixture("STUDENT1", "B-");
        Student student2 = createStudentFixture("STUDENT2", "A+");

        teacherFixture.addStudent(student1);
        teacherFixture.addStudent(student2);

        Teacher teacherSaved = teacherService.save(teacherFixture);
        System.out.println( String.format("[GENERAL CascadeType.ALL] 
SAVED TEACHER %s", teacherSaved.toString() ) );

        // Delete And Insert And Update        
        List<Student> students = Lists.newArrayList();
        Student student3 = createStudentFixture("STUDENT3", "A-");
        students.add(student3);

        Student student4 = createStudentFixture("STUDENT1", "F");
        student4.setId(student1.getId());  
        // Update가 되려면 ID를 동일하게 세팅해야한다.        
        students.add(student4);

        teacherService.deleteAndMerge(teacherSaved.getId(), students);
        Teacher teacherSelected = teacherService.findById(teacherSaved.getId());

        System.out.println( String.format("[CascadeType.REMOVE 
or CascadeType.ALL] Delete And Merge Result %s", 
teacherSelected ) );

        teacherService.removeFirstStudent(teacherSelected.getId());
        Teacher teacherSelected2 = teacherService.findById(teacherSaved.getId());

        System.out.println(String.format("[orphanRemove = true] 
RemoveFirstStudent Result %s", teacherSelected2));

        teacherService.deleteById(teacherSelected.getId());
        System.out.println( String.format("[orphanRemove = true] 
delete parent") );
    }

    private Student createStudentFixture(String name, String grade) {
        Student student = new Student();
        student.setName(name);
        student.setGrade(grade);
        return student;
    }

    private Teacher createTeacherFixture() {
        Teacher teacher = new Teacher();
        teacher.setName("Undle Bae");
        teacher.setSubject("Computer Science");
        return teacher;
    }
}

- 위 테스트는 아래 4가지를 테스트한다.
A. 일반적인 CascadeType.ALL 동작

B. Delete And Insert Update 처리 확인

C. 리스트에서 첫번째 자식 객체를 제거한경우 처리 확인

4. 부모 제거시 동작 확인

6. 생성된 스키마 확인. 

    create table STUDENT (
        id bigint generated by default as identity,
        grade varchar(255),
        name varchar(255),
        TEACHER_ID bigint,
        primary key (id)
    )

    create table TEACHER (
        TEACHER_ID bigint generated by default as identity,
        name varchar(255),
        subject varchar(255),
        primary key (TEACHER_ID)
    )

    alter table STUDENT 
        add constraint UK_3leirxifnbbkn35r06ggygpj0  unique (name)

    alter table STUDENT 
        add constraint FK_kqtywag05gv4ixllub1d0ayp1 
        foreign key (TEACHER_ID) 
        references TEACHER


- 상기 내역을 보면 STUDENT에서 foreign key로 부모의 TEACHER_ID가 설정되었다. 

7. Cascade.ALL에 의한 부모 및 자식객체 저장 

    /* insert kr.co.unclebae.jpa.domain.Teacher
        */ insert
        into
            TEACHER
            (TEACHER_ID, name, subject)
        values
            (null, ?, ?)

    /* insert kr.co.unclebae.jpa.domain.Student
        */ insert
        into
            STUDENT
            (id, grade, name, TEACHER_ID)
        values
            (null, ?, ?, ?)

    /* insert kr.co.unclebae.jpa.domain.Student
        */ insert
        into
            STUDENT
            (id, grade, name, TEACHER_ID)
        values
            (null, ?, ?, ?)

- TEACHER 테이블에 에 인서트가 되고나서
- SUTDENT 테이블에 2건 인서트가 되었다.

    select
        teacher0_.TEACHER_ID as TEACHER_1_1_0_,
        teacher0_.name as name2_1_0_,
        teacher0_.subject as subject3_1_0_
    from
        TEACHER teacher0_
    where
        teacher0_.TEACHER_ID=?

    select
        students0_.TEACHER_ID as TEACHER_4_1_0_,
        students0_.id as id1_0_0_,
        students0_.id as id1_0_1_,
        students0_.grade as grade2_0_1_,
        students0_.name as name3_0_1_,
        students0_.TEACHER_ID as TEACHER_4_0_1_
    from
        STUDENT students0_
    where
        students0_.TEACHER_ID=?

- 해당 내역을 출력하기 위해서 TEACHER를 먼저 호출하고, STUDENT 를 TEACHER_ID로 호출하여 2건을 가져오고 있다.

- 출력 결과 : 
[GENERAL CascadeType.ALL]
SAVED TEACHER Teacher ID 1, name Undle Bae, subject Computer Science,
students [
{Student id 1, name STUDENT1, grade B-},
{Student id 2, name STUDENT2, grade A+},
]

8. Cascade.ALL, orphanRemove = true 처리 (삭제, 인서트, 업데이트)

    /* insert kr.co.unclebae.jpa.domain.Student
        */ insert
        into
            STUDENT
            (id, grade, name, TEACHER_ID)
        values
            (null, ?, ?, ?)

    /* update
        kr.co.unclebae.jpa.domain.Student */ update
            STUDENT
        set
            grade=?,
            name=?,
            TEACHER_ID=?
        where
            id=?

    /* delete kr.co.unclebae.jpa.domain.Student */ delete
        from
            STUDENT
        where
            id=?

- 우선 신규로 추가되는 STUDENT3이 인서트 된다.

- 다음으로 STUDENT1이 업데이트 된다. (ID가 같으므로 업데이트함)

- 마지막으로 STUDENT2가 삭제 된다.

# 중요한 것은 JPA는 ID값이 존재하면 업데이트를, ID가 없으면 인서트를 수행한다.
# orphanRemove를 통해서 부모와 연결이 끊어진 객체 STUDENT2는 트랜잭션이 종료 될때 제거된다.

- 처리 결과 : 
[CascadeType.REMOVE or CascadeType.ALL]
Delete And Merge Result Teacher ID 1, name Undle Bae, subject Computer Science,
students [
{Student id 1, name STUDENT1, grade F},
{Student id 3, name STUDENT3, grade A-},
]

9. orphanRemove = true설정후 부모 객체 삭제 

    /* delete kr.co.unclebae.jpa.domain.Student */ delete
        from
            STUDENT
        where
            id=?

- students의 첫번째 엘리먼트만 제거한경우에는 트랜잭션이 종료 되면서 해당 내역을 DB에서 제거한다.

- 처리결과 : 
[orphanRemove = true] RemoveFirstStudent Result Teacher ID 1, name Undle Bae, subject Computer Science, students [{Student id 3, name STUDENT3},]

    /* delete kr.co.unclebae.jpa.domain.Student */ delete 
        from
            STUDENT 
        where
            id=?

    /* delete kr.co.unclebae.jpa.domain.Teacher */ delete 
        from
            TEACHER 
        where
            TEACHER_ID=?

- 부모인 TEACHER객체를 제거하게 되면 부모를 잃은 자식 객체도 자동적으로 제거 된다. 
- 로그 : 
[orphanRemove = true] delete parent

10. 설정하기. 

10.1 hibernateConfig.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:jpa="http://www.springframework.org/schema/data/jpa"

       xmlns:context="http://www.springframework.org/schema/context" 

       xmlns:tx="http://www.springframework.org/schema/tx"

       xsi:schemaLocation="
          http://www.springframework.org/schema/beans 
          http://www.springframework.org/schema/beans/spring-beans.xsd 
          http://www.springframework.org/schema/context 
          http://www.springframework.org/schema/context/spring-context.xsd 
          http://www.springframework.org/schema/tx 
          http://www.springframework.org/schema/tx/spring-tx.xsd 
          http://www.springframework.org/schema/data/jpa 
          http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

   <jpa:repositories base-package="kr.co.unclebae.jpa.repository" />

    <tx:annotation-driven/>

    <context:component-scan base-package="kr.co.unclebae.jpa.service, 
kr.co.unclebae.jpa.repository"/>

    <bean id="dataSource" class="org.apache.tomcat.jdbc
.pool.DataSource">
        <property name="driverClassName" value="org.h2.Driver"/>
        <property name="url" value="jdbc:h2:mem:jpashop"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>

    <bean id="transactionManager" class="org.springframework
.orm.jpa.JpaTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean class="org.springframework.dao.annotation.
 PersistenceExceptionTranslationPostProcessor"/>

    <bean id="entityManagerFactory" class="org.springframework.
orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="packagesToScan" value="kr.co.unclebae.
jpa.domain"/> 
        <property name="jpaVendorAdapter">

            <bean class="org.springframework.orm.jpa.vendor.
HibernateJpaVendorAdapter"/>
        </property>
        <property name="jpaProperties"> 
            <props>
               <prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
               <prop key="hibernate.show_sql">true</prop> 
               <prop key="hibernate.format_sql">true</prop>
               <prop key="hibernate.use_sql_comments">true</prop>
               <prop key="hibernate.id.new_generator_mappings">true</prop>
               <prop key="hibernate.hbm2ddl.auto">create</prop>       
            </props>
        </property>
    </bean>

</beans>


10.2 POM 설정하기 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>JPATest</groupId>
    <artifactId>JPATest</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>JPATest</name>
    <packaging>war</packaging>

    <properties>

        <java.version>1.8</java.version>

        <spring-data-jpa.version>1.8.0.RELEASE</spring-data-jpa.version>
        <spring-framework.version>4.1.6.RELEASE</spring-framework.version>

        <querydsl.version>3.6.3</querydsl.version>
        <hibernate.version>4.3.10.Final</hibernate.version>
        <tomcat-jdbc.version>7.0.52</tomcat-jdbc.version>
        <h2db.version>1.4.187</h2db.version>

        <jsp.version>2.2</jsp.version>
        <jstl.version>1.2</jstl.version>
        <servlet.version>3.0.1</servlet.version>

        <logback.version>1.1.1</logback.version>
        <slf4j.version>1.7.6</slf4j.version>

        <junit.version>4.12</junit.version>

    </properties>

    <dependencies>

        <!-- 스프링 데이터 JPA -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>${spring-data-jpa.version}</version>
        </dependency>

        <!-- QueryDSL -->
        <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <version>${querydsl.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <version>${querydsl.version}</version>
        </dependency>

        <!-- 스프링 MVC -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>

        <!-- JPA, 하이버네이트 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>

        <!-- H2 데이터베이스 -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>${h2db.version}</version>
            <scope>runtime</scope>
        </dependency>

        <!-- 커넥션 풀 -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
            <version>${tomcat-jdbc.version}</version>
            <scope>compile</scope>
        </dependency>

        <!-- WEB -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>${jstl.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${servlet.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.3.1</version>
            <scope>provided</scope>
        </dependency>

        <!-- 로깅 SLF4J & LogBack -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
            <scope>runtime</scope>
        </dependency>

        <!-- 테스트 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring-framework.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <!-- Spring Data JPA 1.8이 스프링 4.0.9에 의존관계를 가지므로 스프링 버전을 직접 관리한다. -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>${spring-framework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>${spring-framework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>${spring-framework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-orm</artifactId>
                <version>${spring-framework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aop</artifactId>
                <version>${spring-framework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>${spring-framework.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <build>
        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <path>/</path>
                    <uriEncoding>UTF-8</uriEncoding>
                </configuration>
            </plugin>

            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>target/generated-sources/java</outputDirectory>
                            <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>