Java - ConnectionTimeout,ReadTimeout,SocketTimeout 차이점?


사실 지금까지 웹개발을 해오면서 ConnectionTimeout,ReadTimeout,SocketTimeout에 대해 대략적으로만 알고있었지 

사실 정확히 설명해봐라 혹은 차이점을 설명해봐라하면 대답하기 힘든 부분이 있었다. 이번 포스팅으로 

정확한 타임아웃 개념을 잡아보려고한다.

 

 

 

ConnectionTimeout이란?

ConnectionTimeout이라는 개념을 설명하기 전에 URL로 HTTP호출을 할 때 어떤 방식으로 수행되는지 이해가 필요하다.웹 브라우저가 네이버 서버에 접속하기 위해서 서버와 연결된 상태가 되어야한다. 보통 연결을 구성하기위해TCP Connection과 동일하게 3-way-HandShake 방식으로 수행하게 된다. 3-way HandShake가 정상적으로
수행하게 되면 웹 브라우저와 네이버 서버는 연결된 상태가 되는데, 이때까지 소요된 시간을 Connection에 소요된 시간이라고 할 수 있다. 

"즉,ConnectionTimeout이란 3-way HandShake가 정상적으로 수행되어 서버에 연결되기까지 소요된시간이다."

 

SocketTimeout이란?

클라이언트와 서버가 연결된 상태 이후에 서버는 페이지를 브라우저에 랜더링하기 위해 데이터를 전송한다. 이때 하나의 패킷으로데이터를 전송하는 것이 아니라 여러 개의 패킷으로 나눠서 전송하게 된다. 여러개의 패킷이 전송될 때, 각 패킷 간의 시간 Gap이생길 수 있는데 이 시간의 임계치를 SocketTimeout이라고 한다.

"즉, SocketTimeout이란 데이터를 여러개의 패킷으로 나누어 보낼때 각 패킷간의 시간 Gap을 이야기한다."

 


위의 두개를 그림으로 표현해보면,

 

결국 위의 두개의 시간설정은 URL을 호출 할때 꼭 필요한 설정이다. 만약 두 시간이 설정되지 않는다면?URL 접속 무한 대기가 발생될 수 있다.

 

ReadTimeout이란?

"Connection 맺은 후 Response(응답)을 받기까지 소요될 시간의 임계치이다."

 

사소하게 넘어 갈 수 있는 것들이지만 네트워크 상에 돌아가는 무엇인가를 개발한다면 꼭 숙지해야 될 것들중 하나인 것 같다.

 

posted by 여성게
:

Java - JDK1.7 특징 try-with-resource 등



JAVA 10버전 이상 나온 시점에 자바 7의 특징을 설명하자니 많이 뒤쳐진 느낌이 들지만 저처럼 아직도 자바 5,6 세대에 머무르고 있는 분들이 있어서는 안되지만 혹시라도 계시는 분들이 있을 것이기에 간단하게 정리해보았습니다. 자바7도 안되있는 상태에서 자바8,9,10 등을 공부하는게 뭔가 순리에 어긋나는 것같습니다. 부족하지만 하나하나 천천히 공부해 나가야 할것 같습니다. 간단히 대략 몇 가지정도만 정리하려고 합니다.



Type Interface



Java 7 이전에는 제너릭 타입 파라미터를 선언과 생성시 중복해서 써주어야했습니다. 사실 저는 현재 생성자 쪽은 제네릭타입을 따로 넣지 않고 사용했었는데, 이게 당연히 원래 되는 건줄 알고 있었는데, 알고보니 JAVA 7에서 개선된 점이었내요.


JDK 7 이전

1
2
Map<String, List<String>> employeeRecords = new HashMap<String, List<String>>();
List<Integer> primes = new ArrayList<Integer>();

JDK 7

1
2
Map<String, List<String>> employeeRecords = new HashMap<>();
List<Integer> primes = new ArrayList<>();



물론 기존에도 제너릭 메소드를 제공함으로써 Type Inference가 가능했는데요. 예를 들어 Static Factory Method를 아래처럼 사용하면 별도의 타입 파라미터 없이도 간단히 제너릭 객체를 리턴 받아 사용 가능합니다. Guava 라이브러리 등에서 자주 사용하던 방식입니다.

1
2
3
4
5
public static <K,V> HashMap<K,V> newContacts() {
   return new HashMap<K,V>();
}
 
HashMap<String, Set<Integer>> contacts = newContacts();

하지만 Java7에서는 스펙 자체에 <> 연산자가 추가되어 굳이 불필요한 Static Factory Method를 안만들어도 된다는 장점이 있겠죠?
<>조차 사용 안하면 어때?라는 생각도 들지만 제너릭 지원 이전의 버젼 호환성을 위해서는 새로운 연산자의 등장은 어쩔 수 없는 선택이었나 봅니다.


String value in Switch


Switch문 내에서 문자열 사용이 가능해졌습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
switch (day) {
    case "NEW":
        System.out.println("Order is in NEW state");
        break;
    case "CANCELED":
        System.out.println("Order is Cancelled");
        break;
    case "REPLACE":
        System.out.println("Order is replaced successfully");
        break;
    case "FILLED":
        System.out.println("Order is filled");
        break;
    default:
        System.out.println("Invalid");
}

try-with-resource


JAVA 7 이전에는 DB Connection,File 등의 자원을 사용한 후에는 항상 try-catch-finally의 finally문에서 close()를 호출하여 닫아줘야했습니다. 항상 같은 패턴이지만 사용한 곳에서는 꼭 넣어줘야만 하는 로직이었습니다. 그래야 자원 반납이 되어 리소스 풀이 나지 않았으니, 그리고 또한 예상치 못한 오류가 발생할 수도 있기에 항상 finally 문 안에서 자원 반납하는 코드가 들어갔습니다.

JDK 7 이전

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void main(String args[]) {
    FileInputStream fin = null;
    BufferedReader br = null;
    try {
        fin = new FileInputStream("info.xml");
        br = new BufferedReader(new InputStreamReader(fin));
        if (br.ready()) {
            String line1 = br.readLine();
            System.out.println(line1);
        }
    } catch (FileNotFoundException ex) {
        System.out.println("Info.xml is not found");
    } catch (IOException ex) {
        System.out.println("Can't read the file");
    } finally {
        try {
            if (fin != null)
                fin.close();
            if (br != null)
                br.close();
        } catch (IOException ie) {
            System.out.println("Failed to close files");
        }
    }
}

하지만 JAVA 7에서는 try-with-resource 구문이 추가 되어 자동으로 자원반납을 해줍니다. 하지만 전제? 필요조건이 있습니다. 그것은 사용한 자원이 AutoClosable 혹은 Closeable 인터페이스를 구현한 경우에만 해당 구문을 사용가능합니다. 기본적으로 우리가 사용하는 자원반납용 객체들은 다행이 구현하고 있습니다.

JDK 7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String args[]) {
    try (FileInputStream fin = new FileInputStream("info.xml");
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    fin));) {
        if (br.ready()) {
            String line1 = br.readLine();
            System.out.println(line1);
        }
    } catch (FileNotFoundException ex) {
        System.out.println("Info.xml is not found");
    } catch (IOException ex) {
        System.out.println("Can't read the file");
    }
}



Underscore in Numeric literal


숫자형(정수,실수)에 _(underscore) 문자열을 사용 할 수 있습니다. 금융권 등에서 큰 숫자들을 다룰 경우 가독성 향상에 도움이 되겠죠?

1
2
3
4
5
int billion = 1_000_000_000; // 10^9
long creditCardNumber = 1234_4567_8901_2345L; //16 digit number
long ssn = 777_99_8888L;
double pi = 3.1415_9265;
float pif = 3.14_15_92_65f;

_ 위치가 다음과 같을 경우엔 컴파일 에러가 발생하니 주의해야합니다.

1
2
3
double pi = 3._1415_9265; // 소수점 뒤에 _ 붙일 경우
long creditcardNum = 1234_4567_8901_2345_L; // 숫자 끝에 _ 붙일 경우
long ssn = _777_99_8888L; // 숫자 시작에 _ 붙일 경우


Catching Multiple Exception Type in Single Catch Block


단일 catch 블럭에서 여러개의 Exception 처리가 가능해졌습니다. (Multi-Catch 구문 지원) JDK7 이전에는 2개의 Exception을 처리하기 위해서는 2개의 catch 블럭이 필요했었죠.

JDK 7 이전

1
2
3
4
5
6
7
try {
    //......
} catch(ClassNotFoundException ex) {
    ex.printStackTrace();
} catch(SQLException ex) {
    ex.printStackTrace();
}

JDK 7

1
2
3
4
5
try {
    //......
} catch (ClassNotFoundException|SQLException ex) {
   ex.printStackTrace();
}

단, Multi-Catch 구문 사용시 다음과 같이 하위클래스 관계라면 컴파일 에러가 발생하므로 주의하세요.

1
2
3
4
5
6
7
8
try {
    //...... }
catch (FileNotFoundException | IOException ex) {
    ex.printStackTrace();
}
 
Alternatives in a multi-catch statement cannot be related by sub classing, it will throw error at compile time :
java.io.FileNotFoundException is a subclass of alternative java.io.IOException at Test.main(Test.java:18)

Java NIO 2.0


JDK7에서 java.nio.file 패키지를 선보였는데요. 기본파일시스템에 접근도 가능하고 다양한 파일I/O 기능도 제공합니다. 예를 들면 파일을 옮기거나 복사하거나 삭제하는 등의 유용한 메소드들을 제공하며, 파일속성이 hidden인지 체크도 가능합니다. 또한 기본파일시스템에 따라 심볼릭링크나 하드링크도 생성 가능합니다. 와일드카드를 사용한 파일검색도 가능하며 디렉토리의 변경사항을 감시하는 기능도 제공합니다. 어쨋든 외부 라이브러리로 해결했던 많은 일들이 JDK안으로 녹아들었네요.


More Precise Rethrowing of Exception


다음 예제를 보면 try 블럭안에서 ParseException, IOException의 Checked Exception이 발생 할 수 있습니다. 각각의 Exception을 처리하기 위해 Multi-Catch 또는 다중 Catch 문으로 예외처리를 할 수 있지만 예제에서는 최상위 클래스인 Exception으로 처리하였습니다. catch 구문에서 발생한 예외를 상위 메소드로 전달하기 위해 throw 할 경우 메소드 선언부에 해당 예외를 선언해주어야하는데요.

JDK7 이전 버젼에서는 다음 예처럼 catch 구문내에서 선언한 예외 유형만 던질 수 있었습니다. (Exception 클래스),

1
2
3
4
5
6
7
8
9
public void obscure() throws Exception {
    try {
        new FileInputStream("abc.txt").read();
        new SimpleDateFormat("ddMMyyyy").parse("12-03-2014");
    } catch (Exception ex) {
        System.out.println("Caught exception: " + ex.getMessage());
        throw ex;
    }
}

하지만 JDK7에서는 좀 더 정확하게 발생한 Exception을 전달할 수 있습니다. (ParseException, IOException 클래스) 물론 이전과 같이 throws Exception으로 처리할 수도 있지만 메소드를 호출한 쪽에 좀 더 정확한 예외를 던져주는게 좋겠죠? 

1
2
3
4
5
6
7
8
9
public void precise() throws ParseException, IOException {
    try {
        new FileInputStream("abc.txt").read();
        new SimpleDateFormat("ddMMyyyy").parse("12-03-2014");
    } catch (Exception ex) {
        System.out.println("Caught exception: " + ex.getMessage());
        throw ex;
    }
}



ArrayList와 HashMap의 개선


JAVA 7 초기 버전까지만 해도 초기값을 명시하지 않은 Default 생성자로 ArrayList 생성시 내부적으로 크기가 10인 Object 배열을 생성했습니다. 생성하고 사용하지 않았는데도 해당 리스트객체는 크기가 10인 Object배열을 쥐고 있는 것이죠. HashMap 또한 Capacity가 16인 Map을 디폴트로 생성합니다.

  • JDK 1.6의 ArayList 생성자
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this(10);
}
 
public ArrayList(int initialCapacity) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    this.elementData = new Object[initialCapacity];
}


  • JDK 1.6의 HashMap 생성자
1
2
3
4
5
6
7
8
9
10
/**
 * Constructs an empty <tt>HashMap</tt> with the default initial capacity
 * (16) and the default load factor (0.75).
 */
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    threshold = (int) (DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
    table = new Entry[DEFAULT_INITIAL_CAPACITY];
    init();
}

JDK 1.7에서 변경된 사항

JDK 1.7 update 40 이후의 ArrayList는 다음과 같이 빈 Object 배열을 가지고 있습니다. static으로 선언되어 있으니 모든 ArrayList 인스턴스에서 공유하겠죠? Default 생성자로 ArrayList 생성시 해당 빈 Object를 초기값으로 설정합니다. 이전 버젼처럼 불필요하게 10인 Object 배열을 생성해 메모리를 낭비하지 않습니다. (Default 생성자의 주석은 아직 update가 안되었나보네요. ‘주석의 나쁜 예’겠죠?)

1
2
3
4
5
6
7
8
9
10
11
12
/**
 * Shared empty array instance used for empty instances.
 */
private static final Object[] EMPTY_ELEMENTDATA = {};
 
/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    super();
    this.elementData = EMPTY_ELEMENTDATA;
}

HashMap도 별반 다르지 않습니다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * An empty table instance to share when the table is not inflated.
 */
static final Entry<?, ?>[] EMPTY_TABLE = {};
 
/**
 * The table, resized as necessary. Length MUST Always be a power of two.
 */
transient Entry<K, V>[] table = (Entry<K, V>[]) EMPTY_TABLE;
 
public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    // .....
}


posted by 여성게
:

해당 환경은 모두 Mac OS환경에서 진행되었습니다.


Lombok(jar) 설치



https://projectlombok.org/download에서 최신버전 혹은 원하는 버젼의 Lombok을 다운로드 받아 줍니다.









Lombok jar 실행


lombok이 설치된 경로로 들어가서 jar파일을 실행시켜줍니다.


java -jar $LOMBOK_DOWNLOAD_PATH/lombok.jar



만약 첫번째 이미지처럼 떴다면 로컬의 IDE를 찾을 수 없다는 alert창입니다. 그렇다면 2,3번째 이미지처럼 IDE의 ini파일의 경로를 적용시켜서 install/Update 버튼을 눌러준 후에 인스톨러를 종료시켜줍니다.


여기까지 따라왔다면 이클립스를 재시작해줍니다.





마지막 이클립스의 ini파일을 열어보면


아래와 같이 javaagent가 등록이 된것을 확인할 수 있습니다.


그리고 pom.xml에 방금 받은 lombok version의 라이브러리를 dependency 하면 런타임시점에 @Getter,@Setter등의 어노테이션이 해당 코드로 generate됩니다.




간단한 롬복 어노테이션




@NonNull
NullPointerException을 발생하지 않도록 미리 체크한다.

@Cleanup
자동 리소스 관리를 수행한다.: 안전하게 close()메소드를 호출한다.

@Getter / @Setter
getter/setter메소드를 자동으로 생성한다.

@ToString
lombok가 자동으로 toString 메소드를 생성해준다. 모든 필드들을 toString으로 노출시켜준다.

@EqualsAndHashCode
equals와 hashCode메소드를 자동으로 생성해준다. 객체의 필드들을 이용하여 구현하게 된다.

@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor
생성자를 만들어 준다.
@NoArgsConstructor : 파라미터 없는 생성자를 만들어준다.
@RequiredArgsConstructor : not null필드나 final필드들을 아규먼트로 하는 생성자를 만든다.
@AllArgsConstructor : 모든 파라미터를 이용하여 생성자를 만든다.

@Data
@ToString, @EqualsAndHashCode, @Getter, @Setter, @RequiredArgsConstructor 를 모두 한번에 적용한 어노테이션이다.

@Value
불변 클래스들을 매우 쉽게 만든다.

@Builder
객체 생성을 빌더로 만들어준다.

@SneakyThrows
checked exceptions를 던진다. 이전에 thrown을 지정하지 않은 경우에 설정된다.

@Synchronized
synchronized가 올바르게 수행되도록 한다. locks를 볼 수 없을 것이다.

@Getter(lazy=true)
Getter을 생성한다. lazy하게.. 느린것이 좋은 것이다.

@Log
Logs를 사용할 수 있도록 해준다.




posted by 여성게
:

Hashtable, HashMap, ConcurrentHashMap 비교


1. Hashtable, HashMap, ConcurrentHashMap

위에 나열된 클래스들은 Map 인터페이스를 구현한 콜렉션들입니다. 이 콜렉션들은 비슷한 역할을 하는것 같으면서도 다르게 구현되어 있습니다. 기본적으로 Map 인터페이스를 구축한다면 <key, value>구조를 가지게 됩니다. 하나씩 살펴봅시다.



2. Hashtable

Hashtable은 putget과 같은 주요 메소드에 synchronized 키워드가 선언 되어 있습니다. 또한 key, value에 null을 허용하지 않습니다. 

3. HashMap

HashMap은 주요 메소드에 synchronized 키워드가 없습니다. 또한 Hashtable과 다르게 key, value에 null을 입력할 수 있습니다.

하지만 HashMap도 

"Map<String,Integer> map = Collections.synchronizedMap(new HashMap<String,Integer>());"

와 같이 선언하면 Thread-safe한 맵으로 사용가능하다.

4. ConcurrentHashMap

HashMap을 thread-safe 하도록 만든 클래스가 ConcurrentHashMap입니다. 하지만 HashMap과는 다르게 key, value에 null을 허용하지 않습니다. 또한 putIfAbsent라는 메소드를 가지고 있습니다. 

5. Common Methods

위의 세종류의 클래스들은 putget 메소드 외에도 기본적인 메소드들을 구현하고 있습니다. 대표적인 몇가지의 메소드들만 알아봅시다.

  • clear()

    해당 콜렉션의 데이터를 초기화 합니다.

  • containsKey(key)

    해당 콜렉션에 입력 받은 key를 가지고 있는지 체크합니다.

  • containsValue(value)

    해당 콜렉션에 입력 받은 value를 가지고 있는지 체크합니다.

  • remove(key)

    해당 콜렉션에 입력 받은 key의 데이터(key도 포함)를 제거합니다.

  • isEmpty()

    해당 콜렉션이 비어 있는지 체크합니다.

  • size()

    해당 콜렉션의 엔트리(Entry) 또는 세그먼트(Segment) 사이즈를 반환합니다.



6. In Multi Threads(ConcurrentModificationException...)

HashMap에 대한 부분은 동기화가 이루어지지 않습니다. 하지만 HashMap을 쓰더라도 synchronized 블록을 선언해 주면 정상으로 동작을 합니다. 따라서 동기화 이슈가 있다면 일반적인 HashMap을 쓰지 말거나 쓰더라도 동기화를 보장하는 HashMap 콜렉션 또는 synchronized 키워드를 이용해 동기화 처리를 반드시 해주는 것이 좋아보입니다. 혹은 Thread-safe한 ConcurrentHashMap을 쓰시는 것을 권장합니다.

만약 하나의 스레드가 Map에 접근하여 요소들을 삭제,수정,삽입 등을 작업하고 있는 도중에 다른 스레드가 해당 Map에 접근 해 무엇인가를 작업한다면 동기화 문제가 발생할 수 있습니다.

밑의 소스에서 일반 HashMap을 사용한다면 위에서 말한 예외가 발생할 경우가 생깁니다. 이 경우를 ConcurrentHashMap을 사용해 Thread-safe한 코드로 변경하였습니다.


@Service

public class SessionService {

    private static final Logger log = LoggerFactory.getLogger(SessionService.class);

    /*Map<String, SessionInfo> sessionMap = new HashMap<>();*/

    /*

     * 

     * 설명 : HashMap을 썼을 경우, ConcurrentModificationException 발생(Thread간의 동기화문제)

     * HashMap -> ConcurrentHashMap

     */

    Map<String, SessionInfo> sessionMap = new ConcurrentHashMap<>();

    Boolean runFlag = true;

    private Thread itsThread = null;

    @Value("${app.session.expire.sec}")

    private int expiredSec;

     

    @PostConstruct

    private void dropExptiredSession() {

        itsThread = new Thread(() -> {

            try {

                while (runFlag) {

                /*

                 * yeoseong_yoon

                 * 설명 : 바로 밑에 메소드 주석 풀면 10초마다 스레드가 돌아가면서 세션만료시간이 된 

                 * 채팅세션을 remove한다.

                 */

                    checkExpiredSession();

                    Thread.sleep(10000);

                }

            } catch (InterruptedException e) {

                log.warn("thread interrupt occured", e);

            }

        });

        itsThread.start();

    }

    @PreDestroy

    private void stop() {

        runFlag = false;

        itsThread.interrupt();

    }

    

private void checkExpiredSession() {

for (Map.Entry entry : sessionMap.entrySet()) {

String sessionKey = (String) entry.getKey();

SessionInfo sessionInfo = (SessionInfo) entry.getValue();

long duration = TimeUtils.getCurrentSec() - sessionInfo.getLastTimeSec();

if (duration > expiredSec) {

sessionMap.remove(sessionKey);

log.info("session time out, sessionkey={}", sessionKey);

}

}

}

}




posted by 여성게
:

https://plposer.tistory.com/29

위의 포스트된 글 일부에 컴포지트 패턴이 설명되어있으며, 예제코드도 나와있음.

posted by 여성게
:

https://tourspace.tistory.com/3?category=788398


위의 포스트된 글 일부에 스트레티지 패턴이 설명되어있으며, 예제코드도 나와있음.



posted by 여성게
:

java - synchronized 란? 사용법?




멀티스레드를 사용하면 프로그램적으로 좋은 성능을 있지만,

멀티스레드 환경에서 반드시 고려해야할 점인 스레드간 동기화라는 문제는 해결해야합니다.

 

예를 들어 스레드간 서로 공유하고 수정할 있는 data 있는데 스레드간 동기화가 되지 않은 상태에서 

멀티스레드 프로그램을 돌리면, data 안정성과 신뢰성을 보장할 없습니다.

 

따라서 data thread-safe 하기 위해 자바에서는 synchronized 키워드를 제공해 스레드간 동기화를 시켜 data thread-safe 가능케합니다.

 

자바에서 지원하느 Synchronized 키워드는 여러개의 스레드가 한개의 자원을 사용하고자 ,

현재 데이터를 사용하고 있는 해당 스레드를 제외하고 나머지 스레드들은 데이터에 접근 없도록 막는 개념입니다.

 

Synchronized 키워드는 변수와 함수에 사용해서 동기화 있습니다.

하지만 Synchronized 키워드를 너무 남발하면 오히려 프로그램 성능저하를 일으킬 있습니다.

 

이윤 Synchronized 키워드를 사용하면 자바 내부적으로 메서드나 변수에 동기화를 하기 위해 block unblock 처리하게 되는데 

이런 처리들이 만약 너무 많아지게 되면 오히려 프로그램 성능저하를 일으킬수 있는 것입니다

(block unblock 프로그램 내부적으로 어느정도  공수가 들어가는 작업입니다.)

 

따라서 적재적소에 Synchronized 키워드를 사용하는 것이 중요합니다!

 

// 1. 메서드에서 사용하는 경우

public synchronized void method(){// 코드}

 

 

// 2. 객체 변수에 사용하는 경우(block)

private Object obj = new Object();

public void exampleMethod(){ synchronized(obj){//코드 }}

 

 

 

 

 

  1. public class ThreadSynchronizedTest {
  2.  
  3.     public static void main(String[] args) {
  4.     // TODO Auto-generated method stub
  5.         Task task = new Task();
  6.             Thread t1 = new Thread(task);
  7.             Thread t2 = new Thread(task);
  8.             </p><p>
  9.             t1.setName("t1-Thread");
  10.             t2.setName("t2-Thread");
  11.              
  12.             t1.start();
  13.             t2.start();
  14.     }
  15.  
  16. }
  17.  
  18. class Account{
  19.     int balance = 1000;
  20.       
  21.     public void withDraw(int money){
  22.      
  23.         if(balance >= money){
  24.             try{
  25.                 Thread thread = Thread.currentThread();
  26.                 System.out.println(thread.getName() + " 출금 금액 ->> "+money);
  27.                 Thread.sleep(1000);
  28.                 balance -= money;
  29.                 System.out.println(thread.getName()+" balance:" + balance);
  30.                  
  31.             }catch (Exception e) {}
  32.        
  33.         }
  34.     }
  35. }
  36.   
  37. class Task implements Runnable{
  38.     Account acc = new Account();
  39.       
  40.     @Override
  41.     public void run() {
  42.         while(acc.balance > 0){
  43.             // 100, 200, 300 중의 한 값을 임의로 선택해서 출금(withDraw)한다.
  44.             int money = (int)(Math.random() * 3 + 1) * 100;
  45.             acc.withDraw(money);
  46.        
  47.         }
  48.     }
  49. }

 

예제에 대한 설명을 하면

 

Account 라는 클래스에는 balance 잔액과 잔액을 삭감시키는 인출메서드가 있습니다.

 

지난시간 스레드를 만들때 Runnable 인터페이스를 구현하여 Task 만들고 Task Thread 생성시 생성자에 매개변수로 넣으면

우리가 만든 스레드가 Task 정의되어있는 대로 일을 실행한다고 했습니다.

 

Runnable 구현하여 만든 Task 100,200,300 랜덤하게 값을 전달받아 moneny 변수에 할당하고 금액만큼 Account 인스턴스의 인출메서드를 호출해

 balance (잔액) 출금시키는 일을 구현해놨습니다.

 

다음 main 메서드에서 스레드 t1, t2 만들고 각각의 스레드 이름을 정의합니다.

t1, t2 스레드가 시작하면 잔액이 0 때까지 스레드가 경쟁하며 출금시킬 것입니다.

 

 

결과화면

 

 

결과를 보면 분명 잔액이 0 될때까지 출금을 하라고 햇는데 잔액이 마이너스가 됬습니다.

여기서 멀티스레드의 문제점이 발견됩니다. balance(잔액) thread-safe 되지 않았기 때문에 t1 스레드가 잔액을 삭감하기 전에 

t2 balance(잔액) 접근해 삭감을 해버리고 다시 t1 slee()에서 깨어나 balance(잔액) 삭감해버리기 때문에

 

잔액이 0 이하의 마이너스 값을 가지게 됩니다.

 

해결하는 방법은 간단합니다

공유데이터에 대한 접근과 수정이 이루어지는 메서드에 synchronized 키워드를 리턴타입 앞에 붙여주면 됩니다

t1스레드가 먼저 공유데이터나 메서드에 점유하고 있는 상태인 경우 block으로 처리하기 때문에 t1 이외의 스레드의 접근을 막습니다

 

t1스레드가 작업을 끝냈다면 .unblock으로 처리하여 t1 이외의 스레드의 접근을 허락합니다.

 

 

 

 

 

synchronized 키워드로 멀티스레드 동기화 처리

 
  1. public class ThreadSynchronizedTest {
  2.  
  3.     public static void main(String[] args) {
  4.         // TODO Auto-generated method stub
  5.            Task task = new Task();
  6.             Thread t1 = new Thread(task);
  7.             Thread t2 = new Thread(task);
  8.              
  9.             t1.setName("t1-Thread");
  10.             t2.setName("t2-Thread");
  11.              
  12.             t1.start();
  13.             t2.start();
  14.     }
  15.  
  16. }
  17.  
  18. class Account{
  19.     int balance = 1000;
  20.       
  21.     public synchronized void withDraw(int money){
  22.      
  23.         if(balance >= money){
  24.             try{
  25.                 Thread thread = Thread.currentThread();
  26.                 System.out.println(thread.getName() + " 출금 금액 ->> "+money);
  27.                 Thread.sleep(1000);
  28.                 balance -= money;
  29.                 System.out.println(thread.getName()+" balance:" + balance);
  30.                  
  31.             }catch (Exception e) {}
  32.        
  33.         }
  34.     }
  35. }
  36.   
  37. class Task implements Runnable{
  38.     Account acc = new Account();
  39.       
  40.     @Override
  41.     public void run() {
  42.         while(acc.balance > 0){
  43.             // 100, 200, 300 중의 한 값을 임의로 선택해서 출금(withDraw)한다.
  44.             int money = (int)(Math.random() * 3 + 1) * 100;
  45.             acc.withDraw(money);
  46.        
  47.         }
  48.     }
  49. }

결과화면

 

 

 

synchronized 키워드를 사용함으로써 balance 공유데이터에 대한 thread-safe를 시켰기 때문에

데이터나 메서드 점유하고 있는 스레드가 온전히 자신의 작업을 마칠 수 있습니다.


다른 블로그에 좋은 글이 있어서 펌해봅니다
메소드 레벨의 동기화는 해당 인스턴스 자체로 락을 걸어버린다.

자바 동기화 블록은 메소드나 블록 코드에 동기화 영역을 표시하며 자바에서 경합 조건을 피하기 위한 방법으로 쓰인다.

 

 

 

자바 synchronized 키워드

 

 자바 코드에서 동기화 영역은 synchronizred 키워드로 표시된다. 동기화는 객체에 대한 동기화로 이루어지는데(synchronized on some object), 같은 객체에 대한 모든 동기화 블록은 한 시점에 오직 한 쓰레드만이 블록 안으로 접근하도록 - 실행하도록 - 한다. 블록에 접근을 시도하는 다른 쓰레드들은 블록 안의 쓰레드가 실행을 마치고 블록을 벗어날 때까지 블록(blocked) 상태가 된다.

 

synchronized 키워드는 다음 네 가지 유형의 블록에 쓰인다.

인스턴스 메소드

스태틱 메소드

인스턴스 메소드 코드블록

스태틱 메소드 코드블록

 

어떤 동기화 블록이 필요한지는 구체적인 상황에 따라 달라진다.

 

 

인스턴스 메소드 동기화

 

다음은 동기화 처리된 인스턴스 메소드이다.

 

public synchronized void add(int value){

      this.count += value;

}

 

메소드 선언문의 synchronized 키워드를 보자. 이 키워드의 존재가 이 메소드의 동기화를 의미한다.

 

인스턴스 메소드의 동기화는 이 메소드를 가진 인스턴스(객체)를 기준으로 이루어진다. 그러므로, 한 클래스가 동기화된 인스턴스 메소드를 가진다면, 여기서 동기화는 이 클래스의 한 인스턴스를 기준으로 이루어진다. 그리고 한 시점에 오직 하나의 쓰레드만이 동기화된 인스턴스 메소드를 실행할 수 있다. 결국, 만일 둘 이상의 인스턴스가 있다면, 한 시점에, 한 인스턴스에, 한 쓰레드만 이 메소드를 실행할 수 있다. 

 

인스턴스 당 한 쓰레드이다. 

 

 

스태틱 메소드 동기화

 

스태틱 메소드의 동기화는 인스턴스 메소드와 같은 방식으로 이루어진다.

 

public static synchronized void add(int value){

      count += value;

}

역시 선언문의 synchronized 키워드가 이 메소드의 동기화를 의미한다.

 

스태틱 메소드 동기화는 이 메소드를 가진 클래스의 클래스 객체를 기준으로 이루어진다. JVM 안에 클래스 객체는 클래스 당 하나만 존재할 수 있으므로, 같은 클래스에 대해서는 오직 한 쓰레드만 동기화된 스태틱 메소드를 실행할 수 있다.

 

만일 동기화된 스태틱 메소드가 다른 클래스에 각각 존재한다면, 쓰레드는 각 클래스의 메소드를 실행할 수 있다.

 

클래스 -쓰레드가 어떤 스태틱 메소드를 실행했든 상관없이- 당 한 쓰레드이다.

 

 

인스턴스 메소드 안의 동기화 블록

 

동기화가 반드시 메소드 전체에 대해 이루어져야 하는 것은 아니다. 종종 메소드의 특정 부분에 대해서만 동기화하는 편이 효율적인 경우가 있다. 이럴 때는 메소드 안에 동기화 블록을 만들 수 있다.

 

public void add(int value){

 

    synchronized(this){

       this.count += value;   

    }

  }

 

 

이렇게 메소드 안에 동기화 블록을 따로 작성할 수 있다. 메소드 안에서도 이 블록 안의 코드만 동기화하지만, 이 예제에서는 메소드 안의 동기화 블록 밖에 어떤 다른 코드가 존재하지 않으므로, 동기화 블록은 메소드 선언부에 synchronized 를 사용한 것과 같은 기능을 한다.

 

동기화 블록이 괄호 안에 한 객체를 전달받고 있음에 주목하자. 예제에서는 'this' 가 사용되었다. 이는 이 add() 메소드가 호출된 객체를 의미한다. 이 동기화 블록 안에 전달된 객체를 모니터 객체(a monitor object) 라 한다. 이 코드는 이 모니터 객체를 기준으로 동기화가 이루어짐을 나타내고 있다. 동기화된 인스턴스 메소드는 자신(메소드)을 내부에 가지고 있는 객체를 모니터 객체로 사용한다.

 

같은 모니터 객체를 기준으로 동기화된 블록 안의 코드를 오직 한 쓰레드만이 실행할 수 있다.

 

다음 예제의 동기화는 동일한 기능을 수행한다.

 

 public class MyClass {

  

    public synchronized void log1(String msg1, String msg2){

       log.writeln(msg1);

       log.writeln(msg2);

    }

 

  

    public void log2(String msg1, String msg2){

       synchronized(this){

          log.writeln(msg1);

          log.writeln(msg2);

       }

    }

  }

 

한 쓰레드는 한 시점에 두 동기화된 코드 중 하나만을 실행할 수 있다. 여기서 두 번째 동기화 블록의 괄호에 this 대신 다른 객체를 전달한다면, 쓰레드는 한 시점에 각 메소드를 실행할 수 있다. -동기화 기준이 달라지므로.

 

 

스태틱 메소드 안의 동기화 블록

 

다음 예제는 스태틱 메소드에 대한 것이다. 두 메소드는 각 메소드를 가지고 있는 클래스 객체를 동기화 기준으로 잡는다.

 

 public class MyClass {

 

    public static synchronized void log1(String msg1, String msg2){

       log.writeln(msg1);

       log.writeln(msg2);

    }

 

  

    public static void log2(String msg1, String msg2){

       synchronized(MyClass.class){

          log.writeln(msg1);

          log.writeln(msg2);  

       }

    }

  }

 

 

같은 시점에 오직 한 쓰레드만 이 두 메소드 중 어느 쪽이든 실행 가능하다. 두 번째 동기화 블록의 괄호에 MyClass.class 가 아닌 다른 객체를 전달한다면, 쓰레드는 동시에 각 메소드를 실행할 수 있다.

 

 


▶︎▶︎▶︎박철우님의 블로그

 

 

posted by 여성게
:

JAXB - unmarshal & marshal


XML은 이제 데이터를 표현하는 표준이며, Java Object를 XML로 나열할 때 사용하는 다양한 XML 파싱 기술들이 개발되어왔고, 그 중에 많이 쓰이는 대표적인 기술이 SAXParser와 DOMParser이다. 하지만 프로그래머들이 즉각적인 테스트를 할 방법으로 필요한 기술을 원했고 이 경우에 사용하는 기술이 JAXB이다.  JAXB는 Java Architecture for XML Bind의 약자로 XML로 부터 Java Object를 직렬화 하는 Unmarshalling과 이 반대의 Marshalling을 수행 할수 있도록 해주는 API이다.




-Binding Compiler(xjc) : 사용자가 정의한 XML Schema를 가지고, Schema에 정의 된 데이터 모델을 Java Object로 표현 할 수 있는 Java interface 또는 classes를 생성해주는 컴파일러이다.

-Schema-Derived Classes&Interface: XML Schema에 정의된 데이터 모델을 Java Object로 표현 할 수 있는 Java Interface & Classes이다.

-marshal: Java Object를 XML문서로 변환

-Unmarshal: XML문서를 Java Object로 매핑해주는 역할.




xml schema 파일 작성



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" 
    targetNamespace="http://www.epril.com/sqlmap" 
    xmlns:tns="http://www.epril.com/sqlmap" elementFormDefault="qualified">
 
    <element name="sqlmap">
        <complexType>
            <sequence>
                <element name="sql" maxOccurs="unbounded" type="tns:sqlType"/>
            </sequence>
        </complexType>        
    </element>
    <complexType name="sqlType">
        <simpleContent>
            <extension base="string">
                <attribute name="key" use="required" type="string"/>
            </extension>
        </simpleContent>
    </complexType>
</schema>
cs


간단한 xml 스키마를 작성하였다. 이 스키마는 jdbc를 사용할 때, DAO클래스에 난무하는 SQL문을 별도의 XML 파일로 분리하여 DAO에서 간단히 이 XML에 정의된 SQL문을 XML파일을 언마샬링하는 별도의 클래스에서 주입받아 사용하기 위해 작성했던 예제에서 쓰던 XML스키마이다.




이 스키마를 해당 프로젝트 워크스페이스 루트 경로에 저장하고 밑의 cmd 명령어를 실행해준다.



우선 워크스페이스에 있는 해당 프로젝트 폴더까지 가준다. 그리고 xjc 명령어로 시작으로 -p com.web.jpa 는 schema-derived classes&interface가 저장될 패키지명을 fullname으로 써준다. 그리고 -d src는 이 패키지를 포함하는 classes&interface가 저장될 루트 경로를 정해준다.( 위의 src는 편의상 넣은 것이고, 별도로 해당 프로젝트의 코드들이 존재하는 패키지의 루트를 같은레벨로 넣어주면 될것 같다.)


실행 후에 ObjectFactory.java & package-info.java & Sqlmap.java & SqlTyple.java 가 생성된 것을 볼 수 있다.


1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<sqlmap xmlns="http://www.epril.com/sqlmap"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.epril.com/sqlmap ../../../../../sqlmap.xsd">
    <sql key="add">insert</sql>
    <sql key="get">select</sql>
    <sql key="delete">delete</sql>
</sqlmap>
cs

언마샬링을 할 xml 예제 파일이다.




Unmarshalling & Marshalling



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.web.jpa;
 
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
 
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
 
import org.aspectj.weaver.tools.cache.GeneratedCachedClassHandler;
 
public class Test {
 
    public static void main(String[] args) throws JAXBException, FileNotFoundException {
        // TODO Auto-generated method stub
        TestClass t= new TestClass();
        t.readSqlmap();
    }
    
}
 
class TestClass{
    public void readSqlmap() throws JAXBException, FileNotFoundException {
        String contextPath=Sqlmap.class.getPackage().getName();
        System.out.println(contextPath);
        JAXBContext context=JAXBContext.newInstance(contextPath);
        Unmarshaller um=context.createUnmarshaller();
        
        Sqlmap sqlmap=(Sqlmap)um.unmarshal(getClass().getResourceAsStream("sqlmap.xml"));
        
        List<SqlType> sqlList=sqlmap.getSql();
        
        Iterator<SqlType> it=sqlList.iterator();
        while(it.hasNext()) {
            SqlType s=it.next();
            System.out.println(s.getKey()+":"+s.getValue());
        }
        Marshaller ms=context.createMarshaller();
        ms.marshal(sqlmap, new FileOutputStream("marshall.xml"));
    }
}
 
cs


위의 코드는 언마샬링을 시작으로 마지막에 마샬링 기능으로 끝나는 코드이다.


String contextPath=Sqlmap.class.getPackage().getName(); => 이 코드는 Schema-Derived-Classes&Interface가 존재하는 contextPath를 얻어 내기위한 코드이다. 이 코드를 이용하여 JAXBContext 객체를 얻을 수 있다. (result : com.web.jpa)


JAXBContext context=JAXBContext.newInstance(contextPath); =>이 코드는 위의 "JAXB를 간략하게 설명한 그림"에서 Schema-Derived-Classes&Interface가 JAXB를 가르키고 있는 화살표에 해당하는 과정이라고 생각하면 된다. contextPath를 이용해 해당 클래스와 인터페이스를 읽어들여서 파싱에 필요한 정보를 얻어내는 과정이다.


Unmarshaller um=context.createUnmarshaller(); =>언마샬링에 필요한 언마샬러 객체를 생성한다. 


Sqlmap sqlmap=(Sqlmap)um.unmarshal(getClass().getResourceAsStream("sqlmap.xml"); => 우선 sqlmap.xml은 이 Test.java 와 같은 디렉토리에 있다는 가정하에 진행한다. 그렇기에 getClass().getResourceAsStream("sqlmap.xml")을 통해 Test.java와 같은 레벨에 있는 sqlmap.xml을 자원을 inputStream객체형태로 리턴을 받는다. 그리고 이 리턴받은 자원을 이용해 unmarshalling을 진행한다. unmarshal( )의 리턴타입은 Object이기에 이것을 Sqlmap클래스 타입으로 cast 해준다. Sqlmap & SqlType 클래스의 코드는 밑과 같다. 코드를 천천히 해석하다보면 전부다는 아니지만 대략적으로 이런 형태를 가졌구나 정도는 이해가 된다. Sqlmap은 SqlType의 sql 엘리먼트를 List 형태로 갖고 있는 형태이고, SqlType은 sql 엘리먼트의 타입을 보여주는 클래스이다. key&value 형태로 되있는 sql 엘리먼트라는 것이 짐작이 갈 것이다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
//
// 이 파일은 JAXB(JavaTM Architecture for XML Binding) 참조 구현 2.2.8-b130911.1802 버전을 통해 생성되었습니다. 
// <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>를 참조하십시오. 
// 이 파일을 수정하면 소스 스키마를 재컴파일할 때 수정 사항이 손실됩니다. 
// 생성 날짜: 2018.06.03 시간 02:20:59 PM KST 
//
 
 
package com.web.jpa;
 
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
 
 
/**
 * <p>anonymous complex type에 대한 Java 클래스입니다.
 * 
 * <p>다음 스키마 단편이 이 클래스에 포함되는 필요한 콘텐츠를 지정합니다.
 * 
 * <pre>
 * <complexType>
 *   <complexContent>
 *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 *       <sequence>
 *         <element name="sql" type="{http://www.epril.com/sqlmap}sqlType" maxOccurs="unbounded"/>
 *       </sequence>
 *     </restriction>
 *   </complexContent>
 * </complexType>
 * </pre>
 * 
 * 
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "sql"
})
@XmlRootElement(name = "sqlmap")
public class Sqlmap {
 
    @XmlElement(required = true)
    protected List<SqlType> sql;
 
    /**
     * Gets the value of the sql property.
     * 
     * <p>
     * This accessor method returns a reference to the live list,
     * not a snapshot. Therefore any modification you make to the
     * returned list will be present inside the JAXB object.
     * This is why there is not a <CODE>set</CODE> method for the sql property.
     * 
     * <p>
     * For example, to add a new item, do as follows:
     * <pre>
     *    getSql().add(newItem);
     * </pre>
     * 
     * 
     * <p>
     * Objects of the following type(s) are allowed in the list
     * {@link SqlType }
     * 
     * 
     */
    public List<SqlType> getSql() {
        if (sql == null) {
            sql = new ArrayList<SqlType>();
        }
        return this.sql;
    }
 
}
 
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//
// 이 파일은 JAXB(JavaTM Architecture for XML Binding) 참조 구현 2.2.8-b130911.1802 버전을 통해 생성되었습니다. 
// <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>를 참조하십시오. 
// 이 파일을 수정하면 소스 스키마를 재컴파일할 때 수정 사항이 손실됩니다. 
// 생성 날짜: 2018.06.03 시간 02:20:59 PM KST 
//
 
 
package com.web.jpa;
 
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
 
 
/**
 * <p>sqlType complex type에 대한 Java 클래스입니다.
 * 
 * <p>다음 스키마 단편이 이 클래스에 포함되는 필요한 콘텐츠를 지정합니다.
 * 
 * <pre>
 * <complexType name="sqlType">
 *   <simpleContent>
 *     <extension base="<http://www.w3.org/2001/XMLSchema>string">
 *       <attribute name="key" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
 *     </extension>
 *   </simpleContent>
 * </complexType>
 * </pre>
 * 
 * 
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "sqlType", propOrder = {
    "value"
})
public class SqlType {
 
    @XmlValue
    protected String value;
    @XmlAttribute(name = "key", required = true)
    protected String key;
 
    /**
     * value 속성의 값을 가져옵니다.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getValue() {
        return value;
    }
 
    /**
     * value 속성의 값을 설정합니다.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setValue(String value) {
        this.value = value;
    }
 
    /**
     * key 속성의 값을 가져옵니다.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getKey() {
        return key;
    }
 
    /**
     * key 속성의 값을 설정합니다.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setKey(String value) {
        this.key = value;
    }
 
}
 
cs



<언마샬링의 결과>



Marshaller ms=context.createMarshaller();
        ms.marshal(sqlmap, new FileOutputStream("marshall.xml"));


=>이 코드는 XML 스키마에 적합한 Sqlmap 형태의 객체(언마샬링한 객체를 그대로 재활용하였다.)를 marshal.xml 이라는 xml파일로 내보내는 코드이다.

    

<마샬링결과>


별도의 경로를 지정해주지 않았음으로 워크스페이스에 해당 프로젝트 폴더 루트에 저장이 된다. 


저희가 사용하는 xBatis 같은 경우에도 모든 sql문을 xml파일로 관리하여 connection관리 등 설정 또한 xml로 관리를 합니다. 그러한 것도 정확히 JAXB기술을 사용하는지는 알수 없으니 이와 비슷한 기술을 사용하여 xBatis를 사용하는 것으로 알고 있습니다.

이상으로 JAXB의 간단한 예제를 보여드렸습니다. 다음에는 JAXB와 동일한 기능을 하는 많은 OXM API들을 소개시켜드리겠습니다.(SAX,DOM포함)

posted by 여성게
: