[LG U+ 유레카 3기] JSP + Servlet + Ajax로 도서 관리 화면 만들기
2025. 10. 31. 12:14ㆍJava/JSP
이 포스트는 JSP + Servlet + Tomcat + Ajax(fetch)로 도서 목록을 JSON으로 받아와 테이블에 출력하고, 동일한 패턴으로 등록/수정/삭제(POST)까지 확장할 준비를 마쳤다.
❶ 프로젝트 구성 & 기본 설정
- 프로젝트: Dynamic Web Project
BookManagerAjax1 - 서버: Tomcat 10.x (Jakarta)
- 패키지:
dao.BookDao,dto.BookDto,servlet.BookServlet - 뷰(View):
webapp/books.jsp - 라이브러리:
gson(JSON 직렬화), JDBC 드라이버(Tomcat lib)
❷ URL 설계 & 서블릿 라우팅
요청을 /books/*로 모으고, request.getRequestURI()에서 contextPath를 뺀 나머지로 분기한다.
Dao,Dto,DBManager 파일은 저번 실습에 이어서 진행하였다.
package servlet;
import java.io.IOException;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import dao.BookDao;
import dto.BookDto;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
// Ajax 를 이용한 데이터 서비스
// JSP forwarding X, JSON 데이터 응답
@WebServlet("/books/*")
public class BookServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// content-type 을 application/json, utf-8 encoding 처리
response.setContentType("application/json; utf-8");
String job = request.getRequestURI().substring(request.getContextPath().length());
switch(job) {
case "/books/list" : listBook(request, response); break;
case "/books/detail" : detailBook(request, response); break;
case "/books/insert" : insertBook(request, response); break;
case "/books/update" : updateBook(request, response); break;
case "/books/delete" : deleteBook(request, response); break;
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
private void listBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
BookDao bookDao = new BookDao();
List<BookDto> bookList = bookDao.listBook(); // model 준비
System.out.println(bookList);
// 아래 jsp 포워딩 대신 List<BookDto> 를 json 으로 응답
// Java Object -> json 문자열 변경 Library
// request.setAttribute("bookList", bookList);
// request.getRequestDispatcher("/bookList.jsp").forward(request, response); // jsp 포워딩
Gson gson = new Gson();
String jsonStr = gson.toJson(bookList); // List<BookDto> -> JSON 문자열로 변경
System.out.println(jsonStr);
response.getWriter().write(jsonStr); // JSON 문자열을 응답
}
private void detailBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
BookDao bookDao = new BookDao();
// bookId parameter
int bookId = Integer.parseInt(request.getParameter("bookId"));
BookDto bookDto = bookDao.detailBook(bookId); // model 준비
System.out.println(bookDto);
Gson gson = new Gson();
String jsonStr = gson.toJson(bookDto);
System.out.println(jsonStr);
response.getWriter().write(jsonStr);
}
private void insertBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
BookDao bookDao = new BookDao();
// bookId, bookName, publisher, price parameter => BookDto
int bookId = Integer.parseInt(request.getParameter("bookId"));
String bookName = request.getParameter("bookName");
String publisher = request.getParameter("publisher");
int price = Integer.parseInt(request.getParameter("price"));
BookDto bookDto = new BookDto(bookId, bookName, publisher, price);
int ret = bookDao.insertBook(bookDto); // model 준비
System.out.println(ret);
Gson gson = new Gson();
JsonObject jsonObject = new JsonObject();
// 작업의 성공, 실패를 json 으로 응답
if( ret == 1 ) {
jsonObject.addProperty("result", "success");
}else {
jsonObject.addProperty("result", "fail");
}
String jsonStr = gson.toJson(jsonObject);
System.out.println(jsonStr);
response.getWriter().write(jsonStr);
}
private void updateBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
BookDao bookDao = new BookDao();
// bookId, bookName, publisher, price parameter => BookDto
int bookId = Integer.parseInt(request.getParameter("bookId"));
String bookName = request.getParameter("bookName");
String publisher = request.getParameter("publisher");
int price = Integer.parseInt(request.getParameter("price"));
BookDto bookDto = new BookDto(bookId, bookName, publisher, price);
int ret = bookDao.updateBook(bookDto); // model 준비
System.out.println(ret);
Gson gson = new Gson();
JsonObject jsonObject = new JsonObject();
// 작업의 성공, 실패를 json 으로 응답
if( ret == 1 ) {
jsonObject.addProperty("result", "success");
}else {
jsonObject.addProperty("result", "fail");
}
String jsonStr = gson.toJson(jsonObject);
System.out.println(jsonStr);
response.getWriter().write(jsonStr);
}
private void deleteBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
BookDao bookDao = new BookDao();
// bookId parameter
int bookId = Integer.parseInt(request.getParameter("bookId"));
int ret = bookDao.deleteBook(bookId); // model 준비
System.out.println(ret);
Gson gson = new Gson();
JsonObject jsonObject = new JsonObject();
// 작업의 성공, 실패를 json 으로 응답
if( ret == 1 ) {
jsonObject.addProperty("result", "success");
}else {
jsonObject.addProperty("result", "fail");
}
String jsonStr = gson.toJson(jsonObject);
System.out.println(jsonStr);
response.getWriter().write(jsonStr);
}
}
❸ JSP (books.jsp) — Ajax로 목록 받아 테이블 렌더링
페이지 로드 시 fetch('/BookManagerAjax1/books/list')로 JSON을 받아 테이블을 구성한다. EL 충돌을 피하기 위해 JS 템플릿 리터럴 내 ${...}는 \${...}로 이스케이프하거나, 아래처럼 행 클릭 이벤트를 JS에서 바인딩한다(권장).
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>도서 관리</title>
</head>
<body>
<h1>도서 관리</h1>
<table>
<thead>
<tr><th>bookId</th><th>bookName</th><th>publisher</th><th>price</th></tr>
</thead>
<tbody id="bookTbody"></tbody>
</table>
<hr>
<form>
<input type="text" name="bookId" id="bookId" placeholder="도서ID"><br>
<input type="text" name="bookName" id="bookName" placeholder="도서명"><br>
<input type="text" name="publisher" id="publisher" placeholder="출판사"><br>
<input type="text" name="price" id="price" placeholder="가격"><br>
</form>
<hr>
<button id="btnInsert">등록</button>
<button id="btnUpdate">수정</button>
<button id="btnDelete">삭제</button>
<button id="btnClear">초기화</button>
<script>
window.onload = function(){
listBook();
//버튼 연결 추가
document.getElementById("btnInsert").addEventListener("click", insertBook);
document.getElementById("btnUpdate").addEventListener("click", updateBook);
document.getElementById("btnDelete").addEventListener("click", deleteBook);
document.getElementById("btnClear").addEventListener("click", clearForm);
}
//도서 목록 불러오기
async function listBook(){
let url = '/BookManagerAjax1/books/list';
let response = await fetch(url);
let data = await response.json();
console.log("도서 목록:", data);
makeListHtml(data);
}
//목록 HTML 구성
function makeListHtml(list){
let listHtml = ``;
list.forEach(book => {
listHtml += `
<tr data-bookid=\${book.bookId}>
<td>\${book.bookId}</td>
<td>\${book.bookName}</td>
<td>\${book.publisher}</td>
<td>\${book.price}</td>
</tr>`;
});
document.querySelector("#bookTbody").innerHTML = listHtml;
document.querySelectorAll("#bookTbody tr").forEach(tr => {
tr.onclick = function(){
let bookId = this.getAttribute("data-bookid");
detailBook(bookId);
};
});
}
//등록 기능
async function insertBook() {
let urlParams = new URLSearchParams({
bookId: document.querySelector("#bookId").value,
bookName: document.querySelector("#bookName").value,
publisher: document.querySelector("#publisher").value,
price: document.querySelector("#price").value
});
await fetch('/BookManagerAjax1/books/insert', {
method: 'POST',
body: urlParams
});
clearForm();
listBook();
}
//도서 상세조회
async function detailBook(bookId){
let url = '/BookManagerAjax1/books/detail?bookId=' + bookId; // 오타 수정: Bodok → Book
let response = await fetch(url);
let data = await response.json();
console.log("도서 상세:", data);
// 폼에 데이터 채워넣기
document.getElementById("bookId").value = data.bookId;
document.getElementById("bookName").value = data.bookName;
document.getElementById("publisher").value = data.publisher;
document.getElementById("price").value = data.price;
}
async function updateBook(){
let urlParams = new URLSearchParams({
bookId: document.querySelector("#bookId").value,
bookName: document.querySelector("#bookName").value,
publisher: document.querySelector("#publisher").value,
price: document.querySelector("#price").value
});
let fetchOptions = {
method: "post",
body: urlParams
};
let url = '/BookManagerAjax1/books/update';
let response = await fetch(url, fetchOptions); // 비동기 요청
let data = await response.json();
console.log(data);
if (data.result == "success") {
alert("도서 수정 성공!");
listBook(); // 도서 목록 갱신
clearForm(); // 입력 폼 초기화
} else {
alert("도서 수정 실패!");
}
}
async function deleteBook(){
let bookId = document.querySelector("#bookId").value;
let url = '/BookManagerAjax1/books/delete?bookId=' + bookId;
let response = await fetch(url); // 비동기 요청
let data = await response.json();
console.log(data);
if (data.result == "success") {
alert("도서 삭제 성공!");
listBook(); // 도서 목록 갱신
clearForm(); // 입력 폼 초기화
} else {
alert("도서 삭제 실패!");
}
}
//폼 초기화 함수
function clearForm() {
document.getElementById("bookId").value = "";
document.getElementById("bookName").value = "";
document.getElementById("publisher").value = "";
document.getElementById("price").value = "";
}
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>홈</title>
</head>
<body>
<h1>안녕하세요, 메뉴를 클릭해 주세요!</h1>
<a href="/BookManagerAjax1/books.jsp">도서 관리</a>
<a href="#">회원 관리</a>
<a href="#">거래처 관리</a>
</body>
</html>



❹ 디버깅 포인트 & 트러블슈팅
- JSON 파싱 오류(Unexpected token '<'): JSON 대신 HTML이 응답될 때 발생.
response.json()호출 전 Status 200/Content-Type: application/json 확인. - JSP EL 충돌: JS 템플릿의
${...}가 JSP EL로 해석됨. 해결:\${...}로 이스케이프 또는 행 클릭 이벤트를 JS에서 바인딩해 값 전달 회피. - onclick 인자 부족 → undefined:
rowClick(id)만 넘기면서 함수 시그니처가 4개 인자를 요구해서undefined출력됨. 해결: JS에서addEventListener로 셀 텍스트를 직접 읽어 채움. - 소스 폴더 문제: 실수로
src/main/java가 빠져 빌드/배포 누락. 해결:Project Properties > Java Build Path > Add Folder로src또는src/main/java를 Source로 복원.
❺ 현재 상태 체크리스트
| 기능 | 상태 | 비고 |
|---|---|---|
| 도서 목록 조회 (GET /books/list) | 완료 | Gson으로 JSON 응답 |
| 테이블 렌더링 | 완료 | innerHTML로 동적 구성 |
| 행 클릭 → 입력창 채움 | 완료 | JS 이벤트 바인딩 |
| 등록/수정/삭제 버튼 | 준비 | fetch(POST)로 연결 예정 |
❻ 개념 정리 (한 줄 실무 관점)
- Gson: Java 객체 ↔ JSON 직렬화.
response.setContentType("application/json; charset=UTF-8")필수. - fetch: 비동기 통신.
await response.json()전에 응답 상태/헤더 점검. - 컨텍스트 경로:
/${context}/...실제 배포명과 일치시켜야 404 회피. - JSP EL vs JS 템플릿: 충돌 시
\${...}또는 DOM 이벤트 바인딩. - MVC 분리: Servlet(Controller) ↔ DAO(Model) ↔ JSP(View) 역할 분명히.
❼ 교훈 / 핵심 요약
- URL과 컨텍스트 경로 정합성이 Ajax 404를 대부분 좌우한다.
- JSP에서는 EL과 JS 템플릿의 충돌을 항상 의식할 것.
- 이벤트는 인라인 onclick보다 JS addEventListener가 안전하고 유지보수성이 높다.
'Java > JSP' 카테고리의 다른 글
| [LG U+ 유레카 3기] JPA 연관관계 & Fetch 전략 (0) | 2025.11.19 |
|---|---|
| [LG U+ 유레카 3기] JSP + Servlet + DAO 기반 도서관리 CRUD 실습 (0) | 2025.10.30 |
| [LG U+ 유레카 3기] JSP Forward/Redirect MVC 흐름 (0) | 2025.10.30 |
| [LG U+ 유레카 3기]Web Server vs WAS 관련 정리 (0) | 2025.10.29 |
| [LG U+ 유레카 3기]Servlet, JSP, MVC 패턴 (+Postman)정리 (0) | 2025.10.29 |