[Java] ToDo List 만들기

ByeolGyu·2024년 5월 23일

✔ 용어 정리

Servlet

자바를 기반으로 한 웹어플리케이션의 동적 처리를 담당하는 서버측 프로그램

  • 사용자의 요청에 동적으로 응답을 생성하고 처리할 수 있음

JSP

HTML 코드에 JAVA 코드를 넣어 동적 웹페이지 생성

DTO

DTO (data-transfer-object)는 DAO로 얻은 테이블의 데이터를 저장하는 클래스

DAO

DAO(Data Access Object)는 데이터베이스 엑세스를 전문으로 하는 클래스

바인딩

  • 데이터를 서블릿 관련 객체에 저장하는 방법
    저장된 데이터는 서블릿이나 JSP에서 공유하여 사용

바인딩 관련 메서드

  • setAttribute : 데이터를 각 객체에 바인딩
  • getAttribute : 객체에서 데이터를 가져옴
  • removeAttribute : 데이터를 삭제

✔ ToDo List 만들기

web.xml

매핑 : 실행할 서블렛 클래스와 브라우저의 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>

Index.jsp

인덱스 페이지

<%@ 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>

list.jsp

  • 확인 -> 취소선
  • 수정 -> modify.jsp로 이동
  • 삭제 -> 삭제
  • 저장 -> 새로 입력한 할 일 추가

사용자가 버튼을 눌러 실행되는 함수는 해당 할 일의 시퀀스를 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>

modify.jsp

  • 할 일 목록을 수정함
  • 확인 상태를 통해 true면 한 일로 판단함
<%@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>

todoDTO

테이블의 데이터 저장

  • 생성자
  • 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 + "]";
	}
}

TodoDAO

- findIdx 메서드
         : todoList에서 특정 TodoDTO의 인덱스를 찾아 반환
- selectAll 메서드
         :
모든 할 일 목록 반환 (기존 todoList데이터를 복사해 새로운 list에 추가하여 새로운 리스트 반환)
- selectOne 메서드
         :
find메서드 사용해 todoDTO객체의 seq값과 동일한 값을 찾아 인덱스 반환
- insert 메서드
         :
새로운 할 일을 추가
- update 메서드
         :
기존 할 일의 내용을 수정
- delete 메서드
         :
find메서드 사용해 todoDTO객체의 seq값과 동일한 값을 찾아 기존 할 일 삭제

TodoDAO (데이터베이스 연동 전)

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);
		}
	}
}

TodoDAO (데이터베이스 연동 후)

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);
		}
	}
}

TodoController

  • 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");
	}
}

JdbcUtil

데이터베이스 접속, 해제

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);
	}
}
profile
ByeolGyu

0개의 댓글