자바를 기반으로 한 웹어플리케이션의 동적 처리를 담당하는 서버측 프로그램
- 사용자의 요청에 동적으로 응답을 생성하고 처리할 수 있음
HTML 코드에 JAVA 코드를 넣어 동적 웹페이지 생성
DTO (data-transfer-object)는 DAO로 얻은 테이블의 데이터를 저장하는 클래스
DAO(Data Access Object)는 데이터베이스 엑세스를 전문으로 하는 클래스
- 데이터를 서블릿 관련 객체에 저장하는 방법
저장된 데이터는 서블릿이나 JSP에서 공유하여 사용
바인딩 관련 메서드
매핑 : 실행할 서블렛 클래스와 브라우저의 url을 연결
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
<display-name>JavaDay03TodoList</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.jsp</welcome-file>
<welcome-file>default.htm</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>todo</servlet-name>
<servlet-class>org.comstudy.todo.TodoController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>todo</servlet-name>
<url-pattern>/todo/*</url-pattern>
</servlet-mapping></web-app>
인덱스 페이지
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Index</title>
</head>
<body>
<h1> 할 일을 하자 </h1>
<ul>
<li><a href="todo/list.do">Todo List</a></li>
</ul>
</body>
</html>
사용자가 버튼을 눌러 실행되는 함수는 해당 할 일의 시퀀스를 URL 매개변수로 추가하여 지정된 액션으로 리디렉션

<%@page import="org.comstudy.todo.model.TodoDTO"%>
<%@page import="java.util.ArrayList"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>todo</title>
<script>
function okBtn(element) {
location.href="ok.do?seq=" + element.dataset.seq;
}
function editBtn(element) {
location.href="edit.do?seq=" + element.dataset.seq;
}
function delBtn(element) {
location.href="del.do?seq=" + element.dataset.seq;
}
</script>
</head>
<body>
<h1>할 일 목록</h1>
<table width="600" border="1" >
<tr>
<th>순서</th><th width="400">할일</th><th>확인</th><th>수정</th><th>삭제</th>
</tr>
<%
ArrayList<TodoDTO> todoList = (ArrayList<TodoDTO>)request.getAttribute("todoList");
for(int i=0; i<todoList.size(); i++) {
TodoDTO todo = todoList.get(i);
%>
<tr>
<td><%=todo.getSeq()%></td>
<td><span style="text-decoration:<%= todo.isDone()? "line-through" : "done"%>"><%=todo.getTitle() %></span></td>
<td><button data-seq="<%=todo.getSeq() %>" onclick="okBtn(this)"><%=!todo.isDone() ? "확인" : "취소" %></button></td>
<td><button data-seq="<%=todo.getSeq() %>" onclick="editBtn(this)">수정</button></td>
<td><button data-seq="<%=todo.getSeq() %>" onclick="delBtn(this)">삭제</button></td>
</tr>
<%
}
%>
</table>
<form method="post" action="save.do">
<br/>
할 일 추가 : <input type="text" name="title" value="새 할 일 추가"/>
<input type="submit" value="저장"/>
</form>
</body>
</html>

<%@page import="org.comstudy.todo.model.TodoDTO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>modify</title>
</head>
<body>
<h1>할 일 내용 수정</h1>
<%
TodoDTO todo = (TodoDTO)request.getAttribute("todo");
%>
<form action="edit.do" method = "post">
<input type="hidden" name="seq" value="<%=todo.getSeq()%>"><br/>
제목 : <input type="text" name="title" value="<%=todo.getTitle() %>"/><br/>
확인 상태 :
<select name="done">
<option <%=todo.isDone()?"selected":"" %>>true</option>
<option <%=!todo.isDone()?"selected":"" %>>false</option>
</select>
<input type="submit" value="저장"/>
</form>
</body>
</html>
테이블의 데이터 저장
- 생성자
- setter & getter
- toString
package org.comstudy.todo.model;
public class TodoDTO {
private int seq;
private String title;
private boolean done;
public TodoDTO() {
}
public TodoDTO(int seq, String title, boolean done) {
this.seq = seq;
this.title = title;
this.done = done;
}
public int getSeq() {
return seq;
}
public void setSeq(int seq) {
this.seq = seq;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public boolean isDone() {
return done;
}
public void setDone(boolean done) {
this.done = done;
}
@Override
public String toString() {
return "TodoDTO [seq=" + seq + ", title=" + title + ", done=" + done + "]";
}
}
- findIdx 메서드
: todoList에서 특정 TodoDTO의 인덱스를 찾아 반환
- selectAll 메서드
: 모든 할 일 목록 반환 (기존 todoList데이터를 복사해 새로운 list에 추가하여 새로운 리스트 반환)
- selectOne 메서드
: find메서드 사용해 todoDTO객체의 seq값과 동일한 값을 찾아 인덱스 반환
- insert 메서드
: 새로운 할 일을 추가
- update 메서드
: 기존 할 일의 내용을 수정
- delete 메서드
: find메서드 사용해 todoDTO객체의 seq값과 동일한 값을 찾아 기존 할 일 삭제
package org.comstudy.todo.model;
import java.util.ArrayList;
import java.util.List;
public class TodoDAO {
public static final List<TodoDTO> todoList = new ArrayList<TodoDTO>();
static {
todoList.add(new TodoDTO(1, "첫 번째 할 일", false));
todoList.add(new TodoDTO(2, "두 번째 할 일", false));
todoList.add(new TodoDTO(1, "세 번째 할 일", false));
}
public static int seq = 4;
private int findIdx(List<TodoDTO> todoList, TodoDTO dto) { // todoList에서 find index 찾기
int idx=-1;
for (int i=0; i<todoList.size(); i++) {
if(dto.getSeq() == todoList.get(i).getSeq()) {
idx = i;
break;
}
}
return idx;
}
public List<TodoDTO> selectAll() { // 전체 데이터
// 기존 데이터를 복사해서 새 데이터 만듦
List<TodoDTO> newTodoList = new ArrayList<TodoDTO>(todoList.size());
for (int i=0; i<todoList.size(); i++) {
int seq = todoList.get(i).getSeq();
String title = todoList.get(i).getTitle();
boolean done = todoList.get(i).isDone();
newTodoList.add(new TodoDTO(seq, title, done)); // 새로 객체를 만듦 (원본에 있는 데이터의 참조를 끊어주기 위해)
}
return newTodoList;
}
public TodoDTO selectOne(TodoDTO dto) {
TodoDTO todo = null;
int i = findIdx(todoList, dto); //찾으면 i, 못찾으면 -1반환
if(i != -1) { // 찾았을 때
String title = todoList.get(i).getTitle();
boolean done = todoList.get(i).isDone();
todo = new TodoDTO(dto.getSeq(), title, done);
}
return todo;
}
public void insert(TodoDTO dto) {
dto.setSeq(seq++);
todoList.add(dto);
}
public void update(TodoDTO dto) {
int i = findIdx(todoList, dto); // 해당 인덱스를 찾아서
if (i != -1) { // 찾으면 수정
if(dto.getTitle() != null) {
todoList.get(i).setTitle(dto.getTitle());
}
todoList.get(i).setDone(dto.isDone());
}
}
public void delete(TodoDTO dto) {
int i = findIdx(todoList, dto);
if (i != -1) {
todoList.remove(i);
}
}
}
DAO를 인터페이스로 분리해 내고
TodoDAO에서 DAO 인터페이스 사용
package org.comstudy.todo.model;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import org.comstudy.todo.dbcp.JdbcUtil;
public class TodoDAO implements DAO {
private Connection conn;
private Statement stmt;
private ResultSet rs;
private PreparedStatement pstmt;
@Override
public List<TodoDTO> selectAll() {
ArrayList<TodoDTO> list = null;
try {
conn = JdbcUtil.getConnection();
stmt = conn.createStatement();
rs = stmt.executeQuery("SELECT * FROM TODOLIST");
list = new ArrayList<TodoDTO>();
while(rs.next()) {
int seq = rs.getInt(1);
String title = rs.getString(2);
boolean done = rs.getBoolean(3);
list.add(new TodoDTO(seq, title, done));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(conn, stmt, rs);
}
return list;
}
@Override
public TodoDTO selectOne(TodoDTO dto) {
int seq = dto.getSeq();
TodoDTO todoDto = null;
try {
conn = JdbcUtil.getConnection();
pstmt = conn.prepareStatement("SELECT * FROM TODOLIST WHERE SEQ=?");
pstmt.setInt(1, seq);
rs = pstmt.executeQuery();
while(rs.next()) {
String title = rs.getString(2);
boolean done = rs.getBoolean(3);
todoDto = new TodoDTO(seq, title, done);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(conn, pstmt, rs);
}
return todoDto;
}
@Override
public void insert(TodoDTO dto) {
String sql = "INSERT INTO TODOLIST (TITLE, DONE) VALUES (?, ?)";
conn = JdbcUtil.getConnection();
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, dto.getTitle());
pstmt.setBoolean(2, dto.isDone());
int cnt = pstmt.executeUpdate();
if(cnt > 0) {
System.out.println("입력성공");
} else {
System.out.println("입력 실패");
}
} catch (SQLException e) {
System.out.println("데이터 입력 시 예외 발생");
} finally {
JdbcUtil.close(conn, pstmt, rs);
}
}
@Override
public void update(TodoDTO dto) {
conn = JdbcUtil.getConnection();
try {
pstmt = conn.prepareStatement("UPDATE TODOLIST SET TITLE=?, DONE=? WHERE SEQ=?");
pstmt.setString(1, dto.getTitle());
pstmt.setBoolean(2, dto.isDone());
pstmt.setInt(3, dto.getSeq());
int cnt = pstmt.executeUpdate();
if(cnt > 0) {
System.out.println("수정 완료!");
} else {
System.out.println("수정 실패!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(conn, pstmt, rs);
}
}
@Override
public void delete(TodoDTO dto) {
conn=JdbcUtil.getConnection();
try {
pstmt = conn.prepareStatement("DELETE FROM TODOLIST WHERE SEQ=?");
pstmt.setInt(1, dto.getSeq());
int cnt = pstmt.executeUpdate();
if(cnt>0) {
System.out.println("삭제 완료!");
} else {
System.out.println("삭제 실패!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtil.close(conn,pstmt, rs);
}
}
}
- doGet 메서드는 GET 요청을 처리
- /todo/list.do
- /todo/ok.do
- /todo/edit.do
- /todo/del.do
- doPost 메서드는 POST 요청을 처리
- /todo/save.do
- /todo/edit.do
확인 버튼 눌렀을 때 취소선 생기지 않음
왜? 새로운 TodoDTO객체를 todo에 담고, todo를 그대로 사용하지 않고 다시 새로 객체를 만들어서 담아 update했기 때문
package org.comstudy.todo;
import java.io.IOException;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.comstudy.todo.model.TodoDAO;
import org.comstudy.todo.model.TodoDTO;
public class TodoController extends HttpServlet {
private TodoDAO dao = new TodoDAO();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html; charset=UTF-8");
String reqUri = req.getRequestURI();
String ctxPath = req.getContextPath();
int beginIndex = ctxPath.length();
String urlPattern = reqUri.substring(beginIndex);
System.out.println("urlPattern: " + urlPattern);
boolean isRedirect = false;
String viewName = "/WEB-INF/views/todo/list.jsp";
if("/todo/list.do".equals(urlPattern)) { // list 화면
List<TodoDTO> todoList = dao.selectAll(); // 목록 바인딩
req.setAttribute("todoList", todoList); // "todolist"변수에 todolist 값을 넣어요.
} else {
if(req.getParameter("seq") != null) {
int seq = Integer.parseInt(req.getParameter("seq") );
System.out.println("seq : " + seq);
}
if("/todo/ok.do".equals(urlPattern)) {
if(req.getParameter("seq") != null) {
int seq = Integer.parseInt(req.getParameter("seq"));
//System.out.println("seq : " + seq);
TodoDTO todo = dao.selectOne(new TodoDTO(seq, null, true));
todo.setDone(!todo.isDone());
dao.update(todo);
}
isRedirect = true; // 처리 후 목록으로 리다이렉트
} else if("/todo/edit.do".equals(urlPattern)) {
int seq = Integer.parseInt(req.getParameter("seq") );
System.out.println("seq => " + seq);
TodoDTO todo = dao.selectOne(new TodoDTO(seq, null, false));
req.setAttribute("todo", todo);
viewName = "/WEB-INF/views/todo/modify.jsp";
} else if("/todo/del.do".equals(urlPattern)) {
if(req.getParameter("seq") != null) {
int seq = Integer.parseInt(req.getParameter("seq") );
System.out.println("seq => " + seq);
dao.delete(new TodoDTO(seq, null, false));
}
isRedirect = true;
}
}
if(isRedirect) {
resp.sendRedirect("list.do");
} else {
RequestDispatcher view = req.getRequestDispatcher(viewName);
view.forward(req, resp);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html; charset=UTF-8");
String reqUri = req.getRequestURI();
String ctxPath = req.getContextPath();
int beginIndex = ctxPath.length();
String urlPattern = reqUri.substring(beginIndex);
System.out.println("urlPattern: " + urlPattern);
if("/todo/save.do".equals(urlPattern)) {// 새 할일 저장
String title = req.getParameter("title");
dao.insert(new TodoDTO(0, title, false));
} else if("/todo/edit.do".equals(urlPattern)) { // 할 일의 제목, 확인 -> 수정
int seq = Integer.parseInt(req.getParameter("seq") );
String title = req.getParameter("title");
boolean done = false;
if("true".equals(req.getParameter("done"))) { // 문자열이 true면
done = true; // true로 바꿔주기
}
dao.update(new TodoDTO(seq, title, done));
}
resp.sendRedirect("list.do");
}
}
데이터베이스 접속, 해제
package org.comstudy.todo.dbcp;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcUtil {
// 데이터베이스 연결을 위한 메서드
public static Connection getConnection() { //getConnection() 메서드는 JDBC URL, 사용자 이름 및 암호를 사용하여 데이터베이스에 연결
// JDBC를 하기위해 드라이버 준비
// 1) main()에서 테스트는 프로젝트 > Build Path > 컨피그 빌드패스 등록
// 드라이브 등록 방법은
// main() 애플리케이션에서와 Web 애플리케이션에서 다름.
// 2) Web 앱에서는 webapp > WEB-INF > lib 에 드라이버 라이브러리 복사
// 두가지 방법 모두 적용해서 준비
// h2 접속 콘솔을 보고 접속 정보 준비
String url = "jdbc:h2:tcp://localhost/~/test";
String user = "sa";
String password = "";
Connection conn = null;
// 예외 처리 필수
try {
// 클래스 확장자는 지우고
Class.forName("org.h2.Driver");
// 접속 할 데이터베이스 엔진이 실행 된 상태여야 함.
// H2 데이터베이스는 매번 수동 실행 해야 함.
conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
} catch (ClassNotFoundException e) {
System.err.println("드라이버 검색 실패!"); // 드라이버 클래스 로드 실패
} catch (SQLException e) {
System.err.println("접속 실패"); //// 데이터베이스 접속 실패
}
return conn;
}
// Connection 객체를 닫는 메서드
public static void close(Connection obj) { // 커넥션을 받아서
if(obj != null) // obj가 null이 아니면
try {
obj.close(); // // Connection 닫음
} catch (SQLException e) {
e.printStackTrace();
}
}
// ResultSet 객체를 닫는 메서드
public static void close(ResultSet obj) {
if(obj != null)
try {
obj.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// Statement 객체를 닫는 메서드
public static void close(Statement obj) {
if(obj != null)
try {
obj.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 오버로딩 : 매개변수의 데이터타입과 개수(시그니처)가 다르면 같은 이름으로 함수명 사용 가능
// 오버로딩된 close 메서드: Connection, Statement, ResultSet을 모두 닫음
public static void close(Connection conn, Statement stmt, ResultSet rs) {
close(conn);
close(stmt);
close(rs);
}
}