<목차>

1. 이벤트 바인딩이란?

2. 이벤트 버블링, 이벤트 캡쳐

3. 동적 이벤트 바인딩이란?

4. JavaScript로 이벤트 동적 바인딩하기.

5. jQuery로 이벤트 동적 바인딩하기.

 

 

 

 

1. 이벤트 바인딩이란?

 

  1-1. 이벤트 바인딩

 

   동적 바인딩에 대한 내용을 정리하기 전에, 가장 먼저 이해해야 할 것이 이벤트 바인딩 입니다. JS코드와 jQuery코드

   는 대부분 이벤트(event)에 의해 동작합니다. 이벤트란 대표적으로 클릭, 키보드 입력 등 사용자의 어떤 행위를 의미

   합니다.

 

   이러한 이벤트 바인딩(Event Binding)은 아래와 같은 과정으로 이루어 집니다.

   

        1. 이벤트를 받아 줄 요소를 선택합니다.

        2. 선택한 요소가 어떤 이벤트에 반응할지, 즉 요소와 이벤트를 연결해주는 바인딩을 합니다.

        3. 이벤트가 발생했을 때 실행될 코드를 작성합니다.

 

   역시 글만 읽으면 이해가 잘 되지 않을 수 있습니다. 아래에서 JS와 jQuery를 이용한 이벤트 바인딩에 대해서 설명드

   리겠습니다. :)

 

   ※ JS를 사용한 이벤트 바인딩

 

      - HTML 이벤트 핸들러

        DOM요소의 onclick속성에 JS함수를 호출하여 바인딩하는 방법입니다.

 

<소스>

<html>
    <body>
        <div class="root" id="root">
            <div class="parent" id="parent">
                <div class="child" id="child">
                    <div class="target" id="target" onclick="targetFunction();">Target</div>
                </div>
            </div>
        </div>
    </body>
</html>
function targetFunction() {
    alert('Target');
}

        해당 버튼을 클릭하면 작성 해놓은 targetFunction()함수가 실행되어 alert로 'Target' 텍스트가 나타날 것입니다.

        그런데HTML과 JS코드는 분리시켜 관리하는 것을 권장하기 때문에 이 방식은 좋은 방식이 아닙니다.

 

 

      - DOM 이벤트 핸들러

        DOM요소에 이벤트를 바인딩하고 이벤트가 발생하면 실행될 코드를 함수로 작성하는 방법입니다.

 

<소스>

<html>
    <body>
        <div class="root" id="root">
            <div class="parent" id="parent">
                <div class="child" id="child">
                    <div class="target" id="target">Target</div>
                </div>
            </div>
        </div>
    </body>
</html>
var target = document.getElementById('target');

target.onclick = function(e) {
    alert('target');
}

        여기서 "on이벤트 이름"이란 JS이벤트 명에 on을 붙인 것입니다.

        예를 들어, click이벤트를 바인딩 하려면 onclick가 되겠고, blur 이벤트를 바인딩 하려면 onblur가 될 것입니다.

 

        위 방식도 그렇게 권장되는 방식은 아닙니다.

 

 

      - DOM 이벤트 리스너

        DOM요소에 addEventListener메서드를 호출하여 이벤트를 바인딩하고, 수행할 함수를 작성합니다.

 

<소스>

<html>
    <body>
        <div class="root" id="root">
            <div class="parent" id="parent">
                <div class="child" id="child">
                    <div class="target" id="target">Target</div>
                </div>
            </div>
        </div>
    </body>
</html>
var target = document.getElementById('target');

target.addEventListener("click", function() {
	alert("target");
});

        해당 방식이 바닐라JS기준으로 가장 많이 사용되는 방식입니다.

        "DOM 이벤트 핸들러"방식과는 달리 "이벤트 이름"에는 원래 JS이벤트명(ex. onclick => click)을 작성하면 됩니다.

 

   ※ jQuery를 사용한 이벤트 바인딩

 

      - DOM 이벤트 핸들러

        DOM요소에 이벤트를 바인딩하고 이벤트가 발생하면 실행될 코드를 함수로 작성하는 방법입니다.

 

<소스>

<html>
    <body>
        <div class="root" id="root">
            <div class="parent" id="parent">
                <div class="child" id="child">
                    <div class="target" id="target">Target</div>
                </div>
            </div>
        </div>
    </body>
</html>
$('#target').on('click', function() {
	alert("target");
});

        JS에서 했던것과 마찬가지로 id가 target인 DOM요소를 찾아서 'click'이벤트를 실행합니다.

 

 

2. 이벤트 버블링, 이벤트 캡쳐

 

  2-1. 이벤트 버블링

 

     이벤트 버블링은 특정 화면 요소에서 이벤트가 발생했을 때 해당 이벤트가 더 상위의 화면 요소들로 전달되어가는

     특성을 의미합니다. 아래와 같은 그림처럼 작동합니다.

     아래 그림은 이벤트 버블링에 대한 설명을 아주 잘 하고있습니다.     

   

(1-1)Event Bubbling

     위 그림은 이벤트 버블링(Event Bubbling)에 대해 설명한 그림입니다.

 

(1-2)Event 흐름을 학습하기 위한 Image

     이벤트 흐름을 학습하기 위해 "(1-2)Event 흐름을 학습하기 위한 Image"를 예제로 설명을 이어나가겠습니다.

     (각 클래스에 이벤트가 바인딩 되어있다고 가정합니다.)

 

     (1) target 클래스를 클릭했을 경우

         이벤트 흐름이 가장 깊은 깊이의 노드(자식 노드)에서 가장 얕은 깊이의 노드(부모 노드)로 향합니다.

         이벤트 흐름으로는 target ▶ child ▶ parent ▶ root ▶ body ▶ html 순서로 이어집니다. 

 

     (2) child 클래스를 클릭했을 경우

        이벤트 흐름으로는 child ▶ parent ▶ root ▶ body ▶ html 로 이어집니다.


     (3) parent 클래스를 클릭했을 경우

        이벤트 흐름으로는 parent ▶ root ▶ body ▶ html 로 이어집니다.

 

     (3) root 클래스를 클릭했을 경우

        이벤트 흐름으로는 root ▶ body ▶ html 로 이어집니다.

 

 

  2-2. 이벤트 캡쳐링

 

     이벤트 캡쳐링은 특정 화면 요소(선택된 Element)에서 이벤트가 발생했을 때 해당 이벤트가 특정 화면 요소(선택된

     Element)까지 아래로 이동하면서 이벤트를 발생시킵니다.

 

(2-1)Event Capturing

위 그림은 이벤트 캡쳐링(Event Capturing)에 대해 설명한 그림입니다.

 

(2-2)Event 흐름을 학습하기 위한 Image

     이벤트 흐름을 학습하기 위해 "(2-2)Event 흐름을 학습하기 위한 Image"를 예제로 설명을 이어나가겠습니다.

     (각 클래스에 이벤트가 바인딩 되어있다고 가정합니다.)

 

     (1) target 클래스를 클릭했을 경우

         이벤트 흐름이 가장 깊은 깊이의 노드(자식 노드)에서 가장 얕은 깊이의 노드(부모 노드)로 향합니다.

         이벤트 흐름으로는 html ▶ body ▶ root ▶ parent ▶ child ▶ target 순서로 이어집니다. 

 

     (2) child 클래스를 클릭했을 경우

        이벤트 흐름으로는 html ▶ body ▶ root ▶ parent ▶ child 로 이어집니다.


     (3) parent 클래스를 클릭했을 경우

        이벤트 흐름으로는 html ▶ body ▶ root ▶ parent 로 이어집니다.

 

     (3) root 클래스를 클릭했을 경우

        이벤트 흐름으로는 html ▶ body ▶ root 로 이어집니다.

 

 

 

     ※ 이벤트 버블링 및 이벤트 캡쳐링 예제

   

        아래 링크에서 이벤트 버블링 및 캡쳐링을 바로 확인할 수 있습니다.

        addEventListener() 함수의 3번째 인자의 값에 따라 버블링 및 캡쳐를 설정할 수 있습니다.

    

        - False : Event Bubbling

        - True : Event Capturing

 

        https://codepen.io/mygumi/pen/vjywaB

 

 

3. 동적 이벤트 바인딩이란?

    동적 이벤트 바인딩이란, 동적으로 생성된 Element에 이벤트를 바인딩 한다는 말입니다. 간단한 예로, Ajax를 사용해

    서 페이지 새로고침 없이 Element를 추가 할 경우(jQuery의 append함수 참고), 어떻게 이벤트를 바인딩 할까요? 

    이미 페이지가 로드되면서 개발자가 작성한 JS는 전부 이벤트가 바인딩 된 상태일 겁니다. 그러나, 동적으로 Element

    를 생성해서 추가한다면 이벤트 버블링을 이용하여 동적으로 생성된 객체에 이벤트를 바인딩 할 수 있습니다.

 

    간단한 예시를 들어보겠습니다.

 

   

(3-1) 동적 Element가 추가되기 전

 

<소스>

<html>
  <body>
    <div class="root">
         
    </div>
    <button class="btn" name="btn">Element 동적 추가</btton>
  </body>
</html>
document.querySelector('.root').addEventListener('click', function(e) {
  alert("root");
  alert(e.target);
});


document.querySelector('.btn').addEventListener('click', function(e) {
  var div = document.createElement('div');
  div.className = 'row';
  
  div.innerHTML = '<h2 class="parent"><button class="child"><span class="target">Target</span></button></h2>';
  
  document.querySelector('.root').appendChild(div);
});
.btn{ 
  margin-top:320px;
}
/* .btn style 추가 */

   소스코드 구성이 위와같다면, Element 동적 추가 버튼을 클릭하면 새로운 Element들이 <div class="root"> 아래로 생

   성될 것 입니다.

 

(3-2) 동적 Element가 추가된 후

 

   이제부터 위와 같이 동적으로 생성된 Element를 이벤트 버블링으로 어떻게 접근하는지 JavaScript와 jQuery를 사용하

   여 알아보겠습니다. !

 

4. JavaScript로 이벤트 동적 바인딩하기.

    JavaScript로 이벤트 동적 바인딩하는 방식은 바로 위, 3. 동적 이벤트 바인딩이란? 에서 이미 보여졌습니다. :) 눈치 채

    신분들도 있으시죠? 위 예제를 그대로 사용해보면 아마 이벤트가 동적으로 바인딩되어, 사용자가 클릭한 위치의

    Element Name이 출력 될 것입니다.

 

    그럼 어떻게 이러한 현상이 나타나는걸까요?

 

   

(4-1) .root Element를 클릭할 경우 이벤트 발생

 

    위 코드에는 document.querySelector('.root').addEventListener('click', function(e) { } ); 함수가 존재합니다.

 

    코드를 하나씩 뜯어보겠습니다.

   

    코드를 풀어서 읽어보면, document객체 아래에 있는 .root 클래스에 대한 click이벤트를 addEventListener함수로 바

    인딩 하겠다는 의미입니다. 즉, .root 클래스 Element를 클릭하면 addEventListener함수가 실행됩니다.

 

    그렇다면, .root 클래스 Element내부에 동적으로 생성된 Element들은 어떻게 이벤트에 바인딩 될까요?

 

    바로, 이벤트 버블링으로 동적으로 생성된 Element를 구분할 수 있습니다.

    아래 예제소스를 보겠습니다.

 

<html>
  <body>
    <div class="root">
         
    </div>
    <button class="btn" name="btn">Element 동적 추가</btton>
  </body>
</html>

<동적으로 Element가 생성되지 않았을 경우>

 

<html>
  <body>
    <div class="root">
      <h2 class="parent">
        <button class="child">
          <span class="target">Target</span>
        </button>
      </h2>
    </div>
    <button class="btn" name="btn">Element 동적 추가</btton>
  </body>
</html>

 

<동적으로 Element가 생성된 경우>

 

    - 동적으로 Element가 생성되었고, target 클래스(span)을 클릭 한 경우

       1. target(span)에서 click 이벤트를 잡지 못하고 child(button)으로 이벤트가 버블링 됩니다.

       2. child(button)에서 click 이벤트를 잡지 못하고 parent(h2)으로 이벤트가 버블링 됩니다.

       3. parent(h2)에서 click 이벤트를 잡지 못하고 root(div)로 이벤트가 버블링 됩니다.

       4. root(div)에서 click 이벤트를 잡아서 아래 소스코드를 실행합니다.(해당 click이벤트는 document까지 버블링 됩

          니다.)

document.querySelector('.root').addEventListener('click', function(e) {
  alert("root");
  alert(e.target);
});

    그럼 함수에 있는 alert("root")와 alert(e.target)가 실행됩니다.

    function(e) { } 인자로 받은 e를 사용하여 e.target을 출력하면 무엇이 나올까요?

    아래 그림과 같은 alert창이 생성됩니다.

 

 

    이벤트는 .root Element에서 받았지만, e.target을 출력하면 사용자가 클릭한 위치의 Element를 알아낼 수 있습니다.

 

    이러한 방식으로 많은 개발자들이 동적으로 생성된 Element를 쉽게 사용할 수 있습니다. : )

 

5. jQuery로 이벤트 동적 바인딩하기.

    4. JavaScript로 이벤트 동적 바인딩하기에서 이미 어떤 방식으로 이벤트 동적 바인딩 하는지 알아봤으니,

    jQuery에서는 간단한 사용법만 알아보겠습니다.

 

    위 예제를 그대로 빌려, 만약 jQuery로 변환한다면 아래와 같은 소스코드가 될 것입니다.

$(document).on('click', '.root', function(e) {
    alert("root");
    alert(e.target);
});

    정말 간단하지 않나요? $(document).on()문법은 정보가 많기때문에 따로 다루지 않겠습니다. ㅎㅎ

    구글링하면 5분내로 실제 사용 가능하니깐요 : )

 

 

 

    혹시라도 지금까지 포스팅된 내용 중 잘못된 부분이 있다면 꼭 말씀 부탁드립니다! 잘못된 내용을 공유하고 싶진 않

    네요 :)

 

 

 

 

Reference

 

최근 DIP에서 개최한 SQL 성능튜닝 수업을 듣고 왔습니다.

 

총 3일간 하루 8시간의 강의로 진행 되었는데, 제가 초짜라.. 굉장히 어려웠던 부분이 많았습니다.(그 중에 많이 졸기도 했지요..)

 

그래서 최대한 수업을 따라가며 핵심 키워드라도 필기해서 나중에 구글링 해서 찾아보자는 생각으로 열심히 받아적었습니다.

 

아마 긴 시간의 포스팅이 될 것 같지만, 포기하지 않고 써내려 가겠습니다. 부족한 내용이 많더라도 양해 부탁드립니다.

 

 

1. 문제가 되는 쿼리 찾아내는 방법

2. 실행계획 보는 방법

3. 다양한 사례로 알아보는 SQL 튜닝

'STUDY > DB' 카테고리의 다른 글

[DB] Oracle SQL 튜닝 및 응용 - 3  (0) 2019.10.30
[DB] Oracle SQL 튜닝 및 응용 - 2  (0) 2019.10.29
[DB] Oracle SQL 튜닝 및 응용 - 1  (0) 2019.10.28
[DB] View란?  (0) 2019.01.02
[DB] SQL학습 사이트  (0) 2019.01.02

1

 

보통 조인이 잘못되면 cartesian join이 일어납니다.(보통 교차곱을 사용 할 경우는 없음) 이를 확인하고 수정하면 됩니다.

 

2

 

Nonequi join이 있는 경우 Hash join이 불가능 합니다. 보통 이러한 방식은 피하는게 좋음

 

ex) WHERE e.sal between s.losal and s.hisal    <==  이러한 방식으로 사용하지 말자

 

3

 

Hint는 안쓰는걸 추천. Hint를 써야 할 상황이면 구조를 변경하거나 새로운 쿼리를 작성하는게 좋다.

 

4

 

/*+ ORDERED */

/*+ APPEND */

/*+ PARALLEL(table degree) */  =>  DBA와 상의가 필요한 부분

 

이 세가지 힌트를 자주 사용합니다.

 

5

 

CASE와 DECODE의 차이는 CASE구분의 성능이 훨씬 좋으니 그냥 CASE써라

 

6

 

ROLLUP을 자주 사용하자 !  => union으로 되어있는 쿼리를 ROLLUP으로 변경할 수 있으면 변경하자

 

7

 

WITH절로 VIEW를 만들면 성능이 좋아진다 ??

 

8

 

CUBE를 사용해보자 ... !

 

9

 

http://www.gurubee.net/lecture/2382

 

윈도우 함수(WINDOW FUNCTION)

제6절 윈도우 함수(WINDOW FUNCTION)행과 행간의 관계를 쉽게 정의하기 위해 만든 함수가 바로 WINDOW FUNCTION이다.윈도우 함수를 활용하면 복잡한 프..

www.gurubee.net

역시 DB 쿼리는 함수위주로 작성해야한다. 쿼리작성할때 함수위주로 생각하자. 만약 안된다면 그때 다른 방식을 찾아보도록하자

 

10

 

테이블 데이터 타입 제대로 확인하고 쿼리를 작성하자. 데이터타입이 맞지않으면 인덱스를 타지 않을 수 있다.

 

11

 

정규표현식이 함수보다 더 성능이 좋다.

 

12

 

인덱스 순서를 잘 보자. 인덱스 순서만 바꿔도 성능이 달라질 수 있다. (가장 적은 데이터를 인덱스를 앞으로 놓자 ! 범위

                                                                                          를 좁혀서 1차 가공해서 많이 걸러내자)

 

13

 

결합인덱스를 생성할때는 순서를 잘 생각해서 생성하자

 

14

 

상호참조 서브쿼리는 성능을 떨어트린다.

 

15

 

사례2 => 사진으로 설명

 

사례3 => 사진

        => 실행계획에서는 index를 사용한다고 하지만 실제로 데이터를 가져올때는 타지 않는다? 확인 필요

 

사례4 => 사진

        => Drived 테이블을 재설정 해야한다.(조인순서 재설정) 왜냐하면 Drived 테이블에서 조인 범위를 좁히면 다음 조

             인에 더욱 유리하다.

 

사례5  => CA와 DA의 값이 많은 줄 알고(데이터 분포 잘못 판단) 의도적으로 인덱스를 막았지만, 인덱스가 먹혀들어갔

              다.

         => 인덱스 순서 뒤집어야한다. (인덱스 순서 새로 조정해서 다시 생성)

         => 사진

 

사례6 => 사진

        => 인덱스를 새로 설정하고(JHCOD + JEPUM) 새로 설정한 컬럼이 인덱스를 탈 수 있도록 WHERE절 수정

 

사례7 => 사진

         => SUGA_SERL_IX 의 인덱스 순서를 바꿔야한다. (EDSC로 먼저 걸러내고 SERL로 걸러내는게 훨씬 좋음)

 

사례8 => 사진

        => 데이터를 1차 가공할때 가장 적은 데이터를 가져오는게 핵심 !!!

 

사례9 => 사진

        => 테이블 A와 테이블B의 조인에서 인덱스를 탈 수 없습니다. 인덱스의 위치가 뒤에있어서

        => 조인 시 사용된 결합 인덱스의 컬럼순서를 변경

 

사례10 => 사진

          => 사례9와 일맥상통

 

사례11, 12, 13 => 사진

                   => 인덱스 순서 재설정

 

사례14 => IS NULL은 인덱스를 타지 못한다.

           => 사진

 

사례15 => OUTER조인 할 때는 IN, OR, SUBQUERY를 사용하지 못합니다.

          => 오류뜸

 

사례16 => OUTER조인으로 값을 잘 가져오다가 마지막에 B.CENTER으로 걸러내버려서 원하는 데이터가 모두 나오지 못

               했습니다. B.CENTER에도 OUTER조인을 걸어주면 해결 할 수 있습니다.

          => 사진

 

사례17, 18, 19 => 개인적으로 해보라는 말씀

 

사례20 => 조인 순서 잘못. 범위를 확 줄일 수 있는 테이블을 먼저 조인해야합니다.

          => ORDERED 힌트로 테이블 조인 순서를 적절하게 변경하면 좋습니다.

 

사례21 => NULL처리

 

사례22 => 어렵네..ㅎ 이건 쫌 봐야긋다잉?

 

사례23 => COUNT값에 이상이 생김

          => MAX에서 MIN을 뺄때, 전체 테이블 조회할 필요없이 MAX쿼리와 MIN쿼리를 따로 구해서 전체 테이블 조회

               를 막을 수 있음

 

 

 

 

 

 

 

 

'STUDY > DB' 카테고리의 다른 글

[DB] SQL 성능 튜닝  (0) 2019.11.01
[DB] Oracle SQL 튜닝 및 응용 - 2  (0) 2019.10.29
[DB] Oracle SQL 튜닝 및 응용 - 1  (0) 2019.10.28
[DB] View란?  (0) 2019.01.02
[DB] SQL학습 사이트  (0) 2019.01.02

1

 

TKPROF 알아보기

 

2

 

https://m.blog.naver.com/PostView.nhn?blogId=genie319&logNo=100147787477&proxyReferer=https%3A%2F%2Fwww.google.com%2F

 

[엉터리 컬럼] Oracle Tuning 쉽게 이해하기 ⑥

밝아온 새해를 맞이하여 다들 '새해 복 많이 받으십시요'. 2012년도 신년계획을 세우셨는지요? 건강하시고,...

blog.naver.com

 

실행계획 보는순서 3 => 5 => 4 => 2 => 6 => 1

 

3

 

ADP. SQLP 자격증 확인

 

4

 

materialized view => 확인해보기

 

5

 

index organized tables 란?

 

6

 

B tree index => 일반적인 테이블 정보에 대한 검색 시 사용

BitMap index => 대용량 데이터베이스 환경에서 나쁜 분포도에 대한 컬럼 검색 시 사용

Revers key index

Descending index => 최신 행 정보를 우선시 조회할 때 사용

Function Based index => 검색 조건에서 계산공식을 사용하는 경우 결과치를 인덱스로 생성

index Organization Table => Primary Key를 이용하여 Text컬럼의 정보를 검색할 때

 

7

 

기본적으로 인덱스가 걸린 컬럼은 형변환과 같은 값이 직접적으로 달라지면 안된다.

 

인덱스 컬럼 가공 사례 사진 추가

 

8

 

visuble invisible => 옵티마이저한테만 인덱스 보여주고 안보여주고 설정하는 방법

 

9

 

 

 

 

 

 

 

 

'STUDY > DB' 카테고리의 다른 글

[DB] SQL 성능 튜닝  (0) 2019.11.01
[DB] Oracle SQL 튜닝 및 응용 - 3  (0) 2019.10.30
[DB] Oracle SQL 튜닝 및 응용 - 1  (0) 2019.10.28
[DB] View란?  (0) 2019.01.02
[DB] SQL학습 사이트  (0) 2019.01.02

<문제되는 쿼리 찾아내기>

 

1

 

성능이란 : 성능향상 ? 사용자가 체감할 수 있는, 수치적 증명 수준

 

성능 저하 요인

 - SQL/APP/DB design (80%)

    - I/O(70%) + CPU(15%) + Memory(10%) + Else(5%)

 - H/W resource(2.5%)

 - DB Configuration(10%)

 -System Design(7.5%)

 

 

2

 

v$로 시작하는 명령어로 DB의 현상태, 성능정보를 확인할 수 있다. (가장 기본적인 성능체크)

but 과거의 데이터를 기준으로 나타내는 성능이기때문에, 그 상황을 고려해야한다.

 

3

 

AWR을 통한 DB분석 후 top_sql.sql을 사용하여 가장 리소스를 많이 잡아먹는 쿼리를 찾아낼 수 있다.

 

https://oracle-base.com/dba/scripts

 

DBA Scripts for Oracle (12c, 11g, 10g, 9i, 8i)

DBA Scripts for Oracle 12c, 11g, 10g, 9i and 8i.

oracle-base.com

4

 

DB의 전체적인 성능을 확인하고, CPU문제인지, 프로세스 또는 메모리 문제인지 정확히 파악 후, 조치가 필요하다.

 

SQL을 튜닝하기 위해서는 어떤 쿼리가 문제를 일으키는지 찾아내는것이 핵심 !!

 

( ※ DB Server Performance Tune Exam 1 ~ 3까지 사진 업로드) 9P ~ 10P

 

5

 

10프로 미만이면 ?

 

6

 

하드파싱 소프트 파싱 => 어차피 하드파싱하면 실행계획 나와서 소프트파싱 할 수 있는거 아닌가.

 

7

 

RAC란 뭘까 ..

 

https://12bme.tistory.com/322

 

[오라클] RAC(Real Application Cluster)이란?

일반적인 Oracle Server 구성방식 * Process: A는 작업장1로 복사해와서 작업을 하고, B는 작업장2로 복사를 해와서 작업을 하며, 저장을 database에 합니다. 이렇게 instance와 database 사이를 왔다갔다 하면서..

12bme.tistory.com

 

8

 

IN / NOT IN => Subquery의 데이터가 작을 경우 유리

 

Exists / Not Exists = > Mainquery의 데이터가 작을 경우 유리

 

9

 

order by는 안하면 제일 좋다. 성능에 안좋음. 그리고 index가 걸린 컬럼을 사용하면 자동정렬이 되서 order by를 사용할필요가 없다.

 

10

 

 

 

 

 

 

 

 

 

 

 

 

'STUDY > DB' 카테고리의 다른 글

[DB] Oracle SQL 튜닝 및 응용 - 3  (0) 2019.10.30
[DB] Oracle SQL 튜닝 및 응용 - 2  (0) 2019.10.29
[DB] View란?  (0) 2019.01.02
[DB] SQL학습 사이트  (0) 2019.01.02
[DB]SQL 공부하기 좋은 사이트  (0) 2018.10.10

Spring에서 RESTful API구현 시, 주로 사용되는 @ResponseBody와 @RequestBody 어노테이션의 개념에 대해 알아보겠습니다.

 

@RequestBody

 - HTTP 요청의 body내용을 자바 객체로 매핑하는 역할을 합니다.

 

@ResponseBody

 - 자바 객체를 HTTP요청의 body 내용으로 매핑하는 역할을 합니다.

 

@RestController
public class LoginWebController
{
	//HTTP요청의 내용을 Member객체에 매핑하기 위해 @RequestBody 어노테이션을 설정
	@RequestMapping(value = "/member/login" method = RequestMethod.POST)
    	public MemberResultDto login(@RequestBody Member member) {
    		MemberResultDto memberResultDto memberService.login(member);
        
        	return memberResultDto;
    }
}

 

위의 예제소스에서는 HTTP요청의 body안에 member 데이터를 파라미터로 받기 위해, @RequestBody를 사용하였습니다.

 

@ResponseBody를 사용하지 않은 이유는 @RestController를 사용하였기 때문입니다. @Controller와는 다르게 @RestController는  리턴값에 자동으로 @ResponseBody를 붙게되어 HTTP응답데이터(body)에 자바 객체가 매핑되어 전달됩니다.

(※ @Controller인 경우에는 @ResponseBody를 적어줘야 합니다.)

 

 

 

Reference

 

 

 

form태그 내부에 있는 input text box에서 Enter를 누르게 되면 자동으로 Submit이 되면서 페이지가 재로드 됩니다.

(Ajax를 사용할때, 이 부분에서 상당히 애를 먹었습니다.)

 

이를 방지하기 위한 3가지 방법이 있습니다.

 

1. form태그의 onsubmit속성값 이용

 

<form name="searchForm" onsubmit="return false;" method="post">

onsubmit="return false;"으로 설정해주면 자동 submit을 방지할 수 있습니다.

 

2. keycode 값으로 enter여부를 판단하여 처리

 

<script language="javascript">
	//enter방지
    function captureReturnKey(e) {
    	if(e.keyCode == 13 && e.srcElement.type != "textarea")
        	return false;
    }
</script>

<form name="searchForm" method="post" onkeydown="return captureReturnKey(event)">
	<input type="text" name="test">
</form>

onkeydown으로 키보드 버튼 클릭 시 captureReturnKey함수를 호출하도록 하고, keyCode 13(엔터)일 경우 return false로 엔터키를 방지할 수 있습니다.

 

3. input 박스 추가

 

<div class="srchForm">
	<input type="text" style="display:none;"/>	<!-- 의미없는 텍스트 박스 -->
    	<input type="search" class="input" onkeydown="javascript: if(event.keyCode == 13) {mf_keyDown();}}">
</div>

엔터키를 눌렀을때 자동으로 submit되는 원인은 form내부에 input 박스가 한 개만 존재하기 때문입니다.

따라서, 의미없는 input 박스를 추가하여 이를 방지할 수 있습니다.

 

위 세가지 방식이 있지만 1번 방법이 가장 깔끔한 것 같습니다.

 

 

Reference

JS코드를 작성하다보면, JS객체 또는 JSON에 key값을 동적으로 할당하고 싶을때가 있습니다.

 

JS객체는 key와 value의 쌍으로 이루어져 있으며 따옴표나 쌍따옴표를 사용하여 작성합니다.

 

아래 keyname은 something객체의 key값에 들어갈 동적인 값 입니다.

let keyname = 'Name';
let something = {
   keyname + 'postfix' : 'value'
}

대충 생각해보면 위와 같은 코드는 정상적으로 동작할 것 같습니다. 하지만, 위 코드는 SyntaxError가 발생합니다.

 

value 부분에는 함수나 String을 이어붙여도 자동으로 처리해주고 변수도 사용할 수 있지만 key부분에는 그런 처리가 허

 

용되지 않습니다.

 

그렇다면 어떻게 동적으로 key값을 적용할 수 있을까요?

 

두 가지 방법이 있습니다.

 

1. 객체 생성과 동시에 동적인 key값 설정

let keyname = 'Name';
let something = {
    [keyname + 'postfix'] : 'value'
};

 

2. 객체 생성 후 동적인 key값 설정

let keyname = 'Name';
let something = { };

something[keyname + 'postfix'] = 'value';

위 두가지 방식을 사용하여 동적인 key값을 넣을 수 있습니다.

 

 

Reference

 

JSON 생성시 Key값을 동적으로 할당하기 :: Outsider's Dev Story

JSON은 아주 유용하고 간편한 데이터 스트럭처이고 다들 아시다 시피 아래와 같이 사용합니다.[code javascript]var something = { name: 'Outsider', blog: 'http://blog.outsider.ne.kr'}[/code]키와 값의...

blog.outsider.ne.kr

 

목차

 

  1. Custom Annotation 만들고 사용하기

 

  2. @RequestMapping 들여다 보기

 

 

 

Custom Annotation 만들고 사용하기

 

   ※ Custom Annotation 생성

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
	String name()	default "Gyu";		//name값을 설정할 수 있으며, 아무런 값을 넣지 않으면 Gyu가 들어갑니다.
	String value();							//value값을 설정할 수 있으며, 아무런 값을 넣지 않으면 Syntax error가 발생합니다.
	int count()	default 1;							//기본 자료형도 사용할 수 있습니다
	String[] arrName()	default "first Index";		//배열값을 넣을 수 있습니다. { }안에 값을 넣어 삽입. 
	TestEnumType[] testEnumType()		default TestEnumType.FIRST;	//enum타입의 값을 설정할 수 있습니다.
	AnnoDateTime annoDateTime();	//Annotation안에 또 다른 Annotation값을 넣을 수 있습니다.
}

@Retention(RetentionPolicy.RUNTIME)
@interface AnnoDateTime {
	String yymmdd();
	String hhmmss();
}

enum TestEnumType {
	FIRST, FINAL
}

 

Custom Annotation을 작성하기 위해 몇 가지 규칙이 존재합니다.

 

1. 요소의 타입은 기본형, String, enum, 어노테이션, Class만 허용된다.

2. 요소의 ()안에 매개변수를 선언할 수 없다.

3. 예외를 선언할 수 없다.

4. 요소를 타입 매개변수로 정의할 수 없다.

5. 어노테이션의 각 요소는 기본값을 가질 수 있다.

 

위 규칙을 생각하며 어노테이션을 작성하면 됩니다.

 

   ※ Custom Annotation 적용

 

   - 클래스에 선언

@MyAnnotation(value = "First Text", annoDateTime = @AnnoDateTime(yymmdd = "190924", hhmmss = "101147"))
public class AnnotationHandler {
	
	public String myField = null;
	
	public void doThis() {
		
	}
	
	public void doThat() {
		
	}
	
}

 

   - 클래스 필드에 선언

public class AnnotationHandler {
	
	@MyAnnotation(value = "First Text", annoDateTime = @AnnoDateTime(yymmdd = "190924", hhmmss = "101147"))
	public String myField = null;
	
	public void doThis() {
		
	}
	
	public void doThat() {
		
	}
	
}

 

   - 메서드에 선언

public class AnnotationHandler {
	
	public String myField = null;
	
	@MyAnnotation(value = "First Text", annoDateTime = @AnnoDateTime(yymmdd = "190924", hhmmss = "101147"))
	public void doThis() {
		
	}
	
	@MyAnnotation(value = "First Text", annoDateTime = @AnnoDateTime(yymmdd = "190924", hhmmss = "101147"))
	public void doThat() {
		
	}	
}

 

   

  ※ 자바 리플렉션으로 커스텀 어노테이션 사용하기

 

@Retention(RetentionPolicy.RUNTIME)값으로 설정한 Custom Annotation은 런타임 시점에 어노테이션 정보를 이용하여 코드를 생성합니다.

이러한 이유로 RUNTIME옵션을 설정한 Custom Annotation은 자바 리플렉션을 사용하여 선언한 어노테이션 값을 가져옵니다.

 

프로그램 구동 시, Custom Annotation에서 어떤 값이 넘어오는지 확인해보는 테스트를 해보겠습니다.

 

MyAnnotation.java

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
	String name()	default "Gyu";		//name값을 설정할 수 있으며, 아무런 값을 넣지 않으면 Gyu가 들어갑니다.
	String value();							//value값을 설정할 수 있으며, 아무런 값을 넣지 않으면 Syntax error가 발생합니다.
	int count()	default 1;							//기본 자료형도 사용할 수 있습니다
	String[] arrName()	default "first Index";		//배열값을 넣을 수 있습니다. { }안에 값을 넣어 삽입. 
	TestEnumType[] testEnumType()		default TestEnumType.FIRST;	//enum타입의 값을 설정할 수 있습니다.
	AnnoDateTime annoDateTime();	//Annotation안에 또 다른 Annotation값을 넣을 수 있습니다.
}

@Retention(RetentionPolicy.RUNTIME)
@interface AnnoDateTime {
	String yymmdd();
	String hhmmss();
}

enum TestEnumType {
	FIRST, FINAL
}

 

AnnotationHandler.java

@MyAnnotation(value = "Class => AnnotationHandler", testEnumType = TestEnumType.FINAL,annoDateTime = @AnnoDateTime(yymmdd = "091005", hhmmss = "000000"))
public class AnnotationHandler {
	
	@MyAnnotation(value = "Field => myField", count = 10, annoDateTime = @AnnoDateTime(yymmdd = "131224", hhmmss = "210556"))
	public String myField = null;
	
	@MyAnnotation(value = "Method => doThis()", testEnumType = TestEnumType.FIRST, annoDateTime = @AnnoDateTime(yymmdd = "190924", hhmmss = "101147"))
	public void doThis() {
		
	}
	
	@MyAnnotation(value = "Method => doThat()", testEnumType = TestEnumType.FINAL, annoDateTime = @AnnoDateTime(yymmdd = "180728", hhmmss = "181447"))
	public void doThat() {
		
	}
	
}

 

AnnotationStudy.java

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class AnnotationStudy {

	public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {
		
		//자바 리플렉션을 사용해 Class정보, getField(필드 정보), getMethod(메서드 정보)를 얻어옵니다.
		Class<AnnotationHandler> cls = AnnotationHandler.class; 	
		Field field = AnnotationHandler.class.getField("myField");
		Method thisMethod = AnnotationHandler.class.getMethod("doThis");
		Method thatMethod = AnnotationHandler.class.getMethod("doThat");

		//각 클래스, 필드, 메서드에 걸려있는 Annotation의 정보를 얻습니다.
		Annotation[] classAnnotations = cls.getDeclaredAnnotations();
		Annotation[] fieldAnnotations = field.getDeclaredAnnotations();
		Annotation[] thisMethodAnnotations = thisMethod.getDeclaredAnnotations();
		Annotation[] thatMethodAnnotations = thatMethod.getDeclaredAnnotations();
		
		//클래스, 메서드, 필드에 적용되어 있는 Annotation정보들을 추출합니다.
		printAnnotation(classAnnotations);
		printAnnotation(fieldAnnotations);
		printAnnotation(thisMethodAnnotations);
		printAnnotation(thatMethodAnnotations);
		
	}

	public static void printAnnotation(Annotation[] annotations) {
		
		for(Annotation annotation : annotations) {
			if(annotation instanceof MyAnnotation) {
				MyAnnotation myAnnotation = (MyAnnotation)annotation;
				
				System.out.println("String name() : " + myAnnotation.name());
				System.out.println("String value() : " + myAnnotation.value());
				System.out.println("int count() : " + myAnnotation.count());
				System.out.println("AnnoDateTime annoDateTime() yymmdd : " + myAnnotation.annoDateTime().yymmdd());
				System.out.println("AnnoDateTime annoDateTime() hhmmss : " + myAnnotation.annoDateTime().hhmmss());
				System.out.println("TestEnumType[] testEnumType() : " + myAnnotation.testEnumType());
				
				for(String str : myAnnotation.arrName()) {
					System.out.println("String[] arrName() : " + str);
				}
			}
		}
		
		System.out.println();
		
	}
}

 

출력 로그

위 소스를 보면 알다시피, RUNTIME에서 동작하는 어노테이션들은 전부 리플렉션을 사용해서 객체의 정보를 얻고, 어노테이션의 정보도 얻습니다.

 

커스텀 어노테이션을 생성해서 사용하면 반복적으로 코딩해야 하는 부분들도 많이 줄일 수 있고 더 비지니스로직에 집중할 수 있는 장점이 있습니다.

 

 

@RequestMapping 들여다 보기

 

Custom Annotation을 만들어봤으면, java 또는 Spring에서 제공하는 Annotation은 어떻게 생겼는지, 어떤식으로 구성되어 있는지 궁금하실 겁니다.

 

사실, Annotation내용을 포스팅하는 이유의 80%이상이 Annotation의 동작방식과 구성요소들을 알아보고, 직접 만들어보며 아무런 생각없이 사용하던 Annotation을 이해하자는 취지이기 때문에 @RequestMapping을 확인해보면 좀 더 친숙하게 다가갈 수 있을꺼라 생각합니다.

 

   ※ 구성 요소

 

@RequestMapping annotation 사용

위 소스는 @ReqeustMapping을 사용한 모습입니다. 익숙한 value값과 method값을 볼 수 있을 것 입니다.

실제 사용에 있어서 annotation의 인수가 하나뿐이면 value값을 지칭하지 않고 사용할 수 있습니다.

즉, @RequestMapping(value = "/cmm/cmmPlayer.do") 대신 @RequestMapping("/cmm/cmmPlayer.do")처럼 사용할 수 있습니다. (annotation 변수명이 value인 값만 가능)

 

그렇다면 세부적으로 어떻게 동작할까요?

 

@RequestMapping annotation 구조

위 소스는 직접 @RequestMapping 어노테이션을 타고 들어가서 본 내용입니다. 이젠 익숙한 내용들이 보일 것 입니다.

 

@Target({ElementType.METHOD, ElementType.TYPE})

해당 annotation은 매서드와 클래스, 인터페이스, enum에서 사용하도록 Target을 설정하겠다는 의미입니다.

 

@Retention(RetentionPolicy.RUNTIME)

해당 어노테이션을 런타임 시점에서 실행시키겠다는 의미입니다.

 

 

@RequestMapping annotation 구조-2

@RequestMapping의 핵심 요소인 String value() default { };RequestMethod[] method() defalut { };를 확인할 수 있습니다. value에 Mapping할 주소를 넣어주면 런타임 시점에 해당 주소와 URI는 서로 연결되게 됩니다.

 

또한, Request방식을 설정할 때 method = RequestMethod.GET 과 같이 GET호출 POST호출 등을 설정할 수 있습니다.

자료형인 RequestMethod[]를 확인해보면 GET, POST, PUT, PATCH 등과 같이 다양한 옵션이 enum으로 설정되어 있습니다.

 

RequestMethod enum 내부구조

 

이제 어느정도 Annotation의 구조가 눈에 파악될 것 입니다. 하지만, 더 깊은 세부 로직까지 아직은 알 필요가 없다고 생각하여 이정도로 정리하겠습니다.

 

※ 잘못된 내용이 있거나, 설명이 불충분한 부분이 있다면 답변 부탁드립니다.

 

 

Reference

 

 

목차

 

1. Annotation

 

2. Annotation 핵심 요소

 

 

Annotation

 

※Annotation 이란?

 

어노테이션 이란, 본래의 의미는 주석이라는 뜻이며 인터페이스를 기반으로 한 문법 입니다.(실제 Annotation을 생성하는 방식에서 @interface를 사용합니다.)

실제 메타 데이터로 이용되며 클래스, 메서드, 변수, 매개변수 및 패키지 등에 주석처럼 달아서 사용합니다.

 

@Controller
public class TestController {
	...
}

 

※ Annotation 사용이유

 

기존의 자바 웹 애플리케이션들은 대부분 설정값을 XML파일에 명시하여 관리했습니다. 변경될 수 있는 데이터들을 코드가 아닌 외부 설정 파일(XML)에 분리하기 때문에, 재컴파일 없이도 쉽게 변경사항을 저장할 수 있었습니다.

하지만, 프로그램 작성을 위해 매번 많은 설정을 해야하며, 수 많은 설정파일들을 관리해야 했습니다. (웹 애플리케이션의 규모가 커지면 그만큼 많은 설정 파일들을 관리해야 했습니다.)

 

이러한 문제점을 해결하기 위해 고안된 문법이 Annotation입니다,

 

어노테이션을 사용하면 데이터에 대한 유효성 검사조건을 보다 쉽게 파악할 수 있게 되며 코드가 깔끔해집니다.

단순히 부가적인 표현뿐만 아니라 리플렉션을 이용하면 어노테이션 지정만으로 원하는 클래스를 주입할 수 있습니다.

 

※ Annotation 용도

 

1. @Override 어노테이션 처럼 컴파일러를 위한 정보를 제공하기 위한 용도

2. 스프링 프레임워크의 @Controller 어노테이션 처럼 런타임리플렉션을 이용해서 특수 기능을 추가하

   기 위한 용도

3. 컴파일 과정에 어노테이션 정보로부터 코드를 생성하기 위한 용도

 

 

Annotation 핵심 요소

 

※ Annotation 종류

 

  • built-in Annotation

      - 이미 java에 내장되어 있는 어노테이션을 built-in Annotation 이라고 합니다.

      - 주로 컴파일러를 위한 위한 것으로 컴파일러에게 유용한 정보를 제공합니다.

      

      @Override

      - 선언한 메서드가 오버라이드 되었다는 것을 나타냅니다.

      - 만약 상위(부모) 클래스(또는 인터페이스)에서 해당 메서드를 찾을 수 없다면 컴파일 에러를 발생 시킵니다.

  

      @Deprecated

      - 해당 메서드가 더 이상 사용되지 않음을 표시합니다.

      - 만약 사용할 경우 컴파일 경고를 발생 시킵니다.

 

      @SuppressWarnings

      - 선언한 곳의 컴파일 경고를 무시하도록 합니다.

 

      @SafeVarargs

      - Java7 부터 지원하며, 제너릭 같은 가변인자의 매개변수를 사용할 때의 경고를 무시합니다. 

 

      @FunctionalInterface

      - Java8 부터 지원하며, 함수형 인터페이스를 지정하는 어노테이션입니다.

      - 오버라이딩 어노테이션과 마찬가지로 실수를 미연에 방지하기 위해 사용됩니다.

 

     

@Service("LoginService")
public class LoginServiceImpl implements LoginService{

  @Override
  public UserVO selectUser(UserVO userVO) {
      return loginDAOImpl.getUserInfo(userVO);
  }
}

 

  • Meta Annotation

      - 어노테이션에 사용되는 어노테이션으로 해당 어노테이션의 동작대상을 결정합니다.

      - 주로 새로운 어노테이션을 정의할 때 사용합니다.

 

     @Retention

     - 자바 컴파일러가 어노테이션을 다루는 방법을 기술하며, 어노테이션이 유지되는 기간을 지정하는데

       사용합니다.

     - 세 가지 유지 정책(retention policy)를 사용할 수 있습니다.

 

       <사용 종류>

       RetentionPolicy.SOURCE : 컴파일 전까지만 유효. (컴파일 이후에는 사라짐)

       RetentionPolicy.CLASS : 컴파일러가 클래스를 참조할 때까지 유효.

       RetentionPolicy.RUNTIME : 컴파일 이후에도 JVM에 의해 계속 참조가 가능. (리플렉션 사용)

 

     @Target     

     - 어노테이션이 적용할 위치를 지정합니다.

     - 여러 개의 값을 지정할 때는 배열에서 처럼 괄호 { } 를 사용해야 합니다.

 

       <사용 종류>     

       ElementType.PACKAGE : 패키지 선언

       ElementType.TYPE : 타입 선언

       ElementType.ANNOTATION_TYPE : 어노테이션 타입 선언

       ElementType.CONSTRUCTOR : 생성자 선언

       ElementType.FIELD : 멤버 변수 선언

       ElementType.LOCAL_VARIABLE : 지역 변수 선언

       ElementType.METHOD : 메서드 선언

       ElementType.PARAMETER : 전달인자 선언

       ElementType.TYPE_PARAMETER : 전달인자 타입 선언

       ElementType.TYPE_USE : 타입 선언

 

     @Documented

     - 해당 어노테이션을 javadoc에 포함시킵니다.

 

     @Inherited   

     - 어노테이션의 상속을 가능하게 합니다.

 

     @Repeatable

     - 연속적으로 어노테이션을 사용할 수 있게 해줍니다.

 

@Color(name = "red")
@Color(name = "blue")
@Color(name = "green")
class Shirt {
	...
}

 

@Inherited // 상속
@Documented // 문서에 정보가 표현
@Retention(RetentionPolicy.RUNTIME) // 컴파일 이후에도 JVM에 의해서 참조가 가능합니다
@Retention(RetentionPolicy.CLASS)   // Compiler가 클래스를 참조할 때까지 유효합니다
@Retention(RetentionPolicy.SOURCE)  // 컴파일 이후 이후 사라집니다
@Target({
		ElementType.PACKAGE, // 패키지 선언시
		ElementType.TYPE, // 타입 선언시
		ElementType.CONSTRUCTOR, // 생성자 선언시
		ElementType.FIELD, // 멤버 변수 선언시
		ElementType.METHOD, // 메소드 선언시
		ElementType.ANNOTATION_TYPE, // 어노테이션 타입 선언시
		ElementType.LOCAL_VARIABLE, // 지역 변수 선언시
		ElementType.PARAMETER, // 매개 변수 선언시
		ElementType.TYPE_PARAMETER, // 매개 변수 타입 선언시
		ElementType.TYPE_USE // 타입 사용시
})
public @interface NesoyAnnotation{
	/* enum 타입을 선언할 수 있습니다. */
	public enum Quality {
		BAD, GOOD, VERYGOOD
	}

	/* String은 기본 자료형은 아니지만 사용 가능합니다. */
	String value() default "NesoyAnnotation : Default String Value";

	/* 배열 형태로도 사용할 수 있습니다. */
	int[] values();

	/* enum 형태를 사용하는 방법입니다. */
	Quality quality() default Quality.GOOD;
}

<출처 : https://nesoy.github.io/articles/2018-04/Java-Annotation>

 

 

※ Annotation 사용구분

 

  • Marker Annotation

      - 이름으로 구분하기 위하여 사용하며 추가적인 데이터를 필요로 하지 않습니다.

      - 멤버 변수가 없으며, 단순히 표식으로서 사용된다. 컴파일러에게 어떤 의미를 전달합니다.

 

create

public @interface TestAnnotation { }

use

@TestAnnotation
public void setMethode() {
		...
}

 

  • Single-Value Annotation

      - 간단한 신텍스를 이용하며 단일 데이터를 필요로 함

      - 멤버로 단일변수만을 갖는다. 단일변수 밖에 없기 때문에 (값)만을 명시하여 데이터를 전달할 수 있습니다.

 

create

public @interface TestAnnotation {
	String doSomething();
}

use

@TestAnnotation("What to do")
public void setMethode() {
		...
}

 

  • Full Annotation

      - 복잡한 신텍스이며, 다중 데이터를 사용하며 name=value 형태를 취함 --> 데이터가 Array인 경우 "{ }"를 이용

      - 멤버로 둘 이상의 변수를 갖는 어노테이션으로, 데이터를 (Key = Value)의 형태로 전달합니다.

 

create

public @interface TestAnnotation {
	String doSomething();
	int count;
	String date();
}

use

@TestAnnotation(doSomething="What to do", count=1, date="09-09-2019")
public void setMethode() {
		...
}

 

※ 잘못된 내용이 있거나, 설명이 불충분한 부분이 있다면 답변 부탁드립니다.

 

Reference

 

+ Recent posts