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를 사용해서 동적인 내용을 출력할 수 있다.
'국비교육(22-23)' 카테고리의 다른 글
30일차(1)/jsp(9) : Request Scope (0) | 2022.11.18 |
---|---|
29일차(2)/CSS(6) : bootstrap CSS 적용하기 (0) | 2022.11.17 |
28일차(2)/jsp(7) : DBCP활용 (Update) (0) | 2022.11.16 |
28일차(1)/jsp(6) : DBCP활용 (Delete) (0) | 2022.11.16 |
27일차(3)/jsp(5) : DB 연결 기본 세팅, DBCP 활용 (select, insert) (0) | 2022.11.15 |