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은 원본 값이 변경되지 않는 함수이다.
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]);
});
documet. 으로 사용할 수 있는 APIs : https://www.w3schools.com/jsref/dom_obj_document.asp
element. 으로 사용할 수 있는 APIs : https://www.w3schools.com/jsref/dom_obj_all.asp
몇 가지 유용 DOM 엘리먼트 속성
tagName
: 엘리먼트의 태그명 반환textContent
: 노드의 텍스트 내용을 설정하거나 반환nodeType
: 노드의 노드 유형을 숫자로 반환몇 가지 유용 DOM 엘리먼트 속성
DOM 탐색 속성
DOM 조작 메소드
현재 div 변수는 div태그 아래 '오늘하루는 정말..좋아'라는 텍스트노드를 넣었다. p 태그를 현재 태그로 선택($0)하고 그 아래에 붙인다. 텍스트도 하나의 노드이니 그 밑에 붙인다.
테이블 태그아래 세번째 tr위에 뭐를 넣어보자
우선 table을 찾았다.
tr 태그 중 3번째 자식을 찾았다. 이제 이 앞에 넣어야하니 insertBefore를 써야한다.
붙여줄 부모 노드를 찾고, 그아래 div를 만들고 text를 추가했다.
부모를 기준으로 두 파라미터를 전달해서 추가했다.
지금 나온 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);
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);
실습2를 insertAdjacentHTML메서드를 사용해서, orange와 banana 사이에 새로운 과일을 추가하시오.
내 코드
var base = document.querySelector("ul li:nth-child(3)");
base.insertAdjacentHTML('beforebegin', '<p>melon</p>')
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를 시켜주는 속성이 있다.
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]);
}
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은 무엇인지 한번 찾아보세요!
어떠한 기능을 쓰고 싶은데, 지원하지 않는 브라우저에서도 동작시키게 하고 싶을때가 있죠. 그때 아주 유용합니다
브라우저의 새로고침 없이 데이터를 얻어오는 방법이 있습니다.
더 좋은 UX(User Experience)를 제공하는 좋은 방법이니, 알아보도록 하죠.
화면이 그려지지만 비동기작업(Ajax 요청)이 있으면 그건 보내놓고 나머지 작업을 하고 돌아오면 그걸 반영하는 것이다.
https://www.youtube.com/watch?v=8aGhZQkoFbQ&ab_channel=JSConf
http://www.phpmind.com/blog/2017/05/synchronous-and-asynchronous/
Ajax통신(jquery 예제)을 코드단위로 비동기로 처리하는 도식화
웹 개발을 하다 보면 Ajax와 같은 요청처리에 대해서 문제가 생길 수 있습니다.
Ajax 통신에서 로직이 문제인지, 아니면 서버 쪽의 문제인지 등 궁금할 때가 많습니다.
이런 부분을 소스코드상으로는 디버깅을 할 수 없습니다.
Ajax뿐만은 아닙니다.
네트워크 통신과정의 상황을 지켜보면서 디버깅하는 방법을 알아봅니다.
크롬 개발자도구는 여러 가지 기능을 제공합니다.
녹화기능을 통해서 HTML, CSS, JavaScript, image파일을 내려받는 상황을 알 수 있습니다.
흔히 겪는 404와 같은 응답 오류에 대해서 문제를 쉽게 찾을 수 있습니다.
얼마나 서버로부터 응답이 걸리는지도 알 수 있습니다.
즉 성능개선을 위해서 진단할 수 있는 도구 역할을 하는 것이죠.
인터벌 사이사이 다른 일들이 발생할 수 있기 때문에 정확한 시간을 보장할 수 없다. 그래서 이것을 잘 사용하지 않는다.
이 방법의 장점은 끝난 다음 재귀로 바로 다음 것을 실행하니 시간을 더 정확히 맞출 수 있다.
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은 꽤 유용하게 쓰일 수 있습니다
CSS 기법으로 애니메이션 구현
transition 을 이용한 방법입니다.
이 방법이 JavaScript로 구현하는 것보다 더 빠르다고 알려져 있다.
특히 모바일 웹에서는 transform을 사용한 element의 조작을 많이 구현합니다.
https://thoughtbot.com/blog/transitions-and-transforms
더 빠른 css3 애니메이션 관련 속성들
GPU가속을 이용하는 속성을 사용하면 애니메이션 처리가 빠릅니다.
- transform : translateXX();
- transform : scale();
- transform : rotate();
- opacity
아래 링크를 참고해보세요.
DOM을 가져오고 있는데 중간에 자바스크립트가 들어가서 뭐를 찾으려고하거나 삭제하려고하면 문제가 생길 수 있다. 그래서 자바스크립트 파일을 아래쪽에 위치하는 것이다. DOM의 로드 시점을 알려줄 수 있다면 더 편리할 것이다.
DOMContentLoaded가 먼저 되고 그 다음 load가 된다.
DOMContentLoaded이후에 서비스 코드를 작성하는 것이 안전한 방식이다.
<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/
이제 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;
}
});
아래 화면에 데이터를 Ajax로 받아와서 화면에 추가해야 한다고 생각해봅니다.
JSON 형태의 데이터를 받을 것이고요.
아래 리스트들의 내용은 모두 다 비슷합니다.
list 태그로 html을 구현해보면 사진, 가격, 이름, 별점, 추가정보(있거나 없거나)를 비슷한 tag를 사용해서 표현하면 됩니다.
여기서 templating 이라는 개념을 도입하면 좋습니다.
반복적인 HTML부분을 template로 만들어두고, 서버에서 온 데이터(주로JSON)을 결합해서, 화면에 추가하는 작업이라고 할 수 있습니다.
아래 그림이 이해가 될 겁니다.
이제 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)
아래와 같은 html 문자열을 어딘가 보관해야 합니다.
javascript코드 안에서 이런 정적인 데이터를 보관하는 건 좋지 않기 때문입니다.
몇 가지 방법을 알려드립니다.
var html = "<li><h4>{title}</h4><p>{content}</p><div>{price}</div></li>";
간단한 것이라면 HTML 안에 숨겨둘 수가 있습니다.
숨겨야 할 데이터가 많다면 별도 파일로 분리해서 Ajax로 가져오는 방법도 좋습니다.
하지만 많지 않은 데이터이므로 우리는 HTML 안에 잘 보관해두겠습니다.
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;
<!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>
<!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>
프레임 워크 모듈
스프링 프레임워크는 약 20개의 모듈로 구성되어 있습니다.
필요한 모듈만 가져다 사용할 수 있습니다.
스프링 코어는 꼭 알고 나머지 모듈은 필요한 것들을 공부하면 된다.
개발자가 서블릿을 작성해도 실제로 메모리에 올리고 실행하는 것은 서블릿 컨테이너가 한 것이다. JSP도 톰캣이라는 WAS가 대신 해준 것이다.
빈 팩토리는 아주 기본적인 기능만 가지고 있는 공장이고, ApplicationContext가 더 많은 기능을 가지고 있고 이용된다.
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 제어 역전이라고 한다.
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();
}
}
@Configuration
@Bean
@ComponentScan
@Component
@Autowired
@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클래스를 읽어들여 IoC와 DI를 적용하게 됩니다.
이때 설정파일 중에 @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어노테이션을 이용하여 직접 생성해주는 방식으로 클래스들을 관리하면 편리합니다.
JDBC를 이용해서 프로그래밍을 하게 되면 반복적인 코드가 많이 발생합니다.
이러한 반복적인 코드는 개발자의 생산성을 떨어트리는 주된 원인이 됩니다.
이러한 문제를 해결하기 위해 등장한 것이 Spring JDBC입니다.
org.springframework.jdbc.core
org.springframework.jdbc.datasource
org.springframework.jdbc.object
org.springframework.jdbc.support
Jdbc만을 이용했을 때는 훨씬 많은 코드를 작성했어야 헀다.
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;
}
// ......
}
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();
}
}
}
}
}
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);
}
}
}
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 + " 입력하였습니다");
}
}
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);
}
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이 발생하므로 예외처리를 하였다.
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으로 바꿔주는 역할을 했는데 이번에도 마찬가지앋.
이런 모델에서는 요청을 JSP가 받으므로 요청만큼 JSP가 존재해야 한다.
이 모델의 단점은 JSP에 자바 코드와 html이 섞여있다.
로직과 뷰가 분리되었다.
Model2의 발전한 형태가 Spring module로 구성되어있고 이를 Spring MVC라고 부른다.
파란색 부분은 전부 Spring MVC가 제공하는 부분이고, 개발자가 만들어야하는 것은 보라색으로 된 부분이다. 녹색은 제공되는 것도 있고 만들어야하는 것도 있다.
요청은 기본적으로 Dispatcher Servlet이 다 받고, 요청을 처리해줄 컨트롤러와 메서드가 뭔지 HandlerMapping에게 물어본다. (개발자가 애노테이션이나 xml로 설정을 한다). 그 다음은 Handler Adapter에게 실행을 요청한다. 결정된 컨트롤러 실행되고, 그 결과 view name이 반행되는데 View resolver가 어떤 뷰인지를 알려주면 뷰를 보여준다.
프론트 컨트롤러는 일을 처리하지않고 요청을 받아서 넘기는 역할만 한다. 보통은 프론트 컨트롤러는 하나만 둔다.
Locale
결정은 어느 언어(한국어, 영어..)를 쓸지 지역화 처리해주는 것이다.
RequestContextHolder
는 thread local 객체로 요청을 받아서 응답할때까지request
와response
를 스프링이 관리하는 객채내에서 사용할 수 있게 해주는 것이다.
FlashMap
은 리다이렉트로 값을 전달할때 사용된다. 매번 리다이렉트 될때 값을 물음표를 이용해서 넘기면 url이 굉장히 길어진다. 그래서 그걸 해결해주는 것이다.멀티파트는 파일을 업로드할 때 사용되는 것이다.
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)
DispatcherServlet도 서블릿이기 때문에 web.xml에서 설정할 수 있다. 그외에도 다양한 방법이 있지만 첫 번째, 세번째가 가장 많이 활용된다.
initParam에 dispatcherServlet이 어떤 역할을 할지 설정해놓은 파일을 전달해준다.
url설정이 '/'로 되어있다. 즉 모든 요청을 다 받는다는 뜻이다.
수업에서는 하지 않는다
DispatcherServlet에 대한 설정은 web.xml에서하고 DispatcherServlet이 읽어들여야할 설정은 별도로 java config로 해준다.
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가 기본 페이지가 되어 나온다.
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";
}
}
el이 제대로 안먹힐 수가 있는데, 그 이유는 web.xml 맨위에 보면 아직 2_3버전으로 되어있다. 지우고
<?xml version="1.0" encoding="UTF-8"?>
로 바꾸어 주자.
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 객체를 가져온다.
이렇게 들어오는 것을 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>
하나의 트랜잭션에는 여러개의 db 작업이 있을 수 있는데 중복되는 것은 별도의 객체로 분리할 수 있다.
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");
%>
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);
}
}
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;
명령어로 해결이 가능하다.
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>
</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>
외부에서 전달받은 json 메서드를 내부에서 사용할 수 있는 객체로 변환하거나 controller를 리턴한 객체가 클라이언트에게 json으로 변화해서 전달할 수 있게 해주는 것이다.
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객체로 바꿔서 클라이언트에게 전달한다.