[웹 풀스택] 부스트코스 - 웹 프로그래밍 - 3. 웹 앱 개발: 예약서비스 1/4

June·2021년 7월 23일
0

1. Javascript - FE

1) 자바스크립트 배열

배열의 선언

배열의 유용한 메서드들

var origin = [1,2,3,4];
var result = origin.concat(2,3);
console.log(origin, result);
> (4) [1, 2, 3, 4] (6) [1, 2, 3, 4, 2, 3]

concat은 원본 값이 변경되지 않는 함수이다.

배열 탐색 - (foreach, map, filter)

2) 자바스크립트 배열

객체

객체 선언

var myFriend = {key: "value", addition: {name: "codesquad"}, {age; 2}]};
console.log(myFriend.key);
console.log(myFriend["key"]);
console.log(myFriend.addition[0].name);

for (key in myFried) {
  console.log(myFriend[key]);
}

객체 탐색

Object.keys(myFriend).forEach(function(v){
  console.log(myFriend[v]);
});

2. DOM API활용 - FE

1) DOM Node조작하기- 1 & 2

1. DOM 조작하기 API

documet. 으로 사용할 수 있는 APIs : https://www.w3schools.com/jsref/dom_obj_document.asp

element. 으로 사용할 수 있는 APIs : https://www.w3schools.com/jsref/dom_obj_all.asp

2. DOM 엘리먼트 오브젝트

몇 가지 유용 DOM 엘리먼트 속성

  • tagName : 엘리먼트의 태그명 반환
  • textContent : 노드의 텍스트 내용을 설정하거나 반환
  • nodeType : 노드의 노드 유형을 숫자로 반환

2. DOM 탐색 APIs

몇 가지 유용 DOM 엘리먼트 속성

  • tagName : 엘리먼트의 태그명 반환
  • textContent : 노드의 텍스트 내용을 설정하거나 반환
  • nodeType : 노드의 노드 유형을 숫자로 반환

DOM 탐색 속성

  • childNodes
    • 엘리먼트의 자식 노드의 노드 리스트 반환(텍스트 노드, 주석 노드 포함)
    • childNodes 예제
  • firstChild
    • 엘리먼트의 첫 번째 자식 노드를 반환
    • firstChild 예제
  • firstElementChild
    • 첫 번째 자식 엘리먼트를 반환
    • firstElementChild 예제
  • parentElement
    • 엘리먼트의 부모 엘리먼트 반환
    • parentElement 예제
  • nextSibling
    • 동일한 노드 트리 레벨에서 다음 노드를 반환
    • nextSibling 예제
  • nextElementSibling
    • 동일한 노드 트리 레벨에서 다음 엘리먼트 반환
    • nextElementSibling 예제

DOM 조작 메소드

  • removeChild()
    • 엘리먼트의 자식 노드 제거
    • removeChild 예제
  • appendChild()
    • 마지막 자식 노드로 자식 노드를 엘리먼트에 추가
    • appendChild 예제
  • insertBefore()
    • 기존 자식노드 앞에 새 자식 노드를 추가
    • insertBefore 예제
  • cloneNode()
    • 노드를 복제
    • cloneNode 예제
  • replaceChild()
    • 엘리먼트의 자식 노드 바꿈
    • replaceChild 예제
  • closest()
    • 상위로 올라가면서 가장 가까운 엘리먼트를 반환
    • closest 예제

현재 div 변수는 div태그 아래 '오늘하루는 정말..좋아'라는 텍스트노드를 넣었다. p 태그를 현재 태그로 선택($0)하고 그 아래에 붙인다. 텍스트도 하나의 노드이니 그 밑에 붙인다.

테이블 태그아래 세번째 tr위에 뭐를 넣어보자

우선 table을 찾았다.

tr 태그 중 3번째 자식을 찾았다. 이제 이 앞에 넣어야하니 insertBefore를 써야한다.

붙여줄 부모 노드를 찾고, 그아래 div를 만들고 text를 추가했다.

부모를 기준으로 두 파라미터를 전달해서 추가했다.

4. HTML을 문자열로 처리해주는 DOM 속성 / 메소드

  • innerText
    • 지정된 노드와 모든 자손의 텍스트 내용을 설정하거나 반환
    • innerText 예제
  • innerHTML
    • 지정된 엘리먼트의 내부 html을 설정하거나 반환
    • innerHTML 예제
  • insertAdjacentHTML()
    • HTML로 텍스트를 지정된 위치에 삽입
    • insertAdjacentHTML() 예제

2) DOM APIs 실습

실습1

지금 나온 DOM API를 사용해서, strawberry 아래에 새로운 과일을 하나 더 추가하시오.
추가 된 이후에는 다시 삭제하시오.

실습링크: https://jsbin.com/mebuha/1/edit?html,js,output

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <h1>selector test</h1>
  
  <ul>
    <li>apple</li>
    <li>orange</li>
    <li>banana</li>
    <li>grape</li>
    <li>strawberry</li>
  </ul>

</body>
</html>

내 코드

var parent = document.querySelector("ul");

var div = document.createElement("li");
var str = document.createTextNode("Melon");
div.appendChild(str);

parent.appendChild(div);

강의 코드

var mango = document.createElement("li");
var mangoText = document.createTextNode("망고");
mango.appendChild(mangoText);

var parent = document.querySelector("ul");
parent.appendChild(mango);

실습 2

insertBefore메서드를 사용해서, orange와 banana 사이에 새로운 과일을 추가하시오.

실습링크: https://jsbin.com/mebuha/1/edit?html,js,output

내 코드

var parent = document.querySelector("ul");
var melon = document.createElement("div");
var str = document.createTextNode("멜론");
melon.appendChild(str);

var base = document.querySelector("ul li:nth-child(3)");
parent.insertBefore(melon, base);

실습 3

실습2를 insertAdjacentHTML메서드를 사용해서, orange와 banana 사이에 새로운 과일을 추가하시오.

내 코드

var base = document.querySelector("ul li:nth-child(3)");
base.insertAdjacentHTML('beforebegin', '<p>melon</p>')

실습 4

apple을 grape 와 strawberry 사이로 옮기시오.

실습 링크: https://jsbin.com/mebuha/1/edit?html,js,output

강의 코드

var strawberry = document.querySelector("li:nth-child(5)");
var apple = document.querySelector('li:nth-child(1)');
var parent = document.querySelector("ul");

parent.insertBefore(apple, strawberry);

insertBefore를 쓰니 따로 복사-제거-붙여넣기를 안해도 된다. move를 시켜주는 속성이 있다.

실습 5

class 가 'red'인 노드만 삭제하시오.

실습 링크: https://jsbin.com/redetul/1/edit?html,css,js,output

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <h1>selector test</h1>
  
  <ul>
    <li class="red">apple</li>
    <li class="red">orange</li>
    <li>banana</li>
    <li>grape</li>
    <li>strawberry</li>
  </ul>

</body>
</html>

css

li{
  list-style:none;
  font-size:1.4em;
  margin-bottom:10px;
}
.red{
  color:#f00;
}

내 코드

document.querySelectorAll(".red").forEach(function(a){a.remove();})

강의 코드

var reds = document.querySelectorAll("li.red");
var parent = document.querySelector("ul");
for (var i = 0; i < reds.length; i++) {
  parent.removeChild(reds[i]);
}

실습 6

section 태그의 자손 중에 blue라는 클래스를 가지고 있는 노드가 있다면, 그 하위에 있는 h2 노드를 삭제하시오.
https://jsbin.com/ricopa/1/edit?html,css,js,output

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <h1>selector test</h1>
  
    
<section>
  <h2> red section </h2>
  <ul>
    <li class="red">apple</li>
    <li class="red">orange</li>
    <li>banana</li>
    <li>grape</li>
    <li>strawberry</li>
  </ul>
</section>
  
  <Br>
  
<section>
  <h2> blue section </h2>
  <ul>
    <li class="green blue">apple</li>
    <li class="red">orange</li>
    <li>banana</li>
    <li>grape</li>
    <li>strawberry</li>
  </ul>
</section>

</body>
</html>
li{
  list-style:none;
  font-size:1.4em;
  margin-bottom:10px;
}
.red{
  color:#f00;
}

.blue{
  color:#00f;
}

.green{
  color:#0f0;
}

강의 코드

var bluenode = document.querySelector("section .blue");

for (var i = 0; i < bluenode.length; i++) {
  var section = bluenode[i].closest("section");
  var h2 = section.querySelector("h2");
  section.removeChild(h2);
}

closest를 이용해서 상위노드를 빨리 찾을 수 있다.

좀 더 알아보기

polyfill은 무엇인지 한번 찾아보세요!

어떠한 기능을 쓰고 싶은데, 지원하지 않는 브라우저에서도 동작시키게 하고 싶을때가 있죠. 그때 아주 유용합니다

3. Ajax - FE

1) Ajax 응답 처리와 비동기

브라우저의 새로고침 없이 데이터를 얻어오는 방법이 있습니다.
더 좋은 UX(User Experience)를 제공하는 좋은 방법이니, 알아보도록 하죠.

화면이 그려지지만 비동기작업(Ajax 요청)이 있으면 그건 보내놓고 나머지 작업을 하고 돌아오면 그걸 반영하는 것이다.

1. AJAX와 비동기

동기/비동기에 대한 아래 자료를 참고

https://www.youtube.com/watch?v=8aGhZQkoFbQ&ab_channel=JSConf

http://www.phpmind.com/blog/2017/05/synchronous-and-asynchronous/

Ajax통신(jquery 예제)을 코드단위로 비동기로 처리하는 도식화

2. Ajax 응답 처리

3. cross domain 문제

디버깅 - 크롬 개발자 도구

웹 개발을 하다 보면 Ajax와 같은 요청처리에 대해서 문제가 생길 수 있습니다.
Ajax 통신에서 로직이 문제인지, 아니면 서버 쪽의 문제인지 등 궁금할 때가 많습니다.
이런 부분을 소스코드상으로는 디버깅을 할 수 없습니다.
Ajax뿐만은 아닙니다.
네트워크 통신과정의 상황을 지켜보면서 디버깅하는 방법을 알아봅니다.

1. 크롬 개발자 도구의 네트워크 패널

크롬 개발자도구는 여러 가지 기능을 제공합니다.
녹화기능을 통해서 HTML, CSS, JavaScript, image파일을 내려받는 상황을 알 수 있습니다.
흔히 겪는 404와 같은 응답 오류에 대해서 문제를 쉽게 찾을 수 있습니다.
얼마나 서버로부터 응답이 걸리는지도 알 수 있습니다.
즉 성능개선을 위해서 진단할 수 있는 도구 역할을 하는 것이죠.

4. Web Animation - FE

1) 웹 애니메이션 이해와 setTimeout 활용

1. 애니메이션

2. FPS

3. JavaScript 애니메이션

인터벌 사이사이 다른 일들이 발생할 수 있기 때문에 정확한 시간을 보장할 수 없다. 그래서 이것을 잘 사용하지 않는다.

이 방법의 장점은 끝난 다음 재귀로 바로 다음 것을 실행하니 시간을 더 정확히 맞출 수 있다.

2) requestAnimationFrame 활용

setTimeout은 animation을 위한 최적화된 기능이라 보기는 어렵습니다.
animation주기를 16.6 미만으로 하는 경우 불필요한 frame 생성이 되는 등의 문제가 생깁니다.
그 대안으로 생긴 것이 바로 requestAnimationFrame입니다.
아래 예제를 살펴보시죠.
먼저 아래처럼 requestAnimationFrame을 한번 실행시켜줘야 합니다.
그 이후에 특정 조건이 될 때까지(함수의 탈출 조건) 계속 함수를 연속적으로 호출하는 것이죠.
이렇게 requestAnimationFrame함수를 통해서 원하는 함수를 인자로 넣어주면, 브라우저는 인자로 받은 그 비동기 함수가 실행될 가장 적절한 타이밍에 실행시켜줍니다.
우리는 어느 정도 브라우저를 믿고 이 함수를 전달해주는 것입니다.

var count = 0;
var el=document.querySelector(".outside");
el.style.left = "0px";

function run() {
   if(count > 70) return;
   count = count + 1;
   el.style.left =  parseInt(el.style.left) + count + "px";
   requestAnimationFrame(run);
}

requestAnimationFrame(run);

예제에서는 연속적으로 requestAnimationFrame를 통해서 run함수를 호출하면서 left 값을 증가시켜주고 있습니다.
(횟수로 70회까지 테스트하는 코드입니다.)
canvas, svg를 사용하면서 그래픽 작업을 하게 될 때 복잡한 애니메이션을 다룰 필요가 생길 수 있습니다.
자바스크립트로 복잡한 애니메이션처리를 처리해야 할 때 requestAnimationFrame은 꽤 유용하게 쓰일 수 있습니다

3) CSS3 transition 활용

CSS 기법으로 애니메이션 구현
transition 을 이용한 방법입니다.
이 방법이 JavaScript로 구현하는 것보다 더 빠르다고 알려져 있다.
특히 모바일 웹에서는 transform을 사용한 element의 조작을 많이 구현합니다.
https://thoughtbot.com/blog/transitions-and-transforms

더 빠른 css3 애니메이션 관련 속성들
GPU가속을 이용하는 속성을 사용하면 애니메이션 처리가 빠릅니다.
- transform : translateXX();
- transform : scale();
- transform : rotate();
- opacity
아래 링크를 참고해보세요.

5.WEB UI- FE

1) 서비스 개발을 위한 디렉토리 구성

2) DOMContentLoaded 이벤트

DOM을 가져오고 있는데 중간에 자바스크립트가 들어가서 뭐를 찾으려고하거나 삭제하려고하면 문제가 생길 수 있다. 그래서 자바스크립트 파일을 아래쪽에 위치하는 것이다. DOM의 로드 시점을 알려줄 수 있다면 더 편리할 것이다.

1. load와 DOMContendLoaded의 차이 확인

DOMContentLoaded가 먼저 되고 그 다음 load가 된다.

DOMContentLoaded이후에 서비스 코드를 작성하는 것이 안전한 방식이다.

3) Event delegation-1

<ul>
  <li>
    <img src="https://images-na.,,,,,/513hgbYgL._AC_SY400_.jpg" class="product-image" >    </li>
  <li>
    <img src="https://images-n,,,,,/41HoczB2L._AC_SY400_.jpg" class="product-image" >    </li>
  <li>
    <img src="https://images-na.,,,,51AEisFiL._AC_SY400_.jpg" class="product-image" >  </li>
 <li>
    <img src="https://images-na,,,,/51JVpV3ZL._AC_SY400_.jpg" class="product-image" >
 </li>
</ul>

var log = document.querySelector(".log");
var lists = document.querySelectorAll("ul > li");

for(var i=0,len=lists.length; i < len; i++) {
  lists[i].addEventListener("click", function(evt) {
     log.innerHTML = "clicked" + evt.currentTarget.firstChild.src;
  });
}

target 정보가 우리를 돕습니다.
자, 이번에는 ul 태그에만 이벤트를 새롭게 등록합니다

ul.addEventListener("click",function(evt) {
    console.log(evt.currentTarget, evt.target);
});

이럴 경우 li안에 이미지를 클릭하면 위 결과는 무엇일까요?
만약 ul > li > img 태그를 클릭했다면 어떤 결과가 나올까요?
그 전에 이벤트는 실행은 될까요?
정답은 '네' 입니다.
li 나 img 태그는 ul 태그에 속하기도 합니다.
따라서 UL에 등록한 이벤트 리스너도 실행이 됩니다.
이것은 이벤트 버블링이라고 합니다.

클릭한 지점이 하위엘리먼트라고 하여도, 그것을 감싸고 있는 상위 엘리먼트까지 올라가면서 이벤트리스너가 있는지 찾는 과정입니다.
만약 img, li, ul에 각각 이벤트를 등록했었다면, 3개의 이벤트 리스너가 실행했을 겁니다.

아래 이미지는 하위엘리먼트는 3번부터 이벤트가 발생하고 2,1 순으로 이벤트가 발생했습니다.

비슷하게 Capturing이라는 것도 있습니다. 반대로 이벤트가 발생하는 것인데요.
기본적으로는 Bubbling 순서로 이벤트가 발생합니다.
따라서 Bubbling을 잘 기억해두는 게 좋습니다.
Capturing 단계에서 이벤트 발생을 시키고 싶다면 addEventListener 메서드의 3번째 인자에 값을 true로 주면 됩니다.

참고 블로그: https://joshua1988.github.io/web-development/javascript/event-propagation-delegation/

3) Event delegation-2

이제 addEventListener 메서드를 한 번만 쓰면서 우리는 모든 list의 image 정보를 확인할 수 있습니다.

더구나 list 태그가 하나 더 추가된다고 하여도 문제없이 동작합니다.

var ul = document.querySelector("ul");
ul.addEventListener("click",function(evt) {
    if(evt.target.tagName === "IMG") {
      log.innerHTML = "clicked" + evt.target.src;
    }
});

currentTarget은 ul이지만 target은 클릭한 img 태그이다. img의 패딩 부분은 li가 나오고, 빈 공간은 ul이 나온다.

그런데 작은 문제가 하나 더 있는 거 같네요.
예제를 보면, 이미지 태그는 padding 값이 있어서, img태그와 li 태그 사이에 공백이 존재합니다.
이 부분(공백)을 클릭하면 tagName이 LI라서 위에서 구현한 조건문으로 들어가지 않았기 때문입니다.
이 부분(공백)을 클릭해도 이미지 url을 출력할 수 있으려면 어떻게 해야할까요?

var ul = document.querySelector("ul");
ul.addEventListener("click",function(evt) {
  debugger;
    if(evt.target.tagName === "IMG") {
      log.innerHTML = "clicked" + evt.target.src;
    } else if (evt.target.tagName === "LI") {
      log.innerHTML = "clicked" + evt.target.firstChild.src;
    }
});

4) HTML templating-1

아래 화면에 데이터를 Ajax로 받아와서 화면에 추가해야 한다고 생각해봅니다.
JSON 형태의 데이터를 받을 것이고요.
아래 리스트들의 내용은 모두 다 비슷합니다.
list 태그로 html을 구현해보면 사진, 가격, 이름, 별점, 추가정보(있거나 없거나)를 비슷한 tag를 사용해서 표현하면 됩니다.
여기서 templating 이라는 개념을 도입하면 좋습니다.

HTML Templating 작업이란?

반복적인 HTML부분을 template로 만들어두고, 서버에서 온 데이터(주로JSON)을 결합해서, 화면에 추가하는 작업이라고 할 수 있습니다.
아래 그림이 이해가 될 겁니다.

4) HTML templating-2

결합과정 해결하기

이제 HTML template과 JSON을 결합하면 됩니다.
간단히 이렇게 구현할 수 있습니다

var data = {  title : "hello",
              content : "lorem dkfief",
              price : 2000
           };
var html = "<li><h4>{title}</h4><p>{content}</p><div>{price}</div></li>";

html.replace("{title}", data.title)
    .replace("{content}", data.content)
    .replace("{price}", data.price)

5) HTML templating 실습

HTML Template의 보관

아래와 같은 html 문자열을 어딘가 보관해야 합니다.
javascript코드 안에서 이런 정적인 데이터를 보관하는 건 좋지 않기 때문입니다.
몇 가지 방법을 알려드립니다.

var html = "<li><h4>{title}</h4><p>{content}</p><div>{price}</div></li>";
  • 서버에서 file로 보관하고 Ajax로 요청해서 받아옵니다.
  • HTML코드 안에 숨겨둔다(?)

간단한 것이라면 HTML 안에 숨겨둘 수가 있습니다.
숨겨야 할 데이터가 많다면 별도 파일로 분리해서 Ajax로 가져오는 방법도 좋습니다.
하지만 많지 않은 데이터이므로 우리는 HTML 안에 잘 보관해두겠습니다.

Templating

HTML 중 script 태그는 type이 javascript가 아니라면 렌더링하지 않고 무시합니다.
바로 이걸 이용하는 겁니다.

<script id="template-list-item" type="text/template">
  <li>
      <h4>{title}</h4><p>{content}</p><div>{price}</div>
  </li>
</script>

text/javascript가 아니라 text/template으로 되어있는것을 보자.

이렇게 간단히 javascript에서 가져올 수가 있을 겁니다.

var html = document.querySelector("template-list-item");

이후 작업은 replace로 하면 끝나죠.

실습 코드

var data = [
        {title : "hello",content : "lorem dkfief",price : 2000},
        {title : "hello",content : "lorem dkfief",price : 2000}
];

//html 에 script에서 가져온 html template.
var html = document.querySelector("#template-list-item").innerHTML;

var resultHTML = "";

for(var i=0; i<data.length; i++) {
    resultHTML += html.replace("{title}", data[i].title)
                      .replace("{content}", data[i].content)
                      .replace("{price}", data[i].price);
}

document.querySelector(".content").innerHTML = resultHTML;

1) Tab UI를 만들기 위한 HTML과 CSS 구조전략

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        h2 {
            text-align: center;
        }
        h2, h4{
            margin: 0px;
        }
        .tab {
            width: 600px;
            margin: 0px auto;
        }
        .tabmenu{
            background-color: bisque;
            cursor: pointer;
        }
        .tabmenu > div {
            display: inline-block;
            width: 140px;
            height: 50px;
            line-height: 50px;
        }
        .content {
            padding: 5%;
            background-color: antiquewhite;
        }
    </style>
</head>
<body>
    <h2>TAB UI TEST</h2>
    <div class="tab">
        <div class="tabmenu">
            <div>crong</div>
            <div>jk</div>
            <div>pobi</div>
            <div>honux</div>
        </div>
        <section class="content">
            <h4>hello jk</h4>
            <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Minima maiores corrupti in quid</p>
        </section>
    </div>
</body>
</html>

2) Tab UI에 생명 불어넣기

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="tabui.css">
</head>
<body>
    <h2>TAB UI TEST</h2>
    <div class="tab">
        <div class="tabmenu">
            <div>crong</div>
            <div>jk</div>
            <div>pobi</div>
            <div>honux</div>
        </div>
        <section class="content">
            <h4>hello jk</h4>
            <p>golf, facebook</p>
        </section>
    </div>
    <script>
        
        function makeTemplate(data, clickedName) {
            var resultHTML = "";
            var html = document.getElementById("tabcontent").innerHTML;

            for (var i = 0; i < data.length; i++) {
                if (data[i].name === clickedName) {
                    resultHTML = html.replace("{name}", data[i],bane)
                                    .replace("{favorites}", data[i].favorites);
                    break;
                }
            }
            document.querySelector(".content").innerHTML = resultHTML;
        }

        function sendAjax(url, clickedName) {
            var oReq = new XMLHttpRequest();
            oReq.addEventListener("load", function(){
                var data = JSON.parse(oReq.responseText);
                makeTemplate(data, clickedName);
            });
            oReq.open("GET", url);
            oReq.send();
        }

        var tabmenu = document.querySelector(".tabmenu");
        tabmenu.addEventListener("click", function(evt) {
            sendAjax("./json.txt", evt.target.innerText)
        });
    </script>
    <script id="tebcontent" type="my-template">
        <h4>hello {name}</h4>
        <p>{favorites</p>
    </script>
</body>
</html>

7. Spring Core - BE

1) Spring이란?

Spring Framework란?

프레임 워크 모듈
스프링 프레임워크는 약 20개의 모듈로 구성되어 있습니다.
필요한 모듈만 가져다 사용할 수 있습니다.

AOP와 Instrumentation

메시징(Messaging)

데이터 엑세스 / 통합

스프링 코어는 꼭 알고 나머지 모듈은 필요한 것들을 공부하면 된다.

2) Spring IoC/DI 컨테이너

컨테이너란?

개발자가 서블릿을 작성해도 실제로 메모리에 올리고 실행하는 것은 서블릿 컨테이너가 한 것이다. JSP도 톰캣이라는 WAS가 대신 해준 것이다.

IoC란?

DI란?

Spring에서 제공하는 IoC/DI 컨테이너

빈 팩토리는 아주 기본적인 기능만 가지고 있는 공장이고, ApplicationContext가 더 많은 기능을 가지고 있고 이용된다.

3) xml파일을 이용한 설정-1

maven-archetype-quickstart

plugin 추가

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.6.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>

spring dependency 추가

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring.version> 4.3.14.RELEASE</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>${spring.version}</version>
	</dependency>
  </dependencies>

main 밑에 resources 폴더를 추가했고 그 밑에 applicationContext.xml이라는 파일을 생성했다.

applicationContext.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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="userBean" class="kr.or.connect.diexam01.UserBean"></bean>

</beans>

userBean을 등록했다. 스프링 컨테이너는 빈 객체를 하나만 생성하는데 싱글톤을 이용한다.

ApplicationContextExam01.java

public class ApplicationContextExam01 {
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
		System.out.println("초기화 완료!!");
		
		UserBean userBean = (UserBean)ac.getBean("userBean");
		userBean.setName("kang");
		
		System.out.println(userBean.getName());
		
		UserBean userBean2 = (UserBean)ac.getBean("userBean");
		
		if (userBean == userBean2) {
			System.out.println("같은 인스턴스입니다");
		}
	}
}

ApplicationContext는 종류가 여러가지가 있는데 위에서 applicationContext.xml에 작성했으므로 Xml을 읽어오는 ApplicationContext에 xml 파일의 위치와 파일명을 알려줬다.

자바 디렉토리에 resources는 소스 폴더이다. 그래서 리소스 폴더에서 생성한 xml 파일은 클래스 파일로 지정되고, 빈 디렉토리에 저장되어있다. 그래서 이렇게 읽어올 수 있다.

이렇게 객체를 대신 생성해주고 싱글톤으로 관리하는 기능 등을 IoC 제어 역전이라고 한다.

3) xml파일을 이용한 설정-2

Engine.java

public class Engine {
	public Engine() {
		System.out.println("Engine 생성자");
	}
	
	public void exec() {
		System.out.println("엔진이 동작합니다");
	}
}

Car.java

public class Car {
	private Engine v8;
	
	public Car() {
		System.out.println("Car 생성자");
	}
	
	public void setEngine(Engine e) {
		this.v8 = e;
	}
	
	public void run() {
		System.out.println("엔진을 이용하여 달립니다.");
		v8.exec();
	}
}

원래라면 아래와 같은 코드가 작성되어야할 것이다.

Engine e = new Engine();
Car c = new Car();
c.setEngine( e );
c.run();

하지만 클래스들을 스프링이 관리하게 해보자.
applicationContext.xml 추가

<bean id="e" class="kr.or.connect.diexam01.Engine"></bean>
<bean id="car" class="kr.or.connect.diexam01.Car">
	<property name="engine" ref="e"></property>
</bean>

Car 클래스의 프로퍼티에 Engine을 사용하도록 설정한 것이다.

ApplicationContextExam02.java

public class ApplicationContextExam02 {

	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
		
		Car car = (Car) ac.getBean("c");
		car.run();
	}
}

4) Java Config를 이용한 설정

Java config를 이용한 설정을 위한 어노테이션

@Configuration

  • 스프링 설정 클래스를 선언하는 어노테이션

@Bean

  • bean을 정의하는 어노테이션

@ComponentScan

  • @Controller, @Service, @Repository, @Component 어노테이션이 붙은 클래스를 찾아 컨테이너에 등록

@Component

  • 컴포넌트 스캔의 대상이 되는 애노테이션 중 하나로써 주로 유틸, 기타 지원 클래스에 붙이는 어노테이션

@Autowired

  • 주입 대상이되는 bean을 컨테이너에 찾아 주입하는 어노테이션

Java Config를 이용해 설정하기

@Configuration
public class ApplicationConfig {
	
	@Bean
	public Car car(Engine e) {
		Car c = new Car();
		c.setEngine(e);
		return c;
	}
	
	@Bean
	public Engine engine() {
		return new Engine();
	}
}

@Configuration 은 스프링 설정 클래스라는 의미를 가집니다.
JavaConfig로 설정을 할 클래스 위에는 @Configuration가 붙어 있어야 합니다.

ApplicationContext중에서 AnnotationConfigApplicationContext는 JavaConfig클래스를 읽어들여 IoCDI를 적용하게 됩니다.

이때 설정파일 중에 @Bean이 붙어 있는 메소드들을 AnnotationConfigApplicationContext는 자동으로 실행하여 그 결과로 리턴하는 객체들을 기본적으로 싱글턴으로 관리를 하게 됩니다.

ApplicationContextExam03.java

public class ApplicationContextExam03 {

	public static void main(String[] args) {
		ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class);
		
		Car car = (Car) ac.getBean(Car.class);
		car.run();
	}

}

파라미터로 요청하는 class 타입으로 지정 가능합니다.
Car car = ac.getBean(Car.class);

ApplicationConfig2.java

@Configuration
@ComponentScan("kr.or.connect.diexam01")
public class ApplicationConfig2 {

}

기존 JavaConfig에서 빈을 생성하는 메소드를 모두 제거했습니다.
단, @Configuration아래에 @ComponentScan이라는 어노테이션을 추가했습니다.

@ComponentScan어노테이션은 파라미터로 들어온 패키지 이하에서 @Controller, @Service, @Repository, @Component 어노테이션이 붙어 있는 클래스를 찾아 메모리에 몽땅 올려줍니다.

기존의 Car클래스와 Engine클래스 위에 @Component를 붙이도록 하겠습니다.

Engine.java

@Component
public class Engine {
	public Engine() {
		System.out.println("Engine 생성자");
	}
	
	public void exec() {
		System.out.println("엔진이 동작합니다");
	}
}

Car.java

@Component
public class Car {
	@Autowired
	private Engine v8;
	
	public Car() {
		System.out.println("Car 생성자");
	}
	
	public void run() {
		System.out.println("엔진을 이용하여 달립니다.");
		v8.exec();
	}
}

ApplicationContextExam04.java

public class ApplicationContextExam04 {

	public static void main(String[] args) {
		ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig2.class);
		
		Car car = (Car) ac.getBean(Car.class);
		car.run();
	}
}

Spring에서 사용하기에 알맞게 @Controller, @Service, @Repository, @Component 어노테이션이 붙어 있는 객체들은 ComponentScan을 이용해서 읽어들여 메모리에 올리고 DI를 주입하도록 하고, 이러한 어노테이션이 붙어 있지 않은 객체@Bean어노테이션을 이용하여 직접 생성해주는 방식으로 클래스들을 관리하면 편리합니다.

8. Spring JDBC - BE

1) Spring JDBC 소개

JDBC를 이용해서 프로그래밍을 하게 되면 반복적인 코드가 많이 발생합니다.
이러한 반복적인 코드는 개발자의 생산성을 떨어트리는 주된 원인이 됩니다.
이러한 문제를 해결하기 위해 등장한 것이 Spring JDBC입니다.

Spring JDBC - 개발자가 해야할 일은?

Spring JDBC 패키지

org.springframework.jdbc.core

  • JdbcTemplate 및 관련 Helper 객체 제공

org.springframework.jdbc.datasource

  • DataSource를 쉽게 접근하기 위한 유틸 클래스, 트랜젝션매니져 및 다양한 DataSource 구현을 제공

org.springframework.jdbc.object

  • RDBMS 조회, 갱신, 저장등을 안전하고 재사용 가능한 객제 제공

org.springframework.jdbc.support

  • jdbc.core 및 jdbc.object를 사용하는 JDBC 프레임워크를 지원

JDBC Template

  • org.springframework.jdbc.core에서 가장 중요한 클래스입니다.
  • 리소스 생성, 해지를 처리해서 연결을 닫는 것을 잊어 발생하는 문제 등을 피할 수 있도록 합니다.
  • 스테이먼트(Statement)의 생성과 실행을 처리합니다.
  • SQL 조회, 업데이트, 저장 프로시저 호출, ResultSet 반복호출 등을 실행합니다.
  • JDBC 예외가 발생할 경우 org.springframework.dao패키지에 정의되어 있는 일반적인 예외로 변환시킵니다.

JdbcTemplate select 예제 1

Jdbc만을 이용했을 때는 훨씬 많은 코드를 작성했어야 헀다.

JdbcTemplate select 예제 2

JdbcTemplate select 예제 3

JdbcTemplate select 예제 4

JdbcTemplate select 예제 5

JdbcTemplate select 예제 6

JdbcTemplate insert 예제

JdbcTemplate insert 예제

JdbcTemplate delete 예제

JdbcTemplate외의 접근방법

2) Spring JDBC 실습-1

DTO란?

DTO의 예

public class ActorDTO {
    private Long id;
    private String firstName;
    private String lastName;
    public String getFirstName() {
        return this.firstName;
    }
    public String getLastName() {
        return this.lastName;
    }
    public Long getId() {
        return this.id;
    }
    // ......
}

DAO란?

ConnectionPool이란?

DataSource란?

Spring JDBC를 이용한 DAO 작성 실습

2) Spring JDBC 실습-2

project -> maven -> maven-archetype-quickstart

<?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>kr.or.connect</groupId>
  <artifactId>daoexam</artifactId>
  <version>0.0.1-SNAPSHOT</version>

  <name>daoexam</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
	<spring.version>4.3.5.RELEASE</spring.version>
  </properties>

  <dependencies>
	<!-- Spring -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-jdbc</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-tx</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<!-- basic data source -->
	<dependency>
		<groupId>org.apache.commons</groupId>
		<artifactId>commons-dbcp2</artifactId>
		<version>2.1.1</version>
	</dependency>
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.45</version>
	</dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.6.1</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

ApplicationConfig.java

package kr.or.connect.daoexam.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({DBConfig.class})
public class ApplicationConfig {

}

한 클래스에서 모든 설정을 관리하는 것이 아니라 분리할 수도 있다. 여기에서는 DB쪽 설정 파일은 DBConfig로 분리하고 그것을 전체 관리하는 파일인 여기서 Import하는 방식이다.

DBConfig.java

package kr.or.connect.daoexam.config;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
public class DBConfig {
	private String driverClassName = "com.mysql.jdbc.Driver";
    private String url = "jdbc:mysql://localhost:3306/connectdb?useUnicode=true&characterEncoding=utf8";

    private String username = "connectuser";
    private String password = "connect123!@#";

    @Bean
    public DataSource dataSource() {
    	BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;

    }
}

DataSource는 커넥션 풀을 관리하는 객체인데 여기서는 DBConfig 객체에서 빈으로 등록해서 관리하고 있다.

@EnableTransactionManagement는 다음 강의에서 다룰 것이다.

DataSourceTest.java

package kr.or.connect.daoexam.main;

import java.sql.Connection;

import javax.sql.DataSource;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import kr.or.connect.daoexam.config.ApplicationConfig;

public class DataSourceTest {

	public static void main(String[] args) {
		ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class);
		DataSource ds = ac.getBean(DataSource.class);
		Connection conn = null;
		try {
			conn = ds.getConnection();
			if(conn != null)
				System.out.println("접속 성공^^");
		}catch (Exception e) {
			e.printStackTrace();
		}finally {
			if(conn != null) {
				try {
					conn.close();
				}catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
}

2) Spring JDBC 실습-3

Role.java

package kr.or.connect.daoexam.dto;

public class Role {
	private int roleId;
	private String description;
	
	public int getRoleId() {
		return roleId;
	}
	public void setRoleId(int roleId) {
		this.roleId = roleId;
	}
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
	
	@Override
	public String toString() {
		return "Role [roleId=" + roleId + ", description=" + description + "]";
	}
}

RoleDaoSqls.java

package kr.or.connect.daoexam.dao;

public class RoleDaoSqls {
	public static final String SELECT_ALL = "SELECT role_id, description FROM role ORDER BY role_id";
	
}

RoleDao.java

package kr.or.connect.daoexam.dao;

import java.util.Collections;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;

import kr.or.connect.daoexam.dto.Role;
import static kr.or.connect.daoexam.dao.RoleDaoSqls.*;

@Repository
public class RoleDao {
	private NamedParameterJdbcTemplate jdbc;
	private RowMapper<Role> rowMapper = BeanPropertyRowMapper.newInstance(Role.class);
	private SimpleJdbcInsert insertAction;
	
	public RoleDao(DataSource dataSource) {
		this.jdbc = new NamedParameterJdbcTemplate(dataSource);
		this.insertAction = new SimpleJdbcInsert(dataSource).withTableName("role");
	}
	
	public List<Role> selectAll() {
		return jdbc.query(SELECT_ALL, Collections.<String, Object>emptyMap(), rowMapper);
	}
}

dao가 NamedParameterJdbcTemplate, SimpleJdbcInsert를 이용한다. jdbcTemplate은 바인딩 할 때 물음표를 사용하는데, 그러면 sql 문자열만 보면 어떤 값이 매핑되는지 알기 어렵다. 그래서 NamedParameterJdbcTemplate은 이름을 이용해서 바인딩하거나 결과값을 가져올 수 있다.

dataSource를 기본 생성자에서 받아들이고 있는데 스프링 4.4부터는 ComponentScan으로 찾았을 때 기본 생성자가 없다면 자동으로 객체를 주입해준다.

selectAll에서 두번째 인자는 결과값을 담을 빈 map 객체다. 세번째 파라미터는 sql문에 바인딩할 값이 있을 경우 바인딩할 값이다. select 한건 한건의 결과를 Dto에 저장하는 것이다. 생성한 Dto를 리스트에 담아서 반환한다.

dbms에서는 단어와 단어 사이 언더바를 사용하고 자바는 카멜 표기법을 이용하는데 이도 전환해준다.

ApplicationCofig.java에 추가

@ComponentScan(basePackages = { "kr.or.connect.daoexam.dao" })

RoleDao에 @Repository를 붙였는데 그것을 잡아서 등록해준다.

SelectAllTest.java

package kr.or.connect.daoexam.main;

import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import kr.or.connect.daoexam.config.ApplicationConfig;
import kr.or.connect.daoexam.dao.RoleDao;
import kr.or.connect.daoexam.dto.Role;

public class SelectAllTest {
	
	public static void main(String[] args) {
		ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class);
		
		RoleDao roleDao = ac.getBean(RoleDao.class);
		
		List<Role> list = roleDao.selectAll();
		
		for(Role role: list) {
			System.out.println(role);
		}
	}
}

2) Spring JDBC 실습-4

insert 하기

insert하는 것은 따로 SQL을 작성하지 않아도 된다. SimpleJdbcInsert가 있기 때문이다.

RoleDao에 추가

	private SimpleJdbcInsert insertAction;
    
   ...
   	public RoleDao(DataSource dataSource) {
		...
		this.insertAction = new SimpleJdbcInsert(dataSource).withTableName("role");
	}
    
    	public int insert(Role role) {
		SqlParameterSource params = new BeanPropertySqlParameterSource(role);
		return insertAction.execute(params);
	}

JDBCTest.java

public class JDBCTest {

	public static void main(String[] args) {
		ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class);
		
		RoleDao roleDao = ac.getBean(RoleDao.class);
		
		Role role = new Role();
		role.setRoleId(201);
		role.setDescription("PROGRAMMER");
		
		int count = roleDao.insert(role);
		System.out.println(count + " 입력하였습니다");
		
	}
}

update 하기

RoleDaoSqls 추가

public static final String UPDATE = "UPDATE role SET description = :description WHERE role_id = :roleId";

예전에는 물음표가 들어갔는데 이제는 뜻을 알 수 있는 변수명이 들어가는 것을 알 수 있다.

RoleDao 추가

	public int update(Role role) {
		SqlParameterSource params = new BeanPropertySqlParameterSource(role);
		return jdbc.update(UPDATE, params);
	}

2) Spring JDBC 실습-5

한 건 SELECT하기

RoleDaoSqls 추가

public static final String SELECT_BY_ROLE_ID = "SELECT role_id, description FROM role where role_id = :roleId";

RoleDao 추가

public Role selectById(Integer id) {
	try {
		Map<String, ?> params = Collections.singletonMap("roldId", id);
		return jdbc.queryForObject(SELECT_BY_ROLE_ID, params, rowMapper);
	}catch(EmptyResultDataAccessException e) {
		return null;
	}
}

한건 select 할때는 queryForObject가 사용되는 것이 차이점이다. 조건이 만족하는 것이 없으면 Exception이 발생하므로 예외처리를 하였다.

DELETE하기

RoleDaoSqls 추가

public static final String DELETE_BY_ROLE_ID = "DELETE FROM role WHERE rold_id = :roleId";

RoleDao 추가

public int deleteById(Integer id) {
	Map<String, ?> params = Collections.singletonMap("roleId", id);
	return jdbc.update(DELETE_BY_ROLE_ID, params);
}

jdbc의 두 번째 인자에 map 객체가 들어가고 있다. update 문에서 SqlParameterSource가 제공하는, 객체에 들어있는 것을 db 컬럼명에 맞춰서 알아서 map으로 바꿔주는 역할을 했는데 이번에도 마찬가지앋.

9. Spring MVC - BE

1) Spring MVC란?

MVC?

MVC Model 1 아키텍처

이런 모델에서는 요청을 JSP가 받으므로 요청만큼 JSP가 존재해야 한다.
이 모델의 단점은 JSP에 자바 코드와 html이 섞여있다.

MVC Model 2 아키텍처

로직과 뷰가 분리되었다.

MVC Model2 발전형태

Model2의 발전한 형태가 Spring module로 구성되어있고 이를 Spring MVC라고 부른다.

2) Spring MVC구성요소-1

Spring MVC 기본 동작 흐름

파란색 부분은 전부 Spring MVC가 제공하는 부분이고, 개발자가 만들어야하는 것은 보라색으로 된 부분이다. 녹색은 제공되는 것도 있고 만들어야하는 것도 있다.

요청은 기본적으로 Dispatcher Servlet이 다 받고, 요청을 처리해줄 컨트롤러와 메서드가 뭔지 HandlerMapping에게 물어본다. (개발자가 애노테이션이나 xml로 설정을 한다). 그 다음은 Handler Adapter에게 실행을 요청한다. 결정된 컨트롤러 실행되고, 그 결과 view name이 반행되는데 View resolver가 어떤 뷰인지를 알려주면 뷰를 보여준다.

2) Spring MVC구성요소-2

DispatcherServlet

프론트 컨트롤러는 일을 처리하지않고 요청을 받아서 넘기는 역할만 한다. 보통은 프론트 컨트롤러는 하나만 둔다.

DispatcherServlet 내부 동작흐름

DispatcherServlet 내부 동작흐름 상세 - 요청 선처리 작업

Locale 결정은 어느 언어(한국어, 영어..)를 쓸지 지역화 처리해주는 것이다.

RequestContextHolder는 thread local 객체로 요청을 받아서 응답할때까지 requestresponse를 스프링이 관리하는 객채내에서 사용할 수 있게 해주는 것이다.

FlashMap은 리다이렉트로 값을 전달할때 사용된다. 매번 리다이렉트 될때 값을 물음표를 이용해서 넘기면 url이 굉장히 길어진다. 그래서 그걸 해결해주는 것이다.

멀티파트는 파일을 업로드할 때 사용되는 것이다.

요청 선처리 작업시 사용된 컴포넌트

DispatcherServlet 내부 동작흐름 상세 - 요청 전달

요청 전달시 사용된 컴포넌트

DispatcherServlet 내부 동작흐름 상세 - 요청 처리

요청 처리시 사용된 컴포넌트

DispatcherServlet 내부 동작흐름 상세 - 예외처리

예외 처리시 사용된 컴포넌트

DispatcherServlet 내부 동작흐름 상세 - 뷰 렌더링 과정

뷰 렌더링 과정시 사용된 컴포넌트

DispatcherServlet 내부 동작흐름 상세 - 요청 처리 종료

3) Spring MVC를 이용한 웹 페이지 작성 실습-1

Controller 작성 실습 1/3

navigator -> main 폴더 및에 java 폴더 생성

pom.xml

<?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>kr.or.connect</groupId>
  <artifactId>mvcexam</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>mvcexam Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
    <spring.version> 4.3.5.RELEASE</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!-- Spring -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-webmvc</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<!-- Servlet JSP JSTL -->
	<dependency>
		<groupId>javax.servlet</groupId>
		<artifactId>javax.servlet-api</artifactId>
		<version>3.1.0</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>
	<dependency>
		<groupId>javax.servlet</groupId>
		<artifactId>jstl</artifactId>
		<version>1.2</version>
	</dependency>
  </dependencies>

  <build>
    <finalName>mvcexam</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.6.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

navigator -> .settings -> facet -> version= (2.3) -> (3.1)

3) Spring MVC를 이용한 웹 페이지 작성 실습-2

DispatcherServlet을 FrontController로 설정하기

DispatcherServlet도 서블릿이기 때문에 web.xml에서 설정할 수 있다. 그외에도 다양한 방법이 있지만 첫 번째, 세번째가 가장 많이 활용된다.

web.xml 파일에서 DispatcherServlet설정하기 1/2

initParam에 dispatcherServlet이 어떤 역할을 할지 설정해놓은 파일을 전달해준다.

url설정이 '/'로 되어있다. 즉 모든 요청을 다 받는다는 뜻이다.

web.xml 파일에서 DispatcherServlet설정하기 2/2

수업에서는 하지 않는다

Spring MVC 설정

DispatcherServlet에 대한 설정은 web.xml에서하고 DispatcherServlet이 읽어들여야할 설정은 별도로 java config로 해준다.

@Configuration

@EnableWebMvc

@ComponentScan

WebMvcConfigurerAdapter

Controller(Handler) 클래스 작성하기

RequestMapping

WebMvcContextConfiguration

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"kr.or.connect.mvcexam.controller"})
public class WebMvcContextConfiguration extends WebMvcConfigurerAdapter {
	@Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/META-INF/resources/webjars/").setCachePeriod(31556926);
        registry.addResourceHandler("/css/**").addResourceLocations("/css/").setCachePeriod(31556926);
        registry.addResourceHandler("/img/**").addResourceLocations("/img/").setCachePeriod(31556926);
        registry.addResourceHandler("/js/**").addResourceLocations("/js/").setCachePeriod(31556926);
    }
 
    // default servlet handler를 사용하게 합니다.
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
   
    @Override
    public void addViewControllers(final ViewControllerRegistry registry) {
    		System.out.println("addViewControllers가 호출됩니다. ");
        registry.addViewController("/").setViewName("main");
    }
    
    @Bean
    public InternalResourceViewResolver getInternalResourceViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
}

DispatcherServlet이 실행될 때 읽어들일 파일이다.

addResourceHandlers()메서드는 css, img, js등 자원에 대한 요청도 처리하기 위함이다. '/'로 들어오는 모든 요청을 처리하도록 만들어놨기 때문에 저런 자원에 대한 요소는 따로 처리되도록 만들어주는 것이다.

configureDefaultServlethandling() 메서드는 기본 default handler가 쓰이게 하는 것이다. 매핑 되지 않는 요청이 오면 static한 화면을 보여주낟.

getInternalResourceViewResolver는 view의 prefix와 suffix를 설정해주는 것이다.

web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  
  <servlet>
   <servlet-name>mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextClass</param-name>
      <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </init-param>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>kr.or.connect.mvcexam.config.WebMvcContextConfiguration</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>mvc</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

DispatcherServlet이 front controller 역할을 하도록 설정해주는 것이다.
'/'에 대해 들어오는 요청을 mvc 라는 서블릿이 처리하도록 설정하였는데, 서블릿 클래스항목에 DispatcherServlet으로 설정되어있는 것을 알 수 있다. 또한 위에서 작성한 WebMvcContextConfiguration 설정을 읽어오도록 설정하였다.

index.jsp를 지우면 main.jsp가 기본 페이지가 되어 나온다.

3) Spring MVC를 이용한 웹 페이지 작성 실습-3

Controller 작성 실습 1/3

plusForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<form method="post" action="plus">
	value1: <input type="text" name="value1"><br>
	value1: <input type="text" name="value2"><br>
	<input type="submit" value="확인">
	</form>
</body>
</html>

plusResult.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
${value1} 더하기 ${value2} (은/는) ${result}입니다.
</body>
</html>

PlusController.java

package kr.or.connect.mvcexam.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class PlusController {
	
	@GetMapping(path="/plusform")
	public String plusform() {
		return "plusForm";
	}
	
	@PostMapping(path="/plus")
	public String plus(@RequestParam(name = "value1", required = true) int value1, 
			@RequestParam(name = "value2", required = true) int value2, ModelMap modelMap) {
		int result = value1 + value2;
		
		modelMap.addAttribute("value1", value1);
		modelMap.addAttribute("value2", value2);
		modelMap.addAttribute("result", result);
		return "plusResult";  
	}
}

Spring MVC가 지원하는 Controller 메소드 인수 타입

Spring MVC가 지원하는 메소드 인수 애노테이션

@RequestParam

@PathVariable

@RequestHeader

Spring MVC가 지원하는 메소드 리턴 값

el이 제대로 안먹힐 수가 있는데, 그 이유는 web.xml 맨위에 보면 아직 2_3버전으로 되어있다. 지우고 <?xml version="1.0" encoding="UTF-8"?>로 바꾸어 주자.

3) Spring MVC를 이용한 웹 페이지 작성 실습-4

Controller 작성 실습 2/3

userform

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form method="post" action="regist">
name: <input type="text" name="name"><br>
email: <input type="text" name="email"><br>
age: <input type="text" name="age"><br>
<input type="submit" value="확인">
</form>
</body>
</html>

User

package dto;

public class User {
	private String name;
	private String email;
	private int age;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "User [name=" + name + ", email=" + email + ", age=" + age + "]";
	}
}

UserController

package kr.or.connect.mvcexam.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import dto.User;

@Controller
public class UserController {
	
	@RequestMapping(path="/userform", method = RequestMethod.GET)
	public String userform() {
		return "userform";
	}
	
	@RequestMapping(path="/regist", method=RequestMethod.POST)
	public String regist(@ModelAttribute User user) {
		System.out.println("사용자가 입력한 user 정보입니. 해당 정보를 이용하는 코드가 와야합니다");
		System.out.println(user);
		return "regist";
	}
	
}

ModelAttribute를 통해서 User 객체를 가져온다.

Controller 작성 실습 3/3

이렇게 들어오는 것을 path variable이라고 한다.

GoodsController

@Controller
public class GoodsController {
	@GetMapping("/goods/{id}")
	public String getGoodsById(@PathVariable(name="id") int id,
							   @RequestHeader(value="User-Agent", defaultValue="myBrowser") String userAgent,
							  HttpServletRequest request,
							  ModelMap model
							  ) {
		
		String path = request.getServletPath();
		
		System.out.println("id : " + id);
		System.out.println("user_agent : " + userAgent);
		System.out.println("path : " + path);
		
		model.addAttribute("id", id);
		model.addAttribute("userAgent", userAgent);
		model.addAttribute("path", path);
		return "goodsById";
	}
}

UserById

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
id : ${id} <br>
user_agent: ${userAgent} <br>
path: ${path} <br>
</body>
</html>

10. 레이어드 아키텍처(Layered Architecture) - BE

1) 레이어드 아키텍처(Layered Architecture) 란?

Controller에서 중복되는 부분을 처리하려면?

컨트롤러와 서비스

서비스(Service) 객체란?

트랜잭션(Transaction)이란?

원자성(Atomicity)

일관성(Consistency)

독립성(Isolation)

지속성 (Durability)

JDBC 프로그래밍에서 트랜잭션 처리 방법

@EnableTransactionManagement

서비스 객체에서 중복으로 호출되는 코드의 처리

하나의 트랜잭션에는 여러개의 db 작업이 있을 수 있는데 중복되는 것은 별도의 객체로 분리할 수 있다.

레이어드 아키텍처

설정의 분리

2) 레이어드 아키텍처(Layered Architecture) 실습-1

방명록 만들기 실습

방명록 요구사항

방명록 클래스 다이어그램

2) 레이어드 아키텍처(Layered Architecture) 실습-2

pom.xml

<?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>kr.or.connect</groupId>
  <artifactId>guestbook</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>guestbook Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <!-- spring -->
    <spring.version>4.3.5.RELEASE</spring.version>
    <!-- jackson -->
    <jackson2.version>2.8.6</jackson2.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!-- Spring -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-webmvc</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<!-- json 라이브러리 databind jackson-core, jackson-annotaion에 의존성이 있다. -->
	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-databind</artifactId>
		<version>2.9.4</version>
	</dependency>

	<dependency>
		<groupId>javax.servlet</groupId>
		<artifactId>javax.servlet-api</artifactId>
		<version>3.1.0</version>
		<scope>provided</scope>
	</dependency>
	
	<dependency>
		<groupId>javax.servlet</groupId>
		<artifactId>jstl</artifactId>
		<version>1.2</version>
	</dependency>
	<!-- spring jdbc & jdbc driver & connection pool -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-jdbc</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-tx</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.45</version>
	</dependency>
	
	<!-- basic data source -->
	<dependency>
		<groupId>org.apache.commons</groupId>
		<artifactId>commons-dbcp2</artifactId>
		<version>2.1.1</version>
	</dependency>

	<!-- Jackson module -->
	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-databind</artifactId>
		<version>${jackson2.version}</version>
	</dependency>
	<dependency>
		<groupId>com.fasterxml.jackson.datatype</groupId>
		<artifactId>jackson-datatype-jdk8</artifactId>
		<version>${jackson2.version}</version>
	</dependency>
  </dependencies>

  <build>
    <finalName>guestbook</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<version>3.6.1</version>
			<configuration>
				<source>1.8</source>
				<target>1.8</target>
			</configuration>
		</plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

WebMvcConfiguration

package kr.or.connect.guestbook.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "kr.or.connect.guestbook.controller" })
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/css/**").addResourceLocations("/css/").setCachePeriod(31556926);
        registry.addResourceHandler("/img/**").addResourceLocations("/img/").setCachePeriod(31556926);
        registry.addResourceHandler("/js/**").addResourceLocations("/js/").setCachePeriod(31556926);
    }
    
    // default servlet handler를 사용하게 합니다.
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
    
    @Override
    public void addViewControllers(final ViewControllerRegistry registry) {
    		System.out.println("addViewControllers가 호출됩니다. ");
        registry.addViewController("/").setViewName("index");
    }
    
    @Bean
    public InternalResourceViewResolver getInternalResourceViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
}

DBConfig.java

package kr.or.connect.guestbook.config;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;

@Configuration
@EnableTransactionManagement
public class DBConfig implements TransactionManagementConfigurer{

	private String driverClassName = "com.mysql.jdbc.Driver";
	private String url = "jdbc:mysql://localhost:3306/connectdb?useUnicode=true&characterEncoding=utf8";
	private String username = "connectuser";
	private String password = "connect123!@#";
	
	@Bean
	public DataSource dataSource() {
		BasicDataSource dataSource = new BasicDataSource();
		dataSource.setDriverClassName(driverClassName);
		dataSource.setUrl(url);
		dataSource.setUsername(username);
		dataSource.setPassword(password);
		return dataSource;
	}
	
	@Override
	public PlatformTransactionManager annotationDrivenTransactionManager() {
		return transactionManger();
	}
	
	@Bean
	public PlatformTransactionManager transactionManger() {
		return new DataSourceTransactionManager(dataSource());
	}
}

@EnableTransactionMangement는 트랜잭션 처리 관련된 설정을 해주는 것이다.

ApplicationConfig

@Configuration
@ComponentScan(basePackages = { "kr.or.connect.guestbook.dao",  "kr.or.connect.guestbook.service"})
@Import({ DBConfig.class })
public class ApplicationConfig {

}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
	<display-name>Spring JavaConfig Sample</display-name>
	<context-param>
		<param-name>contextClass</param-name>
		<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext
		</param-value>
	</context-param>
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>kr.or.connect.guestbook.config.ApplicationConfig
		</param-value>
	</context-param>
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener
		</listener-class>
	</listener>

	<servlet>
		<servlet-name>mvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet
		</servlet-class>
		<init-param>
			<param-name>contextClass</param-name>
			<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext
			</param-value>
		</init-param>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>kr.or.connect.guestbook.config.WebMvcContextConfiguration
			</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>mvc</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter
		</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
</web-app>

모든 '/'아래로 들어오는 요청에 대해 mvc 라는 서블릿이 처리하도록 하였는데 DispatcherServlet으로 설정하였다. 그리고 설정들을 읽어오기 위해 WebMvcContextConfiguration을 읽어오도록 하였다. listener를 설정하는 부분이 있는데, 비즈니스 로직쪽에서 사용되는 DBConfig와 ApplicationConfig를 읽어들이게 하는 것이다. 리스너가 실행될때 context-param에 등록된 것들을 참고해서 실행한다.

필터는 요청이 수행되기전, 응답이 나가기 전 한번씩 거쳐서 수행되는 부분이다.

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<% 
	response.sendRedirect("list");
%>

2) 레이어드 아키텍처(Layered Architecture) 실습-3

CREATE TABLE guestbook (
	id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
	name varchar(255) NOT NULL,
	content text,
	regdate datetime,
	PRIMARY KEY(id)
	);

CREATE TABLE log (
	id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
	ip varchar(255) NOT NULL,
	method varchar(10) NOT NULL,
	regdate datetime,
	PRIMARY KEY (id)
 	);

guestbook, log 테이블 생성

LogDao.java

package kr.or.connect.guestbook.dao;

import javax.sql.DataSource;

import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;

import kr.or.connect.guestbook.dto.Log;

@Repository
public class LogDao {
	private NamedParameterJdbcTemplate jdbc;
	private SimpleJdbcInsert insertAction;
	
	public LogDao(DataSource dataSource) {
		this.jdbc = new NamedParameterJdbcTemplate(dataSource);
		this.insertAction = new SimpleJdbcInsert(dataSource)
				.withTableName("log")
				.usingGeneratedKeyColumns("id");
	}
	
	public Long insert(Log log) {
		SqlParameterSource params = new BeanPropertySqlParameterSource(log);
		return insertAction.executeAndReturnKey(params).longValue();
	}
}

usingGeneratedKeyColumns는 id 컬럼의 값을 자동으로 생성해주는 것이다.
insert 함수는 내부적으로 insert문을 생성해서 실행하고, 생성된 아이디를 반환해준다.

GuestBookDaoSqls

public class GuestbookDaoSqls {
	public static final String SELECT_PAGING = "SELECT id, name, content, regdate FROM guestbook ORDER BY id DESC limit :start, :limit";
	public static final String DELETE_BY_ID = "DELETE FROM guestbook WHERE id = :id";
	public static final String SELECT_COUNT = "SELECT count(*) FROM guestbook";
}

GuestbookDaoTest.java

package kr.or.connect.guestbook.dao;

import java.util.Date;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import kr.or.connect.guestbook.config.ApplicationConfig;
import kr.or.connect.guestbook.dto.Guestbook;
import kr.or.connect.guestbook.dto.Log;

public class GuesbookDaoTest {
	public static void main(String[] args) {
		ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class); 
		
		GuestbookDao guestbookDao = ac.getBean(GuestbookDao.class);
		
		Guestbook guestbook = new Guestbook();
		guestbook.setName("강경미");
		guestbook.setContent("반갑습니다. 여러분.");
		guestbook.setRegdate(new Date());
		Long id = guestbookDao.insert(guestbook);
		System.out.println("id : " + id);
        
        LogDao logDao = ac.getBean(LogDao.class);
		Log log = new Log();
		log.setIp("127.0.0.1");
		log.setMethod("insert");
		log.setRegdate(new Date());
		logDao.insert(log);
	}
}

2) 레이어드 아키텍처(Layered Architecture) 실습-4

GuestbookService.java

package kr.or.connect.guestbook.service;

import java.util.List;

import kr.or.connect.guestbook.dto.Guestbook;

public interface GuestbookService {
	public static final Integer LIMIT = 5;
	public List<Guestbook> getGuestbooks(Integer start);
	public int deleteGuestbook(Long id, String ip);
	public Guestbook addGuestbook(Guestbook guestbook, String ip);
	public int getCount();
}

인터페이스이다. 그래서 구현체를 만들어야한다.

GuestbookServiceImpl.java

package kr.or.connect.guestbook.service.impl;

import java.util.Date;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import kr.or.connect.guestbook.dao.GuestbookDao;
import kr.or.connect.guestbook.dao.LogDao;
import kr.or.connect.guestbook.dto.Guestbook;
import kr.or.connect.guestbook.dto.Log;
import kr.or.connect.guestbook.service.GuestbookService;

@Service
public class GuestbookServiceImpl implements GuestbookService{
	@Autowired
	GuestbookDao guestbookDao;
	
	@Autowired
	LogDao logDao;

	@Override
	@Transactional
	public List<Guestbook> getGuestbooks(Integer start) {
		List<Guestbook> list = guestbookDao.selectAll(start, GuestbookService.LIMIT);
		return list;
	}

	@Override
	@Transactional(readOnly=false)
	public int deleteGuestbook(Long id, String ip) {
		int deleteCount = guestbookDao.deleteById(id);
		Log log = new Log();
		log.setIp(ip);
		log.setMethod("delete");
		log.setRegdate(new Date());
		logDao.insert(log);
		return deleteCount;
	}

	@Override
	@Transactional(readOnly=false)
	public Guestbook addGuestbook(Guestbook guestbook, String ip) {
		guestbook.setRegdate(new Date());
		Long id = guestbookDao.insert(guestbook);
		guestbook.setId(id);
		
//		if(1 == 1)
//			throw new RuntimeException("test exception");
//			
		Log log = new Log();
		log.setIp(ip);
		log.setMethod("insert");
		log.setRegdate(new Date());
		logDao.insert(log);
		
		return guestbook;
	}

	@Override
	public int getCount() {
		return guestbookDao.selectCount();
	}
}

addGuestbook()에서 주석처리된 부분을 해지하면 오류를 발생시키는데, @Transactional(readOnly=false)가 붙은 덕분에, guestbook에는 데이터가 들어가고 log에는 데이터가 안들어가는 일이 발생되지 않는다. 저 애노테이션이 붙으면 하나의 트랜잭션으로 처리하기 때문에 다 atomicity가 보장된다.

GuestbookServiceTest.java

package kr.or.connect.guestbook.service.impl;

import java.util.Date;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import kr.or.connect.guestbook.config.ApplicationConfig;
import kr.or.connect.guestbook.dto.Guestbook;
import kr.or.connect.guestbook.service.GuestbookService;

public class GuestbookServiceTest {

	public static void main(String[] args) {
		ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class); 
		GuestbookService guestbookService = ac.getBean(GuestbookService.class);
		
		Guestbook guestbook = new Guestbook();
		guestbook.setName("kang kyungmi22");
		guestbook.setContent("반갑습니다. 여러분. 여러분이 재미있게 공부하고 계셨음 정말 좋겠네요^^22");
		guestbook.setRegdate(new Date());
		Guestbook result = guestbookService.addGuestbook(guestbook, "127.0.0.1");
		System.out.println(result);		
	}
}

혹시 MySQL에서 한글 깨짐 때문에 오류가 생기면 ALTER TABLE (테이블명) convert to charset utf8; 명령어로 해결이 가능하다.

2) 레이어드 아키텍처(Layered Architecture) 실습-5

GuestbookController.java

package kr.or.connect.guestbook.controller;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import kr.or.connect.guestbook.dto.Guestbook;
import kr.or.connect.guestbook.service.GuestbookService;

@Controller
public class GuestbookController {
	@Autowired
	GuestbookService guestbookService;
	
	@GetMapping(path="/list")
	public String list(@RequestParam(name="start", required=false, defaultValue="0") int start,
					   ModelMap model) {
		
		// start로 시작하는 방명록 목록 구하기
		List<Guestbook> list = guestbookService.getGuestbooks(start);
		
		// 전체 페이지수 구하기
		int count = guestbookService.getCount();
		int pageCount = count / GuestbookService.LIMIT;
		if(count % GuestbookService.LIMIT > 0)
			pageCount++;
		
		// 페이지 수만큼 start의 값을 리스트로 저장
		// 예를 들면 페이지수가 3이면
		// 0, 5, 10 이렇게 저장된다.
		// list?start=0 , list?start=5, list?start=10 으로 링크가 걸린다.
		List<Integer> pageStartList = new ArrayList<>();
		for(int i = 0; i < pageCount; i++) {
			pageStartList.add(i * GuestbookService.LIMIT);
		}
		
		model.addAttribute("list", list);
		model.addAttribute("count", count);
		model.addAttribute("pageStartList", pageStartList);
		
		return "list";
	}
	
	@PostMapping(path="/write")
	public String write(@ModelAttribute Guestbook guestbook,
						HttpServletRequest request) {
		String clientIp = request.getRemoteAddr();
		System.out.println("clientIp : " + clientIp);
		guestbookService.addGuestbook(guestbook, clientIp);
		return "redirect:list";
	}
}

list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>방명록 목록</title>
</head>
<body>

	<h1>방명록</h1>
	<br> 방명록 전체 수 : ${count }
	<br>
	<br>

	<c:forEach items="${list}" var="guestbook">

${guestbook.id }<br>
${guestbook.name }<br>
${guestbook.content }<br>
${guestbook.regdate }<br>

	</c:forEach>
	<br>

	<c:forEach items="${pageStartList}" var="pageIndex" varStatus="status">
		<a href="list?start=${pageIndex}">${status.index +1 }</a>&nbsp; &nbsp;
</c:forEach>

	<br>
	<br>
	<form method="post" action="write">
		name : <input type="text" name="name"><br>
		<textarea name="content" cols="60" rows="6"></textarea>
		<br> <input type="submit" value="등록">
	</form>
</body>
</html>

11. Controller - BE

1) RestController란?

@RestController

MessageConverter

외부에서 전달받은 json 메서드를 내부에서 사용할 수 있는 객체로 변환하거나 controller를 리턴한 객체가 클라이언트에게 json으로 변화해서 전달할 수 있게 해주는 것이다.

json 응답하기

2) RestController를 이용하여 web api작성하기

GuestbookApiController.java

package kr.or.connect.guestbook.controller;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import kr.or.connect.guestbook.dto.Guestbook;
import kr.or.connect.guestbook.service.GuestbookService;

@RestController
@RequestMapping(path="/guestbooks")
public class GuestbookApiController {
	@Autowired
	GuestbookService guestbookService;
	
	@GetMapping
	public Map<String, Object> list(@RequestParam(name="start", required=false, defaultValue="0") int start) {
		
		List<Guestbook> list = guestbookService.getGuestbooks(start);
		
		int count = guestbookService.getCount();
		int pageCount = count / GuestbookService.LIMIT;
		if(count % GuestbookService.LIMIT > 0)
			pageCount++;
		
		List<Integer> pageStartList = new ArrayList<>();
		for(int i = 0; i < pageCount; i++) {
			pageStartList.add(i * GuestbookService.LIMIT);
		}
		
		Map<String, Object> map = new HashMap<>();
		map.put("list", list);
		map.put("count", count);
		map.put("pageStartList", pageStartList);
		
		return map;
	}
	
	@PostMapping
	public Guestbook write(@RequestBody Guestbook guestbook,
						HttpServletRequest request) {
		String clientIp = request.getRemoteAddr();
		// id가 입력된 guestbook이 반환된다.
		Guestbook resultGuestbook = guestbookService.addGuestbook(guestbook, clientIp);
		return resultGuestbook;
	}
	
	@DeleteMapping("/{id}")
	public Map<String, String> delete(@PathVariable(name="id") Long id,
			HttpServletRequest request) {
		String clientIp = request.getRemoteAddr();
		
		int deleteCount = guestbookService.deleteGuestbook(id, clientIp);
		return Collections.singletonMap("success", deleteCount > 0 ? "true" : "false");
	}
}

Map으로 반환하는것, Guestbook으로 반환하는 것 모두 내부에서 json객체로 바꿔서 클라이언트에게 전달한다.

0개의 댓글