[Spring] 도서 등록, 검색

2023. 12. 31. 16:25학부 강의/웹프로그래밍 (Spring)

0. 출처

 

아직 배우고 있는 중이라 부정확한 정보가 포함되어 있을 수 있습니다!
주의하세요!

 

 

올인원 스프링 프레임워크 참고.

 

올인원 스프링 프레임워크 : 네이버 도서

네이버 도서 상세정보를 제공합니다.

search.shopping.naver.com

 


1. 관리자 관련 기능

 

 

  • 관리자 계정 정보 수정
  • 새 비밀번호 생성 및 메일 발송
  • 신규 도서 등록 (+파일 업로드)
  • 도서 검색 및 상세 정보 출력
  • 도서 정보 수정과 삭제

 


2. 신규 도서 등록

 

 


가. 환경 설정

 

필요한 패키지와 클래스를 추가한다.

package com.office.library.book;

public class BookVo {
    int b_no;
    String b_thumbnail;
    String b_name;
    String b_author;
    String b_publisher;
    String b_publish_year;
    String b_isbn;
    String b_call_number;
    int b_rental_able;
    String b_reg_date;
    String b_mod_date;

    public int getB_no() {
        return b_no;
    }
    public void setB_no(int b_no) {
        this.b_no = b_no;
    }
    ... //getter, setter 생략

}

BookVo를 구현한다.

 

file은 없다.

 

CREATE TABLE tbl_book(
    b_no INT AUTO_INCREMENT,
    b_thumbnail VARCHAR(100),
    b_name VARCHAR(30) NOT NULL,
    b_author VARCHAR(20) NOT NULL,
    b_publisher VARCHAR(20) NOT NULL,
    b_publish_year CHAR(4) NOT NULL,
    b_isbn VARCHAR(30) NOT NULL,
    b_call_number VARCHAR(30) NOT NULL,
    b_rental_able TINYINT NOT NULL DEFAULT 1,
    b_reg_date DATETIME,
    b_mod_date DATETIME,
    PRIMARY KEY(b_no)
);

데이터베이스에 테이블을 생성한다.

 

 


나. 도서 등록 화면

 

신규 도서를 등록하는 페이지로 이동하는 기능 구현하기.

 

@Controller
@RequestMapping("/book/admin")
public class BookController {

    @GetMapping("/registerBookForm")
    public String registerBookForm() {
        System.out.println("[BookController] registerBookForm()");

        String nextPage = "admin/book/register_book_form";

        return nextPage;
    }
}

BookController.java를 만들고 registerBookForm() 선언한다.

 

...
<form action="<c:url value='/book/admin/registerBookConfirm' />" name="register_book_form" method="post" enctype="multipart/form-data">

    <input type="text"        name="b_name"             placeholder="INPUT BOOK NAME."> <br>
    <input type="text"        name="b_author"         placeholder="INPUT BOOK AUTHOR."> <br>
    <input type="text"        name="b_publisher"        placeholder="INPUT BOOK PUBLISHER."> <br>
    <input type="text"        name="b_publish_year"     placeholder="INPUT BOOK PUBLISH YEAR."> <br>
    <input type="text"        name="b_isbn"             placeholder="INPUT BOOK ISBN."> <br>
    <input type="text"        name="b_call_number"     placeholder="INPUT BOOK CALL NUMBER."> <br>
    <select name="b_rental_able">
        <option value="">SELECT BOOK RANTAL ABLE.</option>
        <option value="0">UNABLE.</option>
        <option value="1">ABLE.</option>
    </select><br>
    <input type="file"        name="file"><br>
    <input type="button"    value="register book" onclick="registerBookForm();"> 
    <input type="reset"        value="reset">

</form>

도서의 정보를 입력하는 admin/book/register_book_form이다.

 

  • <form ... enctype="multipart/form-data">
    : HTML <form> 태그에서 사용되는 인코딩 유형을 지정하는 속성.
    : <input type="file">을 사용하여 파일을 업로드할 때 필수.
    : 비텍스트 데이터(파일, 이미지, 비디오 등)를 서버로 전송하기 위해 필요. 일반 텍스트 데이터와 달리 이진 형태로 처리하기 때문.
  • "multipart/form-data"
    : 다양한 유형과 크기의 데이터를 하나의 요청으로 분할하여 전송하는 인코딩 방식
  • <input type="file" name="file"><br>
    : 파일을 선택해서 업로드할 수 있다.
  • <input type="button" value="register book" onclick="registerBookForm();">
    : 도서 정보 입력을 마치고 서버로 보낸다.
    : registerBookForm() 실행하여 입력하지 않은 정보가 있는지 확인한다.
  • <input type="reset" value="reset">
    : 도서의 정보를 리셋하는 버튼.

 


다. 파일 업로드

 

스프링에서 파일을 업로드하려면 commons-fileupload 모듈을 사용한다.

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.5</version>
</dependency>

pom.xml에 추가.

 

앞서 사용된 enctype="multipart/form-data"의 multipart를 사용하기 위해서CommonMultipartResolver를 빈으로 등록한다.

 

<?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"
    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">

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="10240000" />
        <property name="defaultEncoding" value="utf-8" />
    </bean>

</beans>

CommonMultipartResolver를 등록하기 위해서 file-context.xml 생성한다.

 

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/spring/root-context.xml
        /WEB-INF/spring/jdbc-context.xml
        /WEB-INF/spring/security-context.xml
        /WEB-INF/spring/mail-context.xml
        /WEB-INF/spring/file-context.xml
    </param-value>
</context-param>

web.xml에 추가.

 

package com.office.library.book.admin.util;

import java.io.File;
import java.util.UUID;

import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

@Service
public class UploadFileService {

    final static public String BOOK_THUMBNAIL_UPLOAD_DIR = makeUploadDir();

    static public String makeUploadDir() {
        String osName = System.getProperty("os.name").toLowerCase();
        if (osName.contains("win")) {
            return "C:\\library\\upload\\";
        } else if (osName.contains("mac")) {
            return System.getProperty("user.home") + "/Desktop/Study_JavaSpring_2/download/upload";
        } else if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix")) {
            return System.getProperty("user.home") + "/Desktop/Study_JavaSpring_2/download/upload";
        } else {
            return null;
        }
    }

    public String upload(MultipartFile file) {

        boolean result = false;

        String fileOriName = file.getOriginalFilename();
        String fileExtension = fileOriName.substring(fileOriName.lastIndexOf("."), fileOriName.length());
        if(BOOK_THUMBNAIL_UPLOAD_DIR == null) return null;

        UUID uuid = UUID.randomUUID();
        String uniqueName = uuid.toString().replaceAll("-", "");

        File saveFile = new File(BOOK_THUMBNAIL_UPLOAD_DIR + File.separator + uniqueName + fileExtension);
        System.out.println("[UploadFileService]" + " UPLOAD_DIR : " + BOOK_THUMBNAIL_UPLOAD_DIR + " saveFile : " + saveFile.getAbsolutePath());

        if(!saveFile.exists()) saveFile.mkdirs();

        try {
            file.transferTo(saveFile);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }

        if(result) {
            System.out.println("[UploadFileService] FILE UPLOAD SUCCESS!!");
            return uniqueName + fileExtension;
        } else {
            System.out.println("[UploadFileService] FILE UPLOAD FAIL!!");
            return null;
        }

    }

}

UploadFileService.upload() 구현.

 

  • final static public String BOOK_THUMBNAIL_UPLOAD_DIR = makeUploadDir();
    : 운영체제에 따라서 파일의 저장 위치를 달리함.

 

makeUploadDir() 등 운영체제에 따라서 저장 위치를 달리하는 기능은 필요에 의해서 없던 기능을 추가한 것.

 


라. 도서 등록 처리

 

@Autowired
UploadFileService uploadFileService;
@Autowired
BookService bookService;

...

@PostMapping("/registerBookConfirm")
public String registerBookConfirm(BookVo bookVo, @RequestParam("file") MultipartFile file) {
    System.out.println("[BookController] registerBookConfirm()");

    String nextPage = "admin/book/register_book_ok";

    String savedFileName = uploadFileService.upload(file);

    if(savedFileName != null) {
        bookVo.setB_thumbnail(savedFileName);
        int result = bookService.registerBookConfirm(bookVo);

        if(result <= 0)
            nextPage = "admin/book/register_book_ng";
    }else {
        nextPage = "admin/book/register_book_ng";
    }

    return nextPage;
}

BookController/book/admin/registerBookConfirm 요청을 처리하는 registerBookConfirm()을 선언.

 

  • public String registerBookConfirm(BookVo bookVo, @RequestParam("file") MultipartFile file) {
    : BookVo에 없는 file은 따로 전달받는다.

  • String savedFileName = uploadFileService.upload(file);
    : 파일을 업로드한다. 성공하면 파일의 이름을 savedFileName에 반환한다. 실패하면 null.

  • int result = bookService.registerBookConfirm(bookVo);
    : BookService에 구체적인 처리를 위임.

 

@Service
public class BookService {

    final static public int BOOK_ISBN_ALREADY_EXIST = 0;
    final static public int BOOK_REGISTER_SUCCESS = 1;
    final static public int BOOK_REGISTER_FAIL = -1;

    @Autowired
    BookDao bookDao;

    ...

    public int registerBookConfirm(BookVo bookVo) {
        System.out.println("[BookService] registerBookConfirm()");

        boolean isISBN = bookDao.isISBN(bookVo.getB_isbn());

        if(!isISBN) {
            int result = bookDao.insertBook(bookVo);

            if(result > 0) return BOOK_REGISTER_SUCCESS;
            else return BOOK_REGISTER_FAIL;
        } else {
            return BOOK_ISBN_ALREADY_EXIST;
        }
    }
}
  • boolean isISBN = bookDao.isISBN(bookVo.getB_isbn());
    : 이미 등록된 도서인지 확인한다.
  • int result = bookDao.insertBook(bookVo);
    : 도서를 등록한다.

 

@Component
public class BookDao {

    @Autowired
    JdbcTemplate jdbcTemplate;

    public boolean isISBN(String b_isbn) {
        System.out.println("[BookDao] isISBN()");
        String sql = "SELECT COUNT(*) FROM tbl_book " + "WHERE b_isbn = ?";
        int result = jdbcTemplate.queryForObject(sql, Integer.class, b_isbn);

        return result > 0 ? true : false;
    }

    public int insertBook(BookVo bookVo) {
        System.out.println("[BookDao] insertBook()");

        String sql = "INSERT INTO tbl_book(b_thumbnail, " 
                    + "b_name, " + "b_author, " + "b_publisher, "
                    + "b_publish_year, " + "b_isbn, " + "b_call_number, "
                    + "b_rental_able, " + "b_reg_date, " + "b_mod_date) "
                    + "VALUES(?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())";
        int result = -1;

        try {
            result = jdbcTemplate.update(sql, 
                    bookVo.getB_thumbnail(), bookVo.getB_name(),
                    bookVo.getB_author(), bookVo.getB_publisher(),
                    bookVo.getB_publish_year(), bookVo.getB_isbn(),
                    bookVo.getB_call_number(), bookVo.getB_rantal_able());
        } catch(Exception e) {
            e.printStackTrace();
        }
        return result;
    }

}
  • isISBN() : 동일한 도서가 존재하는지 확인한다.
  • int result = jdbcTemplate.queryForObject(sql, Integer.class, b_isbn);
    : 결과가 단일 객체인 SQL 쿼리를 실행. RowMapper가 필요 없다.
  • insertBook() : 새로운 도서 정보를 데이터베이스에 삽입.

 

 


3. 도서 검색

 

등록된 도서를 검색하여 찾는 기능을 구현한다.

 


가. 도서 검색

 

<div class="search">

    <form action="<c:url value='/book/admin/searchBookConfirm' />" name="search_book_form" method="get">
        <input type="text" name="b_name" placeholder="Enter the name of the book you are looking for.">
        <input type="button" value="search" onclick="searchBookForm();">
    </form>

</div>

BookRentalPjt/src/main/webapp/WEB-INF/views/admin/include/nav.jsp에서 검색창에 해당하는 부분이다.

 

버튼을 누르면 /book/admin/searchBookConfirm?b_name=[입력한 키워드] 요청을 보낸다.

 

@Controller
@RequestMapping("/book/admin")
public class BookController {
    ...
    @GetMapping("/searchBookConfirm")
    public String searchBookConfirm(BookVo bookVo, Model model) {
        System.out.println("[BookController] searchBookConfirm()");
        String nextPage = "admin/book/search_book";

        List<BookVo> bookVos = bookService.searchBookConfirm(bookVo);

        model.addAttribute("bookVos", bookVos);

        return nextPage;
    }
}

BookControllersearchBookConfirm()을 추가한다.

 

  • List<BookVo> bookVos = bookService.searchBookConfirm(bookVo);
    : 서비스에 처리를 위임
  • model.addAttribute("bookVos", bookVos);
    : 검색 결과를 뷰에 전달하기 위해서 저장

 

@Service
public class BookService {
    ...
    public List<BookVo> searchBookConfirm(BookVo bookVo) {
        System.out.println("[BookService] searchBookConfirm()");

        return bookDao.selectBooksBySearch(bookVo);
    }
}

BookService다.

 

@Component
public class BookDao {

    ...

    public List<BookVo> selectBooksBySearch(BookVo bookVo) {
        System.out.println("[BookDao] selectBooksBySearch()");
        String sql = "SELECT * FROM tbl_book "
                + "WHERE b_name LIKE ? "
                + "ORDER BY b_no DESC";

    List<BookVo> bookVos = null;

    try {
        bookVos = jdbcTemplate.query(sql,  new RowMapper<BookVo>() {
            @Override
            public BookVo mapRow(ResultSet rs, int rowNum) throws SQLException {
                BookVo bookVo = new BookVo();

                bookVo.setB_no(rs.getInt("b_no"));
                bookVo.setB_thumbnail(rs.getString("b_thumbnail"));
                bookVo.setB_name(rs.getString("b_name"));
                bookVo.setB_author(rs.getString("b_author"));
                bookVo.setB_publisher(rs.getString("b_publisher"));
                bookVo.setB_publish_year(rs.getString("b_publish_year"));
                bookVo.setB_isbn(rs.getString("b_isbn"));
                bookVo.setB_call_number(rs.getString("b_isbn"));
                bookVo.setB_rantal_able(rs.getInt("b_rental_able"));
                bookVo.setB_reg_date(rs.getString("b_reg_date"));
                bookVo.setB_mod_date(rs.getString("b_mod_date"));

                return bookVo;
            }
        }, "%" + bookVo.getB_name() + "%");
    }catch(Exception e) {
        e.printStackTrace();
    }
        return bookVos.size() > 0 ? bookVos : null;
    }

}

BookDao는 데이터베이스에 SELECT * FROM tbl_book WHERE b_name LIKE %입력한키워드% ORDER BY b_no DESC 질의문을 전달한다.

 

%입력한키워드%%는 와일드카드로 0개 이상의 문자열을 뜻한다.

 

이름에 키워드가 포함된 모든 도서를 조회한다.

 

 


나. 도서 상세 보기

<tbody>
    <c:forEach var="item" items="${bookVos}">
        <tr>
            <td>
            <c:url value='/book/admin/bookDetail' var='detail_url'>
                <c:param name='b_no' value='${item.b_no}'/>
            </c:url>
            <a href="${detail_url}">${item.b_name}</a>
            </td>
            ...
        </tr>
    </c:forEach>
</tbody>

BookRentalPjt/src/main/webapp/WEB-INF/views/admin/book/search_book.jsp 일부다.

 

도서명 클릭 시 /book/admin/bookDetail?b_no=[클릭한 도서의 b_no]” 요청이 발생한다.

 

@Controller
@RequestMapping("/book/admin")
public class BookController {
    ...

    @GetMapping("/bookDetail")
    public String bookDetail(@RequestParam("b_no") int b_no, Model model) {
        System.out.println("[BookController] bookDetail()");

        String nextPage = "admin/book/book_detail";
        BookVo bookVo = bookService.bookDetail(b_no);
        model.addAttribute("bookVo", bookVo);
        return nextPage;
    }
}

BookControllerbookDetail() 추가.

 

조회한 도서의 상세 정보를 뷰에 전달한다.

 

@Service
public class BookService {
    ...
    public BookVo bookDetail(int b_no) {
        System.out.println("[BookService] bookDetail()");
        return bookDao.selectBook(b_no);
    }
}

BookServicebookDetail() 추가.

 

@Component
public class BookDao {
    ...
    public BookVo selectBook(int b_no) {
        System.out.println("[BookDao] selectBook()");

        String sql = "SELECT * FROM tbl_book WHERE b_no = ?";

        List<BookVo> bookVos = null;

        try {
            bookVos = jdbcTemplate.query(sql, new RowMapper<BookVo>() {
                @Override
                public BookVo mapRow(ResultSet rs, int rowNum) throws SQLException {
                    BookVo bookVo = new BookVo();

                    bookVo.setB_no(rs.getInt("b_no"));
                    bookVo.setB_thumbnail(rs.getString("b_thumbnail"));
                    bookVo.setB_name(rs.getString("b_name"));
                    bookVo.setB_author(rs.getString("b_author"));
                    bookVo.setB_publisher(rs.getString("b_publisher"));
                    bookVo.setB_publish_year(rs.getString("b_publish_year"));
                    bookVo.setB_isbn(rs.getString("b_isbn"));
                    bookVo.setB_call_number(rs.getString("b_isbn"));
                    bookVo.setB_rental_able(rs.getInt("b_rental_able"));
                    bookVo.setB_reg_date(rs.getString("b_reg_date"));
                    bookVo.setB_mod_date(rs.getString("b_mod_date"));

                    return bookVo;
                }
            }, b_no);
        }catch(Exception e) {
            e.printStackTrace();
        }
        return bookVos.size() > 0 ? bookVos.get(0) : null;
    }

}

BookDao.selectBook() 구현.

 

b_no 바탕으로 도서의 정보를 조회.

 

 

아직 이미지를 가져오지 못한다.

 

 

 

1) 외부 url 요청을 물리적 경로에 매핑하기

 

정적인 파일을 서비스하기 위해서 경로를 등록해야 한다.

 

<img src="<c:url value="/libraryUploadImg/${bookVo.b_thumbnail}"/>">

/libraryUploadImg/${bookVo.b_thumbnail} 요청을 실제 물리적 경로에 매핑해야 한다.

 

윈도우의 경우 C:\\library\\upload\\에 파일이 저장되어 있다. (macOS, linux, unix → ~/Desktop/Study_JavaSpring_2/download/upload)

 

servlet-context.xml<resource>를 추가한다.

 

<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<resources mapping="/libraryUploadImg/**" location="file:///C:/library/upload/" />

이미 CSS와 일부 이미지를 반환하기 위해서 존재하는 resources를 볼 수 있다.

 

/libraryUploadImg/ 요청을 C:/library/upload/로 매핑했다.

 

 


'학부 강의 > 웹프로그래밍 (Spring)' 카테고리의 다른 글

[Spring] 사용자 관련 기능 구현  (0) 2024.01.04
[Spring] 도서 수정, 삭제  (0) 2024.01.01
[Spring] Mail 보내기  (2) 2023.12.21
[Spring] 관리자 로그인  (2) 2023.12.20
[Spring] 데이터베이스  (0) 2023.11.09