국비교육(22-23)

29일차(1)/jsp(8) : DBCP활용 예제 (todo 테이블)

서리/Seori 2022. 11. 17. 16:32

29일차(1)/jsp(8) : DBCP활용 예제 (todo 테이블)

 


- jsp 페이지에서 DB를 활용해 정보를 불러오는 기능은

  상품정보, 회원보기, 게시글 목록보기 등등 다양한 방면으로 사용될 수 있다.

 

<index.jsp> 에 todo 추가하기

<%@page import="test.util.DbcpBean"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/index.jsp</title>
</head>
<body>
	<div class="container">
		<h1>인덱스 페이지입니다.</h1>
		<ul>
			<li><a href="${pageContext.request.contextPath }/member/list.jsp">회원 목록 보기</a></li>
			<li><a href="${pageContext.request.contextPath }/todo/list.jsp">할일 목록 보기</a></li>
		</ul>
	</div>
</body>
</html>

 

[ todo 할일에 관련된 기능 구현하기 ] 

 

test.todo.dto.TodoDto
test.todo.dto.TodoDao


/todo/list.jsp
/todo/insertform.jsp
/todo/insert.jsp
/todo/delete.jsp
/todo/updateform.jsp
/todo/update.jsp

 

- 날짜 수정기능은 없이

 

- webapp에 todo 폴더 생성

- java resources-src 안에 test.todo.dto / test.todo.dao 패키지 생성

 

 


 

<list.jsp>

<%@page import="test.todo.dao.TodoDao"%>
<%@page import="java.util.List"%>
<%@page import="test.todo.dto.TodoDto"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	//TodoDao 객체를 이용해서 할일 목록을 얻어온다.
	TodoDao dao=TodoDao.getInstance();
	List<TodoDto> list=dao.getList();
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/member/list.jsp</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
</head>
<body>	
	<div class="container">
		<h1>할일 목록입니다.</h1>
		<a href="${pageContext.request.contextPath }/todo/insertform.jsp">할일 추가 하러가기</a>
		<table class="table table-striped table-hover">
			<thead>
				<tr class="table-warning">
					<th>번호</th>
					<th>내용</th>
					<th>등록일</th>
					<th>수정</th>
					<th>삭제</th>					
				</tr>			
			</thead>
			<tbody class="table-group-divider">
				<%for(TodoDto tmp:list){ %>
					<tr>
						<td><%=tmp.getNum() %></td>
						<td><%=tmp.getContent() %></td>
						<td><%=tmp.getRegdate() %></td>
						<td><a href="${pageContext.request.contextPath }/todo/updateform.jsp?num=<%=tmp.getNum()%>">수정</a></td>
						<td><a href="${pageContext.request.contextPath }/todo/delete.jsp?num=<%=tmp.getNum()%>">삭제</a></td>						
					</tr>				
				<% } %>
			</tbody>			
		</table>	
	</div>
</body>
</html>

 

- 클라이언트가 링크를 눌렀다는 것은 목록을 보고 싶어 한다는 것 : 테이블 DB 연결하기
- 이 jsp 페이지는 나중에 servlet으로 바뀔 예정

 

- 그대로 servlet으로 바뀐다. 모든 문자열이 변환되어서 클라이언트에게 그대로 출력된다.

 

- <%%> 이라는 특별한 영역에 작성한 것이 아닌 이상 전부 출력된다.

 

- 이 페이지는 servlet으로 바뀔 예정이므로 필요시 java 코딩도 할 수 있다.

 

- <tbody> 안에 들어갈 내용은 Oracle DB안에 있다. <%%> 영역안에서 코딩하면 된다.

 


 

<TodoDto>

package test.todo.dto;

public class TodoDto {
	//필드
	private int num;
	private String content;
	private String regdate;
	
	//생성자
	public TodoDto() {}

	public TodoDto(int num, String content, String regdate) {
		super();
		this.num = num;
		this.content = content;
		this.regdate = regdate;
	}

	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		this.num = num;
	}

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}

	public String getRegdate() {
		return regdate;
	}

	public void setRegdate(String regdate) {
		this.regdate = regdate;
	}		
}


- DTO를 만들때에는 DB의 칼럼명을 그대로 가져다 쓰는 것이 편하다.

 

- num : int / content : String / regdate : String

- regdate는 Date 타입이지만 java 코딩이 힘들어지므로 String으로!

- Dto를 규칙에 맞게 만들면 자동으로 할 수 있는 것이 아주 많다.(이후 Spring Framework에서도)

- 기본생성자 반드시 만들기!
- 우클릭으로 필드가 있는 생성자, setter/getter 생성


Q) 테이블 하나당 DTO 하나가 있어야 하는지? 
A) 아니다. 카테고리, 주제당 하나면 된다.  
emp, dept 테이블이 있다고 할 때, 사원의 모든 정보를 담으려면 List<EmpDto> List<DeptDto> 2개의 리스트가 필요해지는데 그러면 사용이 불편하다.
사원정보+부서정보를 같이 담는, 모든 정보를 담을 Dto를 하나만 만들어야 한다.

 


 

<TodoDao>

package test.todo.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import test.todo.dto.TodoDto;
import test.util.DbcpBean;

public class TodoDao {
	//오직 한개만 생성된 TodoDao type 객체의 참조값을 담을 static 필드
	private static TodoDao dao;
	
	//외부에서 객체 생성하지 못하도록 접근지정자를 private로 한다.
	private TodoDao() {}
	
	//TodoDao 객체의 참조값을 리턴하는 static 메소드
	public static TodoDao getInstance() {
		//만일 필드에 저장된 값이 null이면 (아직 TodoDao 객체가 생성된 적이 없으면)
		if(dao==null) {
			//객체를 생성해서 참조값을 static 필드에 담아둔다.
			dao=new TodoDao();
		}
		//static 필드에 저장되어 있는 TodoDao 객체의 참조값 리턴
		return dao;
	}
		
	//전체 할일 목록을 리턴(getList)
	public List<TodoDto> getList() {
		List<TodoDto> list=new ArrayList<>();			
		Connection conn=null;
		PreparedStatement pstmt=null;
		ResultSet rs=null;
		
		try {
			conn=new DbcpBean().getConn();
			String sql="SELECT num, content, TO_CHAR(regdate, 'YYYY.MM.DD AM HH:MI') AS regdate"
					+" FROM todo"
					+" ORDER BY num ASC";			
			pstmt=conn.prepareStatement(sql);
			rs=pstmt.executeQuery();								
			while(rs.next()) {
				TodoDto dto=new TodoDto();
				dto.setNum(rs.getInt("num"));
				dto.setContent(rs.getString("content"));
				dto.setRegdate(rs.getString("regdate"));				
				list.add(dto);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			try {
				if(rs!=null) {rs.close();}
				if(pstmt!=null) {pstmt.close();}
				if(conn!=null) {conn.close();}
			} catch (Exception e2) {}
		}		
		return list;
	}		
			
	//할일 추가(insert)
	public boolean insert(TodoDto dto) {
		Connection conn = null;
		PreparedStatement pstmt = null;
		int rowCount = 0;
		try {
			conn = new DbcpBean().getConn();
			String sql = "INSERT INTO todo"
					+ " (num, content, regdate)"
					+ " VALUES(todo_seq.NEXTVAL, ?, SYSDATE)";
			pstmt = conn.prepareStatement(sql);
			//?에 바인딩			
			pstmt.setString(1, dto.getContent());			
			//INSERT or UPDATE or DELETE문을 수행하고 수정,삭제,추가된 row의 개수 리턴
			rowCount = pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (pstmt != null)
					pstmt.close();
				if (conn != null)
					conn.close();
			} catch (Exception e2) {
			}
		}

		if (rowCount > 0) {
			return true;
		} else {
			return false;
		}
	}
	
	
	//할일 삭제(delete)
	public boolean delete(int num) {
		Connection conn = null;
		PreparedStatement pstmt = null;
		int rowCount = 0;
		try {
			conn = new DbcpBean().getConn();
			String sql = "DELETE FROM todo"
					+ " WHERE num=?";
			pstmt = conn.prepareStatement(sql);
			//?에 바인딩
			pstmt.setInt(1, num);
			//INSERT or UPDATE or DELETE문을 수행하고 수정,삭제,추가된 row의 개수 리턴
			rowCount = pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (pstmt != null)
					pstmt.close();
				if (conn != null)
					conn.close();
			} catch (Exception e2) {
			}
		}

		if (rowCount > 0) {
			return true;
		} else {
			return false;
		}
	}

	//할일 한개 보기(getData)
	public TodoDto getData(int num) {		
		TodoDto dto=null;
		//필요한 객체를 담을 지역변수를 미리 만들어둔다.
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			//Connection Pool에서 Connection 객체를 하나 얻어온다.
			conn = new DbcpBean().getConn();
			//실행할 sql문의 뼈대 구성하기
			String sql = "SELECT num, content, TO_CHAR(regdate, 'YYYY.MM.DD HH24:MI') AS regdate"
					+ " FROM todo"
					+ " WHERE num=?";						
			pstmt = conn.prepareStatement(sql);
			//sql문의 ?에 바인딩할게 있으면 한다.
			pstmt.setInt(1, num);
			//SELECT문을 수행하고 결과값을 받아온다.
			rs = pstmt.executeQuery();
			//반복문 돌면서 resultSet에서 필요한 값을 얻어낸다.
			if (rs.next()) {
				dto=new TodoDto();
				dto.setNum(rs.getInt("num"));
				dto.setContent(rs.getString("content"));
				dto.setRegdate(rs.getString("regdate"));
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (rs != null)
					rs.close();
				if (pstmt != null)
					pstmt.close();
				if (conn != null)
					conn.close(); //Connection Pool에 Connection 반납하기

			} catch (Exception e2) {
			}
		}
		return dto;
	}	
	
	//할일 수정하기(update)
	public boolean update(TodoDto dto) {
		Connection conn = null;
		PreparedStatement pstmt = null;
		int rowCount = 0;
		try {
			conn = new DbcpBean().getConn();
			String sql = "UPDATE todo"
					+ " SET content=?"
					+ " WHERE num=?";
			pstmt = conn.prepareStatement(sql);
			//?에 바인딩
			pstmt.setString(1, dto.getContent());			
			pstmt.setInt(2, dto.getNum());			
			//INSERT or UPDATE or DELETE문을 수행하고 수정,삭제,추가된 row의 개수 리턴
			rowCount = pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (pstmt != null)
					pstmt.close();
				if (conn != null)
					conn.close();
			} catch (Exception e2) {
			}
		}

		if (rowCount > 0) {
			return true;
		} else {
			return false;
		}
	}	
}

 

private TodoDao() {} : 외부에서 곧바로 참조할 수 없다.

- TodoDao라는 static field를 만들어준다.

 

- TodoDao 객체를 heap영역에 오직 한 개만 만들어두고, (new를 한번만 한 것)

 그 객체의 참조값을 dao라는 static field에 넣어두고 계속해서 사용하는 개념!

 

- new TodoDao(); 는 이 클래스 안에서만 할 수 있다.(private이므로)

 

 

- static 자원은 클래스명에 . 을 찍어서 접근한다.
- if와 return에 있는 dao앞에 사실은 TodoDao.dao인데 생략된 것이다.

 같은 클래스안에 있는데 클래스명을 써서 호출할 필요가 없기 때문에!

 

- TodoDao.dao (private로 지정되어 외부 접근 불가능)
- TodoDao.getInstance (public이므로 외부 접근 가능)

 

 

 

* TodoDao에서 update 문 작성시 주의사항!

- TO_CHAR(regdate, 'YYYY.MM>DD HH24:MI') 라고 작성하면

 'YYYY.MM>DD HH24:MI' 가 칼럼명이 되기 때문에

아래에서 setRegdate 할 때도 이렇게 해야한다. 불편하다!

 

- AS로 칼럼명을 바꾸어주면 훨씬 편리하게 작성할 수 있다.

 

 

- List에 추가하려면 원래는 이렇게 작성해야 하지만,

 

- 출력하고 싶은 위치에 출력할 내용을 <%= %> 으로 표기하면 된다.

  이렇게 출력해주면 자동으로 out.print로 바꾸어준다.

 


 

<insertform.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/todo/insertform.jsp</title>
</head>
<body>
	<div class="container">
	<h1>할일 추가 폼입니다.</h1>
		<form action="${pageContext.request.contextPath }/todo/insert.jsp" method="post">
			<div>
				<label for="content">내용</label>
				<input type="text" name="content" id="content" />
			</div>
			<button type="submit">추가</button>
		</form>
	</div>
</body>
</html>

 

- 날짜는 자동으로 입력되도록 했으므로 할일 내용 content 값만 입력해주면 된다.

- input의 id와 label for을 일치시켜주기!(웹 접근성과 관련)

 


 

<insert.jsp>

<%@page import="test.todo.dto.TodoDto"%>
<%@page import="test.todo.dao.TodoDao"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	//폼 전송되는 할일을 읽어와서
	request.setCharacterEncoding("utf-8");
	String content=request.getParameter("content");	
	//TodoDto에 담고
	TodoDto dto=new TodoDto();
	dto.setContent(content);
	//DB에 저장하고
	boolean isSuccess=TodoDao.getInstance().insert(dto);
	//응답하기
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/todo/insert.jsp</title>
</head>
<body>
	<div class="container">
		<% if(isSuccess) {%>
			<p>
				할일 추가에 성공했습니다.
				<a href="list.jsp">리스트로 돌아가기</a>
			</p>
		<%}else { %>
			<p>
				할일 추가에 실패했습니다.
				<a href="insertform.jsp">다시 시도</a>
			</p>
		<%} %>
	</div>
</body>
</html>

 

- 입력된 content를 받아와서 DB에 저장하기

- div.container로 전체를 감싸준다. 나중에 bootstrap을 넣기 위한 준비작업이다.

- 이동하는 경로는 상대경로로 적어봄.

 


 

<delete.jsp>

<%@page import="test.todo.dao.TodoDao"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	//삭제할 번호
	int num=Integer.parseInt(request.getParameter("num"));	
	//삭제하기
	TodoDao.getInstance().delete(num);
	//리다일렉트 응답하기(특정경로로 요청을 다시하라고 강요하는 응답)
	String cPath=request.getContextPath();
	response.sendRedirect(cPath+"/todo/list.jsp");
%>

 

<a href="${pageContext.request.contextPath }/todo/updateform.jsp?num=<%=tmp.getNum()%>">

- 리스트 삭제칼럼에 삭제 페이지로 이동하는 링크 추가. 삭제할 번호를 직접 들고가기!

 

TodoDao dao=TodoDao.getInstance();
boolean isSuccess=dao.delete(num);

- 불리언 값을 받아서 아래 html에서 if문으로 작성하는 대신 그냥 상단의 <% %> 안에서 전부 처리하는 것으로 한다.

- 데이터의 리턴값을 꼭 받아야 하는 것은 아니다. 필요없으면 받지 않아도 된다.

 

TodoDao.getInstance().delete(num);

- 위 형태로 작성하고 삭제를 누르면 바로 새로고침이 되도록!

 


 

- list에 수정하는 링크 추가!

<a href="${pageContext.request.contextPath }/todo/updateform.jsp?num=<%=tmp.getNum()%>">

- 동일하게 수정할 내용의 primary key를 주고창에 달고 가도록 작성하기.

 

<updateform.jsp>

<%@page import="test.todo.dao.TodoDao"%>
<%@page import="test.todo.dto.TodoDto"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%
	//수정할 회원의 번호를 읽어와서
	int num=Integer.parseInt(request.getParameter("num"));
	//해당 할일의 번호를 DB에서 불러온 다음
	TodoDao dao=TodoDao.getInstance();
	TodoDto dto=dao.getData(num);
	//수정할 수 있는 폼을 응답한다.
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/todo/updateform.jsp</title>
</head>
<body>
	<div class="container">
	<h1>할일 수정 폼입니다.</h1>	
		<form action="${pageContext.request.contextPath }/todo/update.jsp" method="post" >
			<div>
				<label for="num">번호</label>
				<input type="text" name="num" id="num" value="<%=dto.getNum() %>" readonly />
			</div>
			<div>
				<label for="content">내용</label>
				<input type="text" name="content" id="content" value="<%=dto.getContent() %>" />		
			</div>
			<div>
				<label for="regdate">등록일</label>
				<input type="text" name="regdate" id="regdate" value="<%=dto.getRegdate() %>" readonly />		
			</div>
			<button type="submit">수정</button>			
			<button type="reset">취소</button>		
		</form>
		
	</div>
</body>
</html>

 

- div 안에 label,input 요소를 넣어두는 것도 나중에 bootstrap 적용을 원활하게 하기 위해서!

 

<input type="text" id="num" value="<%=dto.getNum() %>" />

- 현재 저장되어 있는 기본값을 input 요소 안에 넣어서 현재 어떤 값을 수정하고 있는지 보여준다.

 

- 번호, 등록일 레이블을 수정되지 못하도록 하려면 disabled를 쓸 수도 있지만,

 readonly 를 쓰면 전송은 되는데 수정은 안 된다.(커서가 들어가지 않는다)

- 레이블의 값이 함께 전달되어야 할 때 disabled 보다 좀더 편리하게 쓸 수 있다.

 


 

<update.jsp>

<%@page import="test.todo.dao.TodoDao"%>
<%@page import="test.todo.dto.TodoDto"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%     
	request.setCharacterEncoding("utf-8");
	int num=Integer.parseInt(request.getParameter("num"));
	String content=request.getParameter("content");
	String regdate=request.getParameter("regdate");

	TodoDto dto=new TodoDto();
	dto.setNum(num);
	dto.setContent(content);
	dto.setRegdate(regdate);
	//수정반영
	boolean isSuccess=TodoDao.getInstance().update(dto);
	//응답
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/todo.update.jsp</title>
</head>
<body>
	<script>
		let a=<%=isSuccess%>;
		
		<% if(isSuccess) {%>
			alert("할일 수정에 성공했습니다.")
			location.href="list.jsp";			
		<%}else { %>
			alert("할일 수정에 실패했습니다.")
			location.href="updateform.jsp?num=<%=num %>";					
		<%} %>
	</script>
</body>
</html>

 

- 이런 처리 작업을 business logic이라고 한다.

- 이 비즈니스 로직과 응답은 보통 분리한다.

 지금은 jsp 페이지에서 함께 작성하지만, spring framework 등에서는 분리해서 많이 사용한다.

 jsp에서는 응답에만 집중하고, 비즈니스 로직은 다른 곳에서 하고 있는 경우가 많다.

 

- javascript로 응답해보기. jsp페이지에서는 클라이언트가 로딩할 javascript의 내용도 바꿀 수 있다.

- jsp에서도 javascript를 사용해서 동적인 내용을 출력할 수 있다.