트리의 모든 노드를 한번씩 방문하여 모든 정보에 대한 선형 순서를 만들어 내는 방법을 알아보자


트리, 이진트리에 대해서는 이전글을 참고자도록 하자.

2016/10/18 - [Yame Programmer/자료구조&알고리즘] - 야매 개발자 자료구조 가이드[트리(tree)]


이진트리나 이진트리의 순회방법을 난 이제 알았는데 컴공 학생들은 2학년때 배운단다... 부럽...



순회방법에는 3가지가 있다.


1. 전위 순회(Preorder Traversal)

2. 중위 순회(Inorder Traversal)

3. 후위 순회(Postorder Traversal)



1. 전위 순회(Preorder Traversal)

<전위 순회의 탐색 순서 : A B D E C F G>


전위 순회의 특징은 루트 노드를 가장 먼저 탐색 한다.

루트 노드 부터 시작해 왼쪽 하위트리를 탐색하고 왼쪽 트리의 탐색이 끝나면 오른쪽 하위 트리를 탐색하는 방식 입니다.



2. 중위 순회(Inorder Traversal)

<중위 순회 탐색 순서 : D B E A F C G>


중위 순회의 특징은 루트노드가 탐색 순서의 중앙에 위치 합니다.

왼쪽 하위트리 부터 시작해 루트를 거쳐 오른쪽 하위 트리를 탐색 합니다. 



3. 후위 순회(Postorder Traversal)

<후위 순회 탐색 순서 : D E B F G C A>


후위순위의 특징은 루트가 가장 마지막에 탐색 됩니다.

왼쪽 하위 트리에서 시작해 오른쪽 하위트리를 탐색하고 루트 노드를 탐색합니다.



구현된 코드를 참조하실분은


http://songeunjung92.tistory.com/29

http://marobiana.tistory.com/83


이분들의 포스팅을 참고하지면 좋을 것 같습니다.



트리(Tree)라는 자료 구조에 대해서 알아보자.

트리는 말그대로 나무와 유사한 자료 구조이다. 


뿌리에서 시작해 가지가 뻗어나가고 뻗어나간 가지에서


다시 잎사귀가 생긴것과 유사한 모양을 가지고 있는데 그 나무를 뒤집어 놓은것과 같다.


아래 그림을 보도록 하자


<트리는 위와 같은 구조를 가지고 있다.>



부모자식관계 : B의 부모는 A , B의 자식은 D,E 

루트 : 트리구조에서 가장 위에 있는 노드, 부모가 없다.

노드 : 트리를 이루는 한 요소 (ex 위 그림의 A, B, C , D ....)

차수 : 어떤 노드가 가지고 있는 자식의 노드 개수를 말한다. 예를 들어 B노드의 차수는 2이다.

        (트리의 차수를 말할땐 가장 높은 차수를 말한다. 예를들어

         D노드에 자식이 하나 더 생긴다면 위 트리의 차수는 3이다)

레벨 : 트리를 이루는 각 층의 번호이다. 깊이 내려갈 수록 레벨이 높아진다. 

높이,깊이 : 루트 노드부터 가장 끝에 있는 노드 까지의 깊이(경로의 길이) 

               위 트리의 깊이는 3이다.




Left chaild-right Sibling 표현법


트리의 표현법이다. 아래의 그림처럼 한개의 노드에 자식과 형제의 포인터를 지니는 구조이다.




Child : 자식노드를 가르킨다

Sibling : 형제노드를 가르킨다


가장 위에 있는 노드부터 시작해 왼쪽에 있는 자식노드를 구하고 그 노드의 자식노드와 형제 노드를 구한다

이렇게 반복을 하면 모든 자식 노드를 찾을 수 있게 된다.





이진 트리 (Binary Tree)


최대 2개의 자식 노드를 가질 수 있는 트리이다.

한 노드당 자식을 1개만 가질 수도 있고 아무것도 없을수도 있고 2개를 가질수도 있다.

이진트리의 종류에는


완전 이진 트리(Complete Binary Tree)와 사향 이진 트리(Skewed Binary Tree), 포화 이진 트리(Full Binary Tree)가 있다.



<완전 이진트리(Complete Binary Tree)>


완전 이진트리는 어떤 노드 두개의 레벨차가 1이하 이며 왼쪽부터 오른쪽으로 채워지는 트리 이다.

만약 왼쪽에서 오른쪽으로 가다가 중간에 노드가 비어있다면 그건 완전 이진트리가 아니다.

예를들어 위의 그림에서 E노드 아래에 자식노드가 생성되면 완전 이진트리가 아니게 된다.




<사향 이진트리(Skewed Binary Tree)>


사향 이진트리는 루트노드를 제외한 모든 노드가 부모 노드의 왼쪽 자식 노드이거나 반대로 오른쪽 자식 노드인 것을 말한다. 즉 노드의 방향이 한쪽 방향으로만 치우쳐진 트리이며 다른말로 경사,편향 이진트리 라고 불리기도 한다.




<포화 이진트리(Full Binary Tree)>


포화 이진트리는 완전이진트리와는 다르게 최대한 노드가 가득차 있는 트리를 말한다. 가장 마지막 노드를 제외한 모든 노드가 자식노드를 2개씩 가지고 있다. 

(모든 포화 이진트리는 완전 이진트리 라고 부를수 있지만 그 반대는 성립하지 않는다.). 



이러한 뭔가 당연한것 같은(따로 공부하지 않아도 될것 같은) 트리구조를 왜 알아야 하는지 얼마 전까진 사실 잘 몰랐다. 

그러나 후에 포스팅할 트리의 순회구조와 그 다음 위상정렬(DFS 깊이 우선 탐색), 너비 우선탐색(BFS)를 거쳐

다익스트라 알고리즘과 같은 조금더 난이도 있는 알고리즘을 이해하기 위해선 이 트리구조에 대해서

기본적인 이해가 필요하기 떄문이라는걸 근 일주일간의 삽질 끝에 체감했다.


역시 기초가 중요하다.



참조: http://blog.eairship.kr/215


순차탐색은 쉬웠으니 빠르게 다음 공부할 이진탐색(Binary Search)에 대해서 알아보자.


이진탐색은 한번 비교를 할때마다 비교를 하는 범위가 반으로 줄어든다


이게 순차탐색과 비교해서 알마나 큰 차이가 있냐면


만약 70억개의 데이터를 검색했을때 순차탐색은 최대 70억 번 평균 35억 번을 비교 하는데 


이진탐색은 최대 33번의 비교로 탐색을 완료 할 수가 있다.


대신 조건이 하나 있는데 이진탐색에 사용하는 배열을 정렬되어진 배열이어야 한다는 것이다.



그림으로 알아보도록 하자




먼저 배열의 중앙을 선택하고 찾고자 하는 값보다 큰지 작은지 비교를 합니다.



5는 7보다 작기 때문에 5에서 오른쪽에 있는 요소들로 이동하여 


다시 그중 중앙에 위치한 값을 선택해 비교 합니다.



8은 7보다 크기 때문에 왼쪽으로 이동을 합니다


이런식으로 범위를 좁혀나가다 보면 원하는 값을 찾게 되겠죠



한번 검색을 수행할떄마다 범위가 반으로 줄어든다는 말이 이해가 되지 않는다면 


노트에 펼쳐놓고 위의 그림을 따라서 그리고 검색하지 않는 범위부터 반으로 접어 보면 이해가 갈 것이다.



이진탐색의 시간 복잡도 : O(log n) 



이진탐색의 구현 소스를 보면 생각보다 간단하다. 위에 그림에 있는 if문을 반복할 뿐이다.


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
int BinarySearch(int dataArr[], int size, int findData)
{
    int low = 0, high = size - 1, mid;
        // low = 검색 할 범위의 가장 왼쪽
        // high = 검색할 범위의 가장 오른쪽 
 
    // high가 low보다 작아진다면 찾으려는 데이터가 데이터 집합에 없다.
    while (low <= high)
    {
        // 중앙값은 low와 high를 더한 값을 2로 나누면 된다.
        mid = (low + high) / 2;
        // 만약 찾으려는 값이 중앙값보다 작다면 high를 mid - 1로 둔다.
        // 찾는값이 중앙값보다 작기 때문이다.
        // 이때 mid-1 = 다음검색할 범위의 가장 오른쪽이 된다.
        if (dataArr[mid] > findData) high = mid - 1;
        // 만약 찾으려는 값이 중앙값보다 크다면 low를 mid + 1로 둔다.
        // 찾는값이 중앙값보다 크기 때문이다
        // 이때 mid+1 = 다음 검색할 범위의 가장 왼쪽이 된다.
        else if (dataArr[mid] < findData) low = mid + 1;
        // 중앙값과 찾으려는 값이 일치하면 mid를 반환한다.
        else return mid;
    }
    // 데이터를 찾지 못하면 -1를 반환한다.
    return -1;
}
 
int main()
{
    int dataArr[] = {123456789101112131415172124262728};
    int length = sizeof dataArr / sizeof dataArr[0];
    int input, ret;
 
        scanf("%d"&input);
        ret = BinarySearch(dataArr, length, input); // 이진탐색 실행
        if (ret != -1) printf("찾으려는 데이터는 %d번째에 있습니다.\n", ret + 1);
        else printf("데이터를 찾을 수 없습니다.\n");
    
    return 0;
}
cs


(참조 http://blog.eairship.kr/246)


주석이 많아서 좀 복잡해 보이는데 주석도 빼버리면 


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
int BinarySearch(int dataArr[], int size, int findData)
{
    int low = 0, high = size - 1, mid;
 
    while (low <= high)
    {
        mid = (low + high) / 2;
        if (dataArr[mid] > findData) high = mid - 1;
        else if (dataArr[mid] < findData) low = mid + 1;
        else return mid;
    }
    return -1;
}
 
int main()
{
    int dataArr[] = {123456789101112131415172124262728};
    int length = sizeof dataArr / sizeof dataArr[0];
    int input, ret;
 
    scanf("%d"&input);
    ret = BinarySearch(dataArr, length, input); // 이진탐색 실행
 
    if (ret != -1)
        printf("찾으려는 데이터는 %d번째에 있습니다.\n", ret + 1);
    else 
        printf("데이터를 찾을 수 없습니다.\n");
    
    return 0;
}
cs


이런 소스가 된다.


생각보다 간단한 코드다.

순차탐색이 뭐지? 하고 검색해 들어 온 사람들 대부분은 해당 내용을 보면 매우 실망할 것이다.


왜냐하면 이미 반복문을 배우면서 적어도 한번 이상은 써봤을 탐색 방법이기 때문이다.


2년동안 이 방식으로 검색을 하면서 이 알고리즘의 이름이 순차탐색 이라는 것을 난 오늘 알았다.



매우 단순하다



시간 복잡도:  최악O(n) , 평균 (n+1)/2


시간복잡도가 최악인 경우는 원하는 값이 가장 마지막에 있을 경우이다.



데이터가 배열에 있으면 배열의 처음부터 끝까지 차례대로 비교하면서 원하는 


데이터를 찾는 알고리즘이다.


이 알고리즘은 단방향으로 탐색을 하기 때문에 선형탐색(Linear Search)라고 부르기도 한다.



코드는 매우 단순하다.


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
 
// for문을 돌면서 데이터를 찾는다. 찾느 데이터가 없는 경우 -1 반환
// 찾는 데이터가 있으면 해당 번째 반환
int search(int dataArr[], int lengthint findData)
{
    for(int i = 0; i < length; i++)
        if (dataArr[i] == findData) return i;
    return -1;
}
 
 
 
int main()
{
    int arr[] = {232514566721322451};
    int length = sizeof arr / sizeof arr[0];
    int findData = 0;
    int findIndex = 0;
 
    printf("찾으시는 데이터를 입력해주세요: ");
    scanf("%d"&findData);
 
    findIndex = search(arr, length, findData); // 
 
    if(findIndex == -1) printf("데이터를 찾지 못했습니다.\n");
    else printf("데이터는 %d번째에 있습니다.\n", findIndex);
    
 
    return 0;
}
cs



(참조 http://blog.eairship.kr/245 )




순차탐색은 코드구현이 매우 쉬워서 짧은 기리의 데이터를 검색할때 사용하는 것이 좋은 것 같다.

얼마전 쿠팡에서 서류 합격 메일이 날아왔다. 구글링을 해보니 쿠팡같은 경우 서류는 거의다 통과시켜 놓고 


problem solving test를 통해서 엄청나게 걸러버리는 듯 하다.


개발자로 취직해서 일한지 2년이 가까워 지면서 웹쪽 HTML CSS JS는 많이 해봤지만


순수 자바 쪽은 거의 건들이지 않았다는 것에서 불안감이 엄청나게 느껴졌다.


학교에서 전공수업 잘 듣고 자료구조나 알고리즘 공부 열심히 했으면 붙을 수 있다고 하는데


난 전공자가 아니다;; 학교에서 기초적인걸 배우긴 했지만 자바 클래스 만들고 'Hello word' 띄우는 수준?


안드로이드로 인텐트 날려서 화면 전환하는 정도 까지만 배웠고 이론적인건 소프트웨어공학 원론쯤?


그 외엔 죄다 마케팅이나 경영, 혹은 발표과제로 점수먹는 수업이라든지 실음과 전공수업이나 들었지 ㅋ


말하자면 전공이라기 보단 반공(?)이라고 말하는게 어울릴 듯 하다.



아무튼 자료구조는 스택과 큐밖에 모르고 정렬 알고리즘은 버블정렬 밖에 모르는 상태에서


어렵다고 소문난 쿠팡 테스트를 통과하려면 짧은 시간동안 공부를 많이 해야 겠다 싶었다.


면접 후기라던지 자료구조 기초라던지 알고리즘을 어떻게 공부해야 하는지 찾아 보면서


퀵정렬이라는 것이 있다는걸 태어나서 어제 처음 알게 되었다.



버블정렬만 있으면 만사 오케이인줄 알았고 심지어 프로젝트중에선 DB에서 불러올때 ORDER BY만 떄려버리면 해결이니


정렬 알고리즘을 사용할 필요성도 못느끼고 있었는데....  좀 충격이었다.



심지어 아래 동영상을 보고 가장 먼저 들었던 생각은


"버블정렬 개쓰레기였네??"


일단 정렬의 종류는 여러가지 있는데 아래 영상을 보도록 하자. 가운데 표시된 퀵정렬이 대체로 가장 빠른 결과를 보여준다. <출처: Viktor Bohush, 유튜브>






그리고 퀵정렬에 대해서 좀더 큰화면으로 보자면 <출처: Timo Bingmann / 유튜브>




이렇게 보여질 수 있다.



해당 내용을 직접 포스팅 하려 해보았으나 알고리즘이나 기초지식이 1도 없는 상태에서 이해 할 수 있는 포스팅 만드는데


시간이 너무 오래 걸린다. 퀵정렬이 무엇인지 이곳 저곳 둘러보던중


아주아주아주 설명을 잘 해놓은 게시글을 발견했다.


허접하게 내가 만드는 것 보다는 정말 잘 만든 다른사람의 글을 보는 것이 더 나을 것이라고 판단 했다.



http://navercast.naver.com/contents.nhn?rid=2871&contents_id=90163


위 주소로 들어가보면 퀵정렬에 대해 아주 잘 설명해 놓은 글이 있다.


한번 쭉 읽어 보고 이해가 좀 되겠다 싶다면 슈도코드 부분은 스킵하고 다음 소스를 보도록 하자



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
// 값을 비교하고 로우와 하이를 이동시키면서 값의 교환이 이루어지는 함수
public static int partition(int arr[], int left, int right) {
 
    int pivot = arr[(left + right) / 2];
 
    while (left < right) {
        while ((arr[left] < pivot) && (left < right))
            left++;
        while ((arr[right] > pivot) && (left < right))
            right--;
 
        if (left < right) {
            int temp = arr[left];
            arr[left] = arr[right];
            arr[right] = temp;
        }
    }
 
    return left;
}
 
 
public static void quickSort(int arr[], int left, int right) {
 
    if (left < right) {
        // 값을 비교하고 로우와 하이를 이동시키면서 값의 교환이 이루어지는 함수
        int pivotNewIndex = partition(arr, left, right);
        // quick sort의 설명중
        // 자주 나오는 '재귀', '분할' 이라고 설명되어지는 부분이다.
        quickSort(arr, left, pivotNewIndex - 1);
        quickSort(arr, pivotNewIndex + 1, right);
    }
 
}
 
// 
public static void main(String[] args) {
    int[] arrs = { 69103021683122 };
    quickSort(arrs, 0, arrs.length - 1);
    System.out.println("결과");
 
    for (int i : arrs) {
        System.out.print(i + " ");
    }
}
cs


<참조: http://creatordev.tistory.com/70>



처음 올린 링크의 글을 정독했다면 위 소스를 보면서 직접 소스를 디코딩 해보도록 하자.

소스의 처음부터 정렬이 종료될때까지 직접 소스를 따라가면서 정렬을 해보는 것이 이해하는데 가장 큰

도움이 될 것이다.



요즘 웹쪽엔 내가 아는 지식이 후달려서 더 포스팅하기 힘들었는데 노드js나 리엑트js 해보기 전까진


자료구조와 정렬에 대해서 포스팅을 해봐야 겠다. 나에게도 큰 도움이 되는듯 하다.

웹 화면의 애니메이션 효과를 jquery의 animation 함수만을 이용해서 사용했었다면 CSS만으로 애니메이션 효과를 주는 방법을 알아보도록 하자


간단하게 


div안의 div위에 마우스르 올렸을때 움직이도록 해보자



1
2
3
<div class="container">
    <div class="move-box"></div>
</div>
cs


간단하게 이런 html요소를 만들어 놓고


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
.container
{
    width:300px;
    height:60px;
background-color:#FFF;
}
.move-box
{
    display:inline-block;
    width:50px;
    height:50px;
    background-color:#F0F;
}
.move-box:HOVER
{
    animation-duration: 3s;
    animation-name: slidein;
}
@keyframes slidein {
  from {
    margin-left: 100%;
  }
  to {
    margin-left: 0%;
  }
}
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
.container
{
    width:300px;
    height:60px;
background-color:#FFF;
}
.move-box
{
    display:inline-block;
    width:50px;
    height:50px;
    background-color:#F0F;
}
.move-box:HOVER
{
    animation-duration: 3s;
    animation-name: slidein;
}
@keyframes slidein {
     0%    {margin-left: 0%;}
    20%    {margin-left: 50%;}
    30%    {margin-left: 70%;}
    50%    {margin-left: 90%;}
    70%    {margin-left: 50%;}
    100%    {margin-left: 0%;}
}
cs



keyframes 안의 내용이 from, to 에서 퍼센테이지로 바뀌었죠


이런식으로 바꾸면


아래와 같이 움직이게 됩니다.







animation-name: slidein;


애니메이션 네임을 지정하고 @keyframes 에 어떤식으로 움직일지 선언을 하면 됩니다.



duration은 애니메이션이 어느 시간에 걸쳐 일어나게 되는지 지정합니다.


이외에도 여러가지가 있는데 


아래와 같습니다.



animation-delay
엘리먼트가 로드되고 나서 언제 애니메이션이 시작될지 지정합니다.
animation-direction
애니메이션이 종료되고 다시 처음부터 시작할지 역방향으로 진행할지 지정합니다.
animation-duration
한 싸이클의 애니메이션이 얼마에 걸쳐 일어날지 지정합니다.
animation-iteration-count
애니메이션이 몇 번 반복될지 지정합니다. infinite로 지정하여 무한히 반복할 수 있습니다.
animation-name
이 애니메이션의 중간 상태를 지정합니다. 중간 상태는  @keyframes 규칙을 이용하여 기술합니다.
animation-play-state
애니메이션을 멈추거나 다시 시작할 수 있습니다.
animation-timing-function
중간 상태들의 전환을 어떤 시간간격으로 진행할지 지정합니다.
animation-fill-mode
애니메이션이 시작되기 전이나 끝나고 난 후 어떤 값이 적용될지 지정합니다.




요즘 들어 frontEnd 개발자로 먹고 산다는 것에 큰 부담감을 느끼고 있다.


지금 있는 회사에선 전자정부 프레임워크와 제이쿼리가지고 개발하는 것이 전부


2년동안 이곳에서 개발하며 늘어난 것은 다른 js frameWork를 사용 하는 노하우?


레퍼런스 뒤지기? 구글링?  이정도와 스프링 기반 프레임워크를 조금 다루는 정도의 수준이다



이직을 고민하며 요즘 올라오는 채용글들을 보니 이건또 뭔가 react.js??


불과 얼마전까지만해도 node js와 angular js였던거 같은데... 또 새로운 기술이??


nodejs는 책사놓고 펼쳐보지도 않고 개인적으로 진행하는 프로젝트가 끝나면 시도해 봐야지 하면서도


프로젝트가 끝나지 않아 계속해서 미뤄지고 있는데


react??? 이놈이 뭔가하고 구글링좀 해보니 페이스북 개발자들이 만든거라고 한다


그런데 flux?? webpack?  


http://www.looah.com/article/view/2054 이곳의 글을 보면


HTML과 제이쿼리로 개발하는 나는 원시인이다....


새로운 기술이 내가 프로젝트 몇개 해보기도 전에 마구마구 튀어나온다.


과연 내가 지금 경쟁력이 있는 개발자 일까? 라는 생각도 들고


불과 1년전 까지만 해도 나는 1년차 중에선 개발 잘하는 킹왕짱 개발자야!!!! 라고 자만심에 쩔어있었지만


지금은... 그냥 원시인 개발자가 된 기분이다. 이러다 내가 30대 후반 40대가 되었을때 대체 얼마나 많은것들이 나올까


지금 개발하는 것에서 지루함을 느끼지만 새로운 것을 배운다는 것에 기대감과 두려움과 부담감들이 섞인다.


망할 매일 입고다니는 옷도 트렌드를 못따라가고 있는데 기술 트렌드라니 ㅋㅋㅋㅋ



새로운 기술을 사용하는 곳으로 빨리 이직을 하는 것이 좋을지 지금 회사를 다니면서 따로 새로운 기술을 익히고


이직을 하는것이 좋을지도 고민이고


요즘들어 이런 생각이 만이 든다.

보통 전자정부같은 Spring기반의 프로젝트를 할때 에러가 나던가 혹은 서버에서 어떤 액션이 일어나면 이클립스 콘솔창에


많은 정보들이 후두두둑 하고 올라간다.


내가 날린 쿼리가 무엇인지, 내가 요청한 URL은 무엇있지 버그나 오류가 있을땐 어떤 오류인지 온갖 정보들이 나타나는데


가끔 쿼리를 실행해도 어떤 쿼리를 실행했는지 나타나지 않는 경우가 있다. 단지 오류가 났을때만 콘솔창에 로그를 뿌려주는 경우


이럴때 버그가 있더라도 시스템에서 오류가 나지 않는다면  어떤 쿼리를 실행시켰는지 어떤 url을 요청했는지 알기 힘들게 된다.



이런경우의 대부분은 로깅 프로퍼티의 설정 레벨이 warn 정도로 되어 있는 경우 인것 같다.


정상적인 쿼리도 콘솔창에 보이게 할수 있도록 설정하는 방법을 알아보자.



log4j를 쓰던 log4j2를 쓰던 logback를 쓰던 설정 xml파일의 이름엔 [log]가 들어간다


프로젝트의 구성,설계에 따라 해당 xml파일은 리소스에 들어가는 경우도 있고 web-inf에 들어가는 경우도 있다


파일을 열어 보면


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration 
    xmlns:log4j="http://jakarta.apache.org/log4j/" 
    debug="false">
    
    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d %5p [%c] %m%n" />
        </layout>   
    </appender>
    
    <logger name="egovframework" additivity="false">
        <level value="DEBUG"/>
        <appender-ref ref="console"/>
    </logger>
 
    <!-- log SQL with timing information, post execution -->
    <logger name="jdbc.sqltiming" additivity="false">
        <level value="WARN" />
        <appender-ref ref="console" />
    </logger>
 
    <logger name="jdbc.audit" additivity="false">
        <level value="WARN" />
        <appender-ref ref="console" />
    </logger>
    
    <logger name="jdbc.resultset" additivity="false">
        <level value="WARN" />
        <appender-ref ref="console" />
    </logger>
    
    <logger name="org.springframework" additivity="false">
        <level value="INFO" />
        <appender-ref ref="console" />
    </logger>
    
    <logger name="java.sql" additivity="false">
        <level value="WARN"/
        <appender-ref ref="console"/
    </logger
    
    <root>
        <level value="INFO" />
        <appender-ref ref="console" />
    </root>
                       
</log4j:configuration>
 
cs


이런식으로 되어 있을 것이다. 이부분은 log4j나 logback나 크게 다른 부분은 없다.


단지 로거 안에 레벨이 들어가냐 안들어가냐 어펜드가 포함되냐 안되냐 그리고 최상단에의 정보들같은  이런 약간의 차이만 있을 뿐이다.



보면 <level value="warn"/> 이라고 되어있는데


여기서 밸류값에 들어가는 종류는


DEBUG, INFO, WARN, ERROR, FATAL, ALL, OFF 가 있다.



ALL은 모든 로깅

OFF는 로깅 해제


DEBUG = 디버깅

INFO = 강조정보

WARN = 경고

ERROR = 오류

FATAL = 심각한 오류


라는 의미 이며


WARN은 경고수준 이하 레벨의 정보는 로깅하지 않는 다는 뜻이다

그러니 정상적으로 돌아가는 SQL쿼리문은 콘솔창에 나타나지 않으니


레벨의 밸류를 전부 DEBUG 로 바꿔주자


로거 name="" << 이부분의 네임은 어떤 부분에서 로그를 띄워줄지 적는 부분이다.


뭔지 모르겠고 sql만 띄우고 싶다면 jdbc.sqltiming  이부분의 레벨만 DEBUG 로 바꿔보도록 하자


설정에 따라  


1
2
3
4
5
6
7
<logger name="org.springframework" level="DEBUG " additivity="false">
     <appender-ref ref="console" />
</logger>
  
<logger name="org.mybatis" level="DEBUG "  additivity="false">
    <appender-ref ref="console" />
</logger>
cs


이런식의 스프링프레임워크나 마이바티스에 관한 로그를 띄울 수도 있다.



아무튼 원하는 부분의 레벨을 DEBUG로 설정한 후 구동시키면 정상적으로 실행되는 정보들도 출력이 된다.


additivity 이부분은 

http://seosh81.info/?p=404  << 이분의 블로그에서 잘 설명이 되어 있으니 궁금하면 들어가서 보자


간단히 설명하자면 로그이벤트를 부모에게 전달하도록 하느냐 마느냐 정도의 설정부분인것 같은데


어차피 나는 야매개발자라서 정확하고 자세하겐 모른다.



 <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d %5p [%c] %m%n" />
        </layout>   
    </appender>


이부분에서 ConversionPattern이부분의 밸류는 콘솔에 표현되는 표현식을 지정하는 부분이다.


표현식은 아래와 같다.


%m: 로그 내용 출력
%p: debug, info, warn, error, fatal 등의 priority 출력
%r: 어플이 시작 후 이벤트가 발생하는 시점까지의 경과시간 밀리세컨드로 출력
%c: package 출력
%c{n}: n(숫자) 만큼의 package를 가장 하단 부터 역으로 출력
        예) %c{2} 일때 a.b.c 는 b.c 로 출력된다.
%n: 개행문자 출력. 플렛폼에 따라 \r\n 또는 \n 출력.
%d: 이벤트 발생 날짜 출력 ( 프로그램의 실행속도를 느리게 한다.)
        예) %d{HH:mm:ss} 또는 %d{dd MMMM yyyy HH:mm:ss}
%C: 호출자의 클래스명 출력
        예) %C{2} 일때 a.b.c.TestClass 는 c.TestClass 로 출력된다.
%M: method 이름.
%F: 프로그램 파일명.
%l: caller의 정보
%L: caller의 라인수
%x: thread와 관련된 NDC(nested diagnostic context) 
%X: thread와 관련된 MDC(mapped diagnostic context) 
%%: % 표시를 출력  
%t: 쓰레드 이름


로그띄울줄 몰라서 들어온 사람이라면 어차피 저 표현식을 쓸일은 없을테니 그냥 이런게 있다는것만 알아두고 넘어가자.



Mssql 에서는 IF EXISTS 함수를 사용해 값이 존재하는지 확인 후


값이 있을때와 없을때 다른 쿼리를 사용 할 수 있다.


oracle에서는 MERGE 함수를 사용해 유사한 기능을 구현 할 수 있다.



1
2
3
4
5
6
7
8
9
10
11
12
13
 MERGE INTO 
     TBL_NAME -- table명
USING DUAL 
    ON (column1 = '존재하는지 확인할 값')
WHEN MATCHED THEN -- 존재하는 경우 실행   
    UPDATE SET  
        column2= 'update할 값1',
        column3= 'update할 값2'
WHEN NOT MATCHED THEN -- 존재하지 않는 경우 실행
    INSERT 
        (column1, column2, column3)
    VALUES
        ('존재하는지 확인할 값','insert할 값1''insert할 값2')
cs



위와 같은 쿼리문을 작성하면 된다


더 간단하게 나타내자면


1
2
3
4
5
6
7
8
 MERGE INTO 
     TBL_NAME -- table명
USING DUAL 
    ON (column1 = '존재하는지 확인할 값')
WHEN MATCHED THEN 
    -- 값이 존재하 경우 실행할 쿼리 
WHEN NOT MATCHED THEN 
    -- 값이 존재하지 않을 경우 실행할 쿼리
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
merge into target_table tt
    using 
        ( -- 조인하는 테이블
            select
                te.te_cd tt_cd
                ,te.description tt_disp
                ,te.jt_no jt_no
                ,decode(te.is_flag, 'normal''Y''N') is_flag
            from
                temp_table te
            left join join_table jt
                on te.te_no=jt.jt_no
            where
                1=1
        ) so
    on (tt.tt_cd=so.tt_cd and tt.jt_no=so.jt_no) -- 조인한 테이블의 값 
     
    when matched then
        update set
            tt.is_flag=so.is_flag
            ,tt.tt_disp=so.tt_disp
 
 
    when not matched then
            insert (tt_no, tt_cd, tt_disp, jt_no, is_flag, reg_date)
            values(
                sq_target_table.nextval
                , so.tt_cd
                , so.tt_disp
                , so.jt_no
                , so.is_flag
                , sysdate
            );
cs

(코드 출처 :  http://yangtaeho.tistory.com/entry/%EC%98%A4%EB%9D%BC%ED%81%B4-MERGE-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-update-insert-%EB%8F%99%EC%8B%9C%EC%97%90  


http://zinlee.tistory.com/185 에서 더 자세한 내용을 확인할 수 있습니다.

 )



이렇게 사용 할 수도 있다. 단순히 한 테이블에 대해서만 값이 존재하는지 확인 하는 것이 아니라


조인했을때의 값도 활용 할 수가 있다.

개발을 시작한지 초반에, 그리고 아직도 종종 자주 하는 실수중에 하나를 했을때 나타나는 메세지 이다.


java.sql.SQLException: ORA-00911: 문자가 부적합합니다.


쿼리문에 잘못된 문자를 사용했다는 의미 인데.


90%정도는 바로 쿼리문 마지막에  -> ';'  요 세미콜론을 붙였기 때문에 나타난다.


쿼리 내용을 넣는 xml문서에는 마지막에 세미콜론을 붙이지 않는다.


오라클 디벨로퍼에서 쿼리 테스트 해보고 그대로 복사한 후 소스에 붙여넣기 했을때 주로 나타나는 에러.


문자가 잘못되었다고 해서 아무리 들여다봐도 잘못된 부분이 없는데 마지막을 보면 저 세미콜론이 붙어 있다.


에러 로그는 요란스럽게 올라오지만 별거 아닌 문제니 천천히 쿼리를 살펴보도록 하자.




java.sql.SQLException: ORA-00911: 문자가 부적합합니다


at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:112)

at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:331)

at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:288)

at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:745)

at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:216)

at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:966)

at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1170)

at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3339)

at oracle.jdbc.driver.OraclePreparedStatement.execute(OraclePreparedStatement.java:3445)

at org.apache.commons.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:172)

at org.apache.commons.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:172)

at com.ibatis.sqlmap.engine.execution.SqlExecutor.executeUpdate(SqlExecutor.java:80)

at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.sqlExecuteUpdate(MappedStatement.java:216)

at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.executeUpdate(MappedStatement.java:94)

at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.update(SqlMapExecutorDelegate.java:457)

at com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl.update(SqlMapSessionImpl.java:90)

at org.springframework.orm.ibatis.SqlMapClientTemplate$9.doInSqlMapClient(SqlMapClientTemplate.java:380)

at org.springframework.orm.ibatis.SqlMapClientTemplate$9.doInSqlMapClient(SqlMapClientTemplate.java:1)

at org.springframework.orm.ibatis.SqlMapClientTemplate.execute(SqlMapClientTemplate.java:200)

at org.springframework.orm.ibatis.SqlMapClientTemplate.update(SqlMapClientTemplate.java:378)

at egovframework.rte.psl.dataaccess.EgovAbstractDAO.update(EgovAbstractDAO.java:101)

at kr.co.narutec.pems2016.widget.service.impl.WidgetDAO.updateUserAutoSetting(WidgetDAO.java:142)

at kr.co.narutec.pems2016.widget.service.impl.WidgetServiceImpl.updateUserAutoSetting(WidgetServiceImpl.java:118)

at kr.co.narutec.pems2016.widget.web.WidgetController.updateUserWidgetSetting(WidgetController.java:170)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:606)

at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:176)

at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:426)

at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:414)

at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:790)

at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)

at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)

at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:560)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)

at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)

at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)

at egovframework.rte.ptl.mvc.filter.HTMLTagFilter.doFilter(HTMLTagFilter.java:51)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)

at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)

at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)

at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)

at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)

at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)

at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)

at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:956)

at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)

at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423)

at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079)

at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:625)

at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:318)

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)

at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)

at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

at java.lang.Thread.run(Thread.java:745)

+ Recent posts