Spring Web 기초
Spring 프레임워크를 이용해서 간단한 웹 애플리케이션을 만들어 보자!
크게 웹 애플리케이션을 만드는 과정을 나눠보면 아래와 같다.
1. 요청 처리에 필요한 모든 객체를 스프링에 맞게 받아 저장한다.
이 때, 필요하다면 @Autowired등을 이용해서 미리 객체의 주소를 설정한다.
2. @RequestMapping, @GetMapping, @PostMapping 등으로 요청 URI를 받는다.
3. Mapper와 DAO등을 활용해 요청 처리 메서드를 생성한다.
4. Controller에서 생성한 메서드 및 기타 처리를 통해 웹페이지에 전송할 데이터와 JSP를 지정한다.
5. 각 기능에 맞는 JSP 페이지를 생성한다.
스프링 프레임워크의 MVC 구조
스프링 MVC 프로세스를 간략하게 나타내면 아래와 같다.
간단하게 정리하자면,
⑴ 클라이언트의 요청이 들어오면 DispatcherServlet이 해당 요청을 전달받는다.
(이후 그에 맞는 처리 결과를 뷰(View)에 전달하여 적절한 응답을 생성해 줄 것이다.)
⑵ HandlerMapping이 클라이언트의 요청 URL을 어떤 컨트롤러가 처리할 지 찾아 준다. (Model을 찾아줌)
⑶ 요청을 처리할 Controller가 처리 후 그 결과(처리내용 및 뷰 정보)를 DispatcherServlet에 전달한다.
⑷ Controller가 준 정보를 기반으로 ViewResolver가 어떤 view를 생성할 지 결정한다.
⑸ View가 처리 결과 화면을 생성하여 사용자에게 보여 준다. 끝!
이러한 동작이 가능하도록 설정한 매뉴얼이 XML과 Annotation이다!
예제 시작!
아래의 왼쪽 화면처럼 회원 정보를 입력하고 "전송" 버튼을 누르면,
오른쪽처럼 입력한 내용을 출력하는 간단한 기능을 만들어 본다.
1. Spring Legacy Project(MVC)를 생성하고, web.xml을 세팅한다.
web.xml에는 톰캣이 읽어 가야 하는 파일을 지정한다.
즉, 웹을 구현하기 위해 필요한 설정/기능 파일들의 경로와 형식을 지정한다.
*servlet-mapping에서 URL 주소를 .do를 주면 DispatcherServlet을 자동으로 호출하도록 한다.
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/application-context.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<!--
request, response 미사용을 권고함. = 한글 변환을 못한다(request를 못 쓰니깐)
그니까 한글 변환을 여기서 해 줘야 한다.
-->
<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>
2. 패키지 단위로 클래스를 읽어 주고, JSP를 찾아 줄 viewResolver bean을 등록한다.
application-context.xml을 설정해주지 않으면 Spring이 .do를 뭐 어떻게 찾을지를 알 수 없다.
application-context.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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 아래 패키지 안에 있는+어노테이션 붙은거에는 다 메모리 할당 할 것임 -->
<context:component-scan base-package="doo.doo.*"/>
<!-- JSP를 찾아 준다. -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/"
p:suffix=".jsp"
/>
</beans>
3. Controller에 사용자의 요청을 처리할 메서드를 작성한다.
메서드의 매개변수로 받을 수 있는 것
1. 사용자가 전송한 값 → ?page=1, ?no=2 이런 식
2. <form>태그로 전송한 데이터 → VO, String[] 등
3. 스프링에서 제공하는 내장 객체
메서드의 리턴형으로 사용할 수 있는 것은 String, void 2가지!
여기서는 output을 3가지로 나눠 생성해볼 것이다.
1) input에서 값을 보내주면서 지정한 name을 기준으로 request에서 일일이 읽어와 request에 담아 전송하기
2) request 없이 각각의 입력값을 매개변수로 받고 별도의 전송 객체(Model 클래스)로 데이터를 전송하기
3) request 없이 입력값을 하나의 MemberVO로 받고 Model 클래스로 데이터 전송하기
어떤 방식을 사용해도 되지만 Spring에서는 쿠키 생성과 같이 특수한 경우 외에는 request를 잘 사용하지 않는다.
→ 사용자의 IP 등 보안상 정보가 담겨 있기 때문이다.
MainController.java (Class)
package doo.doo.web;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller //모델이라는걸 알려줌
@RequestMapping("main/") //경로명의 중복을 제거한다. 이제 main/input.do 이렇게 써줄 필요가 없어짐.
public class MainController {
@RequestMapping("input.do")
public String main_input(HttpServletRequest request, HttpServletResponse response) {
return "main/input";
}
@RequestMapping("output.do")
public String main_output(HttpServletRequest request, HttpServletResponse response) {
try {
request.setCharacterEncoding("UTF-8");
} catch (Exception ex) {}
String name = request.getParameter("name");
String sex = request.getParameter("sex");
String loc = request.getParameter("loc");
String content = request.getParameter("content");
String[] hobby = request.getParameterValues("hobby");
request.setAttribute("name", name);
request.setAttribute("sex", sex);
request.setAttribute("loc", loc);
request.setAttribute("content", content);
request.setAttribute("hobby", hobby);
return "main/output"; //.jsp를 붙이지 않음!!!
}
@RequestMapping("output1.do")
public String main_output1(String name, String sex, String loc, String content, String[] hobby, Model model) {
//Model : 데이터 전송 객체
//request 없이 매개변수로 받아 Model을 통해 데이터를 전송하고 구현해 보자.
model.addAttribute("name", name); //=request.setAttribute("name",name);
model.addAttribute("sex", sex);
model.addAttribute("loc", loc);
model.addAttribute("content", content);
model.addAttribute("hobby", hobby);
return "main/output";
}
@RequestMapping("output2.do")
public String main_output2(MemberVO vo, Model model) {
model.addAttribute("vo", vo);
return "main/output1";
}
}
(참고) Model 클래스의 addAttribute 메서드는 아래와 같은 기능을 한다!
public void addAttribute(String key, Object obj){
request.setAttribute(key,obj);
}
4. 화면을 구성한다!
input.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>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<style type="text/css">
.container{
margin-top : 50px;
}
.row{
margin: 0px auto;
width: 500px;
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<h1 class="text-center">회원 정보</h1>
<form method="post" action="output2.do">
<table class="table">
<tr>
<td class="text-right" width="20%">이름</td>
<td width=80%>
<input type=text name=name size=20 class="input-sm">
</td>
</tr>
<tr>
<td class="text-right" width="20%">성별</td>
<td width=80%>
<input type=radio name=sex value="남자" checked>남자
<input type=radio name=sex value="여자" checked>여자
</td>
</tr>
<tr>
<td class="text-right" width="20%">지역</td>
<td width=80%>
<select name="loc">
<option>서울</option>
<option>경기</option>
<option>인천</option>
<option>제주</option>
<option>부산</option>
</select>
</td>
</tr>
<tr>
<td class="text-right" width="20%">소개</td>
<td width=80%>
<textarea rows="10" cols="50" name="content"></textarea>
</td>
</tr>
<tr>
<td class="text-right" width=20%>취미</td>
<td width=80%>
<input type="checkbox" name=hobby value="운동">운동
<input type="checkbox" name=hobby value="등산">등산
<input type="checkbox" name=hobby value="여행">여행
<input type="checkbox" name=hobby value="게임">게임
<input type="checkbox" name=hobby value="낚시">낚시
</td>
</tr>
<tr>
<td colspan="2" class="text-center">
<input type="submit" value="전송" class="btn btn-sm btn-danger">
</td>
</tr>
</table>
</form>
</div>
</div>
</body>
</html>
output.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>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
이름:${name }<br>
성별:${sex }<br>
지역:${loc }<br>
<c:if test="${hobby!=null }">
<c:forEach var="hobby" items="${hobby}">
<li>${hobby }</li>
</c:forEach>
</c:if>
<c:if test="${hobby==null }">
</c:if>
</body>
</html>
output2.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>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
이름:${vo.name }<br>
성별:${vo.sex }<br>
지역:${vo.loc }<br>
<c:if test="${vo.hobby!=null }">
<c:forEach var="ho" items="${vo.hobby}">
<li>${ho }</li>
</c:forEach>
</c:if>
<c:if test="${vo.hobby==null }">
</c:if>
</body>
</html>
GetMapping과 PostMapping
위 예제에서는 @RequestMapping 어노테이션만 사용했지만,
이번에는 요청방식에 따라 @GetMapping과 @PostMapping을 분리해서 사용해 보자.
매개변수로 어떤 값이 넘어오느냐에 따라 다 달라진다.
매개변수로 쓸 수 있는 것 : 내장객체는 다 사용 가능~!
→ request, response, session, application, config, out
그 외에 사용자가 보내준 값도 받아올 수 있다.
두 가지 정도로 나뉘는데 VO단위(커맨드 객체), 낱개로 각 데이터형으로 받는 방법.
위같은 화면을 구성해서, 전송 텍스트를 클릭하면 GET 방식으로 output으로 데이터를 보내고,
전송 버튼을 클릭하면 POST 방식으로 output에 데이터를 전송한다.
GET방식은 URL에 ?가 붙으면 GET방식!
<a>, location.href, sendRedirect() 등이 포함됨 == @GetMapping으로 받아야함
<form>, ajax, axios == post, get이냐에 따라 @PostMapping으로 받아야 함
잘못된 방식으로 요청을 받아오면 bad request 에러가 뜨니 주의.
input.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>
<a href="output.do?no=1&name=홍길동&avg=10.5&isadmin=true">전송</a>
<p>
<form method="post" action="output1.do">
ID:<input type="text" name=id size=20 ><br>
PW:<input type="password" name=pwd size=20><br>
<button>전송</button>
</form>
</body>
</html>
output.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>
번호:${no }<br>
이름:${name }<br>
평균:${avg }<br>
관리자임?:${isadmin }
</body>
</html>
그리고 Controller에서 각 방식별로 처리를 해 보자.
MainController.java (Class)
package doo.doo.web;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class MainController {
@GetMapping("main/input")
public String main_input() {
return "main/input";
}
@GetMapping("main/output.do")
public String main_output(int no, String name, double avg, boolean isadmin, Model model) {
model.addAttribute("no", no);
model.addAttribute("name", name);
model.addAttribute("avg", avg);
model.addAttribute("isadmin", isadmin);
return "main/output";
}
@PostMapping("main/output1.do") //post로 보내줬으니까 PostMapping 으로 받아야 함!!
//405 에러 - 허용하지 않는 메서드
public String main_output1(String id, String pwd, Model model) {
return "main/output";
}
}
MyBatis까지 추가해 봅시다! (코드만 첨부)
application-context.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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="doo.doo.*"/>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/food/"
p:suffix=".jsp"
/>
</beans>
application-datasource.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"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
<util:properties id="db" location="/WEB-INF/config/db.properties"/>
<bean id="ds"
class="org.apache.commons.dbcp.BasicDataSource"
p:driverClassName="#{db['driver']}"
p:url="#{db['url']}"
p:username="#{db['username']}"
p:password="#{db['password']}"
/>
<bean id="ssf"
class="org.mybatis.spring.SqlSessionFactoryBean"
p:dataSource-ref="ds"
/>
<mybatis-spring:scan base-package="doo.doo.mapper"/>
</beans>
FoodMapper.java (Interface)
package doo.doo.mapper;
import java.util.*;
import org.apache.ibatis.annotations.Select;
import doo.doo.dao.FoodVO;
public interface FoodMapper {
@Select("SELECT fno, name, poster, num "
+ "FROM (SELECT fno, name, poster, rownum as num "
+ "FROM (SELECT fno, name, poster "
+ "FROM food_location ORDER BY fno ASC )) "
+ "WHERE num BETWEEN #{start} AND #{end}")
public List<FoodVO> foodListData(Map map);
@Select("SELECT CEIL(COUNT(*)/12.0) FROM food_location")
public int foodTotalPage();
//상세보기
@Select("SELECT fno, name, poster, address, tel, type, menu, parking, price "
+ "FROM food_location WHERE fno=#{fno}")
public FoodVO foodDetail(int fno); //매개변수는 반드시 한 개만 사용이 가능하다. 여러 개 하고싶으면 VO나 map을 써야함.
//검색
@Select("SELECT fno, name, poster, num "
+ "FROM (SELECT fno, name, poster, rownum as num "
+ "FROM (SELECT fno, name, poster "
+ "FROM food_location WHERE address LIKE '%'||#{address}||'%' ORDER BY fno ASC )) "
+ "WHERE num BETWEEN #{start} AND #{end}")
public List<FoodVO> foodFindData(Map map);
}
FoodDAO.java(Class)
package doo.doo.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.*;
import doo.doo.mapper.*;
@Repository
public class FoodDAO {
@Autowired
private FoodMapper mapper;
public List < FoodVO > foodListData(Map map) {
return mapper.foodListData(map);
}
public int foodTotalPage() {
return mapper.foodTotalPage();
}
public FoodVO foodDetail(int fno) {
return mapper.foodDetail(fno);
}
public List < FoodVO > foodFindData(Map map) {
return mapper.foodFindData(map);
}
}
FoodController.java(Class)
package doo.doo.web;
import java.util.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import doo.doo.dao.*;
@Controller
public class FoodController {
@Autowired
private FoodDAO dao;
//스프링에서 생성된 객체를 받아서 사용한다. => 자동으로 주소값을 얻어 온다
@GetMapping("food/list.do")
public String food_list(String page, Model model) {
//전송할 게 있으면 model을 갖다놓고 시작해야함
if (page == null)
page = "1";
int curPage = Integer.parseInt(page);
Map map = new HashMap();
int rowSize = 12;
int start = (rowSize * curPage) - (rowSize - 1);
int end = rowSize * curPage;
map.put("start", start);
map.put("end", end);
int totalPage = dao.foodTotalPage();
List < FoodVO > list = dao.foodListData(map);
//////////////////////////////////////////////
model.addAttribute("curPage", curPage);
model.addAttribute("list", list);
model.addAttribute("totalPage", totalPage);
//////////////////////////////////////////////
return "list";
}
@GetMapping("food/detail.do")
public String food_detail(int fno, Model model) {
//parseInt, getParameter 다 알아서 해 줌.
FoodVO vo = dao.foodDetail(fno);
model.addAttribute("vo", vo);
return "detail";
}
@GetMapping("food/find.do")
public String food_find() {
return "find";
}
}
'부트캠프(END) > -Spring' 카테고리의 다른 글
Spring CURD 게시판(소스코드만 정리) (0) | 2022.08.31 |
---|---|
Spring 기초 : AOP (0) | 2022.08.25 |
Spring에서 MyBatis 사용하기 + DI (0) | 2022.08.24 |