본문 바로가기
Spring

웹 페이지 파일(이미지) 업로드 & 페이지네이션

by SuldenLion 2023. 7. 24.
반응형

로컬에 있는 이미지 파일을 웹 페이지에 갖다 올리고 다운로드 하는 기능을 만들어 보고 페이징 처리까지 해보도록 하겠다.

 

 

웹 프로그램은 도서 CRUD를 기반으로 할 것이고 CSS가 작살나서 디자인은 적용하지 않고 기능적인 부분만 정리해볼 것이다.

 

이전 도서 CRUD 프로그램 포스팅

https://suldenlion.tistory.com/117

 

스프링 CRUD Library 프로그램 버전별 정리 後

웹에서 동적으로 CRUD를 할 수 있는 도서관리 프로그램을 스프링을 이용하여 만들고 몇 가지 기술을 정리해 볼 것이다. https://suldenlion.tistory.com/113 Servlet 프로그래밍 (동적 Library CRUD 프로그램) JNDI

suldenlion.tistory.com

 

 

 도서 등록 화면에서 책과 함께 이미지를 등록하면 이미지가 내부적으로 저장될 수 있게 하고, 상세 페이지에서는 해당 이미지를 다운로드 받을 수 있게, 수정 페이지에서는 이미지도 갈아 끼울수 있게 동작하도록 만들어 볼 것이다.

 

 

도서 등록 초기 화면

 

 

이미지와 파일 선택 기능이 추가됨

 

도서를 등록하면 이미지와 함께 등록될 것이다.

 

리스트는 페이지네이션 처리를 해서 페이지당 특정 개수의 항목만 볼 수 있게 해줄 것이다.

 

 

 

 

페이지 버튼은 그룹화를 해주어 현재 보고있는 화면에서 몇 개의 버튼이 보일지 설정해 줄 것이다.

위의 예시는 1,2 / 3,4 / 5,6 과 같이 두 개씩 나뉘도록 그룹화를 해주었다.

 

 

 

등록화면에서 추가해준 도서를 클릭하여 상세 페이지로 넘어온다.

 

업로드했던 이미지를 확인할 수 있으며, 다운로드 할 수 있게 해줄 것이다.

 

 

 

다운로드 버튼을 누르면 이미지를 받을 수 있다.

 

 

 

수정 화면에서도 마찬가지로 파일 업로드를 통한 이미지 수정을 할 수 있게 해준다.

 

 

 

● Upload와 Download

업로드와 다운로드를 하기 위해선 pom.xml 파일에 아래 두개의 dependency를 추가해준다.

 

▶Tomcat 외부 파일(이미지) 불러오기 설정

Tomcat으로 서버 배포시에 war 파일 바깥쪽에 있는 파일을 불러올 경우 절대경로를 이용하여 외부 디렉터리에 접근을 시도하면 접근이 불가능함. (war 파일을 기본 경로로 가지기 때문)

 

Tomcat의 server.xml 파일의 설정을 변경해주면 됨.

<Context docBase="파일 경로" path="호출할 주소" reloadable="true"/>

 

불러오고자 하는 파일이 "C:\Users\suldenlion\Desktop\images"에 위치하고 "/images"라는 url을 통해 호출하려 한다면 다음과 같이 태그를 추가한다.

<Context docBase="C:\Users\suldenlion\Desktop\images" path="/images" reloadable="true"/>

 

 

이제 이미지를 업로드할 준비가 어느정도 되었다.

Front 페이지를 보겠다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>도서 등록</title>
<!-- <link href="../resources/css/basic_style.css" rel="stylesheet" type="text/css"> -->
    <script type="text/javascript" src="https://code.jquery.com/jquery-latest.min.js"></script>
	<script type="text/javascript" src="../resources/js/book.js"></script>
</head>
<body>
<%-- <% String cmd = request.getParameter("cmd"); %>
<%=cmd.equals("success")?"<script>alert('hello');</script>":""%> --%>
<form action="" method="post" id="frm" enctype="multipart/form-data">
    <table>
        <tr><th colspan="4" id="form">도서등록</th></tr>
        <tr><th>구분</th><th class="data_ui" colspan="2">데이터입력</th><th>비고</th></tr>
        <tr>
            <td>도서번호</td>
            <td colspan="2">
            	<input type="text" id="book_seq" name="book_seq" disabled="disabled">
            </td>
            <td id="message">자동생성</td></tr>
        <tr>
           <td>도서이미지</td>
           <td colspan="2">
           		<img src="http://placehold.it/300X200" width="300" height="200" id="preview">
           		<input type="file" name="book_image" id="up_image">
           </td>     
        </tr>
        <tr>
        	<td>ISBN</td>
        	<td colspan="2">
        		<input type="text" id="isbn" name="isbn">
        	</td>
        	<td>
        		<input type="hidden" id="flag" value="false">
        	</td>
        </tr>
        <tr>
        	<td>도서명</td>
        	<td colspan="2">
        		<input type="text" id="book_title" name="book_title">
        	</td><td></td>
        </tr>
        <tr>
        	<td>저자/역자</td>
        	<td colspan="2">
        		<input type="text" id="author" name="author">
        	</td><td></td>
        </tr>

        <tr>
        	<td>출판일</td>
        	<td colspan="2">
        		<input type="text" id="publish_date" size="35" name="publish_date">
        	</td>
        	<td></td>
        <tr>
        <tr>
        	<td>도서위치</td>
        	<td colspan="2">
        		<select name="book_position" disabled="disabled">
        			<option value='BS'>--도서 위치--
        			<option value='BS-001' selected>일반서가
        			<option value='BS-002'>예약서가
        			<option value='BS-'>회원
        		</select>
        	</td>
        	<td>기본값삽입</td>
        <tr>
        <tr>
        	<td>도서상태</td>
        	<td colspan="2">
        		<select name="book_status" disabled="disabled">
        			<option value='BM'>--도서 상태--
        			<option value='BM-001' selected>도서대출서비스
        			<option value='BM-002'>도서수선
        			<option value='BM-003'>도서저장고
        		</select>
        	</td>
        	<td>기본값삽입</td>
        <tr>
        <tr>
        	<td colspan="4" id="sending">
        		<input type="submit" value="도서등록" id="go_book_regist"> 
        		<input type="submit" value="도서리스트" id="go_book_list">
        	</td>
        </tr>
    </table>
</form>
</body>
</html>

 

테이블 내에 이미지를 위한 공간을 만들어 두었다.

 

img 태그를 두어 이미지가 보일 요소를 만들어 준다. 기본 이미지로는 placehold.it/300X200의 이미지를 둔다.

그리고 file을 받을 수 있는 input 태그를 둔다. up_image라는 id로 js에서 동작시킬 것이다.

 

 

$(document).ready(function(){
	
	$('#go_view_update').on('click',function(){
		alert($('#book_seq').val());
		$('#frm').attr('action','view_update.do?bookSeq='+$('#book_seq').val());
		$('#frm').submit();
	});
	
	$('#go_book_list').on('click',function(){
		$('#frm').attr('action','list.do');
		$('#frm').attr('method','get');
		$('#frm').submit();
	});
	
	$('#go_book_regist').on('click',function(){
		alert($('#publish_date').val());
		$('#frm').attr('action','upload.do');
		$('#publish_date').val($('#publish_date').val()+" 00:00:00");
		$('#frm').submit();
	});
	
	
	
	$('#go_book_update').on('click',function(){
		$('#frm').attr('action','update.do?bookSeq='+$('#book_seq').val());
		$('#publish_date').val($('#publish_date').val()+" 00:00:00");
		$('#frm').submit();
	});
	
        $('#up_image').on('change',function(){
              alert('in');
              if (this.files && this.files[0]) {
                var reader = new FileReader();
                reader.onload = function(e) {
                  document.getElementById('preview').src = e.target.result;
                };
                reader.readAsDataURL(this.files[0]);
              } else {
                document.getElementById('preview').src = "";
              }
        });
	
});

book.js 파일이다.

 

이미지 업로드 버튼을 눌렀을 때 아래의 함수가 동작하게 해준다.

 

if (this.files && this.files[0])는 파일 입력이 파일을 선택했는지 확인한다. FileReader API가 브라우저에서 지원되고 파일이 선택되었는지 확인함.

var reader = new FileReader();는 FileReader API의 일부인 FileReader 개체의 새 인스턴스를 만든다. FileReader 개체를 사용하면 사용자가 선택한 파일의 내용을 읽을 수 있다.

reader.onload = function(e) 는 FileReader의 로드 이벤트에 대한 이벤트 핸들러를 설정한다. 파일을 성공적으로 읽을 때 안에있는 함수가 실행됨. (이미지 미리보기의 소스를 선택한 파일의 데이터 URL로 설정함 = 이미지 업데이트)

reader.readAsDataURL(this.files[0]);는 선택한 파일을 데이터 URL로 읽는다. FileReader는 선택한 파일의 내용을 읽고 서버 요청없이 브라우저에서 직접 이미지를 표시하는데 사용할 수 있는 데이터 URL로 변환함.

 

 

<form action="" method="post" id="frm" enctype="multipart/form-data">

그리고 html의 form 태그 안에 enctype 속성을 넣어줘야 한다. 데이터 양식을 인코딩하여 서버로 보내는 방법을 지정한다. 이 경우 "multipart/form-data"로 설정하는데, 이는 폼이 하나 이상의 파일 입력 요소( <input type="file">)를 포함할 때 사용된다. (파일 업로드를 올바르게 처리하는데 필요)

 

regist 화면에 attribute를 upload.do로 설정하여 action 양식이 제출될 때 URL upload.do로 가게한다. (데이터 처리를 위해 서버의 upload.do URL로 전송되는 것을 의미)

 

 

이제 서버 쪽 컨트롤러를 보겠다.

package bitedu.bipa.book.controller;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import bitedu.bipa.book.service.BlmService4;
import bitedu.bipa.book.utils.PageInfo;
import bitedu.bipa.book.utils.PagingbarGenerator;
import bitedu.bipa.book.vo.BookCopy;
import bitedu.bipa.book.vo.PageData;

@Controller("bookController8")
@RequestMapping("/mybatisdb2")
public class BookController8 {
	
	@Autowired
	private BlmService4 blmService;
			
	@RequestMapping(value="/list.do", method=RequestMethod.GET)
	public ModelAndView list(@RequestParam(required = false) String group,@RequestParam(required = false) String page) {
		ModelAndView mav = new ModelAndView();
	
		PageData<BookCopy> pData = blmService.getPageData(5, group, page);
		
//		mav.addObject("list",list);
		mav.addObject("pData", pData);
		mav.setViewName("./manager/book_list");
		return mav;
	}
	
	@RequestMapping(value="/view_regist.do", method=RequestMethod.GET)
	public ModelAndView viewRegist() {
		ModelAndView mav = new ModelAndView();		
		
		mav.setViewName("./manager/book_regist");
		return mav;
	}
	
	@RequestMapping(value="/view_detail.do", method=RequestMethod.GET)
	public ModelAndView viewDetail(HttpServletRequest request) {
		ModelAndView mav = new ModelAndView();
		String bookSeq = request.getParameter("bookSeq");
		BookCopy copy = blmService.findBook(bookSeq);
		mav.addObject("copy",copy);
		mav.setViewName("./manager/book_detail");
		return mav;
	}
	
	@RequestMapping(value="/view_update.do", method=RequestMethod.POST)
	public ModelAndView viewUpdate(HttpServletRequest request) {		
		ModelAndView mav = new ModelAndView();
		String bookSeq = request.getParameter("bookSeq");
		BookCopy copy = blmService.findBook(bookSeq);
		mav.addObject("copy",copy);
		mav.setViewName("./manager/book_update");
		return mav;
	}
	
	@RequestMapping(value="/remove.do", method=RequestMethod.GET)
	public ModelAndView remove(HttpServletRequest request) {
		ModelAndView mav = new ModelAndView();
		String bookSeq = request.getParameter("bookSeq");
		boolean flag = blmService.removeBook(bookSeq);
		
		mav.setViewName("redirect:list.do");
		return mav;
	}
	
	@RequestMapping(value="/update.do", method=RequestMethod.POST)
	public ModelAndView update(HttpServletRequest request) {
		ModelAndView mav = new ModelAndView();
		System.out.println("upload");
		String path = "C:\\Users\\jungw\\Desktop";
		DiskFileItemFactory factory = new DiskFileItemFactory();
		factory.setRepository(new File(path));
		factory.setSizeThreshold(1024*1024*10);
		ServletFileUpload upload = new ServletFileUpload(factory);
		List<FileItem> items = null;
		try {
			items = upload.parseRequest(request);
		} catch (FileUploadException e) {
			e.printStackTrace();
		}
		BookCopy copy = null;
		try {
			copy = blmService.upload(items);
			System.out.println(copy.getBookSeq());
			System.out.println(copy.getTitle());
			System.out.println(copy.getBookImage());
			System.out.println(copy.getAuthor());
			System.out.println(copy.getBookPosition());
			System.out.println(copy.getBookStaus());
			System.out.println(copy.getIsbn());
			System.out.println(copy + "======" + copy.getBookImage());
			System.out.println(blmService.modifyBook(copy));
		} catch (Exception e) {
			e.printStackTrace();
		}
		mav.setViewName("redirect:list.do");
		
		return mav;
		
		/* ModelAndView mav = new ModelAndView();
		try {
			request.setCharacterEncoding("UTF-8");
		} catch (UnsupportedEncodingException e1) {
			e1.printStackTrace();
		}
		BookCopy copy = new BookCopy();
		copy.setBookSeq(Integer.parseInt(request.getParameter("book_seq")));
		copy.setIsbn(request.getParameter("isbn"));
		copy.setTitle(request.getParameter("book_title"));
		copy.setAuthor(request.getParameter("author"));
		String date = request.getParameter("publish_date");
		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
		try {
			Date now = df.parse(date);
			copy.setPublishDate(new Timestamp(now.getTime()));
		} catch (ParseException e) {
			e.printStackTrace();
		}
		
		boolean flag = blmService.modifyBook(copy);
		System.out.println("regist");
		mav.setViewName("redirect:list.do");
		return mav; */
	}
	
	@RequestMapping(value="/regist.do", method=RequestMethod.POST)
	public ModelAndView regist(HttpServletRequest request) {
		ModelAndView mav = new ModelAndView();
		try {
			request.setCharacterEncoding("UTF-8");
		} catch (UnsupportedEncodingException e1) {
			e1.printStackTrace();
		}
		BookCopy copy = new BookCopy();
		copy.setIsbn(request.getParameter("isbn"));
		copy.setTitle(request.getParameter("book_title"));
		copy.setAuthor(request.getParameter("author"));
		copy.setPublisher(request.getParameter("publisher"));
		String date = request.getParameter("publish_date");
		System.out.println("date "+date);
		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
		try {
			Date now = df.parse(date);
			copy.setPublishDate(new Timestamp(now.getTime()));
		} catch (ParseException e) {
			e.printStackTrace();
		}
		blmService.registBook(copy);
		System.out.println("regist");
		mav.setViewName("redirect:list.do");
		return mav;
	}
	
	@RequestMapping(value="/upload.do", method=RequestMethod.POST)
	public ModelAndView upload(HttpServletRequest request) {
		ModelAndView mav = new ModelAndView();
		System.out.println("upload");
		String path = "C:\\Users\\jungw\\Desktop";
		DiskFileItemFactory factory = new DiskFileItemFactory();
		factory.setRepository(new File(path));
		factory.setSizeThreshold(1024*1024*10);
		ServletFileUpload upload = new ServletFileUpload(factory);
		List<FileItem> items = null;
		try {
			items = upload.parseRequest(request);
		} catch (FileUploadException e) {
			e.printStackTrace();
		}
		System.out.println(items);
		BookCopy copy = null;
		try {
			copy = blmService.upload(items);
			System.out.println(copy);
			blmService.registBook(copy);
		} catch (Exception e) {
			e.printStackTrace();
		}
		mav.setViewName("redirect:list.do");
		
		return mav;
	}
	
	// 이동하면 안되므로 타입은 void
	@RequestMapping(value="/download.do",method = RequestMethod.GET)
	public void download(@RequestParam("fileName") String fileName,HttpServletResponse resp) {
		File downloadFile = new File("C:\\Users\\jungw\\Desktop\\travelImages\\"+fileName);
		
		try {
			fileName = new String(fileName.getBytes("UTF-8"),"ISO-8859-1");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		resp.setContentType("text/html; charset=UTF-8");
		resp.setHeader("Cache-Control", "no-cache");
		resp.addHeader("Content-Disposition", "attatchment;filename="+fileName);
		
		try {
			FileInputStream fis = new FileInputStream(downloadFile);
			OutputStream os = resp.getOutputStream();
			byte[] buffer = new byte[256];
			int length = 0;
			while((length=fis.read(buffer))!=-1) {
				os.write(buffer, 0, length);
			}
			os.close();
			fis.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}		
	}
	
//	@ExceptionHandler(value = Exception.class)
//	public ModelAndView exceptionHandler(Exception e) {
//		ModelAndView mav = new ModelAndView();
//		mav.addObject("e",e);
//		mav.setViewName("./error/exception");
//		return mav;
//	}
//	
}

 

 

upload.do가 실행되면 호출될 upload 메서드이다.

뷰를 생성하고 파일 업로드시 임시 파일들이 저장될 경로 path를 둔다.

그리고 파일 업로드를 처리하는데 사용될 DiskFileItemFactory 객체를 생성한다.

setRepository() 메서드를 사용하여 파일 업로드시 임시 파일들이 저장될 경로를 설정한다. 

setSizeThreshold() 메서드는 업로드되는 파일의 임시 저장 크기를 설정한다. 1024*1024*10은 최대 허용 크기가 될 것이다. 

ServletFileUpload 객체를 생성하여 실제로 파일 업로드를 처리하는데 사용한다.

 

List items에는 request로 넘어온 요소들을 담아줄 것이다.

그리고 BookCopy 객체를 만들어 service의 upload() 메서드를 시행한다.

 

 

package bitedu.bipa.book.service;

import java.io.File;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.commons.fileupload.FileItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import bitedu.bipa.book.dao.BlmDAO3;
import bitedu.bipa.book.utils.PageInfo;
import bitedu.bipa.book.utils.PagingbarGenerator;
import bitedu.bipa.book.vo.BookCopy;
import bitedu.bipa.book.vo.PageData;

@Service("blmService4")
public class BlmService4 {

	@Autowired
	private BlmDAO3 dao;
	
	public boolean registBook(BookCopy copy) {
		boolean flag = false;
		flag = dao.insertBook(copy);
		return flag;
	}
	
	public ArrayList<BookCopy> searchBookAll(){
		ArrayList<BookCopy> list = null;
		list = dao.selectBookAll();
		return list;
	}
	public boolean removeBook(String bookSeq) {
		boolean flag = false;
		flag = dao.deleteBook(Integer.parseInt(bookSeq));
		return flag;
	}
	public BookCopy findBook(String bookSeq) {
		BookCopy copy = null;
		copy = dao.selectBook(Integer.parseInt(bookSeq));
		System.out.println(copy);
		return copy;
	}
	public boolean modifyBook(BookCopy copy) {
		boolean flag = false;
		flag = dao.updateBook(copy);
		return flag;
	}
	
	public BookCopy upload(List<FileItem> items) throws Exception {
		BookCopy copy = null;
		copy = new BookCopy();
		for(FileItem item : items) {
			if(item!=null&item.isFormField()) {
				//일반적인 Form값 추출
				String fieldName = item.getFieldName();
				if (fieldName.equals("book_seq")) {
					copy.setBookSeq(Integer.parseInt(item.getString("UTF-8")));
				} else if (fieldName.equals("isbn")) {
					copy.setIsbn(item.getString("UTF-8"));
				} else if (fieldName.equals("book_title")) {
					copy.setTitle(item.getString("UTF-8"));
				} else if (fieldName.equals("author")) {
					copy.setAuthor(item.getString("UTF-8"));
				} else if (fieldName.equals("publisher")) {
					copy.setPublisher(item.getString("UTF-8"));
				} else if (fieldName.equals("publish_date")) {
					String date = item.getString("UTF-8");
					SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
					try {
						Date now = df.parse(date);
						copy.setPublishDate(new Timestamp(now.getTime()));
					} catch (ParseException e) {
						e.printStackTrace();
					}
				} else if (fieldName.equals("book_position")) {
					copy.setBookPosition(item.getString("UTF-8"));
				} else if (fieldName.equals("book_status")) {
					copy.setBookStaus(item.getString("UTF-8"));
				}
			} else {
				// upload할 데이터에 대한 정보 추출
				String fieldName = item.getFieldName();
				if(fieldName.equals("book_image")) {
					String temp = item.getName();
					System.out.println("book_image "+temp);
					int index = temp.lastIndexOf("\\");
					String fileName = temp.substring(index+1);
					copy.setBookImage(fileName);
					File uploadFile = new File("C:\\Users\\jungw\\Desktop\\travelImages\\"+fileName);
					item.write(uploadFile);
				}
			}
		}
		return copy;
	}

	public PageData<BookCopy> getPageData(int itemsPerPage, String group, String page) {
		
		int listSize = dao.selectListSize();
		page = page == null?"1":page;
		group = group == null?"1":group;
		//PageInfo pageInfo = new PageInfo(itemsPerPage, groupsPerPage, listSize);
		PageInfo pageInfo = new PageInfo(5, 2, listSize);
		int startIndex = pageInfo.calcuOrderOfPage(page);
		
		ArrayList<BookCopy> listForShow = dao.selectCurrentPageData(startIndex, itemsPerPage);
		//System.out.println(listForShow);	
		
		PagingbarGenerator pGenerator = new PagingbarGenerator();
		String navBar = pGenerator.generatePagingInfo(Integer.parseInt(group), Integer.parseInt(page),
							pageInfo);
		
		return new PageData<BookCopy>(listForShow, navBar, page);
	}
	
}

 

Service의 upload() 메서드에서는 request로 넘어온 필드값들을 BookCopy 객체에 세팅해준다.

이미지의 값을 세팅할 때는 BookCopy의 String 멤버 변수인 bookImage에 이름을 저장하고 경로명이 적힌 파일 객체를 만들어 FileItem에 write() 해준다. (업로드된 파일의 데이터를 uploadFile 경로에 저장. 해당 경로에 업로드된 파일이 저장되며, 이 과정에서 FileItem 객체의 데이터를 실제 파일로 쓰는 작업이 이루어짐.)

 

위의 작업을 마치면 등록할 정보를 담은 BookCopy 객체가 만들어지게 되고 Service의 registBook()을 통해 등록이 완료된다. 그리고 list를 redirect 해줌으로 리스트를 보여주고 등록작업이 끝난다.

 

 

다음은 상세 보기 화면이다.

이미지 보여지는 것과 다운로드에 대한 것만 정리해 보겠다.

<%@page import="bitedu.bipa.book.utils.DateCalculation"%>
<%@page import="bitedu.bipa.book.vo.BookCopy"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>도서 등록</title>
<!--     <link href="../resources/css/basic_style.css" rel="stylesheet" type="text/css" /> -->
    <script type="text/javascript" src="https://code.jquery.com/jquery-latest.min.js"></script>

</head>
<body>
<%
	BookCopy copy = (BookCopy)request.getAttribute("copy");
%>
<form action="" method="post" id='frm' enctype="multipart/form-data">
    <table>
        <tr><th colspan="4" id="form">도서 상세</th></tr>
        <tr><th>구분</th><th class="data_ui" colspan="2">내용</th><th>비고</th></tr>
        <tr>
            <td>도서번호</td>
            <td colspan="2">
            	<%=copy.getBookSeq()%>
            	<input type="hidden" id="book_seq" value="<%=copy.getBookSeq()%>">
            </td>
            <td id="message"></td></tr>
       	<tr>
            <td>도서이미지</td>
            <td colspan="2">
            	<img src="/images/<%=copy.getBookImage()%>" width="300" height="200" id="preview">
            	<!-- <input type="file" name="book_image" id="up_image"> -->
            </td>
			<td>
				<a href="download.do?fileName=<%=copy.getBookImage()%>">다운로드</a>
			</td>
         </tr>  
        <tr>
        	<td>ISBN</td>
        	<td colspan="2">
        		<%=copy.getIsbn()%>
        	</td>
        	<td>
        	</td>
        </tr>
        <tr>
        	<td>도서명</td>
        	<td colspan="2">
        		<%=copy.getTitle()%>
        	</td><td></td>
        </tr>
        <tr>
        	<td>저자/역자</td>
        	<td colspan="2">
        		<%=copy.getAuthor()%>
        	</td><td></td>
        </tr>
        <tr>
        	<td>출판일</td>
        	<td colspan="2">
        		<%=DateCalculation.getDate(copy.getPublishDate())%>
        	</td>
        	<td></td>
        <tr>
        <tr>
        	<td>도서위치</td>
        	<td colspan="2">
        	<%=copy.getBookPosition().equals("BS-0001")?"일반서가":""%>
        	<%=copy.getBookPosition().equals("BS-0002")?"예약서가":""%>
        	<%=copy.getBookPosition().substring(0,3).equals("BB-")?"회원":""%>
        	</td>
        	<td></td>
        <tr>
        <tr>
        	<td>도서상태</td>
        	<td colspan="2">
        	<%=copy.getBookStaus().equals("BM-0001")?"도서대출서비스":""%>
        	<%=copy.getBookStaus().equals("BM-0002")?"도서수선":""%>
        	<%=copy.getBookStaus().equals("BM-0003")?"도서보관":""%>
        	</td>
        	<td></td>
        <tr>
        <tr>
        	<td colspan="4" id="sending">
        		<input type="submit" value="수정화면으로" id="go_view_update"> 
        		<input type="submit" value="도서리스트" id="go_book_list"> 
        	</td>
        </tr>
    </table>
</form>
	<script type="text/javascript" src="../resources/js/book.js"></script>
</body>
</html>

 

scriptlet으로 copy 객체를 받아오고, img 태그에 "/images/<%=copy.getBookImage()%>"와 같이 사용하여 이미지를 불러오게 한다.

 

저렇게 사용함의 이유은 위에서 다뤘던 server.xml 에 Context를 아래와 같이 했기 때문이다.

 

그리고 다운로드를 하기위한 hypertext 태그를 둔다.

버튼을 누르면 download.do를 시행하게 될 것이고, fileName이 get방식으로 넘어갈 것이다.

 

 

다운로드 작업은 페이지 이동이 있으면 안되므로 메서드의 타입은 void로 준다.

requestParam으로 fileName이 넘어오고 File 객체 downloadFile에 경로명을 적는다.

fileName의 인코딩 설정을 해주고, 응답의 인코딩 설정, Cache Control 설정(브라우저에게 응답 데이터를 cache하지 말라고 지시. 이렇게 설정하면 브라우저는 매번 서버로부터 새롭게 데이터를 요청하게 되므로 항상 최신의 데이터를 보여줄 수 있음), Content disposition 헤더 설정(응답 데이터를 첨부 파일로 다운로드하도록 지시. filename 속성에는 다운로드되는 파일의 이름을 지정할 수 있음. 이 경우 fileName 변수에 저장된 파일 이름으로 설정되며, 사용자가 해당 응답을 받을 때 브라우저는 파일을 다운로드하는 창을 표시함) 등을 설정해준다.

 

FileInputStream과 OutputStream으로 downloadFile을 뽑아온다.

 

도서 수정 페이지는 등록과 상세 페이지와 중복되는 내용이므로 생략한다. 

mybatis로 쿼리문 매핑하여 쓰는데, bookImage에 대한 부분만 잘 추가해서 처리해주면 된다.

 

 

마지막으로 페이지네이션이다.

<%@page import="bitedu.bipa.book.vo.BookCopy"%>
<%@page import="java.util.ArrayList"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Book List</title>
<!-- <link href="../resources/css/basic_style.css" rel="stylesheet" type="text/css" /> -->
<script type="text/javascript" src="https://code.jquery.com/jquery-latest.min.js"></script>
</head>

<body>
${param.flag=='true'?"<script>alert('삭제성공');</script>":""}
<table>
	<tr><th colspan="5" id="title">도서리스트</th></tr>
	<tr><td id='seq'>순번</td><td>타이틀</td><td>저자</td><td>출판일</td><td></td></tr>
<c:forEach var="copy" items="${pData.list}">
	<tr>
		<td>${copy.bookSeq}</td>
		<td><a href='view_detail.do?bookSeq=${copy.bookSeq}'>${copy.title}</a></td>
		<td>${copy.author}</td>
		<td><fmt:formatDate value="${copy.publishDate}" pattern="yyyy-MM-dd"/> </td>
		<td><a href="remove.do?bookSeq=${copy.bookSeq}">삭제</a></td></tr>
</c:forEach>
	<tr>
		<td colspan="5"><a href="view_regist.do"><button>도서등록</button></a></td>
		
	</tr>
</table>
${pData.navBar}
<script type="text/javascript" src="../resources/js/book.js"></script> 
</body>
</html>

list 페이지의 테이블 바깥에 pData.navBar라는 요소를 추가해 줄 것이다.

 

서버 쪽에서 페이지 버튼에 대한 navBar를 만들어서 보내주도록 할 것이다.

list에 대해서는 버튼이 몇번째 그룹인가에 대한 group정보와 page number에 대한 정보가 실려올 것이다.

 

이 경우는 1번 그룹, 2 페이지이다

 

BookCopy의 정보를 담고있는 PageData 객체를 만들어 줄 것이다.

package bitedu.bipa.book.vo;

import java.util.ArrayList;

/*
 *  paging bar와 해당 content를 셋팅한다.
 *  paging bar는 전/후 페이지의 존재 여부를 셋팅하고 page는 출력할 컨넨츠의 목록을 지정한다.
 *  paging bar를 만들어 내기 위해서는 요청 페이지가 있는 그룹정보와 페이지 정보가 필요하다.
 *  previous가 존재할 경우는 현재 그룹의 앞 그룹의 첫번째 페이지 정보를 가지고 있고
 *  next가 존재할 경우는 현재 그룹의 뒤 그룹의 첫번째 페이지 정보를 가지고 있다.
 *  가장 처음 리스트를 호출할 경우는 첫번째 그룹의 첫번째 페이지 정보를 가지고 있다.
 */

public class PageData<T> {
	private ArrayList<T> list;
	private String navBar;
	private int currentPage;

	public PageData(ArrayList<T> list, String navBar, String page) {
		this.list = list;
		this.navBar = navBar;
		page = page==null?"1":page;
		this.currentPage = Integer.parseInt(page);
	}
	public ArrayList<T> getList() {
		return list;
	}
	public void setList(ArrayList<T> list) {
		this.list = list;
	}
	
	public String getNavBar() {
		return navBar;
	}
	public void setNavBar(String navBar) {
		this.navBar = navBar;
	}
	public int getCurrentPage() {
		return currentPage;
	}
	public void setCurrentPage(int currentPage) {
		this.currentPage = currentPage;
	}
	@Override
	public String toString() {
		return "PageData [list=" + list + ", navBar=" + navBar + ", currentPage=" + currentPage + "]";
	}

}

PageData는 이러한 정보(목록이 담길 ArrayList, String 형태를 띈 navBar, 현재 페이지 넘버)를 담는다.

 

그리고 Service의 getPageData() 메서드를 실행해준다. 

메서드의 매개 변수로는 페이지당 컨텐츠 수, 버튼 그룹 정보, 현재 페이지 정보가 넘어온다.

페이지당 컨텐츠 수는 5개씩 보여지고 싶으므로 하드코딩으로 5라고 지정해두었다.

dao를 통해 전체 리스트의 사이즈를 구해오고, 페이지와 그룹이 null인 경우 1을 가리키도록 정의해준다.

 

PageInfo 객체를 하나 두는데 페이지네이션의 알고리즘을 처리해줄 객체이다.

package bitedu.bipa.book.utils;

public class PageInfo {
	private int itemsPerPage;
	private int groupsPerPage;
	private int endPage;

	public PageInfo(int itemsPerPage, int groupsPerPage) {
		this.itemsPerPage = itemsPerPage;
		this.groupsPerPage = groupsPerPage;
	}
	
	public PageInfo(int itemsPerPage, int groupsPerPage, int totalCount) {
		this.itemsPerPage = itemsPerPage;
		this.groupsPerPage = groupsPerPage;
		this.endPage = (int)(Math.ceil(totalCount/(float)itemsPerPage));
	}
	public int getItemsPerPage() {
		return itemsPerPage;
	}
	public int getGroupsPerPage() {
		return groupsPerPage;
	}
	public int getEndPage() {
		return endPage;
	}
	public void setCount(int count) {
		this.endPage = (int)(Math.ceil(count/(float)itemsPerPage));
	}
	
	public int calcuOrderOfPage(String page) {
		int result = 0;
		page = page==null?"1":page;
		result = (Integer.parseInt(page)-1)*this.itemsPerPage;
		return result;
	}
	
	@Override
	public String toString() {
		return "{'itemsPerPage':'" + itemsPerPage + "', groupsPerPage':'" + groupsPerPage + "', endPae':'" + endPage
				+ "}";
	}
	
}

 

페이지네이션에 대한 내용은 아래 포스팅에 정리되어있다. 대충 알고리즘적인 내용은 같다.

https://suldenlion.tistory.com/114

 

페이지네이션 (Pagination) - Servlet 프로그래밍

● 페이지네이션(Pagination) - 페이지네이션(Pagination) 또는 페이징(Paging)은 데이터나 콘텐츠를 페이지로 나누는 기술이나 기법을 의미함 - 주로 대량의 데이터나 긴 목록을 여러 페이지로 나누어

suldenlion.tistory.com

 

groupsPerPage에 2를 두어 2개 단위로 페이지가 보여지게 한다.

calcuOrderOfPage()를 통해 쿼리문을 수행할 startIndex 값을 구하고 dao에서 selectCurrentPageData() 메서드를 실행한다.

package bitedu.bipa.book.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import bitedu.bipa.book.utils.ConnectionManager;
import bitedu.bipa.book.vo.BookCopy;

@Repository("blmDAO3")
public class BlmDAO3 implements IBlmDAO {
	
	@Autowired
	private SqlSession sqlSession;
	
	@Override
	public boolean insertBook(BookCopy copy){
		boolean flag = false;
		
		int affectedCount1 = sqlSession.insert("mapper.book.insertBook", copy);
		int affectedCount2 = sqlSession.insert("mapper.book.insertCopy", copy);
		if (affectedCount1 > 0 && affectedCount2 > 0) {
			flag = true;
		}
		
		return flag;
	}
	
	@Override
	public ArrayList<BookCopy> selectBookAll(){
		ArrayList<BookCopy> list = null;
		list = (ArrayList)sqlSession.selectList("mapper.book.selectAllBook");
		System.out.println(list.size());
		
		return list;
	}
	
	@Override
	public boolean deleteBook(int parseInt) {
		boolean flag = false;
		
		int affectedCount = sqlSession.delete("mapper.book.deleteBook", parseInt);
		if (affectedCount > 0) {
			flag = true;
		}
		
		return flag;
	}
	public BookCopy selectBook(int parseInt) {
		BookCopy copy = null;
		copy = sqlSession.selectOne("mapper.book.selectBookBySeq",parseInt);
		
		return copy;
	}
	
	@Override
	public boolean updateBook(BookCopy copy) {
		boolean flag = false;
		
		int affectedCount = sqlSession.update("mapper.book.updateBookImage", copy);

		if (affectedCount>0) {
			flag = true;
		}
		
		return flag;
	}

	public ArrayList<BookCopy> selectCurrentPageData(int start, int range) {
		ArrayList<BookCopy> list = null;
		HashMap<String, Integer> tmp = new HashMap<String, Integer>();
		tmp.put("start", start);
		tmp.put("range", range);
//		ArrayList<Integer> 로 하면 start랑 range를 동시에 못가져와서 안되는 듯
//		ArrayList<Integer> tmp = new ArrayList<Integer>();
//		tmp.add(start);
//		tmp.add(range);
		
		list = (ArrayList)sqlSession.selectList("mapper.book.selectCurrentPageData", tmp);		
//		list = (ArrayList)sqlSession.selectList("mapper.book.selectCurrentPageData", start, end);
		
		return list;
	}

	public int selectListSize() {
		int size = sqlSession.selectOne("mapper.book.selectListSize");
		
		return size;
	}
}

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Config 3.0//EN"
	"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.book">
	<resultMap id="bookMap" type="bookCopyVO">
		<id property="bookSeq" column="book_seq"></id>	
		<result property="isbn" column="book_isbn"/>
		<result property="title" column="book_title"/>
		<result property="author" column="book_author"/>
		<result property="publishDate" column="book_published_date"/>
		<result property="bookPosition" column="book_position"/>
		<result property="bookStaus" column="book_status"/>
		<result property="bookImage" column="book_image"/>
	</resultMap>
	<select id="selectAllBook" resultMap="bookMap" resultType="bookCopyVO">
		<![CDATA[
			select a.*, b.* from book_info a inner join book_copy b on a.book_isbn=b.book_isbn
		]]>
	</select>
	<select id="selectBookBySeq" resultMap="bookMap" resultType="bookCopyVO" parameterType="java.lang.Integer">
		<![CDATA[
			select a.*, b.* from book_info a inner join book_copy b on a.book_isbn=b.book_isbn 
			where b.book_seq = #{bookSeq}
		]]>
	</select>
	<select id="updateBook" parameterType="bookCopyVO">
		<![CDATA[
			update book_info set book_title = #{title}, book_author=#{author}, book_published_date = #{publishDate} where book_isbn = #{isbn}
		]]>
	</select>
	<select id="updateBookImage" parameterType="bookCopyVO">
		<![CDATA[
			update book_copy c inner join book_info i
			on c.book_isbn = i.book_isbn
			set c.book_position = #{bookPosition}, c.book_status=#{bookStaus}, c.book_image=#{bookImage},
			i.book_title=#{title}, i.book_author=#{author}, i.book_published_date=#{publishDate} where c.book_seq = #{bookSeq};
		]]>
	</select>
	<select id="deleteBook" parameterType="java.lang.Integer">
		<![CDATA[
			delete from book_copy where book_seq = #{bookSeq}
		]]>
	</select>
	<select id="insertBook" parameterType="bookCopyVO">
		<![CDATA[
			insert into book_info values (#{isbn},#{title},#{author},#{publishDate})
		]]>
	</select>
	<select id="insertCopy" parameterType="bookCopyVO">
		<![CDATA[
			insert into book_copy(book_isbn, book_image) values (#{isbn}, #{bookImage})
		]]>
	</select>
	<select id="selectCurrentPageData" resultMap="bookMap" resultType="bookCopyVO" parameterType="map"> <!-- parameterType에 java.util.HashMap아님 그냥 map -->
		<![CDATA[
			select i.*, c.* from book_info i
			inner join book_copy c
			on i.book_isbn=c.book_isbn limit #{start}, #{range}
		]]>
	</select> 
	<select id="selectListSize" resultType="java.lang.Integer">
		<![CDATA[
			select count(*) from book_info i inner join book_copy c on i.book_isbn=c.book_isbn
		]]>
	</select>
</mapper>

mybatis에 매핑한 xml파일이다.

id가 selectCurrentPageData인 부분을 보면 된다. start와 range에 대한 값들이 map 타입으로 넘어와서 bookMap에 대한 정보들을 돌려준다.

 

이렇게 페이지에 띄워줄 리스트들에 대한 정보를 받은 후 PagingbarGenerator를 생성하여 navBar에 문자 형태로 담아줄 것이다. 

package bitedu.bipa.book.utils;

public class PagingbarGenerator {
	public static String generatePagingInfo(int group,int page,PageInfo info) {
		String result = null;
		StringBuilder sb = new StringBuilder();
		String space = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
		int endGroup = (int)Math.ceil((float)info.getEndPage()/info.getGroupsPerPage());
		System.out.println("endGroup "+endGroup);
		int temp1 = info.getEndPage()%info.getGroupsPerPage()==0?5:info.getEndPage()%info.getGroupsPerPage();
		int limit = endGroup==group?temp1:(info.getGroupsPerPage());
		System.out.println(limit+","+info.getEndPage()+","+endGroup);
		if(group>1) {
			String prev = "<a href='./list.do?group="+(group-1)+"&page="+((group-1)*info.getGroupsPerPage())+"'>Prev</a>\n";
			sb.append(prev);
			//sb.append(space);
		}
		//((itemCount-1)*info.getItemsPerPage())
		for(int i=1;i<=limit;i++) {
			String temp = "<a href='./list.do?&group="+group+"&page="+((group-1)*info.getGroupsPerPage()+i)+"'>"+((group-1)*info.getGroupsPerPage()+i)+"</a>\n";
			//sb.append(space);
			sb.append(temp);
		}
		if(group<endGroup) {
			//sb.append(space);
			String next = "<a href='./list.do?group="+(group+1)+"&page="+((group*info.getGroupsPerPage())+1)+"'>Next</a>\n";
			sb.append(next);
		}
		result = sb.toString();
		return result;
	}
}

PagingbarGenerator는 Html 태그를 포함하고 있으며, book_list.jsp 파일에 간단히 심어주기 위해 사용한다.

 

모든 요소를 담은 PageData를 넘겨주고 리스트를 보여주면 끝.

반응형

댓글