@GetMapping("/host/roomOne")
public String hostRoomOne(Model model,
@RequestParam("roomId") int roomId, @RequestParam("detailAddressId") int detailAddressId) {
Map<String, Object> result = roomService.getHostRoomOne(roomId, detailAddressId);
model.addAttribute("room",result.get("room"));
model.addAttribute("address",result.get("address"));
model.addAttribute("hashtag",result.get("hashtag"));
return "/host/room/roomOne";
}
@PostMapping("/host/modifyRoom")
public String postModifyRoom(RedirectAttributes redirect, Room room, Address address, String hashtag) {
address = roomService.modifyRoom(room, address, hashtag);
redirect.addAttribute("roomId",room.getRoomId());
redirect.addAttribute("detailAddressId",address.getDetailAddressId());
return "redirect:/host/roomOne";
}
[ 숙소의 정보 수정이 완료되면, 숙소 상세보기 페이지를 띄우도록 하는 redirect 요청입니다]
redirect를 사용하는 이유 : 저는 Controller에서 메서드의 작업이 끝난 후 return값으로 /webapp/WEB-INF/jsp 위치의 jsp파일명을 반환합니다. 숙소 수정이 완료된 후, 숙소 상세보기 페이지인 roomOne.jsp가 열리도록 해야하는데, 해당 숙소의 상세정보도 받아와야 합니다. 그렇기 때문에 간단하게 jsp만 여는것이 아니라 숙소 상세정보를 가져온 후 해당 jsp를 포워딩하는 /host/roomOne으로 redirect를 진행합니다.
- postModifyRoom 메서드의 작업이 끝나면 hostRoomOne 메서드로 redirect를 진행합니다.
- redirect를 진행하는 메서드에서는 param값으로 'RedirectAttributes redirect'를 받고, redirect.addAttribute("이름",값);으로 redirect를 하는 위치로 값을 전달한다.
- redirect를 요청받은 메서드의 param값은 @RequestParam("이름") 자료형 변수명의 형식으로 값을 받아 사용합니다.
spring 프로젝트의 파일을 살펴보면 src/main/java 밑에 패키지가 하나 있는 것을 볼 수 있다.
동일한 위치에 controller만을 모아둘 수 있는 패키지를 만들고, 작업을 원하는 컨트롤러를 생성한다.(class 생성)
<FilmController.java>
package com.practice.fjdkslvn.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class FilmController {
@GetMapping("/film")
public String filmList() {
System.out.println("FilmController.test()실행");
return "/WEB-INF/jsp/filmList.jsp";
}
}
클래스 위에 @Controller 어노테이션을 작성하여 컨트롤러 작동이 가능하게 한다.
@GetMappint("/film") 어노테이션을 작성하여 '/film'으로 get 요청을 받았을 경우 처리할 수 있도록 filmList 메서드를 생성하였다. spring은 아주 편리하게도 return 값으로 jsp의 경로를 반환하면 자동으로 forward 해준다.
여기서, forward를 위해 return하는 jsp경로를 더욱 편리하게 반환하는 방법이 있다.
<FilmController.java>
@Controller
public class FilmController {
@GetMapping("/film")
public String filmList() {
System.out.println("FilmController.test()실행");
return "filmList";
}
}
- return값으로 jsp 이름만 반환하는 방법
@Controller
public class FilmController {
@GetMapping("/film")
public void filmList() {
System.out.println("FilmController.test()실행");
// 반환값이 없으면 메서드 명을 사용하여 forward 진행
}
}
- return값이 없기 때문에 메서드 명을 사용하여 forward가 진행되는 방법
=> 2개의 방법 모두 이전과 달리 /WEB-INF/jsp/와 .jsp를 생략하고 사용하는 방법이다. 다만, 이렇게 사용하기 위한 설정이 필요하다.
src/main/resources를 열면, 애플리케이션을 구동할 때 자동으로 실행되는 application.properties를 확인할 수 있다.
server.port=80을 통해 사용할 포트번호를 80으로 변경한다. 변경 전에는 보통 8080으로 설정 되어있는데, 80으로 변경하면 http://localhost:8080/film -> http://localhost/film 과 같이 간편하게 접근 가능하다.
본인은 mariaDB를 사용하기 때문에 관련 코드를 작성해주었다. 자신이 사용하고 있는 설정에 맞게 입력해주면 될 것 이다.
마지막으로 forward를 위한 jsp경로를 간단하게 return하기 위해 사용되는 부분이다. 여기서 prefix는 접두어로 사용되고 suffix는 접미어로 사용된다. 해당 설정으로 인해 간단하게 jsp명만 return하는 경우 "WEB-INF/jsp/리턴값.jsp"과 같은 경로로 forwarding이 진행된다. return값이 없어서 메서드명이 전달되는 경우도 마찬가지다.
이제 jsp를 만들어야 한다. 실제로는 webapp까지밖에 없을것이다.
webapp 밑에 WEB-INF파일을 만들고 그 아래에는 jsp를 모아둘 파일을 생성한 뒤, 원하는 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>
<h1>영화 목록 출력 예정</h1>
</body>
</html>
jsp화면은 간단하게 제목만 출력하도록 했다.
🎵실행
프로젝트 우클릭 -> Run As -> Spring Boot App을 클릭하여 서버를 실행한다.
localhost/film을 입력하여 get방식으로 요청하면, filmList.jsp가 forward되어 나오고 console에 컨트롤러가 실행되었음을 나타내는 system.out.print가 출력된다.
🔖<FilmController.java> => 값 받는 방법
@Controller
public class FilmController {
@GetMapping("/film")
public String filmList(int filmId, String title) {
System.out.println("FilmController.test()실행");
System.out.println("영화번호 : " + filmId);
System.out.println("제목 : "+title);
return "filmList";
}
}
@GetMapping 어노테이션을 사용한 경우에 get방식으로 데이터를 전달하면, 메서드 입력변수로 해당 값들을 받을 수 있다.
<input type="file" name="image">와 같이 type을 file로 지정한다.
버튼을 클릭하면 action.jsp로 이동한다. (action.jsp에서 사진 업로드 처리)
action.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.oreilly.servlet.MultipartRequest" %> <!-- request 대신 -->
<%@ page import="com.oreilly.servlet.multipart.DefaultFileRenamePolicy" %> <!-- 파일이름 중복을 피할 수 있도록 -->
<%
// 웹 프로젝트 위치에 넣어도 된다 (이클립스에서 사진 추가되는 것을 실시간으로 확인 가능)
// MultipartRequest mr = new MultipartRequest(request, "C:/Users/fjdks/Desktop/erp/WebContent/image", 1024*1024*1024, "utf-8", new DefaultFileRenamePolicy());
MultipartRequest mr = new MultipartRequest(request, request.getRealPath("/image"), 1024*1024*1024, "utf-8", new DefaultFileRenamePolicy());
System.out.println("저장되는 경로(실제 서버) : "+request.getRealPath("/image"));
String fileName = mr.getFilesystemName("image");
System.out.println("사진 이름 : "+fileName);
%>
MultipartRequest를 통해 사진을 저장한다. (웹 프로젝트 위치 or 서버 위치)
request.getRealPath()로 실제 서버 위치에 접근할 수 있다.
(이클립스에서는 사진이 추가되는 것이 보이지 않는다. 웹 프로젝트 위치로 접근하면 사진이 추가되는 것을 실시간으로 확인할 수 있다)
form.jsp에서 enctype="multipart/form-data"으로 받아왔기 때문에, request.getParameter()를 사용할 수 없다.
파일명은 mr.getFilesystemName()을 사용하여 받고, 일반적인 값은 mr.getParameter()를 통해 받아온다.
* request.getRealPath("/image")가 작동되도록 WebContent 아래에 image 파일을 생성했다 *
📂 이전 파일 삭제
action.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.oreilly.servlet.MultipartRequest" %> <!-- request 대신 -->
<%@ page import="com.oreilly.servlet.multipart.DefaultFileRenamePolicy" %> <!-- 파일이름 중복을 피할 수 있도록 -->
<%@ page import="java.io.File" %>
<%
// 이렇게 원하는 위치를 직접 넣어도 되긴 함(프로젝트 파일에 실시간으로 추가되는거 볼 수 있음 ㅋㅋ)
// MultipartRequest mr = new MultipartRequest(request, "C:/Users/fjdks/Desktop/erp/WebContent/image", 1024*1024*1024, "utf-8", new DefaultFileRenamePolicy());
MultipartRequest mr = new MultipartRequest(request, request.getRealPath("/image"), 1024*1024*1024, "utf-8", new DefaultFileRenamePolicy());
System.out.println("저장되는 경로(실제 서버) : "+request.getRealPath("/image"));
String fileName = mr.getFilesystemName("image");
System.out.println("저장된 사진 이름 : "+fileName);
// 실제로는 이전 사진 이름을 DB에서 불러온다
String beforeImg = "fullmetalAlchemist.PNG";
// 이전 사진명을 통해 실제로 삭제될 사진을 가져온다
String deleteImgName = request.getRealPath("/image/") + beforeImg;
File deleteImg = new File (deleteImgName);
// 이전 사진이 경로에 존재한다면 삭제한다
if (deleteImg.exists() && deleteImg.isFile()){
deleteImg.delete();// 사진 삭제
System.out.println("삭제된 이전 사진 : "+beforeImg);
}
%>
임시로 이전 사진의 이름을 "fullmetalAlchemist.PNG"로 설정하였지만 실제로는 DB에서 값을 받아와야 한다.
경로 + 사진 이름까지 담겨진 deleteImgName 변수를 File 형태로 만든다.
만약 deleteImg의 경로에 파일이 존재하고 deleteImg가 파일이라면, 해당 사진(이전 사진)을 삭제한다.
<!-- 상품 목록 출력 -->
<%
// 페이지
int currentPage = 1;
if(request.getParameter("currentPage")!=null){
currentPage = Integer.parseInt(request.getParameter("currentPage"));
}
System.out.println(currentPage+" <--selectEbookList currentPage");
final int ROW_PER_PAGE = 10; // 페이지에 보일 주문 개수
int beginRow = (currentPage-1)*ROW_PER_PAGE; // 리스트 목록 시작 부분
// 전체 목록
EbookDao ebookDao = new EbookDao();
// 화면에 보일 리스트를 추출해오는 함수(리스트 시작 부분, 받아올 리스트 개수)
ArrayList<Ebook> ebookList = ebookDao.selectEbookList(beginRow, ROW_PER_PAGE);
%>
<h2>전체 상품 목록</h2>
<!-- 상품 목록 출력 -->
<table class="table" border="1">
<tr>
<%
int i = 0;
// 반복을 통해 카테고리 목록을 표로 출력
for(Ebook e : ebookList){
%>
<td>
<div><a href=""><img src="<%=request.getContextPath() %>/image/<%=e.getEbookImg() %>" width="200" height="200"></a></div>
<div><a href=""><%=e.getEbookTitle() %></a></div>
<div>₩ <%=e.getEbookPrice() %></div>
</td>
<%
i+=1; // for문 끝날때마다 i는 1씩 증가
if(i%5==0){
%>
</tr><tr> <!-- 줄바꿈 -->
<%
}
}
%>
</tr>
</table>
<div></div>
<!-- 페이지 -->
<%
// 페이징을 위해 구해야 할 마지막 페이지 연산
int lastPage;
int currentnumPage=0; // 현재 페이지가 몇번째 묶음인지(이전,다음 구현을 위함)
int lastnumPage=0; // 마지막 페이지가 몇번째 묶음인지(마지막 페이지에서 다음이 나오지 않도록 하기 위함)
lastPage = ebookDao.selectEbookListLastPage(ROW_PER_PAGE); // 상품의 개수를 통해 마지막 페이지가 몇번인지 추출
%>
<ul class="pagination body-back-color">
<%
if(currentPage!=1){
%>
<li class="page-item"><a class="page-link" href="<%=request.getContextPath() %>/index.jsp?currentPage=<%=1 %>">처음</a></li>
<%
}
if(currentPage%ROW_PER_PAGE==0){ // 현재 페이지가 몇번째 묶음인지
currentnumPage =(currentPage/ROW_PER_PAGE)-1;
} else{
currentnumPage = currentPage/ROW_PER_PAGE;
}
%>
<%
if((currentnumPage)>0){ // 이전
%>
<li class="page-item"><a class="page-link" href="<%=request.getContextPath() %>/index.jsp?currentPage=<%=ROW_PER_PAGE*(currentnumPage-1)+1 %>">이전</a></li>
<%
}
for(int i=0;i<ROW_PER_PAGE;i++){ // 중간 번호들
if(lastPage>=(ROW_PER_PAGE*currentnumPage)+i+1){
%>
<li class="page-item"><a class="page-link" href="<%=request.getContextPath() %>/index.jsp?currentPage=<%=(ROW_PER_PAGE*currentnumPage)+i+1 %>"><%=(ROW_PER_PAGE*currentnumPage)+i+1 %></a></li>
<%
}
}
if(lastPage%ROW_PER_PAGE==0){ // 마지막 페이지가 몇번째 묶음인지
lastnumPage =(lastPage/ROW_PER_PAGE)-1;
} else{
lastnumPage = lastPage/ROW_PER_PAGE;
}
if(lastnumPage>currentnumPage){
%>
<li class="page-item"><a class="page-link" href="<%=request.getContextPath() %>/index.jsp?currentPage=<%=ROW_PER_PAGE*(currentnumPage+1)+1 %>">다음</a></li>
<%
}
if(currentPage!=lastPage && lastPage!=0){
%>
<li class="page-item"><a class="page-link" href="<%=request.getContextPath() %>/index.jsp?currentPage=<%=lastPage %>">맨끝</a></li>
<%
}
%>
</ul>
- currentPage(현재페이지), beginRow(리스트 시작 위치), ROW_PER_PAGE(화면에 보일 리스트 개수 및 하단 페이징 단위), lastPage(마지막 페이지), currentNumPage(현재 페이지가 위치한 묶음 단위<이전, 다음을 위함>), lastnumPage(마지막 페이지가 위치한 묶음 단위<다음 버튼의 오류 방지>)
- 각 변수들의 사용 목적을 숙지하면 코드를 이해하기 좋습니다.
EbookDao.java
// [관리자 & 고객] 전자책 목록 전체 출력
public ArrayList<Ebook> selectEbookList(int beginRow, int rowPerPage) throws ClassNotFoundException, SQLException{
ArrayList<Ebook> list = new ArrayList<>();
// mariaDB 연동
Class.forName("org.mariadb.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mariadb://127.0.01:3307/shop", "root", "java1004");
String sql = "select ebook_no ebookNo, category_name categoryName, ebook_title ebookTitle, ebook_img ebookImg, ebook_price ebookPrice, ebook_state ebookState from ebook order by create_date desc limit ?,?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, beginRow);
stmt.setInt(2, rowPerPage);
System.out.println("ebook 목록 출력 stmt : "+stmt);
ResultSet rs = stmt.executeQuery();
// 각 전자책의 정보를 리스트에 담는다
while(rs.next()) {
Ebook ebook = new Ebook();
ebook.setEbookNo(Integer.parseInt(rs.getString("ebookNo")));
ebook.setCategoryName(rs.getString("categoryName"));
ebook.setEbookTitle(rs.getString("ebookTitle"));
ebook.setEbookImg(rs.getString("ebookImg"));
ebook.setEbookPrice(rs.getInt("ebookPrice"));
ebook.setEbookState(rs.getString("ebookState"));
list.add(ebook);
}
rs.close();
stmt.close();
conn.close();
return list;
}
// [관리자] 전자책 마지막 페이지 연산
public int selectEbookListLastPage(int rowPerPage) throws ClassNotFoundException, SQLException {
DBUtil dbUtil = new DBUtil();
Connection conn = dbUtil.getConnection();
// 전체 페이지수 구하기
PreparedStatement stmt;
String sql = "select count(*) from ebook";
stmt = conn.prepareStatement(sql);
System.out.println("전체 전자책 수 stmt : "+stmt);
ResultSet rs = stmt.executeQuery();
rs.next();
int totalData = rs.getInt("count(*)");
int lastPage= totalData/rowPerPage; // 마지막 페이지 번호
if(totalData%rowPerPage!=0){
lastPage +=1;
}
rs.close();
stmt.close();
conn.close();
return lastPage;
}
[전자책 목록 전체 출력]
- 전자책 목록을 mariaDB에서 받아온 후, ArrayList에 담는 메서드 입니다. 쿼리에서 중요한 부분은 limit입니다.
- limit ?,? => 첫번째 물음표에 테이블에서 추출할 리스트의 시작부분을 넣고, 두번째 물음표에 시작부분 부터 몇개의 리스트를 추출할 것인지를 넣는 것 입니다.
[마지막 페이지 연산]
- 추출할 요소의 수를 받아온 후 rowPerPage를 통해 마지막 페이지가 몇인지 구합니다.