복습

ORM (Object Relational Mapping)프레임워크

- MyBatis, JPA

 

JSP Model 2 - MyBatis 게시판 프로그램 

- 이전에 만들었던 JSP Model 2 게시판 프로그램에서 DB 연동을 Connection Pool 대신 MyBatis 와 연동한 것이다

- Connection Pool 사용한 JSP Model 2 게시판 프로그램  : https://laker99.tistory.com/129

 

컬럼 설명

-- 모델2 게시판
select * from tab;
select * from seq;
select * from model22;

create table model22(
	board_num number,
	board_name varchar2(20),
	board_pass varchar2(15),
	board_subject varchar2(50),
	board_content varchar2(2000),
	board_file varchar2(50),
	board_re_ref number,
	board_re_lev number,
	board_re_seq number,
	board_readcount number,
	board_date timestamp,
	primary key(board_num)
);

create sequence model22_seq
start with 1
increment by 1
nocache;
출처: https://laker99.tistory.com/140 [레이커 갓생일기:티스토리]

- 번호값이 들어가는 컬럼이 주로 Primary Key 가 된다

- 원문, 댓글 상관없이 board_num 은 sequence 가 들어간다

 

글 개수 (board.xml 부분)

	<!-- 글갯수 -->
	<select id="board_count" resultType="int">
	 select count(*) from model22
	</select>

- 돌려줄 값의 자료형이 int 이므로 returnType 에 int 를 써야 한다


JSP Model 2 - MyBatis 게시판 프로그램 : 원문 글 작성 기능

- index.jsp 또는 프로젝트를 실행하면 바로 목록을 가져오는 요청인 "/BoardListAction.do" 로 요청한다

- index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

 모델2 게시판
 
<%
	response.sendRedirect("./BoardListAction.do");
%> 

<script>
//	location.href="./BoardListAction.do";
</script>

</body>
</html>

- location.href 로 목록을 가져오기 위한 "/BoardListAction.do" 요청을 해도 되고, sedRedirect 로 "/BoardListAction.do" 로 포워딩해서 요청해도 된다.

- Controller -> Service -> DAO -> Service -> Controller View 파일로 가면 목록이 나타나고 공유된 값들을 출력시켜줌

 

- 목록 페이지에서 '글쓰기' 를 누르면 "/BoardForm.do" 로 요청한다

- qna_board_list.jsp 부분

<a href="./BoardForm.do">글쓰기</a> <br>

 

- Controller 클래스로 간다

- Controller 클래스에서 "/BoardForm.do" 부분만

		// 글작성 폼	
		}else if(command.equals("/BoardForm.do")) {	
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/board/qna_board_write.jsp");
		}

 

- 바로 View 페이지 qna_board_write.jsp 로 이동한다

- qna_board_write.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>MVC 게시판</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>

<form action="<%=request.getContextPath() %>/BoardAddAction.do" method="post" 
	  enctype="multipart/form-data">
<table cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">MVC 게시판</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">글쓴이</div>
		</td>
		<td>
			<input name="board_name" id="board_name" type="text" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">제 목</div>
		</td>
		<td>
			<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">내 용</div>
		</td>
		<td>
			<textarea name="board_content" id="board_content" cols="67" rows="15"></textarea>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">파일 첨부</div>
		</td>
		<td>
			<input name="board_file" type="file"/>
		</td>
	</tr>
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="등록">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

- 첨부파일이 있을때는 form 에서 반드시 post 방식으로 전송, enctype 코드를 써야한다

- 입력하고 '등록' 을 누르면 "/BoardAddAction.do" 로 전송된다

+ 그럼 이 첨부파일을 원문 글 작성 Service 클래스인 BoardAddAction.java 에서 MultipartRequest 로 받아야함

 - "/BoardAddAction.do" 로 요청되었음을 확인 가능

 

- Controller 클래스에서 "/BoardAddAction.do" 부분만

		// 글작성
		if(command.equals("/BoardAddAction.do")) {
			try {
				action = new BoardAddAction();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 원문 글 작성 Service 클래스인 BoardAddAction.java 로 이동

- BoardAddAction.java

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;

import dao.BoardDAO;
import model.BoardBean;

public class BoardAddAction implements Action{
	
	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("BoardAddAction");
		
		String path = request.getRealPath("boardupload");
		System.out.println("path:"+path);
		
		int size = 1024 * 1024; // 1MB
		
		MultipartRequest multi = 
			new MultipartRequest(request,
								 path,
					             size,
					             "utf-8",
						         new DefaultFileRenamePolicy());
		
		BoardBean board = new BoardBean();
		board.setBoard_name(multi.getParameter("board_name"));
		board.setBoard_pass(multi.getParameter("board_pass"));
		board.setBoard_subject(multi.getParameter("board_subject"));
		board.setBoard_content(multi.getParameter("board_content"));
		board.setBoard_file(multi.getFilesystemName("board_file"));
		
		BoardDAO dao = BoardDAO.getInstance();
		int result = dao.insert(board);
		if(result==1) {
			System.out.println("insert");
		}
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(true);
		forward.setPath("./BoardListAction.do");
		
		return forward;
	}

}

첨부파일을 업로드 하기 위해 할 일

1) 첨부파일이 실제 저장되는 경로인 "boardupload" 의 진짜 path를 구한다

2) 업로드할 첨부파일 최대 크기를 정한다

3) MultipartRequest 객체를 생성하면서 첨부파일을 서버로 업로드시킴

- 매개변수가 5개인 생성자 사용하고 있다

- 여기서 한글값 인코딩을 하므로 request.setCharacterEncoding() 을 쓰지 않아도 됨

 

- DTO 객체 board 를 생성하고 그 객체에 앞의 원문 글 작성폼으로부터 넘어온 값들 (첨부파일명 포함) 을 MultipartRequest 객체 multi.getParameter() 로 받아서 저장시킨다

- 실제 서버에 저장된 첨부파일명을 multi.getFilesystemName() 으로 구해와서 DTO 객체 board 에 저장

+ 클라이언트가 업로드한 파일명을 구하는 메소드는 따로 있다

- DAO 객체 생성 후 DAO 의 insert() 메소드를 호출함, 매개변수로는 삽입할 데이터를 저장한 객체 board 를 넘김

<DAO insert() 에서 돌아온 후>

- ActionForward 객체 forward 를 생성하고 목록을 가져오는 요청인 "/BoardListAction.do" 로 요청

+ 바로 목록 페이지 qna_board_list 로 가면 아무것도 없다, 먼저 목록을 가져오는 요청을 해야함

 

+ 첨부파일이 업로드되는 경로가 콘솔창에 찍혀 나온다

 

- MemberDAO.java 에서 SqlSession 객체를 구해오는 getSession() 부분

	public SqlSession getSession() {
		SqlSession session=null;
		Reader reader=null;			
		try {
			reader = Resources.getResourceAsReader("mybatis-config.xml");
			SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(reader);
			session = sf.openSession(true);	 // auto commit	
		}catch(Exception e) {
			e.printStackTrace();
		}		
		return session;
	}

- MyBatis의 환경설정 파일을 읽어오면서 DB연동을 수행한다

- Builder 객체로 read 객체를 읽어서 SqlSessionFactory 객체 sf 를 생성

- sf 객체로 openSession(true) 로 세션을 구해옴, 이때 자동 커밋한다

 

- MemberDAO.java 에서 getMember() 메소드 부분만

	//글작성(원문작성)
	public int insert(BoardBean board) throws Exception {
		int result=0;
		SqlSession session = getSession();
		result = session.insert("board_insert", board);		
		System.out.println("result:"+result);
		
		return result;
	}

- DAO 클래스에서 SQL문을 실행하는 것은 맞다! Mapper 파일에서 SQL문을 가져와서 여기서 실행함

- 메소드 insert() 는 SqlSession 에서 지원되는 메소드

+ SqlSession 은 MyBatis 에서 지원되는 인터페이스이다

 

- board.xml 에서 원문 글 작성 처리하는 SQL문 부분 (id 가 board_insert)

	<!-- 글작성(원문) -->
	<insert id="board_insert" parameterType="board">
	 insert into model22 values(model22_seq.nextval,#{board_name},
	 #{board_pass},#{board_subject},#{board_content},
	 #{board_file,jdbcType=VARCHAR},model22_seq.nextval,0,0,0,sysdate)
	</insert>

- paramteterType 속성으로 값을 받는다, 받는 값의 자료형을 써야하므로 DTO 의 alias 값인 "board" 가 들어감

- board_num 자리에는 model22_seq.nextval 로 sequence 로 값을 넣는다

- #{board_name} 은 넘어온 객체 board 에서 이름 컬럼을 가져오는 board.getBoard_Name() 의 의미이다

- 원문 글이므로 board_re_ref 컬럼 자리에는 sequence 로 값이 입력된다

- 원문 글 작성이므로 board_re_lev, board_re_seq,board_readcount 자리에는 0 을 입력

 

첨부파일명 컬럼 (board.xml 에서 id 가 "board_insert"인 태그의 부분)

#{board_file,jdbcType=VARCHAR}

- 첨부파일은 사용자가 첨부를 선택할 수도 있고, 하지 않을 수도 있다

- 첨부파일을 선택하지 않으면 객체 board의 board_file 에 null 이 들어감, 그럼 DB에 insert 할때 board_file 에 null 값이 들어감

- MyBatis는 null 값이 들어가는 것을 허용하지 않는다

- ,jdbcType=VARCHAR 를 하면 board_file 컬럼에 null 값을 허용도록 만들어주는 코드이다

- 이 jdbcType=VARCHAR 가 없으면 첨부파일을 선택하지 않고 글 작성시 오류가 발생한다

 

- 원문 글 작성 성공시 콘솔창

 


JSP Model 2 - MyBatis 게시판 프로그램 : 글 목록 가져오기 기능 / 목록 페이지

- 원문 글 작성 후 목록을 가져오는 요청인 "/BoardListAction.do" 로 포워딩하며 요청함

- Controller 클래스로 이동한다

 

- Controller 클래스에서 "/BoardListAction.do" 부분만

		// 글목록	
		}else if(command.equals("/BoardListAction.do")) {
			try {
				action = new BoardListAction();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 목록 가져오기 Service 클래스인 BoardListAction.java 로 이동

- BoardListAction.java

package service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;

public class BoardListAction implements Action{
	
	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("BoardListAction");
		
		int page=1;
		int limit=10;
		
		if(request.getParameter("page")!=null) {
			page = Integer.parseInt(request.getParameter("page"));					
		}
		
		int startRow = (page-1) * limit + 1;
		int endRow = page * limit;
		
		List boardlist = null;
		BoardDAO dao = BoardDAO.getInstance();		
		int listcount = dao.getCount();
//		boardlist = dao.getList(startRow, endRow);
//		boardlist = dao.getList(page);

//      Map 처리	----------------------------------	
		Map map = new HashMap();
		map.put("start", startRow);
		map.put("end", endRow);
		
//		boardlist = dao.getList(map);		
		boardlist = dao.getList(page);		
//-------------------------------------------------		
		System.out.println("listcount:"+listcount);
		System.out.println("boardlist:"+boardlist);		
		
		int pageCount = listcount/limit + ((listcount%limit==0) ? 0:1);
		
		int startPage = ((page-1)/10) * limit + 1;
		int endPage = startPage + 10 - 1;
		
		if(endPage > pageCount) endPage = pageCount;
		
		request.setAttribute("page", page);
		request.setAttribute("listcount", listcount);
		request.setAttribute("boardlist", boardlist);
		request.setAttribute("pageCount", pageCount);
		request.setAttribute("startPage", startPage);
		request.setAttribute("endPage", endPage);		
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/board/qna_board_list.jsp");
		
		return forward;
	}

}

- 기본변수 3개, 파생된 변수 5개를 여기 글 목록 가져오기 Service 클래스에서 구해와야한다

+ 출력하는 파일은 따로 있다

<기본변수, 파생변수 구하기>

- 현재 페이지 번호 page의 기본값은 1 페이지로 지정, 한 화면에 10개씩 출력하겠다는 의미로 limit 는 10

- 만약 이 Service 클래스로 올때 페이지번호를 가져왔다면 그 번호가 page 의 값이 된다

- startRow, endRow 는 page, limit 에 의해 구해진다

- listcount 변수는 총 데이터 개수를 저장하고 있다, getCount() 로 부터 돌려받은 값

<총 데이터 개수 구하기>

- DAO 객체 생성 후 총 데이터 개수를 구하는 DAO 의 메소드 getCount() 메소드 호출해서 총 데이터 개수를 돌려받음

<목록 구하기>

- 실제 목록을 잘라서 목록을 구해오는 DAO 의 메소드 getList() 메소드를 호출, 매개변수로는 페이지번호 page 를 전달

* 목록을 가져올때 문제점 아래에 설명

- getList() 는 리스트로 리턴하고, 그 결과는 List 객체 boardlist 로 리턴받음

<DAO getList() 에서 돌아온 후>

- 총 페이지수가 저장될 파생변수 pageCount 를 listcount 와 limit 으로 구한다

+ 나눗셈을하면 정수형으로 값이 구해짐을 주의

- 각 블럭을 시작하는 페이지인 startPage 와 각 블럭의 마지막 페이지인 endPage 를 page 와 limit 로 구한다

- View 페이지에서 페이지 메뉴바에서 페이지 번호를 출력할때 무조건 10번 루프를 돌리므로 실제 존재하지 않는 페이지가 나타나는것을 막기 위해 마지막 페이지일땐 endPage 를 pageCount 로 설정해준다

<공유설정>

- 기본 변수 및 파생 변수인 page, listcount, boardlist, pageCount, startPage, endPage 를 request 객체로 공유설정한다

- View 페이지에서 출력할때 boardlist 는 리스트이므로 forEach 의 items 태그에 들어감, 나머지는 기본변수이므로 ${page} 처럼 출력함

<포워딩 방식, 페이지 지정>

- qna_board_list.jsp 로 포워딩, 거기서 목록을 출력할 것

- request 공유설정했으므로 Dispatcher 방식으로 포워딩해야한다

 

+ Model 2 게시판 프로그램 글 목록 구하기 변수들 설명 : https://laker99.tistory.com/129?category=1080281


목록을 가져올때 문제점

- MyBatis 연동 전에는 startRow 와 endRow 를 getList() 의 매개변수로 전달했었다, select 문의 where 절에 사용했음

- MyBatis 와 연동시 값이든 주소든 단 하나만 SqlSession 지원 메소드 5개의 매개변수로 전달 가능하다

+ Service 클래스에서 DAO 메소드로는 여러개의 값을 매개변수로 전달 가능하지만, SqlSession 지원 메소드의 매개변수로는 첫번째 매개변수에 id 를 넣고, 두번째 매개변수에 값을 전달할 수 있다, 단 하나의 값만 전달 가능

 

해결방법

- 해결방법이 여러개 있지만 여기선 이 방법을 사용하고 있다

- startRow 와 endRow 를 전달하는 대신 페이지 번호인 page 를 전달한다

- 이 page 번호에 따라 startRow 와 endRow 가 정해지므로, limit 는 정해진 값이므로 그 자리에 10을 쓰면 된다

- SQL문 상에서 page 번호로 startRow 와 endRow를 계산하기


또다른 해결방법

- 이 부분에서만 여러 파일의 내용을 변경할 것(주석 풀기 등), 이 부분을 나가면 다시 원래대로

- Map 객체 map 을 생성하고 put() 으로 startRow, endRow 를 저장 후 Map 객체 map 을 전달하면 됨

 

//      Map 처리	----------------------------------	
		Map map = new HashMap();
		map.put("start", startRow);
		map.put("end", endRow);

- Map 객체 map 에 key , value 값으로 startRow 와 endRow 를 모두 저장시켰다

ex) 사용자가 2 페이지 선택시 11 과 20 을 Map 객체에 저장하는 것

- key 값으로 value 를 가져와서 사용할 것

- 그리고 Map 객체를 DAO의 getList() 로 전달

		boardlist = dao.getList(map);

- MemberDAO.java 에서 getList() 메소드 부분에서 주석을 바꿈

	// 데이터 목록
//	public List getList(int page) throws Exception {
	public List getList(Map map) throws Exception {
		List list = new ArrayList();
		SqlSession session=getSession();
		list = session.selectList("board_list", map); 
		
		return list;
	}

- map 객체를 selectList() 의 매개변수로 전달한다

- Mapper 파일인 board.xml 로 가자

 

	<!-- Map 전달 -->
	<select id="board_list" parameterType="Map" resultType="board">
	 select * from (select rownum rnum, board.* from (
	  select * from model22 order by board_re_ref desc,board_re_seq asc) board )
	  where rnum &gt;= #{start} and rnum &lt;= #{end}
	</select>

- parameterType 을 Map 으로 써야한다, HashMap 이어도 괜찮다

- #{start} 와 #{end} 의 "start" 와 "end" 는 map 에 저장한 key 값이다

- #{start} 처럼 key 값을 이용해서 value 값을 구해올 수 있다

 

+ 세번째 방법

- DTO 클래스 안에 startRow 와 endRow 를 저장할 수 있는 필드를 만들고 그 DTO 객체에 startRow, endRow 를 저장해서 넘겨주는 방법도 있다

- 가져올땐 getter 메소드 사용

 

어떤 방법을 써야할까?

- 우리가 쓰고 있는 페이지번호 page 를 넘기고 SQL문 상에서 startRow, endRow 를 계산하는 방법은 쓰지 못할 때가 있다

- 검색기능을 수행시에 '작성자명', '제목' 등으로 검색한다, 그 검색어도 SQL상으로 전달되어야함, 넘어가는 값이 많아질땐 페이지번호를 넘기는 방법 쓰지 못하게 됨

- 이땐 DTO 객체를 쓰거나, Map 객체안에 저장해서 전달해야함

- 주로 DTO 객체를 넘기는 경우가 많다, Map 은 DTO보다 처리속도가 느림


- MemberDAO.java 에서 getCount() 메소드 부분만

	// 총데이터 갯수 구하기
	public int getCount() throws Exception{
		int result=0;
		SqlSession session=getSession();
		result = (Integer)session.selectOne("board_count");
		
		return result;
	}

- group 함수로 구하기때문에 검색하는 데이터는 1개이므로 selectOne() 메소드 사용

- session.selectOne() 에서 리턴자료형은 Object 이므로 다운캐스팅이 원칙이다

 

- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_count)

	<!-- 글갯수 -->
	<select id="board_count" resultType="int">
	 select count(*) from model22
	</select>

- 돌려주는 데이터는 글 개수 이므로 resultType 은 "int" 이다

+ 주의 : returnType 은 무조건 DTO 가 아니다, 돌려주는 데이터의 자료형임

 

- MemberDAO.java 에서 getList() 메소드 부분만

	// 데이터 목록
	public List getList(int page) throws Exception {
//	public List getList(Map map) throws Exception {
		List list = new ArrayList();
		SqlSession session=getSession();
		list = session.selectList("board_list", page); 
		
		return list;
	}

- 메소드 selectList() 에서도 다른 SqlSession 지원 메소드처럼 단 하나의 값만 매개변수로 전달 가능하다

+ 첫번째 매개변수는 id 값, 두번쨰 매개변수에만 값을 전달 가능

- page 번호만 있으면 startRow, endRow를 계산할 수 있으므로 페이지번호 page 만 전달한다

+ Map 객체로 넘어올때는 주석처리 되어있음

 

- board.xml 에서 목록을 자르고 목록을 가져오는 SQL문 부분 (id 가 board_list)

	<!-- 글목록 -->
	<!-- page 전달-->
	<select id="board_list" parameterType="int" resultType="board">
	 select * from (select rownum rnum, board.* from (
	  select * from model22 order by board_re_ref desc,board_re_seq asc) board )
	  where rnum &gt;= (#{page}-1) * 10 + 1   and rnum &lt;= #{page}*10
	</select>

- selectList() 메소드를 실행하고 결과를 List 로 돌려줌, 이때 결과를 DTO 객체들을 저장한 List 로 전달한다고 해도 resultType 에는 DTO 인 member 를 써야한다

- 리스트에 알아서 순차적으로 저장해서 돌려준다

- 전달받은 값은 페이지번호 이므로 parameterType 은 int 로 작성

<SQL문>

- 첫번째 서브쿼리는 rownum 컬럼에 대한 별칭 rnum 을 준다

- 두번째 서브쿼리는 필요한 내용을 검색하고 정렬한다, 댓글게시판이므로 board_re_ref 로 최근글이 위로 가도록 정렬하고, 부모글과 자식글은 board_re_ref 가 동일하므로 한번 더 정렬조건으로 board_re_seq 를 준다

- 두번쨰 서브쿼리에 대해서 board 라는 별칭을 주고 첫번째 서브쿼리에서 board 의 모든 컬럼을 검색하고 있다

- startRow, endRow 를 전달받은 page 번호로 계산한다

- startRow = (#{page} -1) * 10 + 1 이다, 10 은 limit 을 의미

- endRow = #{page} * 10 이다, 10은 limit 을 의미

- xml 파일에서는 < 와 > 를 잘 인식하지 못함, > 대신 특수문자 &gt; 를 사용, < 대신 특수문자 &li; 을 사용

+ <와 > 를 대신하는 다른 방법도 있다, 여기서는 &gt; 과 &lt; 을 사용

 

- 목록을 성공적으로 가져올때 콘솔창

 

- 이제 목록을 가져오는 작업은 끝났다, Controller 클래스로 갔다가 Dispatcher 방식으로 qna_board_list.jsp 로 포워딩됨

- qna_board_list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
   

<a href="./BoardForm.do">글쓰기</a> <br>
데이터 갯수 : ${listcount} 개

<table border=1 width=700 align=center>
	<caption>게시판 목록</caption>
	<tr>
		<th>번호</th>
		<th>제목</th>
		<th>작성자</th>
		<th>날짜</th>
		<th>조회수</th>
	</tr>
		
	<c:set var="num" value="${listcount - (page-1) * 10 }"/>	
	<c:forEach var="b" items="${boardlist}">
	<tr>
		<td>
			${num}
			<c:set var="num" value="${num-1}"/>
		</td>	
		<td>
		<c:if test="${b.board_re_lev > 0}">
			<c:forEach var="i" begin="0" end="${b.board_re_lev}">
				&nbsp;
			</c:forEach>
		</c:if>
		<c:if test="${b.board_re_lev > 0}">
			Re.
		</c:if>
		<a href="./BoardDetailAction.do?board_num=${b.board_num}&page=${page}">
			 ${b.board_subject}
		</a>
		
		</td>	
		<td>${b.board_name}</td>	
		<td>
		
		<fmt:formatDate value="${b.board_date}" pattern="yyyy-MM-dd H:mm"/>
		
		</td>	
		<td>${b.board_readcount}</td>	
	</tr>
	</c:forEach>

</table><br>


<center>
<c:if test="${listcount > 0}">

	<a href="./BoardListAction.do?page=1" style="text-decoration:none"> < </a>

	<c:if test="${startPage > 10}">
		<a href="./BoardListAction.do?page=${startPage - 10}">[이전]</a>
	</c:if>

	<c:forEach var="i" begin="${startPage}" end="${endPage}">
		<c:if test="${i == page}">
			[${i}]
		</c:if>
		<c:if test="${i != page}">
			<a href="./BoardListAction.do?page=${i}">[${i}]</a>
		</c:if>
	</c:forEach>

	<c:if test="${endPage < pageCount}">
		<a href="./BoardListAction.do?page=${startPage + 10}">[다음]</a>
	</c:if>
	
	<a href="./BoardListAction.do?page=${pageCount}" style="text-decoration:none"> > </a>	

</c:if>
</center>

- JSTL 태그들을 쓰기 위해 코어 라이브러리와 국제화 라이브러리를 불러온다

- '글쓰기' 를 누르면 원문 글 작성 폼으로 가는 요청인 "/BoardForm.do" 으로 요청

- 글개수 출력은 Service 클래스 BoardListAction.java 에서 request 객체로 공유했던 listcount 를 바로 구해서 출력

- 그 페이지 화면출력번호 num 을 구함 * 아래에 설명

<데이터 뿌리기>

- Service 클래스 BoardListAction.java 에서 request 객체로 공유했던 값들을 EL 태그로 구해서 출력하고 있다

- forEach 태그를 통해서 각 데이터(행)를 출력, forEach 태그의 items 에 목록을 가지고 있는 공유된 리스트 boardlist 가 온다, 그럼 각 데이터 하나가 변수 b 에 저장됨

- 제목을 출력할때 ${b.board_subject} 로 출력, 표기법만 이럴뿐 실제 의미는 b.getBoard_Subject() 를 출력하는 것이다

- 국제화 라이브러리 formatDate 태그로 패턴을 지정해서 날짜 ${b.board_date} 를 원하는 포맷으로 출력

<댓글 관련>

- if 태그를 써서 ${b.board_re_lev} > 0 이면 즉, 댓글이면 깊이만큼 제목 앞에 공백 출력, Re. 출력

<상세페이지로 가는 링크>

- 제목에 링크를 걸어서 글 번호와 원래 페이지번호를 가져가고 있다

<페이지 메뉴바>

- forEach 태그의 begin 과 end 속성으로 공유된 값 startPage 와 endPage 를 사용함, startPage ~ endPage 까지를 페이지 메뉴바에 출력

- 현재 페이지가 아닌 경우 링크를 "/BoardListAction.do" 로 걸고 페이지번호 page 를 get 방식으로 전달, 이렇게 하면 해당 페이지의 목록을 가져와서 다시 출력함

- 이렇게 전달시 BoardListAction.java 의 이 코드에서 페이지번호 page 를 받음 (BoardListAction.java 부분)

		if(request.getParameter("page")!=null) {
			page = Integer.parseInt(request.getParameter("page"));					
		}

- < 클릭시 page 번호를 1 을 넘겨주며 "/BoardListAction.do" 로 간다

- > 클릭시 page 번호를 마지막 페이지(= 총 페이지 수) 를 넘겨주며 "/BoardListAction.do" 로 간다 (총 페이지수는 공유된 pageCount값을 가져온 것이다)

- [이전] 메뉴는 startPage > 10 인 경우, 즉 첫번째 페이지가 아닌 경우에만 출력, 링크는 현재 블럭의 startPage 에서 10 을 빼서 이전 블럭의 가장 작은 페이지로 이동

- [다음] 메뉴는 endPage < pageCount 인 경우 출력, 링크는 현재 블럭의 startPage 에서 10 을 더해서 다음 블럭의 가장 작은 페이지로 이동

ex) 1page 일떄 endPage 는 10, pageCount 는 13 이므로 [다음] 메뉴 나타남

- 11page일때 endPage 는 13(보정했었다), pageCount 도 13 이므로 만족하지 않음, [다음] 메뉴 나타나지 않는다

 

+ Model 2 게시판 프로그램 글 목록 출력 설명 : https://laker99.tistory.com/129?category=1080281


화면 출력 번호

- Service 클래스 BoardListAction.java 에서 구하지 않은 마지막 파생변수인 화면 출력 번호를 여기서 정의

	<c:set var="num" value="${listcount - (page-1) * 10 }"/>

- 3개의 기본 변수값을 사용해서 화면 출력 번호 num 을 생성, 10 은 limit 값이다

- limit 값은 고정된 값 (10) 이므로 공유설정하지 않았음

- forEach 로 각각의 행을 출력할때마다 1이 감소됨


- 페이징 처리를 보기 위해 글을 여러개 작성해서 많은 데이터를 넣자
+ BoardAddAction.java 에서 포워딩할때 Dispatcher 방식으로 바꾸고, 포워딩 페이지 설정에서 . 을 빼주면 글 작성 후 새로고침 시 같은 데이터가 다시 들어감

- 여기서 새로고침 눌러서 데이터 많이 넣기

 

- 이렇게 페이지 메뉴바에서 5 를 클릭시

- 페이지번호에 따라 startRow, endRow 가 결정되므로 가져올 글들이 달라짐


JSP Model 2 - MyBatis 게시판 프로그램 : 상세 페이지 기능

- qna_board_list.jsp 에서 제목을 클릭하면 "/BoardDetailAction.do" 로 요청하며 글 번호와 페이지 번호를 가져간다

		<a href="./BoardDetailAction.do?board_num=${b.board_num}&page=${page}">
			 ${b.board_subject}
		</a>

- 여기가 출발점임, 바로 상세페이지를 실행하면 안된다

- 상세페이지로 가거나, 수정하고 오거나, 삭제하거나 할때 원래 페이지로 돌아가기 위해서 페이지 번호를 가져감

- 하나의 글에 대한 상세 정보를 구해와야 하므로 글 번호가 필요함

 

- Controller 클래스에서 "/BoardDetailAction.do" 부분만

		// 상세 페이지	
		}else if(command.equals("/BoardDetailAction.do")) {
			try {
				action = new BoardDetailAction();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 상세 페이지 Service 클래스인 BoardDetailAction.java 로 이동

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardDetailAction implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("BoardDetailAction");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String page = request.getParameter("page");
		
		BoardDAO dao = BoardDAO.getInstance();
//		BoardBean board = dao.updateContent(board_num);
		
		//조회수 증가
		dao.updateCount(board_num);
		BoardBean board = dao.getContent(board_num); // 상세정보 구하기
		
		request.setAttribute("board", board);
		request.setAttribute("page", page);
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/board/qna_board_view.jsp");
		
		return forward;
	}

}

- 목록페이지에서 넘어온 글 번호와 페이지 번호를 받아서 변수 board_num 와 page 에 저장

- 여기서는 2가지 작업을 해야함, 2개의 DAO 메소드를 호출해야한다

1) updateCount() : 조회수 1 증가

2) getContent() : 상세 정보 구해오기

- 이때 글번호 board_num 을 전달해야 그 글의 조회수를 증가시킬 수 있고, 상세 정보를 구할 수 있다

<DAO updateCount() 와 getContent() 에서 돌아온 후>

- 가져온 상세정보를 DTO 객체 board 로 받음

- 그 객체 board 와 페이지번호 page 를 공유설정함

+ 글 번호는 객체 board 안에 board_num 필드안에 저장되어있으므로 공유설정 따로 하지 않음

+ DTO 객체 board 가 공유되면 View 에서 ${board.필드명} 으로 결과를 출력함

<포워딩>

- 상세정보를 보여줄 View 페이지인 qna_board_view.jsp 로 포워딩 페이지 설정

+ 실제 포워딩은 Controller 로 가서 포워딩한다

 

- MemberDAO.java 에서 updateCount() 메소드 부분만

	// 조회수 증가
	public void updateCount(int board_num) throws Exception{
		SqlSession session = getSession();
		session.update("board_updatecount", board_num);
	}

- 전달받은 글 번호 board_num 을 session.update() 의 두번째 매개변수로 전달함

 

- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_updatecontent)

	<!-- 조회수 증가 -->
	<update id="board_updatecount" parameterType="int">
     update model22 set board_readcount=board_readcount+1 where board_num = #{board_num}	
	</update>

- 글 번호를 전달받았고, parameterType 에는 전달받은 값의 자료형을 써야하므로 "int"

- 조회수 값을 1 증가시켜주는 update 를 한다, where 절에 #{board_num} 으로 넘어온 글 번호를 쓴다

 

- MemberDAO.java 에서 getContent() 메소드 부분만

	// 상세 페이지, 수정 폼
	public BoardBean getContent(int board_num) throws Exception {
		BoardBean board = new BoardBean();

		SqlSession session=getSession();
		board  = (BoardBean)session.selectOne("board_content", board_num);
		
		return board;
	}

- 1개 글에 대한 상세정보를 구해서 돌려줘야하므로 selectOne() 메소드 호출

- 두번째 매개변수로 글 번호 board_num 을 넘겨준다

- 검색되는 결과가 1개이므로 DTO 객체가 리턴자료형으로 왔다, 검색되는 결과가 여러개일땐 List 가 온다

+ 무조건 DTO 가 온다는건 Mapper 파일의 SQL문 부분에 해당함, DAO에서는 검색되는 결과가 여러개일땐 List 가 온다

 

- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_content)

	<!-- 상세페이지, 수정폼 -->
	<select id="board_content" parameterType="int" resultType="board">
	 select * from model22 where board_num = #{board_num}
	</select>

- #{board_num} 번호값에 만족하는 데이터를 검색해서 데이터 1개에 대한 상세정보를 자동으로 DTO 객체에 매핑해서 그 DTO 객체를 리턴함

+ 테이블 컬럼명과 DTO 프로퍼티명이 일치할때만 자동 매핑 가능

- resultType 은 DTO alias 인 board

 

- 다음은 이 상세정보를 출력하는 View 페이지인 qna_board_view.jsp

- qna_board_view.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> 
    
<table border=1 width=600 align=center>
	<caption>상세 페이지</caption>
	<tr>
		<td>작성자</td>
		<td>${board.board_name}</td>
	</tr>
	<tr>
		<td>조회수</td>
		<td>${board.board_readcount}</td>
	</tr>
	<tr>
		<td>날짜</td>
		<td>
			<fmt:formatDate value="${board.board_date}"
							pattern="yyyy-MM-dd H:mm"/>		
		</td>
	</tr>
	<tr>
		<td>제목</td>
		<td>${board.board_subject}</td>
	</tr>
	<tr>
		<td>내용</td>
		<td><pre>${board.board_content}</pre></td>
	</tr>
	<tr>
		<td>첨부 파일</td>
		<td>
			<c:if test="${board.board_file !=  null }">
				<a href="./board/file_down.jsp?file_name=${board.board_file}">${board.board_file}</a>
			</c:if>
		</td>
	</tr>
	<tr>
		<td colspan=2 align=center>
			<input type="button" value="댓글" 
				   onClick="location.href='./BoardReplyAction.do?board_num=${board.board_num}&page=${page}&board_re_ref=${board.board_re_ref}&board_re_lev=${board.board_re_lev}&board_re_seq=${board.board_re_seq}'">
			<input type="button" value="수정" 
				   onClick="location.href='./BoardModifyAction.do?board_num=${board.board_num}&page=${page}'">
			<input type="button" value="삭제" 
				   onClick="location.href='./BoardDeleteAction.do?board_num=${board.board_num}&page=${page}'">
			<input type="button" value="목록" 
			       onClick="location.href='./BoardListAction.do?page=${page}'">
		</td>
	</tr>
</table>

- Service 클래스 BoardDetailAction.java 에서 공유설정한 객체 board 와 페이지번호 page 를 EL 로 가져와서 활용

ex) 조회수는 ${board.board_readcount} 로 가져와서 출력하고 있다

- if 태그에 ${board.board_file != null} 로, 만약 첨부파일이 있다면 첨부파일명을 출력

- 첨부파일명을 클리하면 file_down.jsp 로 이동하며 다운이 된다

- 내용은 pre 태그로 감싸서 출력해야 줄이 바뀌어서 출력됨

- 상세페이지에 아래에 출력될 버튼을 만들고 링크를 걸었다

'목록' 버튼 클릭시

- 목록을 가져오는 요청인 "/BoardListAction.do" 로 요청, 이때 페이지번호 page 를 전달

+ 페이지 번호 page는 BoardDetailAction.java 에서 공유설정했던 값을 가져왔다

'댓글' 버튼 클릭시

- 댓글을 다는 폼으로 가기 위해 "/BoardReplyAction.do" 로 요청, 이때 5가지의 값을 가져가고 있다

- 부모글이 될 현재 글의 글 번호, 현재 페이지 번호 뿐 아니라 부모글이 될 현재 글의 ref, lev, seq 값도 가져간다

- 만약 글 번호, 페이지 번호만 넘길때는 Service클래스로 가서 부모글에 대한 정보(ref,lev,seq)를 DB 에서 가져와야한다

- 여기선 5가지 값을 다 가져가므로 Service 클래스로 갈 필요 없이 바로 댓글 작성 폼 View 페이지로 가도 된다 

- '수정' 버튼 / '삭제' 버튼 

 


JSP Model 2 - MyBatis 게시판 프로그램 : 댓글 작성 기능

- qna_board_view 에서 출력된 버튼 중 '댓글' 버튼을 클릭시 "/BoardReplyAction.do" 로 요청

<input type="button" value="댓글" 
		onClick="location.href='./BoardReplyAction.do?board_num=${board.board_num}&page=${page}&board_re_ref=${board.board_re_ref}&board_re_lev=${board.board_re_lev}&board_re_seq=${board.board_re_seq}'">

- "/BoardReplyAction.do" 로 가면서 글번호, 페이지번호 뿐 아니라 부모글에 대한 정보인 board_re_ref, board_re_lev, board_re_seq 도 전달

- 그러므로 Service 클래스로 가서 이 정보를 가져오기 위해 DB 연동할 필요 없이 바로 댓글 작성 폼으로 이동 가능

- Service 클래스로 안가고 바로 댓글 작성 폼으로 이동한다

 

- Controller 클래스에서 "/BoardReplyAction.do" 부분만

		// 댓글 폼	
		}else if(command.equals("/BoardReplyAction.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/board/qna_board_reply.jsp");
		}

- 즉 댓글 작성 폼인 View 페이지 qna_board_reply.jsp 로 이동한다

- Model 2 게시판 프로그램 작성시에는 댓글작성폼으로 갈때 2개의 값만 전달하고 나머지는 DB와 연동해서 부모글의 정보를 구하는 작업을 선행했었다, 검색어 (게시판 프로그램 : 댓글 작성폼 Service 클래스)

- 여기서는 부모글의 정보도 qna_board_view 에서 전달하므로 DB연동 필요없이 바로 View페이지로 가고 있다

 

- qna_board_reply.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>댓글 게시판</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>

<form action="<%=request.getContextPath() %>/BoardReply.do" method="post">
<input type="hidden" name="board_num" value="${param.board_num}">
<input type="hidden" name="page" value="${param.page}">
<input type="hidden" name="board_re_ref" value="${param.board_re_ref}">
<input type="hidden" name="board_re_lev" value="${param.board_re_lev}">
<input type="hidden" name="board_re_seq" value="${param.board_re_seq}">

<table cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">댓글 게시판</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">글쓴이</div>
		</td>
		<td>
			<input name="board_name" id="board_name" type="text" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">제 목</div>
		</td>
		<td>
			<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">내 용</div>
		</td>
		<td>
			<textarea name="board_content" id="board_content" cols="67" rows="15"></textarea>
		</td>
	</tr>	
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="댓글">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

- qna_board_view 에서 get 방식으로 전달된 값을 param EL 내장객체로 가져오고 있다

ex) ${param.board_re_ref} 는 request.getParamter("board_re_ref") 를 의미

- get 방식으로 전달된 5개의 값을 qna_board_reply 의 form 에서 "/BoardReply.do" 로 요청하며 hidden 을 통해서 전달

- 댓글을 작성하고 '댓글' 버튼을 누르면 "/BoardReply.do"로 요청

 

- Controller 클래스에서 "/BoardReply.do" 부분만

		// 댓글	
		}else if(command.equals("/BoardReply.do")) {
			try {
				action = new BoardReply();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

 

- 댓글 작성 Service 클래스인 BoardReply.java 로 이동

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardReply implements Action {

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("BoardReply");
		
		request.setCharacterEncoding("utf-8");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		int board_re_ref = Integer.parseInt(request.getParameter("board_re_ref"));
		int board_re_lev = Integer.parseInt(request.getParameter("board_re_lev"));
		int board_re_seq = Integer.parseInt(request.getParameter("board_re_seq"));
		String page = request.getParameter("page");
		
		BoardBean board = new BoardBean();
		board.setBoard_re_ref(board_re_ref);
		board.setBoard_re_seq(board_re_seq);
		
		BoardDAO dao = BoardDAO.getInstance();
		dao.updateSeq(board); 	// board_re_seq값 증가 		
		
		board.setBoard_re_seq(board_re_seq+1);
		board.setBoard_re_lev(board_re_lev+1);		
		board.setBoard_name(request.getParameter("board_name"));
		board.setBoard_pass(request.getParameter("board_pass"));
		board.setBoard_subject(request.getParameter("board_subject"));
		board.setBoard_content(request.getParameter("board_content"));
					
		dao.boardReply(board); // 댓글 작성 : insert SQL		
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/BoardDetailAction.do?board_num="+board_num+"&page="+page); // 상세페이지로 이동
//		forward.setPath("/BoardListAction.do?page="+page); // 목록페이지로 이동
		
		return forward;
	}

}

- 댓글 작성 폼에서 한글값이 넘어올 수 있으므로 한글값 인코딩

- hidden 으로 넘어오는 5개의 값을 request.getParameter() 로 받아서 변수에 저장

- 또한 사용자가 직접 댓글 작성폼에 작성한 정보 4가지도 받는다

- updateSeq() 메소드에서 부모글의 board_re_ref, board_re_seq 값이 필요하므로 생성한 DTO 객체 board 에 부모글의 board_re_ref, board_re_seq  를 저장하고 그 객체 board 를 updateSeq() 의 매개변수로 전달

<DAO updateSeq() 메소드로부터 돌아온 뒤>

- 댓글을 작성해야하므로 insert 될 데이터를 DTO 객체 board 에 세팅

- 이때 board_re_seq 값과 board_re_lev 값은 부모보다 1 증가된 값으로 세팅

- 또한 사용자가 직접 입력한 4가지 정보도 세팅하고 DAO의 boardReply() 메소드 호출, 매개변수로는 객체 board 를 넘김

<DAO boardReply() 메소드로부터 돌아온 뒤>

- 댓글을 단 이후 상세페이지로 넘어갈땐, "/BoardDetailAction.do" 로 포워딩 페이지 지정하며 글번호와 페이지번호를 전달

+ BoardDetailAction.java 에서 글번호와 페이지번호를 getParameter() 로 받는 작업을 가장 먼저하므로 두 값이 필요

- 댓글을 단 이후 목록페이지로 넘어갈땐, "/BoardListAction.do" 로 포워딩 페이지 지정하며 페이지 번호를 전달

 

이 Service 클래스에서 2번의 DAO 메소드를 실행해야함

1) updateSeq() 메소드 : update SQL문

- update 문을 수행하기 위해 부모글의 ref 값과 seq 값을 전달

- 댓글의 ref 는 부모글의 ref 값과 같은값을 넣고, 부모글의 seq 보다 큰 글들의 seq 를 +1 함

2) boardReply() 메소드 : insert SQL문 

- 댓글을 DB에 insert 함

- Mapper 파일로 처리해야하므로 두 메소드의 SQL문을 하나의 메소드에서 실행 불가, 메소드를 따로 작성 해야함

 

- MemberDAO.java 에서 댓글 출력순서를 조절하기 위한 updateSeq() 부분

	// 댓글 출력 순서 (board_re_seq값 증가)
	public void updateSeq(BoardBean board) {
		SqlSession session = getSession();
		session.update("board_updateseq", board);
	}

- 부모글의 board_re_ref 와 board_re_seq 값을 저장한 객체 board 를 두번째 매개변수로 전달한다

 

- board.xml 에서 원문 글 작성 처리하는 SQL문 부분 (id 가 board_updateseq)

	<!-- 댓글 출력순서 -->
	<update id="board_updateseq" parameterType="board">
	 update model22 set board_re_seq=board_re_seq+1 
	  where board_re_ref = #{board_re_ref} and board_re_seq &gt; #{board_re_seq}
	</update>

- 부모글과 board_re_ref 값이 같으면서 , 부모글보다 board_re_seq 값이 큰 글의 board_re_seq 값을 1 증가시켜줘야함

의미

1) 부모가 원문이면 그 원문의 모든 댓글의 seq 값을 1 씩 증가

2) 부모가 댓글이면 그 부모보다 먼저 달린 형제 댓글들과 그 형제 댓글들의 대댓글들의 seq 값을 1씩 증가

 

- MemberDAO.java 에서 댓글을 삽입하는 boardReply() 부분

	// 댓글작성
	public void boardReply(BoardBean board) {
		SqlSession session = getSession();
		session.insert("board_reply", board);
	}

 

- board.xml 에서 원문 글 작성 처리하는 SQL문 부분 (id 가 board_reply)

	<!-- 댓글 작성 -->
	<insert id="board_reply" parameterType="board">
	 insert into model22 values(model22_seq.nextval,#{board_name},
	 #{board_pass},#{board_subject},#{board_content},
	 #{board_file,jdbcType=VARCHAR},#{board_re_ref},#{board_re_lev},#{board_re_seq},0,sysdate)
	</insert>

- board_num 은 원문이든 댓글이든 모두 sequence 로 들어간다

- 댓글은 첨부파일을 올리지 못하므로 첨부파일명을 저장하는 컬럼 board_file 이 null 이 되어야함

- MyBatis 는 null 값을 허용하지 않는다

- jdbcType=VARCHAR 속성값을 추가하면 null 값을 허용하게 됨 

- 이미 BoardReply.java 에서 부모의 board_re_seq, board_re_lev 에서 1 증가시켜서 board 객체의 board_re_seq 와 board_re_lev에 세팅했으므로 여기선 그대로 가져와서 삽입

- 조회수 컬럼엔 0, 날짜 컬럼엔 sysdate 로 현재 날짜와 시간을 넣어줌

 

 


JSP Model 2 - MyBatis 게시판 프로그램 : 글 수정폼 기능

- qna_board_view 에서 출력된 버튼 중 '수정' 버튼을 클릭시 "/BoardModifyAcion.do" 로 요청

<input type="button" value="수정" 
	onClick="location.href='./BoardModifyAction.do?board_num=${board.board_num}&page=${page}'">

- "/BoardModifyAcion.do" 로 가면서 글번호, 페이지번호 전달

- 이 글번호, 페이지번호는 목록 페이지 -> 상세페이지 -> 수정폼 -> 수정 까지 넘어간다

- 먼저 상세정보를 구해와서 그걸 수정폼에 뿌려야한다

 

- Controller 클래스에서 "/BoardModifyAcion.do" 부분만

		// 수정 폼	
		}else if(command.equals("/BoardModifyAction.do")) {
			try {
				action = new BoardModifyAction();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 수정폼에 뿌릴 상세 정보를 가져오는 Service 클래스 BoardModifyAction.java

- BoardModifyAction.java

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardModifyAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("BoardModifyAction");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String page = request.getParameter("page");
		
		BoardDAO dao = BoardDAO.getInstance();
		BoardBean board = dao.getContent(board_num);
				
		request.setAttribute("board", board);
		request.setAttribute("page", page);
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/board/qna_board_modify.jsp");
		
		return forward;
	}

}

- 상세 페이지 qna_board_view.jsp 에서 get 방식으로 넘어온 글 번호와 페이지 번호를 받아서 변수에 저장

- DAO의 getContent() 메소드로 글 하나에 대한 상세정보를 가져온다, 매개변수로는 글 번호를 넘겨준다

- getContent() 에서 구해온 상세정보를 board 객체에 저장 한다

- board 객체를 공유설정하고 페이지 번호인 page 변수도 request 객체로 공유설정함

+ View인 수정폼에서 가져와서 뿌려줄 수 있도록 공유설정해야한다, DTO 객체가 공유되었다면  View 에서 ${공유되는네임값.필드명} 으로 사용

- 포워딩 페이지는 qna_board_modify.jsp 로 지정한다

 

- MemberDAO.java 에서 getContent() 메소드 부분만 

	// 상세 페이지, 수정 폼
	public BoardBean getContent(int board_num) throws Exception {
		BoardBean board = new BoardBean();

		SqlSession session=getSession();
		board  = (BoardBean)session.selectOne("board_content", board_num);
		
		return board;
	}

- 두번째 매개변수로 글 번호 board_num 전달

 

- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_content)

	<!-- 상세페이지, 수정폼 -->
	<select id="board_content" parameterType="int" resultType="board">
	 select * from model22 where board_num = #{board_num}
	</select>

- DTO 객체에 상세정보를 매핑해서 리턴해줌

 

- 포워딩되는 페이지이자 수정폼 View 페이지인 qna_board_modify.jsp 를 보자

- qna_board_modify.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>게시판 수정</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>

<form action="<%=request.getContextPath() %>/BoardModify.do" method="post">
<input type="hidden" name="board_num" value="${board.board_num}">
<input type="hidden" name="page" value="${page}">

<table cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">게시판 수정</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">글쓴이</div>
		</td>
		<td>
			<input name="board_name" id="board_name" type="text" size="10" maxlength="10" 
				value="${board.board_name }"/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">제 목</div>
		</td>
		<td>
			<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100" 
				value="${board.board_subject}"/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">내 용</div>
		</td>
		<td>
			<textarea name="board_content" id="board_content" cols="67" rows="15">${board.board_content}</textarea>
		</td>
	</tr>	
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="수정">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

+ 첨부파일을 수정하는 기능은 빠져있다

- Service 클래스인 BoardModifyAction.java 에서 공유설정했던 DTO 객체 board 의 값을 ${board.필드명} 으로 가져와서 출력하고 있다

ex) ${board.subject} 으로 해당 글의 제목을 가져오고 있다

- 공유설정했던 DTO 객체 board에서 글 번호인 ${board.board_num} 을 hidden 으로 "/BoardModify.do" 로 넘겨준다

- 또한 수정이 끝난 후 원래 페이지로 돌아가기 위해 공유설정된 페이지번호 ${page} 를 hidden 으로 "/BoardModify.do" 로 넘겨준다

- 즉 글 번호와 페이지 번호를 글 수정을 하는 Service 클래스로 넘기고 있음

 

 


JSP Model 2 - MyBatis 게시판 프로그램 : 글 수정 기능

- 수정폼 qna_board_modify.jsp 에서 '수정' 을 누르면 form 을 통해 "/BoardModify.do" 로 요청한다 

- hidden 으로 글 번호와 페이지 번호가 넘어왔음

- 이 글번호, 페이지번호는 목록 페이지 -> 상세페이지 -> 수정폼 -> 수정 까지 넘어간다

- 또한 수정폼에서 사용자가 입력한 값들도 넘어왔다

 

- Controller 클래스에서 "/BoardModify.do" 부분만

		// 수정	
		}else if(command.equals("/BoardModify.do")) {
			try {
				action = new BoardModify();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 수정을 하는 Service 클래스인 BoardModify.java

package service;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardModify implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("BoardModify");
		
		response.setContentType("text/html; charset=utf-8");
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		
		String page = request.getParameter("page");
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String board_pass = request.getParameter("board_pass");
		
		BoardBean board = new BoardBean();
		board.setBoard_num(board_num);
		board.setBoard_name(request.getParameter("board_name"));
		board.setBoard_subject(request.getParameter("board_subject"));
		board.setBoard_content(request.getParameter("board_content"));
		
		BoardDAO dao = BoardDAO.getInstance();
		BoardBean old = dao.getContent(board_num);
		
		if(old.getBoard_pass().equals(board_pass)) {//비번 일치시
			dao.update(board);
			
		}else {	// 비번 불일치시
			
			out.println("<script>");
			out.println("alert('비밀번호가 일치하지 않습니다.');");
			out.println("history.go(-1);");
			out.println("</script>");
			out.close();
			
			return null;
		}		
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/BoardDetailAction.do?board_num="+board_num+"&page="+page);
		
		return forward;
	}

}

- hidden 으로 넘어온 글 번호와 페이지 번호를 받아서 변수에 저장한다

- 수정을 하기 위해 필요한 정보들을 저장한 DTO 객체 board 에 세팅

- DAO의 getContent() 메소드를 호출해서 매개변수로 글번호를 전달, 데이터의 상세정보를 구해와서 객체 old 에 저장

- old.getBoard_pass() 인 DB속 비밀번호와 사용자가 수정폼에 입력한 비밀번호 board_pass 가 일치시 글을 수정하는 DAO의 update() 메소드를 호출해서 매개변수로 객체 board 를 넘겨줌

- 비밀번호가 일치하지 않았다면 이전페이지인 수정폼으로 돌아감

- 비밀번호 일치 후 수정을 성공적으로 완료했다면 상세페이지로 가도록 "/BoardDetailAction.do" 로 요청하며 글 번호, 페이지 번호를 넘긴다

 

- MemberDAO.java 에서 getContent() 메소드 부분만 

	// 상세 페이지, 수정 폼
	public BoardBean getContent(int board_num) throws Exception {
		BoardBean board = new BoardBean();

		SqlSession session=getSession();
		board  = (BoardBean)session.selectOne("board_content", board_num);
		
		return board;
	}

 

- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_content)

	<!-- 상세페이지, 수정폼 -->
	<select id="board_content" parameterType="int" resultType="board">
	 select * from model22 where board_num = #{board_num}
	</select>

 

- MemberDAO.java 에서 update() 메소드 부분만 

	// 글수정
	public void update(BoardBean board) throws Exception {
		SqlSession session=getSession();
		session.update("board_update", board);
	}

- session.update() 는 수정한 글 개수를 자동으로 반환해주지만 여기서는 리턴받지 않았다, select 가 아니면 리턴받을 필요 없음

 

- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_update)

	<!-- 글수정 -->
	<update id="board_update" parameterType="board">
	 update model22 set board_name=#{board_name}, board_subject=#{board_subject},
	 board_content=#{board_content} where board_num=#{board_num}
	</update>

- 객체 board 가 넘어왔으므로 paramterType 은 DTO alias 인 "board"

- update SQL문을 사용해서 넘어온 DTO 객체의 값들을 #{필드명} 으로 가져옴

ex) #{board_name} 은 board.getBoard_Name() 을 의미

 


JSP Model 2 - MyBatis 게시판 프로그램 : 글 삭제폼 / 글 삭제 기능

- qna_board_view 에서 출력된 버튼 중 '삭제' 버튼을 클릭시 "/BoardDeleteAcion.do" 로 요청

<input type="button" value="삭제" 
	onClick="location.href='./BoardDeleteAction.do?board_num=${board.board_num}&page=${page}'">

- "/BoardDeleteAcion.do" 로 가면서 글번호, 페이지번호 전달

- 이 글번호, 페이지번호는 목록 페이지 -> 상세페이지 -> 삭제폼 -> 삭제 까지 넘어간다

- 먼저 삭제폼으로 가게 된다

 

- Controller 클래스에서 "/BoardDeleteAcion.do" 부분만

		// 삭제 폼	
		}else if(command.equals("/BoardDeleteAction.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/board/qna_board_delete.jsp");
		}

- 삭제 폼에서는 DB 접속이 필요하지 않으므로 Service 클래스가 아닌 삭제폼 qna_board_delete.jsp 로 바로 포워딩 설정 후 Controller 클래스 하단에서 포워딩

 

- qna_board_delete.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>게시판 삭제</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>

<form action="<%=request.getContextPath() %>/BoardDelete.do" method="post">
<input type="hidden" name="board_num" value="${param.board_num}">
<input type="hidden" name="page" value="${param.page}">

<table width=300 cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">게시판 삭제</td>
	</tr>	
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>	
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="삭제">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

- EL 내장객체 param 으로 상세 페이지 qna_board_view.jsp 에서 get 방식으로 넘어온 글 번호와 페이지 번호를 받음

- 받은 글 번호와 페이지번호를 바로 form의 hidden 을 통해 "/BoardDelete.do" 로 전달함

+ 원하는 글 1개를 삭제하기 위해 반드시 글 번호를 전달해야함, 그리고 삭제 후 원래 페이지로 돌아가기 위해 페이지 번호 전달

- 그리고 사용자가 삭제폼에 입력한 비밀번호도 "/BoardDelete.do" 전달

 

- Controller 클래스에서 "/BoardDelete.do" 부분만

		// 삭제	
		}else if(command.equals("/BoardDelete.do")) {
			try {
				action = new BoardDelete();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 글 삭제 Service 클래스 BoardDelete.java

package service;

import java.io.File;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardDelete implements Action {

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("BoardDelete");
		
		response.setContentType("text/html; charset=utf-8"); 
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		
		String path = request.getRealPath("boardupload");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String page = request.getParameter("page");
		String board_pass  = request.getParameter("board_pass");
				
		BoardDAO dao = BoardDAO.getInstance();
		BoardBean old = dao.getContent(board_num); // 상세정보 구하기
		
		if(old.getBoard_pass().equals(board_pass)) { //비번 일치시
			dao.delete(board_num); // delete SQL
			
			if(old.getBoard_file() != null) { // 첨부파일이 있으면
				
				File file = new File(path);
				file.mkdirs();
				
				File[] f = file.listFiles();
				for(int i=0; i<f.length; i++) {
					if(f[i].getName().equals(old.getBoard_file())) {
						f[i].delete();
					}
				}				
			}
			
		}else {	// 비번 불일치시
	
			out.println("<script>");
			out.println("alert('비밀번호가 일치하지 않습니다.');");
			out.println("history.go(-1);");
			out.println("</script>");
			out.close();

			return null;			
		}
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/BoardListAction.do?page="+page);
		
		return forward;
	}

}

- qna_board_delete.jsp 에서 hidden 으로 넘어온 글 번호, 페이지 번호와 사용자가 삭제폼에 입력한 비밀번호를 받아서 변수에 저장한다

- DAO의 getContent() 메소드를 호출, 매개변수로 글 번호를 전달해서 해당 글의 상세정보를 구해온다

- 구해온 상세 정보에서 비밀번호를 가져와서 사용자가 삭제폼에 입력한 비밀번호와 비교

- 비번이 일치시 DAO의 delete() 메소드를 호출해서 글을 삭제함, 글 삭제 이후 첨부파일도 지워준다 * 아래에 설명

- 비번이 불일치시 위에서 생성한 out 객체로 alert 창을 출력하고 이전 페이지인 삭제폼으로 돌아감

- 비번이 일치하고 삭제 성공시 목록페이지로 가기 위해 포워딩 페이지를 목록을 가져오는 요청인 "/BoardListAction.do" 로 지정하고 페이지 번호 전달

 

이 Service 클래스에서 DAO의 메소드 2개를 호출해야한다

1) getContent() : 비번을 가져와서 비교하기 위해 호출

2) delete() : 글 삭제를 위해 호출

 

첨부파일 삭제 (BoardDelete.java 부분)

String path = request.getRealPath("boardupload");

if(old.getBoard_file() != null) { // 첨부파일이 있으면
				
				File file = new File(path);
				file.mkdirs();
				
				File[] f = file.listFiles();
				for(int i=0; i<f.length; i++) {
					if(f[i].getName().equals(old.getBoard_file())) {
						f[i].delete();
					}
				}				
			}

- "boardupload" 폴더의 경로로 File 객체 file 을 구한 후 listFiles() 로 File 배열 f 에 모든 파일을 저장

- 배열에 들은 파일명을 getName() 으로 하나씩 가져와서 DB에서 삭제한 글의 첨부파일명과 일치하면 그 첨부파일 삭제

 

- Model 2 게시판 프로그램에서 첨부파일 삭제 설명하는 부분 : https://laker99.tistory.com/131?category=1080281 

- Model 2 게시판 프로그램에서 첨부파일 삭제 설명하는 부분 검색어 : 첨부파일 지우기 (BoardDelete.java 부분)

 

- MemberDAO.java 에서 getContent() 메소드 부분만 

	// 상세 페이지, 수정 폼
	public BoardBean getContent(int board_num) throws Exception {
		BoardBean board = new BoardBean();

		SqlSession session=getSession();
		board  = (BoardBean)session.selectOne("board_content", board_num);
		
		return board;
	}

 

- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_content)

	<!-- 상세페이지, 수정폼 -->
	<select id="board_content" parameterType="int" resultType="board">
	 select * from model22 where board_num = #{board_num}
	</select>

 

- MemberDAO.java 에서 delete() 메소드 부분만 

	// 글삭제
	public void delete(int board_num) {
		SqlSession session=getSession();
		session.delete("board_delete", board_num);
	}

- 글 삭제를 위해 글 번호를 넘겨준다

 

- board.xml 에서 글 개수를 구해주는 처리하는 SQL문 부분 (id 가 board_delete)

	<!-- 글삭제 -->
	<delete id="board_delete" parameterType="int">
	 delete from model22 where board_num=#{board_num}
	</delete>

 

- 글을 삭제해보자

 

- 삭제폼으로 갈땐 Service 클래스로 가지 않으므로 콘솔창에 Service 클래스로 간 흔적이 없다

- 삭제 되었음, 후에 목록을 가져오고 목록페이지로 간다

 

복습

Maven

- pom.xml 에 의존 라이브러리 추가시 원격 저장소에서 로컬 저장소로 다운받음

- Spring Project 에서도 로컬 저장소(레포지토리) 는 똑같은 폴더를 쓴다

 

+ Spring

- Spring Project 도 이와 비슷한 폴더 구조로 만들어진다

- Spring Project 에서는 생성 시 부터 pom.xml 에 기본적인 라이브러리들이 들어있다

 

MyBatis 환경설정 파일

- 마켓플레이스에서 플러그인 다운받으면 생성할 수 있었지만 현재는 불가능

- 한개의 파일만 있으면 된다

- 들어가는 내용

1) Alias 설정

2) DB 연동

3) Mapper 파일 불러오기

 

Mapper 파일

- DAO 클래스 안의 SQL문이 들어있는 파일

- 마켓플레이스에서 플러그인 다운받으면 생성 가능

- 검색되는 컬럼명과 DTO 필드명이 다를때는 resultMap 태그로 매핑을 직접 잡아준다

 


JSP Model 2 - MyBatis 연동 : 회원가입 프로그램

Model 2 과 MyBatis 연동

- 클라우드에서 Model 2 과 MyBatis 가 이미 연동이 완료된 회원가입 프로그램 mybatismember 프로젝트 import

- zip 파일을 압축 풀어서 import

- import 하는 프로젝트는 Maven 프로젝트이다

- Maven 프로젝트이므로 구조는 다음과 같다

- src/main/java 안에 확장자가 java 인 파일들이 저장되어있다 (Controller, Service, DAO, DTO)

+ Model 2 이므로 src/main/java 안에서도 DTO, DAO, Service, Controller 가 서로 다른 패키지 안에 들어가있음

- src/main/resources 안에 DB 연동에 필요한 MyBatis 환경설정 파일 등이 들어가 있다

- src/main/webapp 안에 View 파일 등이 들어간다

 

- import한 mybatismember 프로젝트의 pom.xml 파일을 보자

- pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.myhome</groupId>
	<artifactId>mybatismember</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>mybatismember Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<!-- 복사 시작 -->
	<repositories>
		<repository>
			<id>codelds</id>
			<url>https://code.lds.org/nexus/content/groups/main-repo</url>
		</repository>
	</repositories>

	<dependencies>

		<!-- 오라클 JDBC Library -->
		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.3</version>
		</dependency>

		<!-- MySQL -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.47</version>
		</dependency>

		<!-- iBatis -->
		<dependency>
			<groupId>org.apache.ibatis</groupId>
			<artifactId>ibatis-sqlmap</artifactId>
			<version>2.3.4.726</version>
		</dependency>

		<!-- mybatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.5.3</version>
		</dependency>		

		<!-- jstl -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.taglibs</groupId>
			<artifactId>taglibs-standard-impl</artifactId>
			<version>1.2.5</version>
		</dependency>

		<!-- cos -->
		<dependency>
			<groupId>servlets.com</groupId>
			<artifactId>cos</artifactId>
			<version>05Nov2002</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<finalName>mybatismember</finalName>
	</build>
</project>

- 프로젝트 시작 시 가장 먼저 라이브러리를 구하는 작업을 해야한다

- dependencies 태그 안에 의존 라이브러리를 dependency 태그로 추가

- 오라클 JDBC, MySQL JDBC, iBatis, MyBatis 3.5.3 ver, JSTL, cos 라이브러리가 추가되었다

- 오라클 공식 저장소에서 다운받는 건 오류 많이 발생

- 오라클은 비공식 저장소 repository 를 원격 저장소 위치로 등록하고 거기서 오라클 JDBC 라이브러리를 불러오기

- MyBatis 연동시에는 반드시 MyBatis 라이브러리가 추가되어 있어야함

 

 

Model 2 에 MyBatis 연동시 구조

- Model 2 에서 MyBatis 를 연동

- Controller -> Service -> DAO -> MyBatis 와 연동해서 DB 연동

 

- db.properties

jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:xe
jdbc.username=totoro
jdbc.password=totoro123

- 여기 db.properties 에 적힌 이 계정으로 접근해서 테이블을 생성해야한다

 

- MyBatis 환경설정 파일인 mybatis-config.xml 파일을 보자

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<properties resource="db.properties" />
	<typeAliases>
		<typeAlias type="model.MemberDTO" alias="member"></typeAlias>
	</typeAliases>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driverClassName}" />
				<property name="url" value="${jdbc.url}" />
				<property name="username" value="${jdbc.username}" />
				<property name="password" value="${jdbc.password}" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper resource="member.xml" />
	</mappers>
</configuration>

 

- Mapper 파일 member.xml 을 보자

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="mymember">

	<!-- 회원가입 -->
	<insert id="insert" parameterType="member">
	  insert into member0609 values(#{id},#{passwd},#{name},#{jumin1},#{jumin2},
	  #{mailid},#{domain},#{tel1},#{tel2},#{tel3},#{phone1},#{phone2},#{phone3},
	  #{post},#{address},#{gender},#{hobby},#{intro},sysdate)
	</insert>
	
	<!-- ID중복검사, 회원인증 -->
	<select id="idcheck" parameterType="String" resultType="member">
	 select * from member0609 where id = #{id}
	</select>
	
	<!-- 회원정보 수정 -->
	<update id="update" parameterType="member">
	  update member0609 set name=#{name}, jumin1=#{jumin1}, jumin2=#{jumin2}, 
	  mailid=#{mailid}, domain=#{domain}, tel1=#{tel1}, tel2=#{tel2}, tel3=#{tel3},
	  phone1=#{phone1}, phone2=#{phone2}, phone3=#{phone3}, post=#{post}, address=#{address},
	  gender=#{gender}, hobby=#{hobby}, intro=#{intro} where id = #{id}
	</update>

	<!-- 회원 삭제 -->
	<delete id="delete" parameterType="String">
	  delete from member0609 where id = #{id}
	</delete> 

</mapper>

 

 


 

- db.properties 파일에서 totoro 계정으로 설정되어있으므로 totoro 계정에서 테이블을 생성해야함

+ import 시에 테이블이 생성되지는 않음

- sql 폴더의 model2member.sql 에서 totoro 계정으로 연결한 후 member0609 테이블을 생성하자

- 커넥션 프로파일 설정

- model2member.sql

-- 회원관리
select * from tab;
select * from member0609;

create table member0609(
	id varchar2(20) primary key,
	passwd  varchar2(20) not null,
	name varchar2(20) not null,
	jumin1 varchar2(6) not null,
	jumin2 varchar2(7) not null,
	mailid varchar2(30), 
	domain varchar2(30), 
	tel1 varchar2(5),
	tel2 varchar2(5),
	tel3 varchar2(5),
	phone1 varchar2(5),
	phone2 varchar2(5),
	phone3 varchar2(5),
	post varchar2(10),
	address varchar2(200),
	gender varchar2(20),
	hobby varchar2(50),
	intro varchar2(2000),
	register timestamp );

- 테이블 member0609 를 생성하자

- 테이블의 필드명과 DTO 클래스의 멤버변수(필드, 프로퍼티) 명을 같은 이름으로 설정해야 매핑이 자동으로 일어난다


JSP Model 2 - MyBatis 회원관리 프로그램 : 기본 파일들

- index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

<!-- 모델2 회원관리 -->

<script>
	location.href="./LoginForm.do";
</script>

</body>
</html>

- index.jsp 실행이나 현재 프로젝트 Run As - Run on Server 로 실행시키자

- 로그인 폼이 나타난다

- '회원가입' 을 눌러 회원가입 폼으로 가서 가입을 시키자

- 이제 로그인 페이지로 넘어간다, 아이디, 비번이 일치하면 로그인 성공한다

- 정보수정을 해보자

- 비번이 일치할때 수정된다!

 

Model 2 흐름

Model 2 요청할때 흐름

- View 에서 Controller 로 찾아갈때 WebServlet 어노테이션의 패턴을 맞춰서 찾아간다

- Controller 에서 해당 Service 클래스로 넘긴다, Service 클래스에서 DAO 의 메소드를 불러서 DB와 연동

 

Model 2 돌아올때 흐름

- DAO 클래스에서 검색한 결과를 Service 로 넘기면 Service 에서 결과를 공유설정한다

- 다시 Controller 클래스로 돌아와서 View 페이지로 포워딩됨

 

- src/main/java 에서 model 패키지의 DTO 클래스인 MemberDTO.java 를 보자

// DTO(Data Transfer Object)

package model;

import java.sql.Timestamp;

public class MemberDTO {
	private String id;
	private String passwd;
	private String name;
	private String jumin1;
	private String jumin2;
	private String mailid;
	private String domain;
	private String tel1;
	private String tel2;
	private String tel3;
	private String phone1;
	private String phone2;
	private String phone3;
	private String post;
	private String address;
	private String gender;
	private String hobby;
	private String intro;
	private Timestamp register;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPasswd() {
		return passwd;
	}
	public void setPasswd(String passwd) {
		this.passwd = passwd;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getJumin1() {
		return jumin1;
	}
	public void setJumin1(String jumin1) {
		this.jumin1 = jumin1;
	}
	public String getJumin2() {
		return jumin2;
	}
	public void setJumin2(String jumin2) {
		this.jumin2 = jumin2;
	}
	public String getMailid() {
		return mailid;
	}
	public void setMailid(String mailid) {
		this.mailid = mailid;
	}
	public String getDomain() {
		return domain;
	}
	public void setDomain(String domain) {
		this.domain = domain;
	}
	public String getTel1() {
		return tel1;
	}
	public void setTel1(String tel1) {
		this.tel1 = tel1;
	}
	public String getTel2() {
		return tel2;
	}
	public void setTel2(String tel2) {
		this.tel2 = tel2;
	}
	public String getTel3() {
		return tel3;
	}
	public void setTel3(String tel3) {
		this.tel3 = tel3;
	}
	public String getPhone1() {
		return phone1;
	}
	public void setPhone1(String phone1) {
		this.phone1 = phone1;
	}
	public String getPhone2() {
		return phone2;
	}
	public void setPhone2(String phone2) {
		this.phone2 = phone2;
	}
	public String getPhone3() {
		return phone3;
	}
	public void setPhone3(String phone3) {
		this.phone3 = phone3;
	}
	public String getPost() {
		return post;
	}
	public void setPost(String post) {
		this.post = post;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	public String getGender() {
		return gender;
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	public String getHobby() {
		return hobby;
	}
	public void setHobby(String hobby) {
		this.hobby = hobby;
	}
	public String getIntro() {
		return intro;
	}
	public void setIntro(String intro) {
		this.intro = intro;
	}
	public Timestamp getRegister() {
		return register;
	}
	public void setRegister(Timestamp register) {
		this.register = register;
	}

}

- 테이블의 필드명과 DTO 클래스의 멤버변수(필드, 프로퍼티) 명을 같은 이름으로 설정해야 매핑이 자동으로 일어난다

- 프로퍼티명이 컬럼명과 동일하게 되어있다, 쉽게 매핑 가능해짐


- Controller 클래스 MemberController.java 를 보자

package controller;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import service.Action;
import service.ActionForward;
import service.Delete;
import service.Idcheck;
import service.Login;
import service.MemberInsert;
import service.Update;
import service.UpdateMember;

/**
 * Servlet implementation class MemberController
 */
@WebServlet("*.do")
public class MemberController extends HttpServlet {
	private static final long serialVersionUID = 1L;

	public void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();               
		String command = requestURI.substring(contextPath.length());  
		
		System.out.println("requestURI:"+requestURI);    // requestURI: /model2member/MemberInsert.do
		System.out.println("contextPath:"+contextPath);  // contextPath: /model2member
		System.out.println("command:"+command);          // command: /MemberInsert.do
		
		Action action = null;
		ActionForward forward = null;
		
		// 회원 가입
		if(command.equals("/MemberInsert.do")) {
			try {
				action = new MemberInsert();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 회원가입 폼	
		}else if(command.equals("/MemberForm.do")) {
			forward = new ActionForward();
			forward.setRedirect(true);
			forward.setPath("./member/memberform.jsp");
			
		// ID중복 검사(ajax)	
		}else if(command.equals("/Idcheck.do")) {
			try {
				action = new Idcheck();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 로그인(회원인증)	
		}else if(command.equals("/Login.do")) {
			try {
				action = new Login();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 로그인 폼	
		}else if(command.equals("/LoginForm.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/member/loginform.jsp");	
			
		// 로그 아웃	
		}else if(command.equals("/Logout.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/member/logout.jsp");
			
		// 회원정보 수정폼	
		}else if(command.equals("/UpdateMember.do")) {
			try {
				action = new UpdateMember();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 회원정보 수정	
		}else if(command.equals("/Update.do")) {
			try {
				action = new Update();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		
		// 회원탈퇴 폼	
		}else if(command.equals("/DeleteMember.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/member/deleteform.jsp");
			
		// 회원탈퇴	
		}else if(command.equals("/Delete.do")) {
			try {
				action = new Delete();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
		
		// 포워딩 처리
		if(forward != null) {
			if(forward.isRedirect()) {		// redirect 방식으로 포워딩
				response.sendRedirect(forward.getPath());
			}else {							// dispatcher 방식으로 포워딩
				RequestDispatcher dispatcher = 
						request.getRequestDispatcher(forward.getPath());
				dispatcher.forward(request, response);
			}
		}		
		
	}// doProcess() end	
	
	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		System.out.println("get");
	
		doProcess(request, response);
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		System.out.println("post");
		
		doProcess(request, response);		
	}

}

- 사용자가 get 방식 요청시 doGet() 이 자동실행되고, post 방식 요청시 doPost() 가 자동 실행됨

- 공통적인 작업을 하기 위한 doProcess() 메소드

 

Controller 클래스로 찾아가는 2가지 방법

1. WebServlet 어노테이션 패턴을 지정하고, 맞는 패턴으로 찾아가는 방법

- 현재 Model 2

2. Spring 에서는 web.xml 파일 안에서 Controller 클래스 이름, 위치가 등록되고, 매핑을 통해 찾아감

- Model 2 도 예전에 이방법을 사용했었다

 


- 다음은 DAO 클래스 MemberDAO.java 를 보자

// DAO(Data Access Object)

package dao;

import java.io.Reader;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import model.MemberDTO;

public class MemberDAO {
	
	private static MemberDAO instance = new MemberDAO();
	
	public static MemberDAO getInstance() {
		return instance;
	}
	
	public SqlSession getSession() {
		SqlSession session=null;
		Reader reader=null;			
		try {
			reader = Resources.getResourceAsReader("mybatis-config.xml");
			SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(reader);
			session = sf.openSession(true);	 // auto commit	
		}catch(Exception e) {
			e.printStackTrace();
		}		
		return session;
	}
		
		
	// 회원가입
	public int insert(MemberDTO member) throws Exception{
		int result=0;
		SqlSession session = getSession();
		result = session.insert("insert", member);		
		System.out.println("result:"+result);
		
		return result;
	}
	
	// ID중복검사
	public int idcheck(String id) throws Exception{
		int result = 0;
		SqlSession session = getSession();
		MemberDTO member = session.selectOne("idcheck", id);
		if(member != null) {	// 중복 ID	
			result = 1;			
		}else {					// 사용가능한 ID
			result = -1;
		}
		
		return result;
	}
	
	// 로그인(회원인증)
	public int memberAuth(String id, String passwd) throws Exception {
		int result = 0;
		SqlSession session = getSession();
		MemberDTO member = session.selectOne("idcheck", id);
		if(member != null) {		// 중복 ID	
			if(member.getPasswd().equals(passwd)) {
				result = 1;			// 회원인증 성공
			}else {
				result = -1;		// 회원인증 실패
			}
		}

		return result;
	}

	
	// 회원 1명 정보 구하기 : 수정폼, 수정, 삭제
	public MemberDTO getMember(String id) throws Exception{
		SqlSession session = getSession();
		MemberDTO member = session.selectOne("idcheck", id);
		
		return member;
	}
	
	
	// 회원정보 수정
	public int update(MemberDTO member) throws Exception{
		int result = 0;
		SqlSession session = getSession();
		result = session.update("update", member);

		return result;
	}

	// 회원 탈퇴
	public int delete(String id) throws Exception{
		int result = 0;
		SqlSession session = getSession();
		result = session.delete("delete", id);		
		
		return result;
	}	

}

- MyBatis 와 연동하면 DAO 이후부터가 달라지는 내용이다

- SQL문이 Mapper xml 파일로 빠지고, 그 중간에 MyBatis 환경설정 파일이 있음

 

SqlSession 객체를 구해주는 메소드 (MemberDAO.java 부분)

 

	public SqlSession getSession() {
		SqlSession session=null;
		Reader reader=null;			
		try {
			reader = Resources.getResourceAsReader("mybatis-config.xml");
			SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(reader);
			session = sf.openSession(true);	 // auto commit	
		}catch(Exception e) {
			e.printStackTrace();
		}		
		return session;
	}

- MyBatis 환경설정 파일을 읽어와서 Reader 객체를 구하고, SqlSessionFactory 객체를 구하고, 세션을 openSession 으로 열어서 그 세션을 반환

- SqlSession 객체가 만들어지면, SqlSession 가 제공하는 메소드 5개를 사용할 수 있다

- Maven 에 추가했던 MyBatis 라이브러리의 클래스와 인터페이스들을 DAO 에서 import 해야한다


- MyBatis 환경설정 파일인 mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<properties resource="db.properties" />
	<typeAliases>
		<typeAlias type="model.MemberDTO" alias="member"></typeAlias>
	</typeAliases>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driverClassName}" />
				<property name="url" value="${jdbc.url}" />
				<property name="username" value="${jdbc.username}" />
				<property name="password" value="${jdbc.password}" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper resource="member.xml" />
	</mappers>
</configuration>

- property 태그 : 멤버변수를 property 라고 한다, 이 변수 "driver", "url" 등이 모두 프로퍼티 명임, Spring 의 DI 개념

- "driver" 변수에는 오라클과 연동하므로 오라클용 JDBC 드라이버 위치가 저장된 변수값을 사용

 

- ${jdbc.driverClassName} 등은 db.properties 파일에 있는 변수임

+ db.properties

jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:xe
jdbc.username=totoro
jdbc.password=totoro123

 

- mappers 태그 안에서 Mapper 파일을 불러온다

- 같은 폴더 내에 있으므로 이름인 member.xml 만으로 불러온다

- 테이블이 늘어나면 Mapper 파일이 늘어나고, 이 mappers 태그 안의 코드도 늘어남

- 이 과정을 통해 MyBatis 환경 설정파일은 Mapper 파일을 불러오고, DAO 는 MyBatis 환경 설정 파일을 불러오므로 DAO 에서 Mapper 파일의 id 를 사용 가능하다


- 다음은 Mapper 파일인 member.xml 을 보자

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="mymember">

	<!-- 회원가입 -->
	<insert id="insert" parameterType="member">
	  insert into member0609 values(#{id},#{passwd},#{name},#{jumin1},#{jumin2},
	  #{mailid},#{domain},#{tel1},#{tel2},#{tel3},#{phone1},#{phone2},#{phone3},
	  #{post},#{address},#{gender},#{hobby},#{intro},sysdate)
	</insert>
	
	<!-- ID중복검사, 회원인증 -->
	<select id="idcheck" parameterType="String" resultType="member">
	 select * from member0609 where id = #{id}
	</select>
	
	<!-- 회원정보 수정 -->
	<update id="update" parameterType="member">
	  update member0609 set name=#{name}, jumin1=#{jumin1}, jumin2=#{jumin2}, 
	  mailid=#{mailid}, domain=#{domain}, tel1=#{tel1}, tel2=#{tel2}, tel3=#{tel3},
	  phone1=#{phone1}, phone2=#{phone2}, phone3=#{phone3}, post=#{post}, address=#{address},
	  gender=#{gender}, hobby=#{hobby}, intro=#{intro} where id = #{id}
	</update>

	<!-- 회원 삭제 -->
	<delete id="delete" parameterType="String">
	  delete from member0609 where id = #{id}
	</delete> 

</mapper>

- MyBatis 환경설정 파일에서 이 Mapper 파일을 불러올때 "mapper" 태그 안에서 불러왔으므로 이 Mapper 파일의 루트 엘리먼트도 "mapper" 이어야함

- namespace 값을 써서 다른 Mapper 파일 내에 id 중복 문제를 피한다

- 넘어오는 값의 자료형을 parameterType 에 쓰고, 돌려주는 값의 자료형을 returnType 에 쓴다


JSP Model 2 - MyBatis 연동 구조



JSP Model 2 - MyBatis 회원관리 프로그램 : 회원가입 기능

흐름 설명

1. index.jsp 에서 "/LoginForm.do" 로 요청

2. 어노테이션 패턴에 맞으므로 Controller 클래스로 찾아감, doGet() -> doProcess() 실행됨

- index.jsp 에서 location.href 로 Controller 로 찾아왔다, 폼이 아니므로 Get 방식 전송

3. command 변수에 저장된 내용을 통해 ActionForward 객체 forward 생성 후 포워딩 페이지, 방식 설정 후 밑에서 포워딩

- loginForm.jsp 로 포워딩 됨

- DB 연동안하므로 Service 클래스가 아닌 폼으로 감

4. 로그인 폼인 loginForm.jsp 로 넘어와서 '회원가입' 버튼을 누르면 "/MemberForm.do" 로 요청, 즉 회원가입 폼으로 간다

5.어노테이션 패턴에 맞으므로 Controller 클래스로 찾아감, doGet() -> doProcess() 실행됨

- loginForm.jsp 에서 location.href 로 Controller 찾아왔다, 폼이 아니므로 Get 방식 전송

6. command 변수에 저장된 내용을 통해 ActionForward 객체 forward 생성 후 포워딩 페이지, 방식 설정 후 밑에서 포워딩

- memberForm.jsp 로 포워딩 됨

7. 회원가입 폼인 memberForm.jsp 로 넘어와서 정보 입력 후 '회원가입' 버튼을 누르면 "/MemberInsert.do" 로 요청

- form에서 post 방식으로 전송

8. 어노테이션 패턴에 맞으므로 Controller 클래스로 찾아감. doPost() -> doProcess() 실행됨

9. command 변수에 저장된 내용을 통해 회원가입을 처리해주는 Service 클래스인 MemberInsert.java 로 보내줌

10.MemberInsert.java 에서 DTO 객체생성 후 회원가입 폼에서 입력된 값들을 모두 DTO 객체에 저장

- Spring 에서는 직접 DTO 객체 생성하고,Setter 메소드로 일일히 저장하는 작업 하지 않는다, 어노테이션으로 처리함

11. DAO 객체를 생성 후 DAO 의 insert() 메소드 호출하고, 매개변수로는 값을 설정한 DTO 객체를 전달

12. DAO 의 insert() 메소드안에서 SqlSession 객체를 받고, session.insert("insert',member) 로 id 가 insert 인 Mapper 파일의 SQL문 호출

- 삽입할 데이터를 전달해줘야 하므로, 전달할 값인 DTO 객체 member 를 두번째 매개변수에 써서 전달

13. Mapper 파일 member.xml 에서 id 가 insert 인 태그 안 SQL문 실행 후 삽입된 데이터 개수 1개를 돌려줌

14. DAO 에서 result 로 그 삽입된 데이터 개수 1개를 받아서 Service 클래스로 리턴함

15. Service 에서 - Service 클래스 하단에서 포워딩 방식, 포워딩할 페이지 지정

- Dispatcher 방식, loginform.jsp 페이지로 포워딩할 것

16. Service 클래스를 호출한 Controller 클래스로 돌아간다

17. Controller 클래스에서 forward != null 이므로 설정된 곳으로 포워딩함

- Dispatcher 방식, loginform.jsp 페이지로 포워딩 한다

 

코드 및 설명

- Controller 클래스에서 "/MemberForm.do" 부분만

		// 회원가입 폼	
		}else if(command.equals("/MemberForm.do")) {
			forward = new ActionForward();
			forward.setRedirect(true);
			forward.setPath("./member/memberform.jsp");
		}

- 회원가입 폼 memberform.jsp 는 길어서 코드 붙이지 않았다 * 나중에 ID중복검사 기능 부분 설명때 붙일 것

- Controller 클래스에서 "/MemberInsert.do" 부분만

		// 회원 가입
		if(command.equals("/MemberInsert.do")) {
			try {
				action = new MemberInsert();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 회원가입 Service 클래스인 MemberInsert.java

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.MemberDAO;
import model.MemberDTO;

public class MemberInsert implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("MemberInsert");
		
		request.setCharacterEncoding("utf-8");
		
		MemberDTO member = new MemberDTO();
		member.setId(request.getParameter("id"));
		member.setPasswd(request.getParameter("passwd"));
		member.setName(request.getParameter("name"));
		member.setJumin1(request.getParameter("jumin1"));
		member.setJumin2(request.getParameter("jumin2"));
		member.setMailid(request.getParameter("mailid"));
		member.setDomain(request.getParameter("domain"));
		member.setTel1(request.getParameter("tel1"));
		member.setTel2(request.getParameter("tel2"));
		member.setTel3(request.getParameter("tel3"));
		member.setPhone1(request.getParameter("phone1"));
		member.setPhone2(request.getParameter("phone2"));
		member.setPhone3(request.getParameter("phone3"));
		member.setPost(request.getParameter("post"));
		member.setAddress(request.getParameter("address"));
		member.setGender(request.getParameter("gender"));
		
		String h = "";
		String[] h1 = request.getParameterValues("hobby");
		for(String h2 : h1) {
			h += h2+"-";			// 공부-게임-
		}
		member.setHobby(h);
		
		member.setIntro(request.getParameter("intro"));
		
		MemberDAO dao = MemberDAO.getInstance();
		int result = dao.insert(member);
		if(result == 1) {
			System.out.println("회원가입 성공");
		}
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/member/loginform.jsp");
		
		return forward;
	}

}

 

- MemberDAO.java 에서 insert() 메소드 부분만

	// 회원가입
	public int insert(MemberDTO member) throws Exception{
		int result=0;
		SqlSession session = getSession();
		result = session.insert("insert", member);		
		System.out.println("result:"+result);
		
		return result;
	}

- Maven 에 추가했던 MyBatis 라이브러리의 클래스와 인터페이스들을 DAO 에서 import 해야한다

- session.insert("아이디", 전달할 값) 로 Mapper 파일의 SQL문을 불러오고 있다

- session.insert() 실행 후 자동으로 삽입 성공 데이터 개수 (1) 를 돌려준다, 그걸 result 변수에 받아서 리턴하고 있음

- DB 연동을 수행하므로 예외처리를 해야한다

- try - catch 로 직접 예외처리를 해도 되고, 메소드를 호출한 곳에 throws 로 예외를 던져도 된다

 

- member.xml 에서 회원 가입을 처리하는 SQL문 부분 (id 가 insert)

	<!-- 회원가입 -->
	<insert id="insert" parameterType="member">
	  insert into member0609 values(#{id},#{passwd},#{name},#{jumin1},#{jumin2},
	  #{mailid},#{domain},#{tel1},#{tel2},#{tel3},#{phone1},#{phone2},#{phone3},
	  #{post},#{address},#{gender},#{hobby},#{intro},sysdate)
	</insert>

- parameterType 속성값인 "member"는 MyBatis 환경설정 파일에서 Alias 값으로 지정한 별칭이다

- #{mailid} 의 의미는 member.getMailid()

 

+ member.xml 의 namespace

- Mapper 파일은 테이블 수에 비례해서 늘어나므로 namespace 가 있어야 서로 다른 파일에서 id 중복이 되더라도 충돌이 일어나지 않음

 

- 회원가입 시도시 콘솔창

- 회원가입이 성공적으로 되었음


JSP Model 2 - MyBatis 회원관리 프로그램 : 아이디 중복검사 기능

코드와 흐름 같이 설명

- ajax 로 처리

- 회원가입 폼인 memberform.jsp 파일을 보자

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원 가입 폼</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>

<script src="http://dmaps.daum.net/map_js_init/postcode.v2.js"></script>
<script>
	function openDaumPostcode() {
		new daum.Postcode({
			oncomplete : function(data) {
				// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
				// 우편번호와 주소 정보를 해당 필드에 넣고, 커서를 상세주소 필드로 이동한다.
//				document.getElementById('join_zip1').value = data.postcode1;
//				document.getElementById('join_zip2').value = data.postcode2;
				document.getElementById('post').value = data.zonecode;
				document.getElementById('address').value = data.address;
				
			}
		}).open();
	}
</script>


<!-- 외부 자바스크립트 파일 불러오기 -->
<script src="<%=request.getContextPath() %>/member/member.js"></script>

</head>
<body>

<form method="post" action="<%=request.getContextPath() %>/MemberInsert.do"> 
<table border=1 width=500 align=center>
	<caption>회원 가입</caption>
	<tr><td>ID</td>
		<td><input type=text autofocus="autofocus" id="id" name="id">
			<input type=button value="ID중복검사" id="idcheck">
			<div id="myid"></div>
		</td>
	</tr>
	<tr><td>비밀번호</td>
		<td><input type=password id="passwd" name="passwd"></td>
	</tr>
	<tr><td>성명</td>
		<td><input type=text id="name" name="name"></td>
	</tr>
	<tr><td>주민번호</td>
		<td><input type=text size=6 maxlength=6 id="jumin1" name="jumin1">-
			<input type=text size=7 maxlength=7 id="jumin2" name="jumin2">
		</td>
	</tr>
	<tr><td>E-Mail</td>
		<td><input type=text size=10 id="mailid" name="mailid">@
		    <input type=text size=10 id="domain" name="domain">
		    <select id="email">
		    	<option value="">직접입력</option>
		    	<option value="naver.com">네이버</option>
		    	<option value="daum.net">다음</option>
		    	<option value="nate.com">네이트</option>
		    	<option value="gmail.com">gmail</option>
		    </select>		    
		 </td>
	</tr>
	<tr><td>전화번호</td>
		<td><input type=text size=4 id="tel1" name="tel1" maxlength=4>-
			<input type=text size=4 id="tel2" name="tel2" maxlength=4>-
			<input type=text size=4 id="tel3" name="tel3" maxlength=4>
		</td>
	</tr>
	<tr><td>핸드폰</td>
		<td><select id="phone1" name="phone1">
				<option value="">번호선택</option>
				<option value="010">010</option>
				<option value="011">011</option>
				<option value="016">016</option>
				<option value="018">018</option>
				<option value="019">019</option>
			</select>-
			<input type=text size=4 id="phone2" name="phone2" maxlength=4>-
			<input type=text size=4 id="phone3" name="phone3" maxlength=4>
		</td>
	</tr>
	<tr><td>우편번호</td>
		<td><input type=text size=5 id="post" name="post">
			<input type=button value="우편번호검색" 
			       onClick="openDaumPostcode()">
		</td>
	</tr>
	<tr><td>주소</td>
		<td><input type=text size=45 id="address" name="address"></td>
	</tr>
	<tr><td>성별</td>
		<td>
			<input type=radio id="male" name="gender" value="남자">남자
			<input type=radio id="female" name="gender" value="여자">여자
		</td>
	</tr>
	<tr><td>취미</td>
		<td>
			<input type="checkbox" id="h1" name="hobby" value="공부" checked>공부
			<input type="checkbox" id="h2" name="hobby" value="게임">게임
			<input type="checkbox" id="h3" name="hobby" value="등산">등산
			<input type="checkbox" id="h4" name="hobby" value="낚시">낚시
			<input type="checkbox" id="h5" name="hobby" value="쇼핑">쇼핑
		</td>
	</tr>	
	<tr><td>자기소개</td>
		<td>
			<textarea id="intro" name="intro" rows="5" cols="50" placeholder="자기소개를 100자 이내로 입력하세요"></textarea>
		</td>
	</tr>
	<tr><td colspan=2 align=center>
			<input type=submit value="회원가입">
			<input type=reset value="취소">
		</td>
	</tr>		
</table>
</form>


</body>
</html>

- ID 중복검사 부분의 버튼의 id 는 "idcheck", 메세지를 출력할 div 태그의 id 값은 "myid", ID 입력양식의 id 는 "id"

- member.jsp 파일을 외부 자바 스크립트 파일로 불러온다

 

- member.jsp 파일에서 ID중복검사 부분을 보자

$(document).ready(function(){	
	
	// ID 중복검사
	$("#idcheck").click(function(){
		if($("#id").val()==""){
			alert("ID를 입력하세요");
			$("#id").focus();
			return false;
		}else{
			
			var id = $("#id").val();	
			
			$.ajax({
				type:"post",
				url:"/mybatismember/Idcheck.do",
				data:{"id":id},
				datatype:"text",
				success:function(data){
//					alert(data);
					
					if(data==1){	// 중복 ID
						$("#myid").text("중복ID");
						$("#id").val("").focus();
					}else{			// 사용 가능한 ID
						$("#myid").text("사용 가능한 ID");
						$("#passwd").focus();
					}					
				}
			});			
		}		
	});
})

- jQuery 로 버튼을 가장 먼저 구해오고, click 함수처리 + click 대신 keyup 이벤트로 처리해도 된다

- 입력양식에 값이 입력되지 않아씅면 경고창 띄우기

- 값이 입력되었을때 그 값을 가져와서 변수 id 에 저장

- $.ajax() 를 통해 "Idcheck.do" 로 요청

- 패키지가 다르므로 패키지명도 앞에 써준다

- 값을 가져갈때는 json 형태로 값을 가져간다 "id" 속성명에 id 값을 가져감

- 콜백함수로 데이터를 돌려받음, 돌려받은 뒤 1이 리턴되면 중복 ID 로 처리함, 리턴되는 값이 1 이 아니면 사용가능한 ID로 처리

 

- Controller 클래스로 간다

- Controller 클래스에서 "/Idcheck.do" 부분만

		// ID중복 검사(ajax)	
		}else if(command.equals("/Idcheck.do")) {
			try {
				action = new Idcheck();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 여기서 Service 클래스인 Idcheck.java 파일로 보내준다

- Idcheck.java

package service;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.MemberDAO;

public class Idcheck implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("Idcheck");
		
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		
		String id = request.getParameter("id");
		System.out.println("id:"+ id);
		
		MemberDAO dao = MemberDAO.getInstance();
		int result = dao.idcheck(id);
		System.out.println("result:"+ result);	// 1 :  중복ID, -1 : 사용가능한 ID
		out.println(result);	// 브라우저에 출력되는 값이 callback함수로 리턴된다.
		
		return null;
	}

}

- member.js 에서 ajax 로 전달한 사용자가 입력한 id 값을 속성명 "id" 를 사용해서 request.getParameter("id") 로 받음

- DB와 연동하여 중복 ID 인지 아닌지 확인해야하므로 DAO 객체 생성 후 DAO의 idcheck() 메소드 호출

- idcheck() 메소드 호출 시 매개변수로 사용자가 입력한 id 값을 전달함

- idcheck() 에서 리턴된 값을 result 로 받음, 이때 중복 ID는 1, 사용가능한 ID는 -1 로 전달해주기로 함

- 콜백함수로 리턴되는 값은 브라우저에 출력되는 값이므로 out 객체로 result 값을 출력

+ System.out.println() 은 콘솔에 출력되는 값, out.println() 은 브라우저에 출력되는 값

 

- MemberDAO.java 에서 idcheck() 메소드 부분만

	// ID중복검사
	public int idcheck(String id) throws Exception{
		int result = 0;
		SqlSession session = getSession();
		MemberDTO member = session.selectOne("idcheck", id);
		if(member != null) {	// 중복 ID	
			result = 1;			
		}else {					// 사용가능한 ID
			result = -1;
		}
		
		return result;
	}

- 매개변수로 사용자가 입력한 id 값을 전달받음

- SqlSession 객체를 변수 session 으로 받아오고 session.selectOne() 메소드를 호출하면서 Mapper 파일에서 id 가 "idcheck" 인 태그의 SQL문 호출

- 그 SQL문을 호출하면서 사용자가 입력한 id 를 넘김

- 사용자가 입력한 id 에 해당하는 검색되는 결과가 1개이므로 selectOne() 메소드로 검색해야함

- selectOne() 을 호출 후 DTO 객체 member 를 반환함, 그 객체가 있으면 DB에 이미 같은 id 인 데이터가 있다는 의미이므로 1 리턴

- 받아온 객체가 null 이면 같은 id 인 데이터가 없다는 의미이므로 사용가능한 id 라는 의미로 -1 리턴

 

  - member.xml 에서 ID중복검사를 처리하는 SQL문 부분 (id 가 idcheck)

	<!-- ID중복검사, 회원인증 -->
	<select id="idcheck" parameterType="String" resultType="member">
	 select * from member0609 where id = #{id}
	</select>

- 전달되는 값은 id 이므로, 전달되는 값의 자료형인 String 을 parameterType 으로 쓴다

+ parameterType 속성값인 "member"는 MyBatis 환경설정 파일에서 Alias 값으로 지정한 별칭이다

- 돌려주는 데이터 타입은 DTO 이므로, resultType 에 member 를 쓴다

- SQL문에서 #{id} 는 전달되는 값의 변수명인 id 를 써준다

 

- 중복 아닌 ID 입력 후 'ID중복검사' 눌렀을때 콘솔 창

- 중복 ID 입력 후 'ID중복검사' 눌렀을때 콘솔 창


JSP Model 2 - MyBatis 회원관리 프로그램 : 로그인 기능

흐름 설명

1. index.jsp 에서 "/LoginForm.do" 로 요청

2. 어노테이션 패턴에 맞으므로 Controller 클래스로 찾아감, doGet() -> doProcess() 실행됨

- index.jsp 에서 location.href 로 Controller 로 찾아왔다, 폼이 아니므로 Get 방식 전송

3. command 변수에 저장된 내용을 통해 ActionForward 객체 forward 생성 후 포워딩 페이지, 방식 설정 후 밑에서 포워딩

- loginForm.jsp 로 포워딩 됨

- DB 연동안하므로 Service 클래스가 아닌 폼으로 감

4. 로그인 폼인 loginForm.jsp 로 넘어와서 아이디, 비번 입력 후 '로그인' 클릭시 form 태그에서 "/Login.do" 로 요청

- 사용자가 입력한 아이디, 비번 값도 전달함

- post 방식으로 요청했다

5. 어노테이션 패턴에 맞으므로 Controller 클래스로 찾아감, doPost() -> doProcess() 실행됨

- loginForm.jsp 에서 form 에서 post 방식으로 Controller 로 찾아왔다

+ 이땐 ActionForward 객체 forward 가 null 이므로 포워딩 코드에 접근하지 않음

6. command 변수에 저장된 내용을 통해 로그인(회원 인증)을 처리해주는 Service 클래스인 Login.java 로 보내줌

7. Login.java 에서 로그인 성공시 필요한 session 객체와 out 객체를 구한다

8. Login.java 에서 사용자가 로그인 폼에 입력한 아이디, 비밀번호를 받아서 저장

9. 회원 인증을 처리하기 위해 DB와 연동해야하고, DAO 객체를 생성하고 DAO의 memberAuth(id, passwd) 메소드 호출

- 매개변수로 사용자가 전달한 id, passwd 를 전달

10. DAO 클래스에서 memberAuth() 에서 SqlSession 객체 session 을 구하고 session.selectOne("idcheck",id) 호출하며 Mapper 파일에서 id 가 "idcheck" 인 태그의 SQL문 호출

- 1명에 대한 데이터를 가져오므로 selectOne() 사용

- 전달하는 값은 사용자가 입력한 id 이다

11. Mapper 파일 member.xml 에서 id 가 idcheck인 태그 안 SQL문 실행 후 id 에 해당하는 1명에 대한 상세정보를 DTO 객체로 리턴함

12. DAO 클래스에서 selectOne() 실행 후 돌려받은 값을 DTO 객체 member 에 저장한다

13. 그 객체 member 가 null 이 아니면 그 id 로 검색한 데이터가 DB에 존재한다는 뜻이므로 id가 일치한다는 의미

14. 다음엔 사용자가 입력한 비번과 member 에서 가져온 DB의 비번이 각각 일치시 회원 인증 성공으로 보고 1을 리턴

- 일치하지 않으면 회원 인증 실패로 보고 -1 리턴

15. Service 클래스로 돌아와서 memberAuth() 가 1을 리턴하면 성공으로 보고, 세션으로 id 값을 공유설정하고 포워딩 방식과 포워딩 페이지 설정 

- 여기서부터 SESSION 영역 시작

- 포워딩 할 페이지는 main.jsp 로 설정

+ session 공유시엔 상관없고, Service 클래스에서 request 방식 공유를 했을땐 Dispatcher 방식으로 포워딩 설정해야만 공유된 값 사용 가능

- 로그인 실패시 메세지를 뿌리고 이전 페이지로 돌아감

16. Service 클래스를 호출한 Controller 클래스로 돌아가서, 생성되고 값이 설정된 forward 객체를 리턴함

17. Controller 클래스에서 forward != null 이므로 Controller 클래스 하단에서 포워딩할 페이지, 방식을 가져와서 main.jsp 로 Dispatcher 방식 포워딩함

- Service 하단에서 설정했던 포워딩 페이지, 방식을 여기서 구해와서 실제 포워딩하는 것

18. main.jsp 에서 세션을 구해옴, 이때 세션이 있을때와 없을때 다른 화면을 보여줌

 

- MemberDAO.java 에서 memberAuth() 메소드 부분만

	// 로그인(회원인증)
	public int memberAuth(String id, String passwd) throws Exception {
		int result = 0;
		SqlSession session = getSession();
		MemberDTO member = session.selectOne("idcheck", id);
		if(member != null) {		// 중복 ID	
			if(member.getPasswd().equals(passwd)) {
				result = 1;			// 회원인증 성공
			}else {
				result = -1;		// 회원인증 실패
			}
		}

		return result;
	}

 

 

- member.xml 에서 로그인(회원 인증) 처리하는 SQL문 부분 (id 가 idcheck)

	<!-- ID중복검사, 회원인증 -->
	<select id="idcheck" parameterType="String" resultType="member">
	 select * from member0609 where id = #{id}
	</select>

+ ID 중복검사 할때와 같은 SQL문 사용

 

- 맞는 아이디, 비밀번호 입력 후 로그인 시

 

- 마지막으로 로그인 성공 후 포워딩되는 메인 페이지인 main.jsp 를 보자

- main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%-- <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> --%> <!-- 안됨 -->


<!--  세션이 있으면 -->
<%-- <c:if test="${not empty sessionScope.id}"> --%>	<!-- 2개 모두 잘됨 -->
<c:if test="${sessionScope.id != null }">
	${sessionScope.id} 님 환영 합니다. <br><br>
	
	<a href="<%=request.getContextPath() %>/UpdateMember.do">회원정보 수정</a>  <br><br>
	
	<a href="<%=request.getContextPath() %>/Logout.do">로그아웃</a>  <br><br>
	
	<a href="<%=request.getContextPath() %>/DeleteMember.do">회원탈퇴</a> <br><br>
</c:if>

<!-- 세션이 없으면 -->
<%-- <c:if test="${empty sessionScope.id}"> --%>
<c:if test="${sessionScope.id == null}">
	<a href="<%=request.getContextPath() %>/MemberForm.do">회원가입</a> <br><br>
	<a href="<%=request.getContextPath() %>/LoginForm.do">로그인</a> <br><br>
</c:if>

- 가장 먼저 EL 의 ${sessionScope.id != null} 로 네임 "id" 로 세션에 설정된 id 값이 있는지 확인한다

+ 또는 ${not empty sessioNScope.id} 를 if 조건으로 써도 된다

- 세션 공유값이 있으면 ~님 환영합니다 화면이 드고, 세션 공유값이 없으면 회원가입, 로그인 메뉴만 띄움 

 

링크 설정, 요청 네임값

- '회원정보 수정' : "/UpdateMember.do"

- '로그아웃' : "/Logout.do"

- '회원탈퇴' : "/DeleteMember.do"

- '회원가입' : "/MemberForm.do"

- '로그인' : "/LoginForm.do"

 

- main.jsp 를 그냥 실행하면 세션값이 없기때문에 아래 화면이 나타남

- 로그인 후에 main.jsp 로 넘어오면 세션값이 있으므로 아래 화면이 나타남


JSP Model 2 - MyBatis 회원관리 프로그램 : 로그아웃 기능

코드와 흐름 같이 설명

- 메인 페이지 main.jsp 에서 '로그아웃'  클릭시 "/Logout.do" 로 요청함

- Controller 클래스로 찾아간다

 

- Controller 클래스에서 "/Logout.do" 부분만

		// 로그 아웃	
		}else if(command.equals("/Logout.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/member/logout.jsp");
		}

- DB연동이 필요없으므로 Service 클래스로 가지 않음

- 바로 여기서 ActionForward 객체 foward 생성 후 포워딩 방식, 포워딩 페이지를 설정함

- logout.jsp 로 Dispatcher 방식으로 포워딩

 

- logout.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%	// 세션 삭제
	session.invalidate();
%>

<script>
	alert("로그아웃");
	location.href="./LoginForm.do";
</script>

logout.jsp 에서 해야할 일

1. 세션 삭제

2. 로그아웃 메세지 출력 후 다시 로그인 폼으로 돌아가기

- 로그인 폼으로 돌아가기 위한 요청인 "/LoginForm.do" 로 요청

 

- 그럼 다시 Controller 클래스에서 로그인 폼으로 포워딩 한다

 

로그아웃 시에는

- Controller 로 갔다가 Service 로 가지 않고 View로 바로 포워딩해서 세션을 끊고 다시 로그인 폼으로 가면 됨

 

- 로그아웃 시도

- '로그아웃' 눌렀을때 콘솔창

 


JSP Model 2 - MyBatis 회원관리 프로그램 : 수정폼 기능

코드와 흐름 같이 설명

+ 수정폼은 DB와 연동해서 상세정보를 구해와서 수정폼에 뿌려줘야함

- 메인 페이지 main.jsp 에서 '회원정보 수정'  클릭시 "/UpdateMember.do" 로 요청함

	<a href="<%=request.getContextPath() %>/UpdateMember.do">회원정보 수정</a>  <br><br>

- 이때 아무 값도 전달하지 않음, id 값은 로그인 성공 시 부터 공유설정되어있는 상태이다

- Controller 클래스로 찾아간다

 

- Controller 클래스에서 "/UpdateMember.do" 부분만

		// 회원정보 수정폼	
		}else if(command.equals("/UpdateMember.do")) {
			try {
				action = new UpdateMember();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

- Controller 클래스에서 해당 Service 클래스로 보내줌

 

- Controller 클래스 포워딩 처리 부분

		// 포워딩 처리
		if(forward != null) {
			if(forward.isRedirect()) {		// redirect 방식으로 포워딩
				response.sendRedirect(forward.getPath());
			}else {							// dispatcher 방식으로 포워딩
				RequestDispatcher dispatcher = 
						request.getRequestDispatcher(forward.getPath());
				dispatcher.forward(request, response);
			}
		}

- Service 에서 돌아온 뒤 여기서 포워딩 처리됨

 

- 수정폼 Service 클래스인 UpdateMember.java 로 이동

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import dao.MemberDAO;
import model.MemberDTO;

public class UpdateMember implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("UpdateMember");
		
		HttpSession session = request.getSession();
		String id = (String)session.getAttribute("id");
		System.out.println("id:"+id);
		
		MemberDAO dao = MemberDAO.getInstance();
		MemberDTO member = dao.getMember(id);
		System.out.println("member:"+member);
		
		String hobby = member.getHobby();	// "공부-게임-등산-"
		String[] h = hobby.split("-");
		
		request.setAttribute("member", member);				
		request.setAttribute("h", h);				
		
		ActionForward forward = new ActionForward();	
		forward.setRedirect(false);        // dispatcher 방식으로 포워딩
		forward.setPath("/member/updateform.jsp");
		
		return forward;
	}

}

<DAO 갔다오기 전>

- session 객체를 생성 후 세션값인 id 를 가장 먼저 구해온다

- getAttribuate() 로 세션값을 구해올때는 Object 형으로 리턴하므로 "id" 네임값에 들어있는 값의 자료형인 String 형으로 다운캐스팅

- DAO 객체를 생성하고 DAO의 getMember() 메소드를 호출해서 1명에 대한 상세정보를 구해온다

- getMember() 의 매개변수로는 세션으로 구해온 id 값을 전달한다

<DAO 갔다온 후>

- DAO의 getMemger(id) 로 구해온 상세정보를 DTO 객체 member 가 받는다

- 그 객체 member 의 주솟값을 찍어봄 * 아래에 캡처

- 받아온 객체 member 를 request 객체로 공유설정해야 View 페이지에서 공유값을 받아서 뿌릴 수 있다

- 문자열들을 잘라서 String 배열 h 에 저장하고 따로 공유 설정

- DTO 객체 member 는 ${member.필드명} 형태로 View 에서 출력, 배열 h 는 forEach 태그의 items 로 들어가서 View 에서 출력

- 메소드 execute() 에서도 DAO 로부터 넘어온 예외를 다시 던짐, 메소드를 호출한 Controller 클래스에서 예외를 처리한다

- ActionForward 객체 forward 생성 후 updateform.jsp 로 Dispatcher 방식으로 포워딩 할 것임을 설정

 

- MemberDAO.java 에서 getMember() 메소드 부분만

	// 회원 1명 정보 구하기 : 수정폼, 수정, 삭제
	public MemberDTO getMember(String id) throws Exception{
		SqlSession session = getSession();
		MemberDTO member = session.selectOne("idcheck", id);
		
		return member;
	}

- 수정폼뿐 아니라 수정, 삭제시에 비번을 꺼낼때도 사용됨

- 검색되는 데이터가 1개이므로 selectOne() 메소드를 사용하고 id 가 "idcheck" 인 태그의 SQL문 호출, 사용자의 id 를 전달

- 예외를 던지므로서 메소드를 호출한 곳(Service 클래스쪽) 에 양도함

+ Service 클래스의 메소드에서도 예외를 던짐, 메소드를 호출한 Controller 클래스에서 예외를 처리한다

 

- member.xml 에서 로그인(회원 인증) 처리하는 SQL문 부분 (id 가 idcheck)

	<!-- ID중복검사, 회원인증 -->
	<select id="idcheck" parameterType="String" resultType="member">
	 select * from member0609 where id = #{id}
	</select>

- 전달되는 값은 id 이므로 parameterType 을 String 으로 하고, 검색한 결과를 돌려줄때는 DTO 객체로 돌려주므로 resultType 은 member 로 한다

+ parameterType 속성값인 "member"는 MyBatis 환경설정 파일에서 Alias 값으로 지정한 별칭이다

- #{id} : selectOne() 을 호출할떄 두번째 매개변수로 넘어온 값으로 변수 id 가 넘어왔으므로 #{id} 로 그 값을 가져옴

- 검색결과를 setter 메소드로 DTO 객체에 저장시킬필요 없이 자동으로 매핑되어 DTO 객체에 저장되서 리턴된다

 

- 다음은 View 부분인 updateform.jsp 를 보자

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원 수정 폼</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>

<script src="http://dmaps.daum.net/map_js_init/postcode.v2.js"></script>
<script>
	function openDaumPostcode() {
		new daum.Postcode({
			oncomplete : function(data) {
				// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
				// 우편번호와 주소 정보를 해당 필드에 넣고, 커서를 상세주소 필드로 이동한다.
//				document.getElementById('join_zip1').value = data.postcode1;
//				document.getElementById('join_zip2').value = data.postcode2;
				document.getElementById('post').value = data.zonecode;
				document.getElementById('address').value = data.address;
				
			}
		}).open();
	}
</script>


<!-- 외부 자바스크립트 파일 불러오기 -->
<script src="<%=request.getContextPath() %>/member/member.js"></script>

</head>
<body>

<form method="post" action="<%=request.getContextPath() %>/Update.do"> 
<input type="hidden" name="id" value="${member.id}">
<table border=1 width=500 align=center>
	<caption>회원 수정</caption>
	<tr><td>ID</td>
		<td>${member.id}</td>
	</tr>
	<tr><td>비밀번호</td>
		<td><input type=password id="passwd" name="passwd"></td>
	</tr>
	<tr><td>성명</td>
		<td><input type=text id="name" name="name" value="${member.name}"></td>
	</tr>
	<tr><td>주민번호</td>
		<td><input type=text size=6 maxlength=6 id="jumin1" name="jumin1" value="${member.jumin1}">-
			<input type=text size=7 maxlength=7 id="jumin2" name="jumin2" value="${member.jumin2}">
		</td>
	</tr>
	<tr><td>E-Mail</td>
		<td><input type=text size=10 id="mailid" name="mailid" value="${member.mailid}">@
		    <input type=text size=10 id="domain" name="domain" value="${member.domain}">
		    <select id="email">
		    	<option value="">직접입력</option>
		    	<option value="naver.com">네이버</option>
		    	<option value="daum.net">다음</option>
		    	<option value="nate.com">네이트</option>
		    	<option value="gmail.com">gmail</option>
		    </select>		    
		 </td>
	</tr>
	<tr><td>전화번호</td>
		<td><input type=text size=4 id="tel1" name="tel1" maxlength=4 value="${member.tel1 }">-
			<input type=text size=4 id="tel2" name="tel2" maxlength=4 value="${member.tel2 }">-
			<input type=text size=4 id="tel3" name="tel3" maxlength=4 value="${member.tel3 }">
		</td>
	</tr>
	<tr><td>핸드폰</td>
		<td><select id="phone1" name="phone1">
				<option value="">번호선택</option>
				<option value="010" <c:if test="${member.phone1 == '010' }">${'selected'}</c:if> >010</option>
				<option value="011" <c:if test="${member.phone1 == '011' }">${'selected'}</c:if> >011</option>
				<option value="016" <c:if test="${member.phone1 == '016' }">${'selected'}</c:if> >016</option>
				<option value="018" <c:if test="${member.phone1 == '018' }">${'selected'}</c:if> >018</option>
				<option value="019" <c:if test="${member.phone1 == '019' }">${'selected'}</c:if> >019</option>
			</select>-
			<input type=text size=4 id="phone2" name="phone2" maxlength=4 value="${member.phone2 }">-
			<input type=text size=4 id="phone3" name="phone3" maxlength=4 value="${member.phone3 }">
		</td>
	</tr>
	<tr><td>우편번호</td>
		<td><input type=text size=5 id="post" name="post" value="${member.post }">
			<input type=button value="우편번호검색" 
			       onClick="openDaumPostcode()">
		</td>
	</tr>
	<tr><td>주소</td>
		<td><input type=text size=45 id="address" name="address" value="${member.address }"></td>
	</tr>
	<tr><td>성별</td>
		<td>
		
		<c:if test="${member.gender == '남자' }">
			<input type=radio id="male" name="gender" value="남자" checked="checked">남자
			<input type=radio id="female" name="gender" value="여자">여자
		</c:if>
		<c:if test="${member.gender == '여자' }">
			<input type=radio id="male" name="gender" value="남자">남자
			<input type=radio id="female" name="gender" value="여자" checked="checked">여자
		</c:if>			
			
		</td>
	</tr>
	<tr><td>취미</td>
		<td>
			<input type="checkbox" id="h1" name="hobby" value="공부"
		<c:forEach var="i" items="${h}">
			<c:if test="${i=='공부'}">${'checked'}</c:if>
		</c:forEach> >공부
		
			<input type="checkbox" id="h2" name="hobby" value="게임"
		<c:forEach var="i" items="${h}">	
			<c:if test="${i=='게임'}">${'checked'}</c:if>
		</c:forEach> >게임
			
			<input type="checkbox" id="h3" name="hobby" value="등산"
		<c:forEach var="i" items="${h}">	
			<c:if test="${i=='등산'}">${'checked'}</c:if>
		</c:forEach> >등산
			
			<input type="checkbox" id="h4" name="hobby" value="낚시"
		<c:forEach var="i" items="${h}">	
			<c:if test="${i=='낚시'}">${'checked'}</c:if>
		</c:forEach> >낚시
			
			<input type="checkbox" id="h5" name="hobby" value="쇼핑"
		<c:forEach var="i" items="${h}">	
			<c:if test="${i=='쇼핑'}">${'checked'}</c:if>
		</c:forEach> >쇼핑
			
		</td>
	</tr>	
	<tr><td>자기소개</td>
		<td>
			<textarea id="intro" name="intro" rows="5" cols="50" placeholder="자기소개를 100자 이내로 입력하세요">${member.intro }</textarea>
		</td>
	</tr>
	<tr><td colspan=2 align=center>
			<input type=submit value="회원수정">
			<input type=reset value="취소">
		</td>
	</tr>		
</table>
</form>


</body>
</html>

- 공유설정된 값을 받아서 수정폼에 뿌려준다

- 공유된 객체 member 안의 필드값들은 ${member.필드명} 으로 가져온다 (updateform.jsp 부분)

	<tr><td>성명</td>
		<td><input type=text id="name" name="name" value="${member.name}"></td>
	</tr>

- 공유된 배열 h 는 forEach 태그의 items 안에 "${h}" 가 들어감 (updateform.jsp 부분)

			<input type="checkbox" id="h1" name="hobby" value="공부"
		<c:forEach var="i" items="${h}">
			<c:if test="${i=='공부'}">${'checked'}</c:if>
		</c:forEach> >공부

- id 값은 수정폼에서 입력양식이 아닌 출력만 하고 있으므로 따로 hidden 객체로 id 값을 넘겨줌 

+ 세션이 있지만 바로 전달하는게 좋다

- 수정폼에서 입력 후 '회원수정' 을 누르면 action 값으로 지정된 "/Update.do" 로 요청

 

- 메인 페이지 main.jsp 에서 '회원정보 수정' 을 눌러 수정폼 으로 들어가보자

- 상세정보를 구해와서 값들을 수정폼에 잘 뿌리고 있다

 

- '회원정보 수정' 을 눌렀을때 콘솔창

- DAO 에서 검색해서 돌려받은 객체 member 가 출력됨 (데이터가 저장된 곳의 주소)


수정폼에서 처리하기 힘든 부분 (updateform.jsp 부분)

- jQuery 를 쓰지 않으면 처리하기 힘든 부분들이다

- 수정폼에서 처기하기 힘든 부분들을 jQuery 를 쓰지 않고 어떻게 처리했는지 보자

 

1. 핸드폰 첫번째 자리

	<tr><td>핸드폰</td>
		<td><select id="phone1" name="phone1">
				<option value="">번호선택</option>
				<option value="010" <c:if test="${member.phone1 == '010' }">${'selected'}</c:if> >010</option>
				<option value="011" <c:if test="${member.phone1 == '011' }">${'selected'}</c:if> >011</option>
				<option value="016" <c:if test="${member.phone1 == '016' }">${'selected'}</c:if> >016</option>
				<option value="018" <c:if test="${member.phone1 == '018' }">${'selected'}</c:if> >018</option>
				<option value="019" <c:if test="${member.phone1 == '019' }">${'selected'}</c:if> >019</option>
			</select>-
			<input type=text size=4 id="phone2" name="phone2" maxlength=4 value="${member.phone2 }">-
			<input type=text size=4 id="phone3" name="phone3" maxlength=4 value="${member.phone3 }">
		</td>
	</tr>

- option 태그 안에 JSTL if 태그를 넣어서 특정 숫자일때 ${'selected'} 를 출력해서 선택되게 만듬

 

2. 성별 라디오 버튼

	<tr><td>성별</td>
		<td>
		
		<c:if test="${member.gender == '남자' }">
			<input type=radio id="male" name="gender" value="남자" checked="checked">남자
			<input type=radio id="female" name="gender" value="여자">여자
		</c:if>
		<c:if test="${member.gender == '여자' }">
			<input type=radio id="male" name="gender" value="남자">남자
			<input type=radio id="female" name="gender" value="여자" checked="checked">여자
		</c:if>			
			
		</td>
	</tr>

- JSTL if 태그를 넣어서 member.gender 값이 '남자' 일때 남자 라디오버튼에 checked 를 만들어서 선택되게 함

- if 태그 안에 라디오 버튼 을 넣어서 조건문을 만족할때만 나타남

 

3. 취미 체크박스

	<tr><td>취미</td>
		<td>
			<input type="checkbox" id="h1" name="hobby" value="공부"
		<c:forEach var="i" items="${h}">
			<c:if test="${i=='공부'}">${'checked'}</c:if>
		</c:forEach> >공부
		
			<input type="checkbox" id="h2" name="hobby" value="게임"
		<c:forEach var="i" items="${h}">	
			<c:if test="${i=='게임'}">${'checked'}</c:if>
		</c:forEach> >게임
			
			<input type="checkbox" id="h3" name="hobby" value="등산"
		<c:forEach var="i" items="${h}">	
			<c:if test="${i=='등산'}">${'checked'}</c:if>
		</c:forEach> >등산
			
			<input type="checkbox" id="h4" name="hobby" value="낚시"
		<c:forEach var="i" items="${h}">	
			<c:if test="${i=='낚시'}">${'checked'}</c:if>
		</c:forEach> >낚시
			
			<input type="checkbox" id="h5" name="hobby" value="쇼핑"
		<c:forEach var="i" items="${h}">	
			<c:if test="${i=='쇼핑'}">${'checked'}</c:if>
		</c:forEach> >쇼핑
			
		</td>
	</tr>

- input 태그 안에 JSTL forEach 태그를 넣어서 배열 h 에서 값을 하나씩 가져와서 차레로 변수 i 에 저장

- 변수 i 가 '공부' 이면 공부 체크박스에 ${'checked'} 로 체크가 되게 함 

ex) h[0] = "공부", h[1] = "게임"

- 모든 체크박스마다 forEach 태그를 써서 일일히 찾아야한다

 

- jQuery 를 쓰지 않고 핸드폰 첫번째자리, 성별 라디오버튼, 취미 체크박스에 값을 뿌렸다



JSP Model 2 - MyBatis 회원관리 프로그램 : 회원 정보 수정 기능

코드와 흐름 같이 설명

- updateform.jsp 에서 '회원수정' 버튼을 누르면 "/Update.do" 로 요청함

- Controller 클래스로 찾아간다

 

- Controller 클래스에서 "/Update.do" 부분만

		// 회원정보 수정	
		}else if(command.equals("/Update.do")) {
			try {
				action = new Update();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 정보 수정 Service 클래스인 Update.java 로 이동

package service;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.MemberDAO;
import model.MemberDTO;

public class Update implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("Update");
		
		response.setContentType("text/html; charset=utf-8");
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		
		MemberDTO member = new MemberDTO();
		member.setId(request.getParameter("id"));
		member.setPasswd(request.getParameter("passwd"));
		member.setName(request.getParameter("name"));
		member.setJumin1(request.getParameter("jumin1"));
		member.setJumin2(request.getParameter("jumin2"));
		member.setMailid(request.getParameter("mailid"));
		member.setDomain(request.getParameter("domain"));
		member.setTel1(request.getParameter("tel1"));
		member.setTel2(request.getParameter("tel2"));
		member.setTel3(request.getParameter("tel3"));
		member.setPhone1(request.getParameter("phone1"));
		member.setPhone2(request.getParameter("phone2"));
		member.setPhone3(request.getParameter("phone3"));
		member.setPost(request.getParameter("post"));
		member.setAddress(request.getParameter("address"));
		member.setGender(request.getParameter("gender"));
		
		String h="";
		String[] h1 = request.getParameterValues("hobby");
		for(String h2 : h1) {
			h += h2+"-";			// h = "공부-게임-등산-"
		}
		member.setHobby(h);
		member.setIntro(request.getParameter("intro"));
		
		MemberDAO dao = MemberDAO.getInstance();
		MemberDTO old = dao.getMember(member.getId());
		
		// 비번 비교
		if(old.getPasswd().equals(member.getPasswd())) { //비번 일치시
			int result = dao.update(member);
			if(result==1) System.out.println("회원수정 성공");
			
		}else {											 //비번 불일치시
			out.println("<script>");
			out.println("alert('비번이 일치하지 않습니다.');");
			out.println("history.go(-1);");
			out.println("</script>");
			out.close();
			
			return null;
		}	
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/member/main.jsp");
		
		return forward;
	}

}

- 수정폼에서 입력값들이 넘어오므로 한글 인코딩

- id 도 hidden 객체로 넘어왔고, 사용자가 입력한 비번도 넘어왔다

- DTO 객체 member 를 생성해서 setter 메소드로 id 와 사용자가 수정폼에 입력한 값들을 member 에 저장

- DAO 의 메소드를 2번 호출해야함, 비번 확인을 위해 상세정보 가져올때 1번, 수정할때 1번

- DAO 객체 생성 후 DB에 저장된 비번을 가져오기 위해 DAO 의 getMember() 메소드를 호출하면서 수정폼에서 넘겨준 id 를 매개변수로 넘겨준다

<DAO getMember() 에서 돌아온 후>

- old.getPasswd() 는 DB에 저장된 비번, member.getPasswd() 는 사용자가 입력한 비번

- 두 비번이 일치시 수정 하는 메소드인 DAO의 update() 메소드 호출, 수정할 데이터를 저장한 DTO 객체 member 를 넘김

<DAO update() 에서 돌아온 후>

- 수정 성공시 1을 돌려주기로 했으므로, 1을 리턴받으면 "회원수정 성공" 메세지를 콘솔에 출력

- 비번 일치가 되면 수정 후 if-else 문 아래 문장인 ActionForward 객체 foward 생성 후, main.jsp 로 Dispatcher 방식 으로 포워딩 페이지, 방식 지정

- 성공시 Controller 클래스로 가서 main.jsp 로 포워딩하면 아직 세션이 남아있으므로 세션이 있을때의 화면이 뜬다

- 비번이 일치하지 않으면 메세지를 출력한 후 다시 이전페이지인 수정폼으로 돌아간다

 

- MemberDAO.java 에서 getMember() 메소드 부분만

	// 회원 1명 정보 구하기 : 수정폼, 수정, 삭제
	public MemberDTO getMember(String id) throws Exception{
		SqlSession session = getSession();
		MemberDTO member = session.selectOne("idcheck", id);
		
		return member;
	}

- 1명에 대한 상세정보를 구하므로 selectOne() 메소드, id 값이 "idcheck" 인 Mapper 파일의 SQL문 호출

- 사용자가 입력한 id 값을 매개변수로 넘겨준다

- 돌려받은 객체 member 를 Service 클래스로 리턴

 

- member.xml 에서 수정 처리하는 SQL문 부분 (id 가 idcheck)

	<!-- ID중복검사, 회원인증 -->
	<select id="idcheck" parameterType="String" resultType="member">
	 select * from member0609 where id = #{id}
	</select>

- DTO 객체에 상세정보가 자동으로 저장(매핑) 되어 돌려준다

 

- MemberDAO.java 에서 update() 메소드 부분만

	// 회원정보 수정
	public int update(MemberDTO member) throws Exception{
		int result = 0;
		SqlSession session = getSession();
		result = session.update("update", member);

		return result;
	}

- SqlSession 객체를 변수 session 으로 받아오고 update SQL문을 부를 것이므로 session.update() 메소드를 호출

- Mapper 파일에서 id 가 "update" 인 태그의 SQL문 호출, 호출하며 수정할 데이터를 저장한 객체 member 를 넘김

- session.update() 가 잘 실행되면 1 을 리턴하고, 그걸 result 변수로 받아서 다시 Service 클래스로 돌려줌

 

- member.xml 에서 수정 처리하는 SQL문 부분 (id 가 update)

	<!-- 회원정보 수정 -->
	<update id="update" parameterType="member">
	  update member0609 set name=#{name}, jumin1=#{jumin1}, jumin2=#{jumin2}, 
	  mailid=#{mailid}, domain=#{domain}, tel1=#{tel1}, tel2=#{tel2}, tel3=#{tel3},
	  phone1=#{phone1}, phone2=#{phone2}, phone3=#{phone3}, post=#{post}, address=#{address},
	  gender=#{gender}, hobby=#{hobby}, intro=#{intro} where id = #{id}
	</update>

- DTO 객체 member 가 넘어왔으므로 parameterType 은 전달되는 값의 자료형인 DTO alias 값 member 를 작성

- DTO 객체가 전달될때 DTO 객체 안의 내용을 가져오는 방법 : #{} 안에 DTO 객체의 필드명을 쓴다

ex) #{name} 은 member.getName() 과 같은 의미, #{jumin1} 은 member.getJumin1() 과 같은 의미

- 수정된 데이터 개수를 자동으로 돌려준다, 성공시 1 반환


JSP Model 2 - MyBatis 회원관리 프로그램 : 회원 탈퇴 기능

코드와 흐름 같이 설명

- 탈퇴 폼 + 탈퇴 기능 한번에 설명

- DB 연동이 필요하지 않으므로 main.jsp 에서 '회원탈퇴' 클릭시 Controller 클래스로 가서 바로 View 탈퇴 폼으로 가기

	<a href="<%=request.getContextPath() %>/DeleteMember.do">회원탈퇴</a> <br><br>

- 회원 탈퇴 폼으로 가기 위해 "/DeleteMember.do" 로 요청함

- Controller 클래스로 넘어감

 

- Controller 클래스에서 "/DeleteMember.do" 부분만

		// 회원탈퇴 폼	
		}else if(command.equals("/DeleteMember.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/member/deleteform.jsp");
		}

- DB연동이 필요하지 않으므로 Service 클래스가 아닌 View 페이지로 바로 이동

- 여기서 ActionForward 객체 forward 생성 후 포워딩 페이지, 방식 지정

- 탈퇴 폼 View 페이지인 deleteform.jsp 로 간다

 

- deleteform.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>    

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원 탈퇴</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>

<!-- 외부 자바스크립트 파일 불러오기 -->
<script src="<%=request.getContextPath() %>/member/member.js"></script>

</head>
<body>

<form method="post" action="<%=request.getContextPath() %>/Delete.do"> 
<input type="hidden" name="id" value="${sessionScope.id}">
<table border=1 width=500 align=center>
	<caption>회원 탈퇴</caption>	
	<tr><td>비밀번호</td>
		<td><input type=password id="passwd" name="passwd"></td>
	</tr>	
	<tr><td colspan=2 align=center>
			<input type=submit value="회원탈퇴">
			<input type=reset value="취소">
		</td>
	</tr>		
</table>
</form>


</body>
</html>

- 아직 SESSION 영역이므로 공유설정된 세션값인 id 가 남아있다, 

- 세션 값을 ${sessionScope.id} 로 가져와서 hidden 객체를 통해 "id" 변수에 저장해서 세션 값을 "/Delete.do" 로 넘긴다

+ hidden 으로 id 값을 "/Delete.do" 로 넘기지 않아도 Delete.java 에서 공유된 세션에서 id 값을 가져와도 된다

+ 하지만 session 객체도 만들어아하고 번거로우므로 그냥 hidden 으로 비밀번호와 함께 form 으로 같이 넘기는 것

- 탈퇴를 위해선 탈퇴폼에 사용자가 비밀번호를 입력해야함

- 해당하는 회원 1명의 정보를 구하기 위해 id 값을 꼭 넘겨야함

- hidden 으로 넘어가는 id 와 입력양식으로 넘어가는 비밀번호 passwd 가 "/Delete.do" 로 요청하며 post 방식으로 넘어감

 

- Controller 클래스에서 "/Delete.do" 부분만

		// 회원탈퇴	
		}else if(command.equals("/Delete.do")) {
			try {
				action = new Delete();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- 회원 탈퇴 Service 클래스인 Delete.java 로 이동

package service;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import dao.MemberDAO;
import model.MemberDTO;

public class Delete implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("Delete");
		
		response.setContentType("text/html; charset=utf-8");
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();
		
		String id = request.getParameter("id");
		String passwd = request.getParameter("passwd");
		
		MemberDAO dao = MemberDAO.getInstance();
		MemberDTO old = dao.getMember(id);
		
		// 비번 비교
		if(old.getPasswd().equals(passwd)) {	// 비번 일치시
			int result = dao.delete(id);
			if(result == 1) System.out.println("회원삭제 성공");
			
			session.invalidate();               // 세션 삭제
			
		}else {									// 비번 불일치시
			out.println("<script>");	
			out.println("alert('비번이 일치하지 않습니다.');");	
			out.println("history.go(-1);");	
			out.println("</script>");	
			out.close();
			
			return null;			
		}
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/LoginForm.do");
		
		return forward;
	}

}

- 회원 삭제(탈퇴) 성공시에 필요한 session 객체, out 객체 생성

- hidden 으로 넘어온 id 값과, 입력양식에서 넘어온 비번 passwd 를 getParameter() 로 받아서 변수 id, passwd 에 저장

- 비번 비교를 위해 DAO의 getMember() 메소드를 호출해서 해당 id 의 회원에 대한 상세정보를 가져온다

- DAO 에서 메소드 호출을 2번 해야한다, 비번을 가져오기 위해 상세정보 가져올때 1번, 회원 삭제할때 1번

<DAO getMember() 에서 돌아온 후>

- old.getPasswd() 는 DB의 비번, passwd 는 사용자가 탈퇴폼에 입력한 비번, 두 비번 일치시 DAO 의 delete() 메소드 호출

- delete() 를 호출하며 id 값을 매개변수로 전달

<DAO update() 에서 돌아온 후>

- 1 을 리턴받으면 성공이라 여기고, "회원삭제 성공" 메세지 출력하고 세션을 삭제한다, SESSION 영역 끝

- 삭제에 성공하면 if-else 문 아래에서 forward 객체를 생성하고 "/LoginForm.do" 로 포워딩 페이지 설정, Dispatcher 방식으로 포워딩 방식 설정

-  삭제에 실패하면 메세지를 출력하고 이전 페이지인 삭제폼으로 이동, return null; 을 추가해서 execute() 를 빠져나가며 아래의 코드를 forward 설정 코드를 실행하지 않도록 함

- 이전 페이지인 삭제폼으로 이동하는데 아래의 forward 설정 코드를 실행하면 오류가 난다. 한번에 두 곳으로 갈 수 없으므로


- MemberDAO.java 에서 getMember() 메소드 부분만

	// 회원 1명 정보 구하기 : 수정폼, 수정, 삭제
	public MemberDTO getMember(String id) throws Exception{
		SqlSession session = getSession();
		MemberDTO member = session.selectOne("idcheck", id);
		
		return member;
	}

- member.xml 에서 수정 처리하는 SQL문 부분 (id 가 idcheck)

	<!-- ID중복검사, 회원인증 -->
	<select id="idcheck" parameterType="String" resultType="member">
	 select * from member0609 where id = #{id}
	</select>

- MemberDAO.java 에서 delete() 메소드 부분만

	// 회원 탈퇴
	public int delete(String id) throws Exception{
		int result = 0;
		SqlSession session = getSession();
		result = session.delete("delete", id);		
		
		return result;
	}

- delete SQL문을 사용하므로 SqlSession 객체의 메소드 delete() 를 사용해야한다

- Mapper 파일에서 id 가 "delete' 인 SQL문을 호출, 매개변수로는 삭제하고자 하는 회원의 id 값을 넘김

- session.delete() 에서 삭제 성공시 리턴하는 1을 result 변수에 저장 후 다시 Service 클래스로 1 리턴

 

- member.xml 에서 수정 처리하는 SQL문 부분 (id 가 delete)

	<!-- 회원 삭제 -->
	<delete id="delete" parameterType="String">
	  delete from member0609 where id = #{id}
	</delete>

- 삭제하고자 하는 회원의 id 값이 넘어오므로 parameterType 은 String 이어야한다

- #{id} : session.delete() 에서 두번째 매개변수로 넘어온 변수 id 의 값을 ${id} 로 가져옴

- 자동으로 삭제된 데이터 개수를 리턴해줌 (성공시 1 리턴)

 

- main.jsp 에서 '회원탈퇴' 클릭시 콘솔창 (탈퇴 폼)

 

- 탈퇴 폼에서 맞는 비밀번호를 입력하고 '회원탈퇴' 클릭시

- 탈퇴에 성공하고 로그인 폼으로 포워딩됨

- 탈퇴 폼에서 '회원탈퇴' 클릭해서 회원탈퇴에 성공 했을때 콘솔 창


JSP Model 2 - MyBatis 연동 구조



JSP Model 2 - MyBatis 연동 : 게시판 프로그램

Model 2 과 MyBatis 연동

- 클라우드에서 Model 2 과 MyBatis 가 이미 연동이 완료된 게시판 로그램 mybatisboard 프로젝트 import

- zip 파일을 압축 풀어서 import

- import 하는 프로젝트는 maven 프로젝트이다

- Maven 프로젝트이므로 구조는 다음과 같다

- src/main/java 안에 확장자가 java 인 파일들이 저장되어있다 (Controller, Service, DAO, DTO)

+ Model 2 이므로 src/main/java 안에서도 DTO, DAO, Service, Controller 가 서로 다른 패키지 안에 들어가있음

- src/main/resources 안에 DB 연동에 필요한 MyBatis 환경설정 파일 등이 들어가 있다 (주로 xml 파일들)

- src/main/webapp 안에 View 파일 등이 들어간다

- Maven 프로젝트는 WEB-INF 폴더 안에 lib 폴더가 생성되지 않는다, 대신 pom.xml 에서 라이브러리 추가함

- boardupload 는 첨부파일이 저장될 폴더이다

 

- import한 mybatismember 프로젝트의 pom.xml 파일을 보자

- pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.myhome</groupId>
	<artifactId>mybatisboard</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>ibatisboard Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<!-- 복사 시작 -->
	<repositories>
		<repository>
			<id>codelds</id>
			<url>https://code.lds.org/nexus/content/groups/main-repo</url>
		</repository>
	</repositories>

	<dependencies>

		<!-- 오라클 JDBC Library -->
		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.3</version>
		</dependency>

		<!-- MySQL -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.47</version>
		</dependency>

		<!-- iBatis -->
		<dependency>
			<groupId>org.apache.ibatis</groupId>
			<artifactId>ibatis-sqlmap</artifactId>
			<version>2.3.4.726</version>
		</dependency>

		<!-- mybatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.5.3</version>
		</dependency>

		<!-- jstl -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>

		<!-- cos -->
		<dependency>
			<groupId>servlets.com</groupId>
			<artifactId>cos</artifactId>
			<version>05Nov2002</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<finalName>mybatisboard</finalName>
	</build>
</project>

- 프로젝트 시작 시 가장 먼저 라이브러리를 구하는 작업을 해야한다

- dependencies 태그 안에 의존 라이브러리를 dependency 태그로 추가

- 오라클 JDBC, MySQL JDBC, iBatis, MyBatis 3.5.3 ver, JSTL, cos 라이브러리가 추가되었다

- 오라클 공식 저장소에서 다운받는 건 오류 많이 발생

- 오라클은 비공식 저장소 repository 를 원격 저장소 위치로 등록하고 거기서 오라클 JDBC 라이브러리를 불러오기

- MyBatis 연동시에는 반드시 MyBatis 라이브러리가 추가되어 있어야함

 

- db.properties

jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:xe
jdbc.username=totoro
jdbc.password=totoro123

- DB 접속에 대한 내용들이 들어가 있다

- 테이블을 생성할때 totoro 계정에서 생성해야 한다


- 테이블을 만들자

- db.properties 파일에서 totoro 계정으로 설정되어있으므로 totoro 계정에서 테이블을 생성해야함

+ import 시에 테이블이 생성되지는 않음

- sql 폴더의 model2.sql 에서 totoro 계정으로 연결한 후 model22테이블을 생성하자

- 커넥션 프로파일 설정

- model2.sql

-- 모델2 게시판
select * from tab;
select * from seq;
select * from model22;

create table model22(
	board_num number,
	board_name varchar2(20),
	board_pass varchar2(15),
	board_subject varchar2(50),
	board_content varchar2(2000),
	board_file varchar2(50),
	board_re_ref number,
	board_re_lev number,
	board_re_seq number,
	board_readcount number,
	board_date timestamp,
	primary key(board_num)
);

create sequence model22_seq
start with 1
increment by 1
nocache;

- 테이블 model22 와 시퀀스 model22_seq 를 생성

- board_num 이 primary key 가 되고, board_num 에는 시퀀스 model22_seq 값이 들어갈 것

 

컬럼 설명

- board_re_ref : 원문은 시퀀스, 댓글은 부모의 ref 값과 동일값

- board_re_lev : 원문은 0, 1단계 댓글은 1, 2단계 댓글은 2

- board_re_seq : 원문은 0, 댓글, 대댓글의 board_re_seq 는 계속 바뀌는 값


JSP Model 2 - MyBatis 게시판 프로그램 : 기본 파일들

 

흐름 보기

- index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

 모델2 게시판
 
<%
	response.sendRedirect("./BoardListAction.do");
%> 

<script>
//	location.href="./BoardListAction.do";
</script>

</body>
</html>

- index.jsp 실행이나 현재 프로젝트 Run As - Run on Server 로 실행시키자

- 목록을 요청하는 "/BoardListAction.do" 로 요청하고 있다, 목록을 요청하고 목록을 출력하는 페이지까지 온다

- 그래서 목록 페이지가 보인다

 

- '글쓰기' 를 눌러서 글 작성 폼으로 가서 게시판에 글을 작성해보자

- 글을 '등록' 하면 아래처럼 목록을 다시 가져와서 목록 페이지로 돌아간다

- 글 작성 성공시 콘솔창

path:C:\Users\admin\eclipse-workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\mybatisboard\boardupload

- 첨부파일도 성공적으로 서버에 업로드 되었음을 확인 가능

 

- 글을 클릭하면 상세 페이지로 넘어가면서 조회수를 1 증가시킴

- 댓글을 달아보자

- 글 삭제하기


- DTO 인 BoardBean.java 파일을 보자

- MyBatis 로 자동 매핑이 가능하도록 테이블의 컬럼명과 DTO 의 프로퍼티명을 일치시켰다

package model;

import java.sql.Timestamp;

public class BoardBean {
	private int board_num;
	private String board_name;
	private String board_pass;
	private String board_subject;
	private String board_content;
	private String board_file;
	private int board_re_ref;
	private int board_re_lev;
	private int board_re_seq;
	private int board_readcount;
	private Timestamp board_date;	
	
	public int getBoard_num() {
		return board_num;
	}
	public void setBoard_num(int board_num) {
		this.board_num = board_num;
	}
	public String getBoard_name() {
		return board_name;
	}
	public void setBoard_name(String board_name) {
		this.board_name = board_name;
	}
	public String getBoard_pass() {
		return board_pass;
	}
	public void setBoard_pass(String board_pass) {
		this.board_pass = board_pass;
	}
	public String getBoard_subject() {
		return board_subject;
	}
	public void setBoard_subject(String board_subject) {
		this.board_subject = board_subject;
	}
	public String getBoard_content() {
		return board_content;
	}
	public void setBoard_content(String board_content) {
		this.board_content = board_content;
	}
	public String getBoard_file() {
		return board_file;
	}
	public void setBoard_file(String board_file) {
		this.board_file = board_file;
	}
	public int getBoard_re_ref() {
		return board_re_ref;
	}
	public void setBoard_re_ref(int board_re_ref) {
		this.board_re_ref = board_re_ref;
	}
	public int getBoard_re_lev() {
		return board_re_lev;
	}
	public void setBoard_re_lev(int board_re_lev) {
		this.board_re_lev = board_re_lev;
	}
	public int getBoard_re_seq() {
		return board_re_seq;
	}
	public void setBoard_re_seq(int board_re_seq) {
		this.board_re_seq = board_re_seq;
	}
	public int getBoard_readcount() {
		return board_readcount;
	}
	public void setBoard_readcount(int board_readcount) {
		this.board_readcount = board_readcount;
	}
	public Timestamp getBoard_date() {
		return board_date;
	}
	public void setBoard_date(Timestamp board_date) {
		this.board_date = board_date;
	}
}

- 테이블의 컬럼명과 DTO 의 프로퍼티명을 일치시켜야 자동 매핑이 가능하다


- Controller 클래스 BoardFrontController.java 를 보자

package controller;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import service.Action;
import service.ActionForward;
import service.BoardAddAction;
import service.BoardDelete;
import service.BoardDetailAction;
import service.BoardListAction;
import service.BoardModify;
import service.BoardModifyAction;
import service.BoardReply;

/**
 * Servlet implementation class BoardFrontController
 */
@WebServlet("*.do")
public class BoardFrontController extends HttpServlet {
	private static final long serialVersionUID = 1L;

	public void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
					
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI:"+requestURI);
		System.out.println("contextPath:"+contextPath);
		System.out.println("command:"+command);
		
		Action action = null;
		ActionForward forward = null;
		
		// 글작성
		if(command.equals("/BoardAddAction.do")) {
			try {
				action = new BoardAddAction();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		
		// 글작성 폼	
		}else if(command.equals("/BoardForm.do")) {	
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/board/qna_board_write.jsp");
			
		// 글목록	
		}else if(command.equals("/BoardListAction.do")) {
			try {
				action = new BoardListAction();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 상세 페이지	
		}else if(command.equals("/BoardDetailAction.do")) {
			try {
				action = new BoardDetailAction();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 댓글 폼	
		}else if(command.equals("/BoardReplyAction.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/board/qna_board_reply.jsp");
			
		// 댓글	
		}else if(command.equals("/BoardReply.do")) {
			try {
				action = new BoardReply();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 수정 폼	
		}else if(command.equals("/BoardModifyAction.do")) {
			try {
				action = new BoardModifyAction();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 수정	
		}else if(command.equals("/BoardModify.do")) {
			try {
				action = new BoardModify();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		// 삭제 폼	
		}else if(command.equals("/BoardDeleteAction.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("/board/qna_board_delete.jsp");
			
		// 삭제	
		}else if(command.equals("/BoardDelete.do")) {
			try {
				action = new BoardDelete();
				forward = action.execute(request, response);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
		
		
		if(forward != null) {
			if(forward.isRedirect()) {	 // redirect 방식으로 포워딩
				response.sendRedirect(forward.getPath());
				
			}else {						// dispatcher 방식으로 포워딩
				RequestDispatcher dispatcher = 
						request.getRequestDispatcher(forward.getPath());
				dispatcher.forward(request, response);
			}			
		}		
		
	} // doProcess() end
	
	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
//		response.getWriter().append("Served at: ").append(request.getContextPath());
		System.out.println("get");		
	
		doProcess(request, response);
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
//		doGet(request, response);
		System.out.println("post");
		
		doProcess(request, response);
	}

}

- command 변수에 저장된 값에 따라 다른 Service 클래스로 이동 또는 View로 포워딩

 

+ 인터페이스 Action.java

- Service 클래스들이 인터페이스를 상속함으로서 안의 추상메소드를 반드시 메소드 오버라이딩 해야함 

- 구현클래스가 여러개 있을때 이 구현 클래스들을 통일성있게 설계하기 위해 인터페이스 상속

 

+ 클래스 ActionForward.java

- 어느 페이지로 포워딩할지, 어떤 방식으로 포워딩할지 설정하는 클래스

 

- DAO 클래스인 BoardDAO.java 를 보자

- BoardDAO.java

package dao;

import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import model.BoardBean;

public class BoardDAO {

	private static BoardDAO instance = new BoardDAO();

	public static BoardDAO getInstance() {
		return instance;
	}
		
	public SqlSession getSession() {
		SqlSession session=null;
		Reader reader=null;			
		try {
			reader = Resources.getResourceAsReader("mybatis-config.xml");
			SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(reader);
			session = sf.openSession(true);	 // auto commit	
		}catch(Exception e) {
			e.printStackTrace();
		}		
		return session;
	}
	
	
	//글작성(원문작성)
	public int insert(BoardBean board) throws Exception {
		int result=0;
		SqlSession session = getSession();
		result = session.insert("board_insert", board);		
		System.out.println("result:"+result);
		
		return result;
	}
	
	
	// 총데이터 갯수 구하기
	public int getCount() throws Exception{
		int result=0;
		SqlSession session=getSession();
		result = (Integer)session.selectOne("board_count");
		
		return result;
	}
	
	
	// 데이터 목록
	public List getList(int page) throws Exception {
//	public List getList(Map map) throws Exception {
		List list = new ArrayList();
		SqlSession session=getSession();
		list = session.selectList("board_list", page); 
		
		return list;
	}
	
	
	// 조회수 증가
	public void updateCount(int board_num) throws Exception{
		SqlSession session = getSession();
		session.update("board_updatecount", board_num);
	}
		
	
	// 댓글 출력 순서 (board_re_seq값 증가)
	public void updateSeq(BoardBean board) {
		SqlSession session = getSession();
		session.update("board_updateseq", board);
	}
		

	// 댓글작성
	public void boardReply(BoardBean board) {
		SqlSession session = getSession();
		session.insert("board_reply", board);
	}
	
	
	// 상세 페이지, 수정 폼
	public BoardBean getContent(int board_num) throws Exception {
		BoardBean board = new BoardBean();

		SqlSession session=getSession();
		board  = (BoardBean)session.selectOne("board_content", board_num);
		
		return board;
	}
	
	
	// 글수정
	public void update(BoardBean board) throws Exception {
		SqlSession session=getSession();
		session.update("board_update", board);
	}

	
	// 글삭제
	public void delete(int board_num) {
		SqlSession session=getSession();
		session.delete("board_delete", board_num);
	}

}

- DB와 연동하기 위한 여러 메소드들만으로 구성되어있다

- getSession() 메소드는 SqlSession 객체를 구해오는 메소드이고, 이 객체가 있어야 지원되는 5개 메소드 사용 가능

 

- MyBatis 환경설정 파일

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<properties resource="db.properties" />
	<typeAliases>
		<typeAlias type="model.BoardBean" alias="board"></typeAlias>
	</typeAliases>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driverClassName}" />
				<property name="url" value="${jdbc.url}" />
				<property name="username" value="${jdbc.username}" />
				<property name="password" value="${jdbc.password}" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper resource="board.xml" />
	</mappers>
</configuration>

1) DTO 인 BoardBean 클래스의 alias (별칭)를 "board" 로 설정함

2) db.properties 파일의 변수들을 불러와서 DB 와 관련된 내용들을 작성함

- property 태그의 name 은 정해져있다, "driver", "url", "username", "password" 는 바뀌지 않음

3) Mapper 파일 board.xml 을 mapper 태그로 불러오고 있다

- Mapper 파일은 테이블 개수에 비례한다

- board.xml 에 따로 패키지가 없다, 즉 같은 폴더 하위에 있으므로 패키지명을 써주지 않고 board.xml 만 쓴다

 

- Mapper 파일 board.xml 을 보자

- board.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="myboard">

	<!-- 글작성(원문) -->
	<insert id="board_insert" parameterType="board">
	 insert into model22 values(model22_seq.nextval,#{board_name},
	 #{board_pass},#{board_subject},#{board_content},
	 #{board_file,jdbcType=VARCHAR},model22_seq.nextval,0,0,0,sysdate)
	</insert>
	
	<!-- 글갯수 -->
	<select id="board_count" resultType="int">
	 select count(*) from model22
	</select>
	
	<!-- 글목록 -->
	<!-- page 전달-->
	<select id="board_list" parameterType="int" resultType="board">
	 select * from (select rownum rnum, board.* from (
	  select * from model22 order by board_re_ref desc,board_re_seq asc) board )
	  where rnum &gt;= (#{page}-1) * 10 + 1   and rnum &lt;= #{page}*10
	</select> 
	
	<!-- Map 전달 -->
	<!-- <select id="board_list" parameterType="Map" resultType="board">
	 select * from (select rownum rnum, board.* from (
	  select * from model22 order by board_re_ref desc,board_re_seq asc) board )
	  where rnum &gt;= #{start} and rnum &lt;= #{end}
	</select> -->
	
	<!-- 조회수 증가 -->
	<update id="board_updatecount" parameterType="int">
     update model22 set board_readcount=board_readcount+1 where board_num = #{board_num}	
	</update>
	
	<!-- 상세페이지, 수정폼 -->
	<select id="board_content" parameterType="int" resultType="board">
	 select * from model22 where board_num = #{board_num}
	</select>
	
	<!-- 댓글 출력순서 -->
	<update id="board_updateseq" parameterType="board">
	 update model22 set board_re_seq=board_re_seq+1 
	  where board_re_ref = #{board_re_ref} and board_re_seq &gt; #{board_re_seq}
	</update>
	
	<!-- 댓글 작성 -->
	<insert id="board_reply" parameterType="board">
	 insert into model22 values(model22_seq.nextval,#{board_name},
	 #{board_pass},#{board_subject},#{board_content},
	 #{board_file,jdbcType=VARCHAR},#{board_re_ref},#{board_re_lev},#{board_re_seq},0,sysdate)
	</insert>

	<!-- 글수정 -->
	<update id="board_update" parameterType="board">
	 update model22 set board_name=#{board_name}, board_subject=#{board_subject},
	 board_content=#{board_content} where board_num=#{board_num}
	</update>

	<!-- 글삭제 -->
	<delete id="board_delete" parameterType="int">
	 delete from model22 where board_num=#{board_num}
	</delete>



	<!-- <select id="getSomething" parameterType="int" resultType="#package.#modelname">
		SELECT
		columnname1,
		columnname2,
		columnname3
		FROM tablename1
		WHERE columnname1 = #{value}
	</select>

	<resultMap type="#modelname" id="YourResultSet">
		<id property="param1" column="columnname1" />
		<result property="param2" column="columnname2" />
		<result property="param3" column="columnname3" />
	</resultMap>

	<select id="getAll" resultMap="YourResultSet">
		SELECT * FROM tablename1
	</select>

	<insert id="insertSomething" parameterType="#modelname">
		INSERT INTO tablename1 (columnname1, columnname2, columnname3)
		VALUES(#{value1},#{value2},#{value3})
	</insert>

	<update id="updateSomething" parameterType="#modelname">
		UPDATE tablename1
		SET
		columnname1=#{param1}
		WHERE
		columnname2 = #{param2}
	</update>

	<delete id="deleteSomething" parameterType="int">
		DELETE FROM tablename1 WHERE
		columnname1 = #{param1}
	</delete> -->

</mapper>

- MyBatis 환경설정 파일에서 이 Mapper 파일 board.xml 을 mapper 태그로 불러왔으므로 이 파일 board.xml 의 루트 엘리먼트는 mapper

 

글 목록 가져오기 주의 (board.xml 부분)

	<!-- 글목록 -->
	<!-- page 전달-->
	<select id="board_list" parameterType="int" resultType="board">
	 select * from (select rownum rnum, board.* from (
	  select * from model22 order by board_re_ref desc,board_re_seq asc) board )
	  where rnum &gt;= (#{page}-1) * 10 + 1   and rnum &lt;= #{page}*10
	</select>

- MyBatis 는 값이든 주소값이든 전달할 값을 단 1개만 전달 가능하므로 페이지번호 page 를 넘긴다

- 페이지번호 page 만 있으면 startRow 와 endRow 를 여기서 계산 할 수 있기 때문이다

 


JSP Model 2 - MyBatis 게시판 프로그램 : 글 작성 기능 

- 내일

복습

Maven

- Spring 프로젝트는 Maven 으로만 라이브러리 관리 가능

- Springboo 프로젝트는 Maven, Gradle 둘 다 사용 가능

 

Maven 파일 구조

- src/main/java 에 확장자가 Java 인 파일을 넣는다

- src/main/resource 는 MyBatis 의 환경 설정 파일, 기존에 DAO 에 있었던 SQL문을 따로 빼서 만든 xml 파일

- src/main/webapp 는 View 페이지가 들어감, index 파일도 아 폴더 내에 들어간다 , 기존의 WebContent 폴더 역할

- src/main/webapp/WEB-INF 안에 프로젝트 환경 설정 파일인 web.xml 이 들어감, 그리고 기존의 lib 폴더는 사라짐

- DAO 클래스는 존재하지만 DAO 클래스의 SQL문을 따로 빼서 따로 파일을 만드는데, 그걸 mapper 파일이라고 부른다

- pom.xml 이 Maven 의 환경 설정 파일, 여기에서 의존 라이브러리를 추가

 

pom.xml 환경 설정 파일

- 오라클 jdbc, mysql jdbc, cos, mybatis, jstl 등 많은 라이브러리를 다운 받았었다

 

local repository 위치

 

프로젝트 구분

- Maven 프로젝트는 m , Spring 프로젝트는 s 가 붙어있다

 

Maven 프로젝트 / Spring 프로젝트 주의

- Maven 프로젝트, Spring 프로젝트는 war 파일로 export / import 하면 Dynamic Web Project 로 풀리므로 주의

- Maven 프로젝트, Spring 프로젝트는 프로젝트를 통째로 복사 붙여넣기해서 export 해야한다

- 이클립스에서 Spring 프로젝트 만드려면 플러그인 추가 또는 STS 라는 툴을 다운받아 사용

- Spring 프로젝트는 Mavne 프로젝트와 달리 기본적인 라이브러리가 이미 pom.xml 에 다 들어가 있다

- 아파치 톰캣이 자동으로 추가되지 않는 경우가 만흥므로 직접 추가해 준다


MyBatis

데이터베이스 접속 방식

- DB 접속 방식 3번째인 ORM 프레임워크 중 MyBatis 사용할 것

 

ORM 프레임워크

- ORM 프레임워크에서 Object 는 DTO 객체를 의미, Mapping 은 연결을 의미

- 즉 DTO 클래스의 필드와 테이블 안의 컬럼을 매핑시키는 것, 매핑 처리를 위해선 두 이름을 일치시켜야함

- ORM 프레임워크를 사용시 코드가 간단해진다

- 전자정부 프레임 워크에서는 MyBatis 사용함

- MyBatis 로 Model 1 에서도 연동해보고, Model 2 에서도 연동해 볼 것

 

프로젝트에 MyBatis 연동하기 위해 필요한 파일

- MyBatis 와 연동하기 위해서는 크게 2가지 파일이 필요하다

1. MyBatis 의 환경설정 파일

2. 기존 DAO 파일에 들어있던 내용인 Mapper 파일

- 이 2개 파일이 src/main/resources 폴더 안에 있어야 한다 

 

프로젝트에 MyBatis 연동하는 방법

1. MarketPlace 에서 MyBatis Plugin 프로그램을 설치해야한다

- Mapper 파일을 생성할 수 있는 메뉴가 추가됨

- 과거에는 MarketPlace 에서 플러그인 설치시 MaBatis 환경설정 파일도 만들 수 있었지만 현재는 불가능

- 그리고 이클립스를 재시작한다

 

2. Mapper 파일을 생성해야 한다

- maventest 프로젝트의 resources 폴더에서 오른쪽 마우스 클릭

- 플러그인인을 설치시 Mapper 파일을 생성할 수 있는 메뉴가 나타남

- resources 폴더 안에 Mapper 파일이 들어갔다

- mapper.xml (예시)

- 이 파일 안에 DAO 의 insert, update, delete, select 가 들어감

- xml 파일이므로 루트 엘리먼트가 있어야하고, 루트 엘리먼트가 mapper

- insert 태그 사이에 insert SQL문 작성, 다른 DML 문도 마찬가지, 태그에는 각 SQL문을 구분하기 위한 id 가 있다

 

3. 다음으로는 MyBatis 환경설정 파일이 다운받아진 프로젝트를 import 해서 MaBatis 환경설정 파일을 살펴보자

- 현재는 플러그인 추가해도 MaBatis 환경설정 파일 생성 불가

 

MaBatis 프로젝트 import 하기

- mybatistest 프로젝트 import

- mabatistest.zip 파일을 다운로드 받아서 압축 해제 후 import

- mybatistest 가 import 되었다

- MyBatis 와 연동되는 이 3개 파일들은 모두 src/main/resources 폴더 안에 저장되어 있어야 함

 

MyBatis 연동 관련 파일 살펴보기

1. db.properties

#mysql
#jdbc.driverClassName=com.mysql.jdbc.Driver
#jdbc.url=jdbc:mysql://localhost:3306/jsptest
#jdbc.username=jspid
#jdbc.password=jsppass

#oracle
jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:xe
jdbc.username=scott
jdbc.password=tiger

- DB 접속에 필요한 내용들이 들어간다

- 위에는 mysql 과 연동하는 내용, 아래는 oracle 과 연동하는 내용을 작성했다

- 왼쪽은 변수명, 오른쪽은 변수에 할당될 값이다

- jdbc.driverClassName 변수명은 정해져 있는 이름이다, 경로를 oracle.jdbc.driver.OracleDriver 로 설정

- jdbc.url 변수에는 jdbc:oracle:thin:@localhost:1521:xe 로 DB 경로를 설정

- 현재는 scott 계정으로 연결 설정했다

- # 은 주석

- mybatis-config.xml 파일에서 이 파일 db.properties를 불러온다

 

2. mybatis-config.xml

- MyBatis 의 환경설정 파일이다

+ 환경 설정 파일은 대부분 xml 파일이다

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<properties resource="db.properties" />
	<typeAliases>
		<typeAlias type="#package.#modelname" alias="#modelname"></typeAlias>
	</typeAliases>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driverClassName}" />
				<property name="url" value="${jdbc.url}" />
				<property name="username" value="${jdbc.username}" />
				<property name="password" value="${jdbc.password}" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper resource="#package/#mapper.xml" />
	</mappers>
</configuration>

- 위의 DOCTYPE configuration 내용을 삭제하면 안된다, mybatis.org/dtd/mabatis-3-config.dtd 문서를 불러오고 있다

- 루트 엘리먼트는 configuration

 

Mabatis 환경설정 파일인 mybatis-config.xml 파일 안에 들어가는 내용 3가지

0) properties 태그에서 db.properties 파일을 불러온다

1) typeAliases 태그로 DTO 클래스의 경로를 alias 로 간단한 별칭을 설정

- typeAlias 태그의 type 속성에서 DTO 클래스의 경로를 패키지부터 해당 클래스까지의 경로로 작성

- typeAlias 태그의 alias 속성에서 alias는 별칭을 의미, 그 경로를 짧은 별칭으로 설정하는 것

- DTO 클래스가 많아지면 typealias 태그를 추가해주면 된다

2) environments 태그 안의 property 태그에서 DB 접속 관련 내용들 작성

- property 태그는 클래스의 멤버변수(필드, 프로퍼티) 를 의미, name 속성값은 고정되어있고, values 값으로 db.property 안의 변수명을 이용해서 변수값을 불러오고 있다

+ 변수명으로 불러오는 대신 그 자리에 바로 값을 써도 된다

ex) <property name="driver" value="oracle.jdbc.driver.OracleDriver"/>

- 그렇게 드라이브의 경로, DB 경로, 계정명, 비밀번호 등을 불러옴

- db 접속하는 내용들이 들어간다

- 위에서 db.property 파일을 불러왔으므로 가능함

3) mappers 태그 안의 mapper 태그로 SQL문이 들어간 파일인 Mapper 파일을 불러오기

- mapper 태그의 resource 속성에 패키지명을 적어주고, Mapper 파일명을 써주기

+ 여기서 mapper 태그 안에서 Mapper 파일을 불러오므로 Mapper 파일의 루트 엘리먼트도 mapper 이다

- Mapper 파일은 테이블의 개수에 비례해서 늘어난다

- Mapper 파일이 늘어나면 mapper 태그를 추가해주면 된다

 

3. mapper.xml

- Mapper 파일 이다

- 이름은 반드시 mapper.xml 이 아니다

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="#package.#mapperinterfacename">

	<select id="getSomething" parameterType="int" resultType="#package.#modelname">
		SELECT
		columnname1,
		columnname2,
		columnname3
		FROM tablename1
		WHERE columnname1 = #{value}
	</select>

	<resultMap type="#modelname" id="YourResultSet">
		<id property="param1" column="columnname1" />
		<result property="param2" column="columnname2" />
		<result property="param3" column="columnname3" />
	</resultMap>

	<select id="getAll" resultMap="YourResultSet">
		SELECT * FROM tablename1
	</select>

	<insert id="insertSomething" parameterType="#modelname">
		INSERT INTO tablename1 (columnname1, columnname2, columnname3)
		VALUES(#{value1},#{value2},#{value3})
	</insert>

	<update id="updateSomething" parameterType="#modelname">
		UPDATE tablename1
		SET
		columnname1=#{param1}
		WHERE
		columnname2 = #{param2}
	</update>

	<delete id="deleteSomething" parameterType="int">
		DELETE FROM tablename1 WHERE
		columnname1 = #{param1}
	</delete>

</mapper>

- select 문은 select 태그로 감싸준다

- 태그들을 구분하기 위해 id 값을 다르게 설정한다

- DAO 파일에서 이 Mapper 파일의 id 값을 불러서 사용한다

<값 전달하기 : parameterType>

- insert , select 할 데이터는 parameterType 속성으로 값을 받는다

- parameterType 속성값에 전달되는 값의 자료형을 작성

< 검색한 값 돌려받기 : returnType >
- select 해서 검색한 결과를 resultType 속성으로 값을 돌려준다

- select SQL문만 returnType 속성을 사용함

- resultType 속성값에는 값을 돌려줄때의 자료형을 작성

- #{} 에서 getter 메소드로 불러올 값들을 작성함

< 검색한 값 돌려받기 : 데이터 1개 검색 >

- 데이터를 1개 검색했을때는 if(rs.next()) 로 가져와서 DTO 객체에 저장했었다, 일일히 setter 메소드로 저장했었음

- 이제는 일일히 setter 메소드를 작성하지 않고 컬럼명에 일치하는 DTO 프로퍼티가 있을땐 자동으로 setter 메소드를 불러서 매핑해준다

- returnType 에 DTO 경로 별칭만 써주면 테이블명과 DTO 프로퍼티가 일치하는 경우에 자동으로 매핑

< 검색한 값 돌려받기 : 데이터 2개 이상 검색 >

- 2개 이상의 데이터를 검색할때는 리스트를 통해 값을 받았었다, 여기서는 resultType 으로 DTO 클래스의 alias 명을 쓰면 된다, 리스트에 저장시키는 것 까지 자동으로 해줌

- returnType 에 DTO 경로 별칭만 써주면 테이블명과 DTO 프로퍼티가 일치하는 경우에 자동으로 매핑, 리스트 저장까지 해줌

<자료형이 DTO일땐>

- 전달되는 값의 자료형이나 돌려주는 값의 자료형이 DTO일땐, MyBatis 환경설정 파일에서 설정한 DTO 클래스의 alias 값이 파라미터 타입값으로 들어가야한다

<resultMap>

- resultMap 은 컬럼명과 DTO 프로퍼티명이 일치하지 않을때, resultMap 으로 일일히 매핑을 잡아줘야한다

- resultMap 태그 안에서 컬럼과 프로퍼티를 일일히 매핑


JSP - MyBatis 연동

Model 1 과 MyBatis 연동

- 클라우드에서 Model 1 과 MyBatis 가 이미 연동이 완료된 member22 프로젝트 import

- zip 파일을 압축 풀어서 import

+ 그냥 member22 파일을 바로 import 하면 . 으로 시작된 파일들이 import 안된다

+ 반드시 zip 파일을 풀어서 생성된 폴더를 import 하기

- src/main/java 안에 확장자가 java 인 파일들이 저장되어있다

- src/main/resources 안에 DB 연동에 필요한 MyBatis 환경설정 파일 등이 들어가 있다

- src/main/webapp 안에 View 파일 등이 들어간다

 

- import한 member22 프로젝트의 pom.xml 파일을 보자

- pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.myhome</groupId>
	<artifactId>member22</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>member22 Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<!-- 오라클 repository -->
	<repositories>
		<repository>
			<id>codelds</id>
			<url>https://code.lds.org/nexus/content/groups/main-repo</url>
		</repository>
	</repositories>

	<dependencies>

		<!-- 오라클 JDBC Library -->
		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.3</version>
		</dependency>

		<!-- MySQL -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.47</version>
		</dependency>

		<!-- iBatis -->
		<dependency>
			<groupId>org.apache.ibatis</groupId>
			<artifactId>ibatis-sqlmap</artifactId>
			<version>2.3.4.726</version>
		</dependency>

		<!-- mybatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.5.3</version>
		</dependency>

		<!-- jstl -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.taglibs</groupId>
			<artifactId>taglibs-standard-impl</artifactId>
			<version>1.2.5</version>
		</dependency>

		<!-- cos -->
		<dependency>
			<groupId>servlets.com</groupId>
			<artifactId>cos</artifactId>
			<version>05Nov2002</version>
		</dependency>
		
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<finalName>member22</finalName>
	</build>
</project>

- dependencies 태그 안에 의존 라이브러리를 dependency 태그로 추가

- 오라클 JDBC, MySQL JDBC, iBatis, MyBatis 3.5.3 ver, JSTL, cos 라이브러리가 추가되었다

- 오라클 공식 저장소에서 다운받는 건 오류 많이 발생

- 오라클은 비공식 저장소 repository 를 원격 저장소 위치로 등록하고 거기서 오라클 JDBC 라이브러리를 불러오기

- MyBatis 연동시에는 반드시 MyBatis 라이브러리가 추가되어 있어야함

 

Model 1 에 MyBatis 연동시 구조

- Model 1 에서 MyBatis 를 연동하면 DAO 이후부터만 달라진다

- 앞부분은 Model 1과 동일하고 달라지는 내용은 DAO 이후 부터 이다

- DAO 메소드 안의 복잡한 내용들이 빠져서 MyBatis 환경설정 파일로 간다

- 안의 SQL문들이 빠져서 Mapper 파일인 member.xml 으로 간다

 

- MyBatis 환경설정 파일인 mybatis-config.xml 파일을 보자

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<properties resource="db.properties" />
	<typeAliases>
		<typeAlias type="model.Member" alias="member"></typeAlias>
	</typeAliases>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driverClassName}" />
				<property name="url" value="${jdbc.url}" />
				<property name="username" value="${jdbc.username}" />
				<property name="password" value="${jdbc.password}" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper resource="member.xml" />
	</mappers>
</configuration>

- model 패키지 안의 DTO 경로를 alias 속성을 통해 member 라는 별칭으로 지정

- mapper 태그로 Mapper 파일인 member.xml 파일을 불러옴, 같은 폴더 안에 있으므로 이름만으로 불러옴

- Mapper 파일이 다른 패키지안에 있으면 불러올 때 패키지명도 명시해줘야함

 

- Mapper 파일인 member.xml 파일을 보자

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="memberns">

	<insert id="insert" parameterType="member">
		insert into member22 values (#{id}, #{password})
	</insert>
	
	<select id="list" resultType="member">
		select * from member22
	</select>
	
	<select id="select" parameterType="String" resultType="member">
		select * from member22 where id = #{id}
	</select>	
	
	<update id="update" parameterType="member">
		update member22 set password = #{password} where id = #{id}
	</update>
	
	<delete id="delete" parameterType="String">
		delete from member22 where id = #{id}
	</delete>
	
</mapper>

 

- db.properties

#mysql
#jdbc.driverClassName=com.mysql.jdbc.Driver
#jdbc.url=jdbc:mysql://localhost:3306/jsptest
#jdbc.username=jspid
#jdbc.password=jsppass

#oracle
jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:xe
jdbc.username=totoro
jdbc.password=totoro123

 

- db.properties 파일에서 totoro 계정으로 설정되어있으므로 totoro 계정에서 테이블을 생성해야함

+ import 시에 테이블이 생성되지는 않음

- sql 폴더의 member.sql 에서 totoro 계정으로 연결한 후 member22 테이블을 생성하자

- member.sql

-- model1 과 mybatis 연동
select * from tab;
select * from member22;

create table member22 (
	id varchar2(10) primary key,
	password varchar2(10)
);

- 테이블 member22 를 생성하자

- 테이블의 필드명과 DTO 클래스의 멤버변수(필드, 프로퍼티) 명을 같은 이름으로 설정해야한다

+ 간소화 시킨 테이블임


흐름 설명

- src/main/java 에서 model 패키지의 DTO 클래스인 Member.java 를 보자

package model;

public class Member {
	private String id;
	private String password;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
}

- 프로퍼티명이 컬럼명과 동일하게 id, password 로 되어있다, 쉽게 매핑 가능해짐

 

- 회원가입 페이지인 joinForm.jsp 에서 form 의 name 값도 똑같이 id, password 로 일치시킨다

- 그렇게 해야 setProperty 로 쉽게 저장 가능 (Model 1)

- joinForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>

<body>
<h2>회원가입</h2>
<form action="joinPro.jsp">
	아이디 : <input type="text" name="id" required="required"><p>
	암호 : <input type="password" name="password" required="required"><p>
	<input type="submit" value="확인">
</form>
</body>
</html>

 

- loginForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>

<body>
<h2>로그인</h2>
<form action="loginPro.jsp" >
	아이디 : <input type="text" name="id" required="required"><p>
	암호 : <input type="password" name="password" required="required"><p>
	<input type="submit" value="확인">
</form>
<p><a href="joinForm.jsp">회원가입</a>
</body>

</html>

 

- index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<html>
<body>
<h2>Hello World!</h2>

<script>
	location.href="member/loginForm.jsp";
</script>

</body>
</html>

- index 파일은 webapp 폴더 안에 저장되어 있어야함

- index 파일을 실행시켜도 되고, 현재 프로젝트 선택 후 Run As - Run on Server 로 실행 하면 index 파일이 실행됨

- 실행시 index.jsp 파일에서 loginForm.jsp 파일을 실행시킴

- 회원 가입을 해보자, 클릭 시 joinForm.jsp 파일로 넘어감

- '확인' 시 joinPro.jsp 파일로 넘어가면서 id, password 를 네임값으로 해서 입력된 값을 전달

- joinPro.jsp 에서 setProperty 로 DTO 객체 mem에 값 설정 후 insert(mem) 호출

- 가입 후 로그인

- master 란 아이디로 가입시 관리자

- 관리자로 로그인 시 나오는 내용이 달라짐

- 회원명단을 볼 수 있는 메뉴가 나타남

- 즉, 관리자로 로그인 시 나오는 내용이 달라짐

- 회원 수정이나 회원 탈퇴 시킬 수 있다

 

- 앞부분은 Model 1과 동일하고 달라지는 내용은 DAO 이후 부터 이다

- DAO 클래스가 SQL문을 실행하는 것은 같지만, DAO 클래스 안에서 SQL문이 빠져서 Mapper 파일로 간다

 

- Mapper 파일인 member.xml 파일을 보자

-member.xml (중복)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="memberns">

	<insert id="insert" parameterType="member">
		insert into member22 values (#{id}, #{password})
	</insert>
	
	<select id="list" resultType="member">
		select * from member22
	</select>
	
	<select id="select" parameterType="String" resultType="member">
		select * from member22 where id = #{id}
	</select>	
	
	<update id="update" parameterType="member">
		update member22 set password = #{password} where id = #{id}
	</update>
	
	<delete id="delete" parameterType="String">
		delete from member22 where id = #{id}
	</delete>
	
</mapper>

- id 값은 태그를 구별하는 역할, 이 파일 내에선 중복되선 안되는 값이다

- update 문은 update 태그 안에 작성

- DAO 클래스에서 이 id 값을 불러온다, 즉, 안의 SQL문을 불러온다

- 전달되는 값의 자료형을 parameterType 에 쓰고 돌려주는 값의 자료형을 resultType 에 쓴다

- resultType 은 select 태그 (select 문) 에만 사용가능하다

- 전달되는 값이 없을땐 parameterType 속성을 사용하지 않음

- DTO 객체가 전달되는 값의 자료형이거나 돌려주는 값의 자료형일땐 alias (별칭) 값을 쓴다

- 2개 이상의 데이터를 검색할때도 똑같이 DTO 객체인 alias 값을 사용하면 자동으로 리스트에 저장해서 돌려줌

 

- 다음은 MyBatis 환경 설정 파일인 mybatis-config.xml 을 보자

- mybatis-config.xml (중복)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<properties resource="db.properties" />
	<typeAliases>
		<typeAlias type="model.Member" alias="member"></typeAlias>
	</typeAliases>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${jdbc.driverClassName}" />
				<property name="url" value="${jdbc.url}" />
				<property name="username" value="${jdbc.username}" />
				<property name="password" value="${jdbc.password}" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper resource="member.xml" />
	</mappers>
</configuration>

1. 이 alias 를 Mapper 파일안에서 값을 받고 돌려줄때 사용한다

+ alias 를 설정하지 않았다면 패키지부터 클래스까지 전부 써야한다

2. DB 접속에 필요한 내용을 property 태그 안에 작성, db.properties 파일의 변수를 불러와서 설정

+ 변수 불러오지 않아도 바로 적어도 된다

3. Mapper 파일을 불러오고 있다

 

DAO 클래스에서 id 값을 불러오는 원리

- DAO 에서 바로 Mapper 파일의 id를 불러올 수 없다

- 먼저 MyBatis 환경설정 파일 3번째 자리에서 Mapper 파일을 불러온다

- DAO에서는 MyBatis 환경 설정 파일을 불러옴으로서 id 를 불러올 수 있음

- 즉 Mapper 파일을 MyBatis 환경설정 파일에서 불러오고, MyBatis 환경설정 파일을 DAO 에서 불러온다

- 연쇄적으로 불러오기 때문에 Mapper 파일에 설정된 id 를 DAO 클래스에서 불러 올 수 있음

 

- DAO 클래스인 MemberDao.java 를 보자

- MemberDao.java

package dao;

import java.io.IOException;
import java.io.Reader;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import model.Member;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class MemberDao {
	
	private SqlSession getSession() {
		SqlSession session=null;
		Reader reader=null;
		try {
			reader = Resources.getResourceAsReader("mybatis-config.xml");
			SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(reader);
			session = sf.openSession(true);			// auto commit
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
		return session;
	}

	public int insert(Member member) {
		int result = 0;
		SqlSession session=null;
		try { 
			session = getSession(); 
			result = session.insert("insert", member);			
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

	public int chk(Member member) {
		int result = 0;
		SqlSession session=null;
		try { session = getSession(); 
			Member mem = (Member) session.selectOne("select", member.getId());
			if (mem.getId().equals(member.getId())) {
				result = -1;
				if (mem.getPassword().equals(member.getPassword())) {
					result = 1;
				}
			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

	public Member select(String id) throws SQLException {
		Member mem = null;
		SqlSession session=null;
		try { session = getSession(); 
		mem = (Member) session.selectOne("select", id);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return mem;
	}

	public List<Member> list() {
		List<Member> list = new ArrayList<Member>();
		SqlSession session=null;
		try { session = getSession(); 
			list = session.selectList("list");
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return list;
	}

	public int delete(String id) {
		int result = 0;
		SqlSession session=null;
		try { session = getSession(); 
			result = session.delete("delete", id);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

	public int update(Member mem) {
		int result = 0;
		SqlSession session=null;
		try { session = getSession(); 
			result = session.update("update", mem);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}
}

- 마찬가지로 DB 연동을 처리하는 클래스이다

- SQL문이 빠지고 Mapper 파일인 member.xml 파일에 들어가있다

- SQL문을 제외한 나머지 내용이 들어가있음, 메소드만으로 구성되어있다

+ MyBatis 지원 클래스/인터페이스 (SqlSession 인터페이스 등) 들을 import 하고 있다 * 아래에 설명

 

SqlSession 객체 구하기 / MyBatis 환경설정 파일 읽어오기 (MemberDao.java 부분 getSession() 메소드)

	private SqlSession getSession() {
		SqlSession session=null;
		Reader reader=null;
		try {
			reader = Resources.getResourceAsReader("mybatis-config.xml");
			SqlSessionFactory sf = new SqlSessionFactoryBuilder().build(reader);
			session = sf.openSession(true);			// auto commit
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
		return session;
	}

- getSession() 메소드에서는 try 안의 3줄을 통해 SqlSession 객체를 구해준다, 그 객체를 통해 SQL문을 실행하는 메소드들을 사용 가능하다

- Reader 객체를 구할때 MyBatis 환경설정 파일인 mybatis-config,xml 을 불러오고 있다, MyBatis 환경설정 파일에서는 Mapper 파일을 불러옴

- 그렇기 때문에 DAO 에서 Mapper 파일의 id 값을 불러올 수 있다, 즉 Mapper 파일의 SQL문을 불러올 수 있음

- sf.openSession(true) 를 하면 자동 커밋이 된다, 자동 커밋이 되지 않는다면 SQL문을 수행 후 일일히 session.commit() 으로 커밋을 수행해야함

* 커밋을 해야 DB에 실제 삽입, 수정, 삭제 등이 됨

 

DAO 안에서 id 로 불러와서 SQL문 실행 (MemberDao.java 부분)

	public int insert(Member member) {
		int result = 0;
		SqlSession session=null;
		try { 
			session = getSession(); 
			result = session.insert("insert", member);			
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

- 가장 먼저 getSession() 메소드로 SqlSession 객체를 구한다

- DAO 안의 메소드 안에서 그 SQL문을 실행해야하는데, 이때 SqlSession 에서 지원하는 메소드를 사용한다

- insert() 메소드를 사용해서 id 값인 "insert" 로 Mapper 클래스의 insert 문을 불러온다

메소드에서 메소드 첫번째 매개변수에는 id값, 전달될 값이 있다면 두번째 매개변수에 전달될 값을 전달

- 여기서는 전달받은 DTO 객체인 member 를 두번째 매개변수를 통해 전달하고 있음

- insert() 메소드를 실행 후 삽입 성공한 데이터 개수를 자동으로 반환해줌

- 위의 insert() 메소드는 SQL문을 실행시켜주는 메소드 5가지 중 하나이다

 

SQL문을 실행시켜주는 메소드 5가지 (MemberDao.java 부분)

- SQL문에 따라서 메소드가 정해져 있다

- 이 메소드를 지원하는 인터페이스 SqlSession 객체를 구해와야만 이 메소드 5개를 사용 가능하다

- 그래서 위에서 getSession() 메소드로 SqlSession 객체를 구해왔다

+ SqlSession 은 MyBatis 에서 지원되는 인터페이스

 

- insert SQL문은 insert() 메소드로 실행해야한다, update, delete 도 마찬가지

- 검색되는 결과가 1개인 select 문은 selectOne() 메소드로 실행

- 결과를 Object 형으로 돌려준다, 다운캐스팅

+ Member 는 DTO 클래스 이름이다

- 검색되는 결과가 2개 이상인 selec 문은 selectList() 메소드로 실행

- 결과를 List 로 돌려준다

 

- insert() / update() / delete() 메소드는 삽입, 수정, 삭제된 데이터 개수를 자동으로 돌려준다

- 메소드 자체적으로 값을 자동으로 돌려주므로 Mapper 파일에서 insert / update / delete 문은 resultType 을 쓰면 안됨

- resultType 은 select 문만 사용가능

<데이터를 1개 검색시>

- selectOne() 메소드를 사용시에는 값을 Object 형 으로 돌려주므로 다운캐스팅 해야한다

- resultType 에는 DTO 객체를 쓴다

<데이터를 2개 이상 검색시>

- 데이터 2개 이상 검색하는 select문에서도 resultType 에는 DTO 객체만 쓴다, List 를 쓰지 않음! 자동으로 리스트를 잡아서 돌려준다

- 일일히 가져와서 DTO 객체 에 setter 메소드로 저장했었지만 이제는 검색된 데이터의 컬럼명과 DTO 프로퍼티명이 같다면 자동으로 순차적으로 DTO 객체에 저장하면서 List 를 만들어서 돌려줌

 

Mapper 의 insert SQL문을 insert() 메소드로 불러와서 실행 (MemberDao.java 부분, 중복) 

- update(), delete() 도 마찬가지

	public int insert(Member member) {
		int result = 0;
		SqlSession session=null;
		try { 
			session = getSession(); 
			result = session.insert("insert", member);			
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

- SqlSession 객체가 있어야 insert() 메소드 사용 가능, 이전에 정의한 getSession() 으로 객체를 받아온다

- insert() 메소드 자체적으로 삽입에 성공한 데이터 개수를 돌려주는 기능이 있으므로 Mapper 파일의 insert에 resultType 을 써선 안됨

- 그렇게 돌려받은 값을 result 변수에 받아서 그 변수를 리턴하고 있음

- insert() 메소드에서 메소드 첫번째 매개변수에는 id값, 전달될 값이 있다면 두번째 매개변수에 전달될 값을 전달

- 여기서는 전달받은 DTO 객체인 member 를 두번째 매개변수를 통해 전달하고 있음

- Mapper 파일의 parameterType 에서도 같은 자료형인 DTO 를 써야함

- Mapper 파일 member.xml 부분

	<insert id="insert" parameterType="member">
		insert into member22 values (#{id}, #{password})
	</insert>

- parameterType 에는 전달될 값의 자료형을 적음, DTO 객체를 전달받아야하므로 alias 로 설정한 별칭인 member 를 적는다

- #{} 안에 DTO 클래스의 필드명을 써서, 값을 가져온다.

- 표기만 이럴뿐 DTO 객체에 직접 접근하는 것이 아니라 getter 메소드로 가져오는 것임

- #{id} 의 의미는 member.getId() 로 리턴받은 값을 의미함

- #{password} 의 의미는 member.getPassword() 로 리턴받은 값을 의미

 

+ 단점 : 값을 1개만 전달 가능

 

Mapper 의 select SQL문을 select() 메소드로 불러와서 실행 1(MemberDao.java 부분)

	public Member select(String id) throws SQLException {
		Member mem = null;
		SqlSession session=null;
		try { session = getSession(); 
		mem = (Member) session.selectOne("select", id);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return mem;
	}

- SqlSession 객체가 있어야 select() 메소드 사용 가능, 이전에 정의한 getSession() 으로 객체를 받아온다

- select() 메소드는 돌려주는 데이터가 몇개인지에 따라 다른 메소드를 쓴다, 1개 데이터를 돌려줄때는 selectOne() 사용

- 두번째 매개변수로 사용자의 id 값을 전달

- 마찬가지로 Mapper 파일의 parameterType 도 String 형으로 설정해야함

- Mapper 파일 member.xml 부분

	<select id="select" parameterType="String" resultType="member">
		select * from member22 where id = #{id}
	</select>

- 넘어오는 값은 사용자의 id 값이므로 paramterType 은 String 으로 설정해야함

- #{id} 의 의미는 전달된 변수 id 값을 가져오는 것임

- 데이터 1개를 돌려주므로 resultType 을 DTO 의 alias 인 member 로 설정

- if(rs.next()) 안에서 수행했던 member.setId(rs.getString("id")) 를 더이상 쓰지 않음, 돌려주는 객체 member 에 자동으로 setter 메소드로 값을 다 저장해서 돌려준다

+ DTO 프로퍼티명과 컬럼명이 일치하는 경우에만 가능

 

Mapper 의 select SQL문을 select() 메소드로 불러와서 실행 2(MemberDao.java 부분)

- 위의 select() 와는 다른 메소드, 이건 로그인 시 회원 인증을 해주는 chk() 메소드이다

	public int chk(Member member) {
		int result = 0;
		SqlSession session=null;
		try { session = getSession(); 
			Member mem = (Member) session.selectOne("select", member.getId());
			if (mem.getId().equals(member.getId())) {
				result = -1;
				if (mem.getPassword().equals(member.getPassword())) {
					result = 1; // 회원인증 성공
				}
			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

- 로그인 시 회원인증을 처리하는 부분이다

- mem.getId() 는 DB에 저장된 id 를 의미하고, member.getId()는 사용자가 입력한 id 를 의미

- mem.getPassword() 는 DB에 저장된 password 를 의미하고, member.getPassword()는 사용자가 입력한 password 를 의미

- id, password 가 모두 일치할때는 변수 result 에 1 을 저장해서 리턴함, 이 값으로 로그인 성공을 판별하는 것은 loginPro.jsp 에서 처리함

- id 는 일치하되, passoword 가 일치하지 않을때는 변수 result 에 -1 을 저장해서 리턴, 이것도 loginPro.jsp 에서 처리

 

- Mapper 파일 member.xml 부분

	<select id="select" parameterType="String" resultType="member">
		select * from member22 where id = #{id}
	</select>

회원가입 기능

Model 1 & MyBatis 회원 가입 흐름

1. 처음 index.jsp 실행 -> loginForm.jsp -> '회원가입' 눌러서 joinForm.jsp 로 넘어감

2. joinForm.jsp 에서 입력된 값을 id, password 네임값에 저장해서 joinPro.jsp 로 전달

3. joinPro.jsp 에서 useBean 과 setProperty 로 DTO 객체 mem 생성 후 값 저장, DAO 객체의 insert(mem) 호출함

4. DAO 에서 insert() 메소드를 실행, 그 안에서 Mapper 파일의 insert SQL문을 id 로 불러온다

- insert() 가 잘 수행되면 1을 돌려줌

5. joinPro.jsp 로 돌아오고, 회원가입 성공 시 loginForm.jsp 로 넘어간다, 실패(중복아이디)시 이전페이지로 이동

 

- joinForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>

<body>
<h2>회원가입</h2>
<form action="joinPro.jsp">
	아이디 : <input type="text" name="id" required="required"><p>
	암호 : <input type="password" name="password" required="required"><p>
	<input type="submit" value="확인">
</form>
</body>
</html>

 

- joinPro.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="dao.*"%>

<jsp:useBean id="mem" class="model.Member"/>
<jsp:setProperty property="*" name="mem"/>

<%
MemberDao md = new MemberDao();
int result = md.insert(mem);

if (result>0) {
%> 
	<script type="text/javascript">
		alert("가입성공");
		location.href="loginForm.jsp";
	</script>
<%
} else {
%> 
	<script type="text/javascript">
		alert("가입 실패");
		history.go(-1);
	</script>
<%
}
%>

 

- MemberDao.java 에서 insert() 메소드 부분만

	public int insert(Member member) {
		int result = 0;
		SqlSession session=null;
		try { 
			session = getSession(); 
			result = session.insert("insert", member);			
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

 


- member.xml 에서 회원 가입을 처리하는 SQL문 부분 (id 가 insert)

	<insert id="insert" parameterType="member">
		insert into member22 values (#{id}, #{password})
	</insert>

로그인 기능

Model 1 & MyBatis 로그인 흐름

1. 처음 index.jsp 실행 -> loginForm.jsp 로 넘어감

2. loginForm.jsp 에서 입력된 값을 id, password 네임값에 저장해서 loginPro.jsp 로 전달

3. loginPro.jsp 에서 useBean 과 setProperty 로 DTO 객체 mem 생성 후 값 저장, DAO 객체의 chk(mem) 호출함

4. DAO 에서 chk() 메소드를 실행, 그 안에서 Mapper 파일의 select SQL문을 id 로 불러온다, selectOne() 가 잘 수행되면 DB에 저장된 사용자의 정보를 가져옴

- 다운캐스팅 해서 DTO 객체에 저장 (다운캐스팅 생략해도 작동한다)

- 그 정보가 사용자가 입력한 값과 일치시 result 에 1을 저장해서 돌려준다

5. loginPro.jsp 로 돌아오고 회원 인증 성공 시 id 를 세션 공유 설정하고 main.jsp 로 넘어간다, 실패(중복아이디)시 이전페이지로 이동

 

- loginForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>

<body>
<h2>로그인</h2>
<form action="loginPro.jsp" >
	아이디 : <input type="text" name="id" required="required"><p>
	암호 : <input type="password" name="password" required="required"><p>
	<input type="submit" value="확인">
</form>
<p><a href="joinForm.jsp">회원가입</a>
</body>

</html>

 

- loginPro.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@page import="dao.MemberDao"%>

<jsp:useBean id="mem" class="model.Member"/>
<jsp:setProperty property="*" name="mem"/>

<%
MemberDao md = new MemberDao();
int result = md.chk(mem);

if (result==1) {
	session.setAttribute("id",mem.getId());
%> 
	<script type="text/javascript">
		alert("환영함");
		location.href="main.jsp";
	</script>
<%
} else if (result == -1) {
%> 
	<script type="text/javascript">
		alert("비번이 다르다");
		history.go(-1);
	</script>
<%
} else {
%> 
	<script type="text/javascript">
		alert("그런 ID가 없습니다.");
		history.go(-1);
	</script>
<%
}
%>

 

- MemberDao.java 에서 chk() 메소드 부분만

	public int chk(Member member) {
		int result = 0;
		SqlSession session=null;
		try { session = getSession(); 
			Member mem = (Member) session.selectOne("select", member.getId());
			if (mem.getId().equals(member.getId())) {
				result = -1;
				if (mem.getPassword().equals(member.getPassword())) {
					result = 1; // 회원인증 성공
				}
			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

 

- member.xml 에서 로그인(회원 인증)을 처리하는 SQL문 부분 (id 가 select)

	<select id="select" parameterType="String" resultType="member">
		select * from member22 where id = #{id}
	</select>

 


DAO 클래스 상단에서 MyBatis 지원 클래스 / 인터페이스 import



중간 페이지 기능

- 로그인 성공시 이동하는 main.jsp 내용을 보자

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
	
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>

<%
 	String id = (String) session.getAttribute("id");
if (id == null || id.equals("")) {
	response.sendRedirect("loginForm.jsp");
} else if (id.equals("master")) {
	out.println("관리자 모드!");	
} 
%>

<body>
	로긴함
	<p>
		정보수정<br>
<%
		if (id != null) {
		if (id.equals("master")) {
%>
		<a href="list.jsp">회원명단</a><br>
<%
		}}
%>
		<a href="logout.jsp">로그아웃</a>
		
</body>
</html>

-로그인 성공시 공유한 공유값 id 값을 가져와서 사용

- getAttribute() 로 공유값을 가져올때는 다운캐스팅을 해야함

- 받아온 공유값인 id 가 null 인 경우, Redirect 포워딩 팡식으로 loginForm.jsp 로 넘어감

- 받아온 공유값인 id 가 "master" 인 경우 "관리자 모드!" 라는 메세지가 출력됨

<body 태그 안>

- 받아온 공유값인 id가 "master" 인 경우 "회원명단" 출력, 클릭시 list.jsp 로 이동하도록 설정

- 어느 아이디로 로그인되었느냐에 따라 main.jsp 내용이 달라짐

- '로그아웃' 클릭시 logout.jsp 로 이동 : 세션 삭제, 로그아웃 메세지 출력하고 로그인 폼으로 이동하는 작업을 한다


로그아웃 기능

- '로그아웃' 을 클릭시 넘어가는 logout.jsp 를 보자

- logout.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<%
session.invalidate();
%>

<script type="text/javascript">
	alert("로그아웃 됨");
	location.href="loginForm.jsp";
</script>

</body>
</html>

1. 세션 삭제(끊어줌)

2. 로그아웃 메세지 출력하고 loginForm.jsp 로 이동

- 이 두가지 작업만 한다

- DB 연동 안함


목록 페이지 기능

- '회원명단' 을 클릭시 넘어가는 list.jsp 를 보자

- list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@page import="java.util.List"%>
<%@page import="model.Member"%>
<%@page import="dao.MemberDao"%>

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

회원 명단
<table border=1><tr><th>아이디</th><th>비밀번호</th><th>수정</th><th>삭제</th></tr>

<%
	MemberDao md = new MemberDao();
	List<Member> list = md.list();
	
for (int i = 0;i<list.size();i++){
%>
	<tr><td><%=list.get(i).getId() %></td><td><%=list.get(i).getPassword() %></td>
	<td><input type="button" value="수정" onclick='location.href="updateForm.jsp?id=<%=list.get(i).getId() %>"'></td>
	<td><input type="button" value="삭제" onclick='location.href="delete.jsp?id=<%=list.get(i).getId() %>"'></td></tr>
<%
}
%>
</table>

<a href="main.jsp">메인으로</a>

</body>
</html>

- id 가 master 인 관리자 계정만 볼 수 있다

- DB 연동을 수행해서 전체를 검색한다, DAO 객체 생성해서 전체 회원 목록을 구해오는 list() 메소드 호출

- 결과를 list 로 돌려받은 다음 반복문에서 get() 과 getter 메소드로 값을 가져와서 테이블에 뿌리고 있다

- '수정' 또는 '삭제' 를 클릭시 해당 회원의 아이디값을 넘기면서 updateForm.jsp 또는 delete.jsp 로 이동한다

- 관리자 모드이므로 '삭제' 누르면 바로 삭제되도록 했다

- 이 list() 메소드가 정의되어 있는 DAO 클래스의 부분을 보자

 

- MemberDao.java 에서 list() 메소드 부분만

	public List<Member> list() {
		List<Member> list = new ArrayList<Member>();
		SqlSession session=null;
		try { session = getSession(); 
			list = session.selectList("list");
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return list;
	}

- 제네릭을 쓰고 있으므로 리스트에서 get() 으로 가져올때 자료형 생략 가능

- SqlSession 객체를 구해와야만 selectList() 를 사용 가능하므로 getSession() 호출해서 SqlSession 객체 구해오기

- 메소드 getSession() 안에서 DB와 연동하는 것임

- List 객체 list 를 업캐스팅으로 생성

- selectList() 메소드는 검색되는 결과가 2개 이상인 경우 실행하는 메소드

- selectList() 메소드 첫번째 매개변수로 Mapper 파일의 id "list" 를 써서 해당 select SQL문을 호출

- selectList() 메소드에서 전달할 값은 없으므로 두번째 매개변수는 쓰지 않았다

- selectList() 메소드로 돌려받은 리스트를 객체 list 에 저장해서 list.jsp로 리턴

 

MyBatis 연동 전 / 후

- while(rs.next()) 에서 DTO 객체 생성하고 setter 메소드로 저장시킨 후 리스트에 객체를 저장하는 행동을 반복했다

- MyBatis 와 연동하면 이런 작업이 Mapping 을 통해서 자동으로 실행되고, 리스트를 반환시켜줌

 

selectOne() vs selectList()

- 돌려줄 데이터가 1개이면 selectOne() 사용, 2개 이상이면 selectList() 사용

- selectOne() 은 리턴자료형이 Object, selectList() 는 리턴자료형이 List

 

데이터를 2개 이상 검색해서 돌려줄때 주의사항

- Mapper 파일의 select 문에서 데이터를 2개이상 돌려주더라도 resultType 에는 리스트가 아니라 DTO 가 오면 된다

ex) 돌려주는 자료형이 List<int> 이더라도 resultType 에 List 가 아닌 int 를 씀

ex) 돌려주는 자료형이 List<DTO> 이면 resultType 에 List 가 아닌 DTO(alias 별칭 member) 를 씀

 

- Mapper 파일인 member.xml 에서 해당 SQL 문 부분만 살펴보자

- member.xml 에서 전체 목록을 가져오는 SQL문 부분

	<select id="list" resultType="member">
		select * from member22
	</select>

+ select는 반드시 resultType 을 써야함, 받을 값은 없으므로 parameterType 은 쓰지 않았다

- 전체 데이터를 검색하고 있다


정보수정 기능 : 수정폼

- list.jsp 에서 '수정' 을 눌렀을때 수정하는 기능

<td><input type="button" value="수정" onclick='location.href="updateForm.jsp?id=<%=list.get(i).getId() %>"'></td>

- '수정' 클릭시 onClick 에 의해 updateForm.jsp 로 넘어간다, 넘어갈 때 해당 회원의 id 값을 get 방식으로 전달

- 세션에 공유된 id값은 관리자 id값인 "master" 이고 여기서 넘어가는 id값은 각 회원의 id 값임

 

- 먼저 수정폼을 해야함

- updateForm.jsp 파일을 보자

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@page import="model.Member"%>
<%@page import="dao.MemberDao"%>

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<%
	String id = request.getParameter("id");
	MemberDao md = new MemberDao();
	Member mem = md.select(id);
%>

<h2>정보 수정</h2>
<form action="updatePro.jsp">
	<input type="hidden" name="id" value="<%=mem.getId() %>">
	<table><tr><td>아이디</td>
			   <td><input type="text" name="id" value="<%=mem.getId() %>" disabled="disabled"></td></tr>
			<tr><td>암호</td>
				<td><input type="text" name="password" value="<%=mem.getPassword() %>"></td></tr>
			<tr><td colspan="2" align="right">
					<input type="submit" value="변경"> 
					<input type="button" onclick="history.go(-1)" value="취소"></td></tr>
	</table>
</form>

</body>
</html>

- 수정폼이므로 DB와 연동하여 1명에 대한 상세정보를 수정폼에 뿌려줘야한다

- list.jsp 에서 넘어온 회원의 id값을 받고 변수 id 에 저장

- DAO 객체를 생성해서 DAO 의 select() 메소드 호출

- select() 메소드를 호출하며 id 값을 넘긴다, 1명에 대한 상세정보를 구해오므로 리턴자료형 DTO

- 검색결과를 받은 mem 객체를 통해 mem.getId() 와 mem.getPassword() 로 id, password 를 수정폼에 뿌려줌

- id 는 disabled 로 설정되어있으므로 값이 action 의 페이지로 넘어가지 않는다, 그래서 hidden 객체로 따로 id 를 넘김

+ readonly 설정은 값이 넘어감, disabled 는 값이 안넘어감 * HTML readonly vs disabled 속성 설명 아래에

- 이후 수정폼에 상세정보를 뿌린 후, 사용자가 수정폼을 입력하고 '변경' 버튼을 누르면 id, password 를 가지고 updatePro.jsp로 이동한다

 

- MemberDao.java 에서 select() 메소드 부분만

	public Member select(String id) throws SQLException {
		Member mem = null;
		SqlSession session=null;
		try { session = getSession(); 
		mem = (Member) session.selectOne("select", id);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return mem;
	}

- getSession() 으로 SqlSession 객체 구해오기

- 1개 데이터를 검색하므로 selectOne() 메소드 호출

- 첫번째 매개변수에 들어가는 Mapper 파일의 id는 "select", 두번째 매개변수에 들어가는 전달될 값은 매개변수로 받은 id

- selectOne() 을 호출하고 결과를 Object 형으로 돌려주므로 다운캐스팅해서 DTO 객체 member 에 저장하고 그걸 리턴

+ 다운캐스팅 명시 생략 가능

- 이 select() 메소드 실행 후 이 메소드를 호출한 updateForm.jsp 로 돌아감  

 

- member.xml 에서 수정폼을 처리하는 SQL문 부분 (id 가 select)

	<select id="select" parameterType="String" resultType="member">
		select * from member22 where id = #{id}
	</select>

- 각 회원의 id 를 받으므로 paramterType 을 String 으로 설정

- 1명에 대한 상세정보를 리턴하므로 resultType 은 DTO 클래스 별칭인 member

- 전달되는 변수명이 id 이므로, #{id} 로 전달되는 값을 가져옴, parameterType 은 "String"

 

- URL 을 보면, id 값이 get 방식으로 넘어갔음을 볼 수 있다

 

+ 매개변수로 전달된 id 를 전달

- selectOne() 에서 변수 id 에 값이 전달되고 있다

- 그래서 member.xml 에서 #{id} 로 SQL문 where절 작성


정보수정 기능 : 실제 수정

- 이후 수정폼에 상세정보를 뿌린 후, 사용자가 수정폼을 입력하고 '변경' 버튼을 누르면 id, password 를 가지고 updatePro.jsp 로 이동한다

 

- updatePro.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@page import="dao.MemberDao"%>
<%@page import="model.Member"%>

<jsp:useBean id="mem" class="model.Member"/>
<jsp:setProperty property="*" name="mem"/>

<%
	MemberDao md = new MemberDao();
	int result = md.update(mem);

if (result == 1) {
%>
	<script type="text/javascript">
		alert("수정 성공");
		location.href="list.jsp";
	</script>
<%
} else {
%>
	<script type="text/javascript">
		alert("수정 실패");
		history.go(-1);
	</script>
<%
}
%>

- useBean, setProperty action tag 사용해서 수정폼에서 hidden 으로 넘어온 id 값과, 비밀번호 입력양식에서 넘어온 password 값을 받아서 DTO 객체 mem 에 저장

+ 입력양식의 name 값과 DTO 의 프로퍼티명이 일치할때만 가능

- DAO 객체 생성 후 DAO의 update() 메소드를 호출하며 매개변수로 객체 mem 을 전달

- 성공시 메세지 출력 후 list.jsp 로 돌아감, 즉 다시 회원목록을 구해서 뿌려준다

 

- MemberDao.java 에서 update() 메소드 부분만

	public int update(Member mem) {
		int result = 0;
		SqlSession session=null;
		try { session = getSession(); 
			result = session.update("update", mem);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

- update() 메소드의 첫번째 매개변수에 Mapper 파일의 id 명인 "update" 를, 두번째 매개변수에 넘겨줄 값인 객체 mem 을 적기

- update() 메소드가 실행되고 성공적으로 수정하면 1 을 리턴해줌, 그 1 을 result 변수에 저장해서 updatePro.jsp 로 리턴함

 

- member.xml 에서 정보 수정을 처리하는 SQL문 부분 (id 가 update)

	<update id="update" parameterType="member">
		update member22 set password = #{password} where id = #{id}
	</update>

- DTO 객체가 넘어올때 #{id} 는 member.getId() 를 의미, #{password} 는 member.getPassword() 를 의미

- update SQL문이 수행되고 자동으로 수정한 데이터 개수를 돌려줌, 즉 1 을 리턴해줌


* HTML readonly vs disabled 속성

 

공통점

- 입력 양식의 값을 수정할 수 없도록 비활성화 시켜주는 역할

차이점

- readonly 속성으로 설정된 양식은 action 으로 설정된 페이지로 값이 전달되지만,
- disabled 속성으로 설정된 양식은 action 으로 설정된 페이지로 값이 전달되지 않는다.


정보수정 기능 : 삭제

- list.jsp 에서 '삭제' 를 눌렀을때 수정하는 기능

<td><input type="button" value="삭제" onclick='location.href="delete.jsp?id=<%=list.get(i).getId() %>"'></td></tr>

- '삭제' 클릭시 onClick 에 의해 delete.jsp 로 넘어간다, 넘어갈 때 해당 회원의 id 값을 get 방식으로 전달

- 즉 삭제폼을 거치지 않고 바로 삭제되게 해뒀다

 

- delete.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@page import="dao.MemberDao"%>

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%
String id = request.getParameter("id");
MemberDao md = new MemberDao();
int result = md.delete(id);
if (result == 1) {
	%>
	<script type="text/javascript">
	alert("삭제성공");
	location.href="list.jsp";
	</script>
	<%
} else {
	%>
	<script type="text/javascript">
	alert("실패");
	location.href="list.jsp";
	</script>
	<%
}
%>
</body>
</html>

- list.jsp 로 부터 전달받은 id 값을 받아서 저장

- DAO 객체를 생성하고 DAO의 delete() 메소드 실행, 특정 회원 삭제를 해야하므로 매개변수로 id 값을 전달한다

- 메소드 호출하여 삭제 성공시 목록 페이지인 list.jsp 로 돌아간다

 

- MemberDao.java 에서 delete() 메소드 부분만

	public int delete(String id) {
		int result = 0;
		SqlSession session=null;
		try { session = getSession(); 
			result = session.delete("delete", id);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		return result;
	}

 

- member.xml 에서 회원 삭제(탈퇴) 를 처리하는 SQL문 부분 (id 가 delete)

	<delete id="delete" parameterType="String">
		delete from member22 where id = #{id}
	</delete>

- #{id} 는 DAO에서 session.delete() 메소드 실행시 두번째 매개변수로 넘어온 변수 id 를 의미함

- delete SQL문이 수행되고 자동으로 삭제한 데이터 개수를 돌려줌, 즉 1 을 리턴해줌

- 자동으로 삭제한 데이터 개수를 돌려주므로 select 외의 insert/update/delete 는 returnType 을 쓰면 안된다

 

#{id} 의 의미

1. 일반 변수 id 가 넘어왔을때 #{id} 는 그 변수 id 값을 의미함

2. DTO 객체 member 가 넘어왔을때 #{id} 는 member.getId() 를 의미, 즉 객체 안의 프로퍼티 값을 의미

- 같은 코드지만 다른 의미이다

 

- master 로 로그인 한 뒤 회원 목록 페이지인 list.jsp 에서 회원을 삭제해보자

 


MyBatis

- DB 연동을 처리하기 위한 프레임 워크

 

프로그램 확장 시

- MyBatis 환경설정 파일은 하나만 있으면 된다

- Mapper 파일은 테이블의 개수에 비례해서 늘어난다

 

태그 맞추기

- Mapper 파일인 member.xml의 루트 엘리먼트는 mapper 이다

- MyBatis 환경 설정 파일에서 태그를 mapper 로 해야한다

- Mapper 파일의 루트 엘리먼트와 MaBatis 환경설정 파일에서 Mapper 파일을 불러올때 태그 이름을 맞춰야한다

 

Mapper 파일의 id 충돌문제

- 같은 Mapper 파일 뿐 아니라 다른 Mapper 파일 끼리도 id 값을 다르게 설정해야한다

- member 테이블, board 테이블 이 있을때 member.xml , board.xml 처럼 Mapper 파일이 각각 생성됨

- 이때 서로 다른 두 Mapper 파일에서도 같은 id값을 쓰면 안된다!, 그래야 DAO에서 불러올때 충돌되지 않음

- id 값이 중복되는 문제 해결하기 위해 namespace

 

namespace

- 논리적인 영역 이름

- namespace를 통해 id값 중복되어 DAO 에서 불러올때 충돌되는 문제 해결가능

+ 일반적으로 namespace를 테이블명+"ns" 로 설정함

- Mapper 파일에서 namespace 값만 서로 다르게 설정하면 다른 Mapper 파일끼리 같은 id값을 사용할 수 있다

- Mapper 파일인 member.xml 부분

- 그래서 insert id 를 불러오는 DAO 코드에서 namespace 를 함께 써서 충돌 피할 수 있다

- 이렇게 하면 다른 테이블의 Mapper 파일에서 id 값을 insert 로 쓴다고 해도 충돌하지 않음

복습

- 목록 가져오기 Service 클래스에서 목록 추출 후 결과를 공유설정

- View 페이지에서 EL 로 출력하기 전에 공유설정되어야함

 

Service 클래스에서 DTO 객체가 공유되면

		request.setAttribute("board", board);

- View 페이지에서 ${공유되는 네임값.필드명} 으로 출력

		<td>
			<input name="board_name" id="board_name" type="text" size="10" maxlength="10" 
				value="${board.board_name}"/>
		</td>

 

Spring 학습 전 앞으로 할 내용

Maven

- 라이브러리 관리자 중 하나

- Spring 프로젝트를 만들면 Maven 으로 라이브러리 관리하는게 기본

 

ORM 프레임워크 : MyBatis

- 기존의 DAO 클래스 안에 있는 내용을 xml 에 따로 뺀다, 간단해짐

- Spring 은 MyBatis 와 연동하는 경우가 많다


- 수정 폼, Controller 클래스에서 연결하는것도 다 구현했다

- 글 수정 Service 클래스, 수정하는 DAO 메소드만 구현하면 된다

 

 게시판 프로그램 : 글 수정 Service 클래스

- service 패키지 안에 글 수정 Service 클래스인 BoardModifyjava.java 를 작성하자

- BoardModify.java (수정 후 1)

package service;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardModify implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardModify");
		
		response.setContentType("text/html; charset=utf-8");
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String page = request.getParameter("page");
		String board_pass = request.getParameter("board_pass");
		
		BoardBean board = new BoardBean();
		board.setBoard_num(board_num);
		board.setBoard_name(request.getParameter("board_name"));
		board.setBoard_subject(request.getParameter("board_subject"));
		board.setBoard_content(request.getParameter("board_content"));

		BoardDAO dao = BoardDAO.getInstance();
		BoardBean old = dao.getDetail(board_num);
		
		// 비번 비교
		if(old.getBoard_pass().equals(board_pass)) { // 비번 일치시
			int result = dao.update(board);		// update SQL문 실행
			if(result == 1) System.out.println("글수정 성공");
		} else {								// 비번 불일치
			out.println("<script>");
			out.println("alert('비번이 일치하지 않습니다');");
			out.println("history.go(-1)");
			out.println("</script>");
			out.close();
			
			return null;
		}
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/BoardListAction.do?page=" + page);
//		forward.setPath("/BoardDetailAction.do?board_num="+board_num+ "&page=" + page); // 상세정보로 갈 수도 있다
		return forward;
	}
}

1. 글 수정폼에서 입력된 정보들이 넘어오므로 한글값 인코딩을 한다

- 비번 불일치시 출력할 alert 창을 위해 out 객체를 생성하고 현재 문서 한글값 인코등을 한다

2.  글 수정 폼에서 hidden 으로 전달된 글 번호, 페이지 번호를 받아와서 저장한다

- 페이지번호, 글 번호는 목록 페이지 -> 상세 정보 -> 글 수정 폼 -> 글 수정 으로 넘어왔다

- 글 번호는 수정시에 필요하고, 페이지 번호는 수정 후 원래 페이지로 돌아갈 때 필요하다

3. DTO 객체 board 를 생성하고, 수정폼에서 넘어온 값들을 받아서 객체 board 에 저장

4. 사용자가 수정폼에 입력한 비번을 받고 DB에 저장된 비번과 비교해서 일치하는지 확인

- 사용자가 수정폼에 입력한 비번은 form 에서 넘어온 값이므로 request.getParamter("board_pass") 로 가져온다

- 상세정보를 가져오는 메소드 getDetail() 을 호출해서 DB 에 저장된 1개 글에 대한 정보를 가져와서 DTO 객체 old 에 저장

5. 비번 일치시 DAO 의 update() 메소드를 실행해서 실제 수정을 수행함

- 이후 DAO 에 갔다가 여기로 다시 돌아온다

6. 비번 불일치시 out 객체로 비밀번호가 틀렸다는 메세지를 뿌린 후 이전 페이지인 수정폼으로 돌아간다

- 비번 불일치시 포워딩 시키면 안되므로 포워딩 객체를 생성하기 전에 return null 로 함수 execute() 를 종료

7. 수정에 성공하면 목록 페이지로 가기 위해 "/BoardListAction.do" 로 요청하여 목록 가져오기를 한다

- 목록페이지 포워딩 시 원래 페이지로 가기 위해 페이지 번호를 get 방식 전달, 목록 페이지에서 그 페이지번호를 받아서 그 페이지에 해당하는 글들을 가져와서 출력해준다

- 상세페이지로 갈 수도 있다, 상세페이지로 갈때는 페이지번호 뿐 아니라 글번호도 가져가야한다

 

DAO 클래스 글 수정 기능 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 update() 부분 코드만

	// 글 수정
	public int update(BoardBean board) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "update model2 set board_name=?,board_subject=?,";
			sql += "board_content=? where board_num=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getBoard_name());
			pstmt.setString(2, board.getBoard_subject());
			pstmt.setString(3, board.getBoard_content());
			pstmt.setInt(4, board.getBoard_num());
			result = pstmt.executeUpdate(); // SQL문 실행
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}		
		}
		return result;
	}

- 수정 폼에서 수정을 위해 작성하는 값들은 이름, 제목, 내용 뿐이므로 이 값들만 update 해주면 된다

- DAO update()가 끝나고 Service 클래스로 돌아간다

 

- Service 클래스, DAO update() 작성 후 수정을 해보자

- index.jsp 실행해서 목록 페이지로 온 다음, 상세 페이지로 가고, 수정폼으로 가고, 수정해보기

- 수정이 잘 된다

 

문제점

- 수정 성공시 성공했다는 메세지가 뜨지 않음

- 수정 성공시 메세지는 alert() 으로 출력되지 않는다

 

문제 해결

			response.sendRedirect("./board/updateresult.jsp?page="+page);

 

- 포워딩할때 Controller 클래스를 거치는 것이 원칙이지만 여기선 바로 수정 성공 메세지를 출력하는 View 페이지로 간다

- 포워딩 시에 페이지번호를 가져가서, 그 페이지 내에서 <script></script> 내에서 출력을 하고 목록페이지로 돌아가야함

- 만약 메세지를 띄우고 싶다면 출력을 담당하는 다른 페이지로 포워딩해서 이 메세지를 띄워야한다

+ 이 작업을 아래의 forward 객체로 포워딩하면 안된다, 여기서 바로 Redirect 방식으로 포워딩해야함

- 아래의 forward 코드는 주석으로 막고 return null 을 해야한다

- Controller 클래스에 "/update.do" 요청을 처리하는 코드를 작성하자

 

- 다음은 수정 성공 후 메세지를 출력할 updateresult.jsp 파일 생성

- updateresult.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<script>
	alert(${param.page});
	alert("글 수정 성공");
	location.href = "<%=request.getContextPath()%>/BoardListAction.do?page="+ ${param.page};
</script>

 

- 글 수정을 하고 메세지 출력 후 목록페이지로 잘 가는지 확인해야한다

- 원래 페이지인 1 페이지로 돌아온다, URL 을 보면 알 수 있음

+ 7 페이지의 글을 수정해도 다시 7 페이지로 돌아왔음을 확인 했다

+ 나중에는 이렇게 복잡하게 메세지를 출력하지 않는다


게시판 프로그램 : 글 삭제

- 먼저 상세 페이지 View 페이지인 qna_board_view.jsp 의 버튼 중 '삭제' 버튼에 링크를 걸어야한다

- qna_board_view.jsp 부분

<input type="button" value="삭제"
onClick="location.href='./BoardDeleteAction.do?board_num=${board.board_num}&page=${page}'">

- 상세페이지에서 글번호 board_num 과 페이지번호 page 가 넘어온다

- 글 삭제 폼으로 가기 위해 "/BoardDeleteAction.do" 로 요청을 한다

 

 Controller 클래스에서 글 삭제폼으로 가기 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardDeleteAction.do 로 요청이 올때의 경우를 처리

		// 삭제 폼
		} else if(command.equals("/BoardDeleteAction.do")) {
			try {
				forward = new ActionForward();
				forward.setRedirect(false); // dispatcher 방식으로 포워딩
				forward.setPath("./board/qna_board_delete.jsp");
			} catch (Exception e) {
				e.printStackTrace();				
			}
		}

- 삭제 폼은 DB와 연동할 필요가 없으므로 Service 클래스로 가지 않는다

- 요청을 Controller 페이지에서 받으면 바로 삭제폼 View 페이지 qna_board_delete.jsp 로 포워딩한다

- 여기서 forword 객체를 생성하고 포워딩 방식, 포워딩 페이지를 설정하면 아래 포워딩 코드에 의해 바로 View 로 포워딩 됨

- Dispatcher 방식으로 포워딩해야만 param 으로 값을 받을 수 있다

+ Dispatcher 방식으로 포워딩해야 호출하는 페이지의 request, response 객체를 공유하기때문이다

 

- board 폴더 안에 삭제폼 View 페이지인 qna_board_delete.jsp 생성 및 작성

- qna_board_delete.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>게시판 삭제</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>
<!-- 글번호, 페이지번호를 request 객체로 받기 -->
board_num1 : <%=request.getParameter("board_num") %> <br>
page1 : <%=request.getParameter("page") %> <br>
<!-- 글번호, 페이지번호를 param 내장객체로 받기 -->
board_num2 : ${param.board_num} <br>
page2 : ${param.page }

<form action="<%=request.getContextPath() %>/BoardDelete.do" method="post">
<input type="hidden" name="board_num" value="${param.board_num}">
<input type="hidden" name="page" value="${param.page}">

<table cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">게시판 삭제</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="삭제">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

- qna_board_modify.jsp 를 복붙 후 수정

- 상세페이지에서 넘겨준 글번호, 페이지번호 를 받아서 글 삭제 Service 클래스 BoardDelete.do 로 넘겨준다

- 삭제 시 "/BoardDelete.do" 로 요청

- 삭제 시 삭제 Service 클래스인 BoardDelete.java 로 글 번호, 원래 페이지 번호, 비밀번호가 넘어가야한다

+ 삭제할 글 번호를 알아야하기때문에, 원래 페이지로 돌아가야하므로, 비밀번호 일치 확인을 해야하므로

- 입력양식은 비번 입력 양식만 남긴다

 

수정폼 vs 삭제폼

- 수정 폼은 DB와 연동해서 Service 클래스에서 객체 board 를 공유했고 그걸 수정폼에 뿌렸었다

- 삭제 폼은 DB와 연동하지 않았으므로 param 객체로 글번호, 페이지번호를 가져와서 hidden 으로 글 수정 Service 클래스로 넘긴다

+ Dispatcher 방식으로 포워딩했으므로 상세페이지의 request, response 객체를 공유하므로 param 으로 그 글의 글 번호, 원래 페이지 번호를 받아옴

 

param 내장 객체 (qna_board_delete.jsp 부분)

<!-- 글번호, 페이지번호를 request 객체로 받기 -->
board_num1 : <%=request.getParameter("board_num") %> <br>
page1 : <%=request.getParameter("page") %> <br>
<!-- 글번호, 페이지번호를 param 내장객체로 받기 -->
board_num2 : ${param.board_num} <br>
page2 : ${param.page }

- param 은 getParameter() 와 같은 코드

- 코드가 간단해짐

 

- 글 삭제 Service 클래스인 BoardDelete.java 를 service 패키지 안에 생성하자

 

게시판 프로그램 : 글 삭제 Service 클래스

- service 패키지 안에 글 삭제 Service 클래스인 BoardDelete.java 를 생성 및 작성하자

- BoardDelete.java

package service;

import java.io.File;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardDelete implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardDelete");
		
		response.setContentType("text/html; charset=utf-8");
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String page = request.getParameter("page");
		String board_pass = request.getParameter("board_pass");
		
		String path = request.getRealPath("boardupload");
		System.out.println(path);
		
		BoardDAO dao = BoardDAO.getInstance();
		BoardBean old = dao.getDetail(board_num); // 상세정보 구하기
		System.out.println(old.getBoard_pass());
		System.out.println(board_pass);
		
		// 비번 비교
		if(old.getBoard_pass().equals(board_pass)) {	// 비번 일치시
			int result = dao.delete(board_num);	// delete SQL문 실행
			if(result == 1) System.out.println("삭제 성공");
			
			// 첨부파일이 있는 경우에 첨부파일 삭제
			if(old.getBoard_file() != null) {
				
				File file = new File(path);
				file.mkdir();
				
				// boardupload 디렉토리의 모든 첨부 구해오기
				File[] f = file.listFiles();
				for(int i=0; i<f.length; i++) {
					if(f[i].getName().equals(old.getBoard_file())) {
						f[i].delete();	// 첨부파일 삭제
					}
				}
			}
		} else {	// 비번 불일치시
			out.println("<script>");
			out.println("alert('비번이 일치하지 않습니다.');");
			out.println("history.go(-1);");
			out.println("</script>");
			out.close();
			
			return null;
		}
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/BoardListAction.do?page=" + page);
		return forward;
	}

}

1. 삭제 폼에서 한글값이 넘어올 수 있으므로 한글값 인코딩 처리를 한다

- 비번이 일치하지 않을때 alert 창에 출력하기 위해 문서 한글 인코딩 처리도 하고 out 객체도 생성한다

2. 삭제 폼에서 hidden 으로 넘어온 글 번호와 페이지 번호를 받아서 변수에 저장한다

- 목록 페이지 -> 상세페이지 -> 삭제폼 -> 삭제 Service 클래스 까지 글 번호와 페이지 번호가 넘어온 것이다

3. 삭제 폼에서 사용자가 입력한 비밀번호도 받아서 DAO 의 getDetail() 메소드로 상세정보를 구해온다

4. 비번 일치시 DAO 의 delete() 메소드를 호출하여 글을 삭제한다

- 매개변수로는 글 번호를 넘겨준다

5. 삭제한 글에 해당하는 첨부파일이 있다면 첨부파일도 지우도록 한다

* 첨부파일 지우기 설명은 아래에

6. 비번 불일치시 out 객체를 통해 alert 창에 메세지를 뿌리고 이전 페이지인 삭제 폼으로 돌아간다 

- 이후 return null 을 작성 아래쪽의 포워딩 객체를 생성하고 설정하는 곳으로 가지 않고 함수를 종료시킨다

7. 글 삭제 성공 후 forward 객체를 생성하고 설정, 포워딩 페이지는 목록을 가져오는 "/BoardListAction.do" 로 한다

- get 방식으로 페이지 번호를 넘겨줘서, 원래 페이지로 돌아갈 수 있게 한다

 

첨부파일 지우기 (BoardDelete.java 부분)

			// 첨부파일이 있는 경우에 첨부파일 삭제
			if(old.getBoard_file() != null) {
				
				File file = new File(path);
				file.mkdir();
				
				// boardupload 디렉토리의 모든 첨부 구해오기
				File[] f = file.listFiles();
				for(int i=0; i<=f.length; i++) {
					if(f[i].getName().equals(old.getBoard_file())) {
						f[i].delete();	// 첨부파일 삭제
					}
				}
			}

- 첨부파일 지우기는 DAO 안에서 처리해도 되고, Service 클래스의 delete() 호출 아래에서 처리해도 된다

1) 첨부파일이 저장된 진짜 경로를 getRealPath() 로 구해서 path 변수에 저장한다

2) 상세정보를 구해온 객체 old.getBoard_file 을 통해 첨부파일이 있는지 없는지 판별가능, 있을때 삭제

3) File 객체 f를 생성할때 생성자의 매개변수에 생성될 디렉토리의 위치인 path 변수가 들어간다

4) file.listFiles() 로 boardupload 디렉토리의 모든 첨부파일을 구해온다

5. 반복문을 통해서 모든 파일을 돌면서, 파일명이 delete()로 삭제한 글의 DB 속에 저장된 파일명과 일치하면 파일 삭제

 

Controller 클래스에서 글 삭제 Service 클래스 BoardDelete.java로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardDelete.do 로 요청이 올때의 경우를 처리

		// 글 삭제
		} else if(command.equals("/BoardDelete.do")) {
			try {
				action = new BoardDelete();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

 

DAO 클래스 글 삭제 기능 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 delete() 부분 코드만

	// 글 삭제
	public int delete(int board_num) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "delete from model2 where board_num = ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, board_num);
			result = pstmt.executeUpdate(); // SQL문 실행
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}			
		}
		return result;
	}

 

- index.jsp 를 실행해서 목록페이지로 오고 10 페이지의 글 하나를 삭제해보자

- 삭제 후 다시 목록페이지로 왔다, 원래 페이지였던 10 페이지로 돌아왔다

C:\Users\admin\eclipse-workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\model2board\boardupload

- Desert21.jpg 첨부파일이 삭제되었는지 확인하기 위해 콘솔에 찍힌 path 를 탐색기에 검색

- 삭제 되었음을 확인 가능하다

JSP Model 2 끝


라이브러리 관리

- 지금까지는 Dynamic Web Project 를 만들고, 직접 라이브러리를 구해서 lib 폴더 안에 모든 라이브러리를 저장했다

- Spring 에서는 Maven 으로 라이브러리를 관리한다

- Maven 은 라이브러리 관리자 중 하나이다

 

라이브러리 관리자 maven

- spring project 에선 기본적으로 maven 으로 라이브러리를 관리함

- springboot project 에선 maven 또는 gradle 로 라이브러리를 관리함, 선택 가능

- 환경설정 파일 안에 라이브러리를 추가하는 식이다

 

라이브러리 관리자 gradle

- android project

- springboot projec

 

Maven 프로젝트 생성

 

Dynamic Web Project vs Maven Project

- 이때까지 Dynamic Web Project 로 프로젝트를 생성해 왔다

- 이젠 Maven Project 로 프로젝트를 생성하자

- Maven Project는 기본적으로 maven 으로 라이브러리를 관리하게끔 설정됨

- Maven Project는 Dynamic Web Project 와 폴더구조나 라이브러리 관리 방식이 크게 다르다

 

- Catalog 를 Internal 로 바꾼다

- 어떤 archetype 을 선택하냐에 따라 프로젝트 모양이 달라짐

- 일반적으로 웹 어플리케이션 프로젝트를 생성할때는 이 maven-archetype-webapp 를 선택

 

- Group id 값은 도메인명 역순을 주로 입력함. 현재는 com.myhome

- Artifact id 값은 프로젝트가 생성되면 프로젝트 명이 된다. 현재는 maventest

- Finish 누르면 maventest 프로젝트가 생성된다

- 기존의 Dynamic Web Project 와 구조가 다름

- pom.xml 파일이 Maven 의 환경설정 파일이다

+ 환경설정 파일은 xml 파일로 되어있는 경우가 많음

- 파일들에서 에러가 발생한다

 

maven project 에러발생

- maven 프로젝트 생성후 index.jsp 파일에 에러가 발생

- Libraries 에 Apache Tomcat Library 를 추가하면 에러가 사라진다.

- 여기 Apache Tomcat Library 가 없다

 

Apache Tomacat Library 추가하는 방법

- 그럼 빨간 에러가 사라진다

- index.jsp 를 실행해보자

 

Maven Project 구조

maintest/src/main 안의 3개 폴더

- 들어가는 내용들이 정해져 있다

 

1. java 폴더

- 확장자가 .java 인 모든 파일은 반드시 이 폴더 안에 저장

- dynamic web project 의 src 와 같은 역할

 

2. resources 폴더

- MaBatis 의 환경 설정 파일들이 주로 들어가있다

- 기존 DAO 클래스 안의 SQL문을 xml 파일로 따로 뺀다, 그 SQL문 파일들이 저장된다

 

3. webapp 폴더

- View 페이지가 저장되는 위치

- index 파일도 여기 안에 만들어야한다, webapp/WEB-INF 폴더 안에 index 파일저장

- 하지만 WEB-INF 폴더 안에 라이브러리는 더이상 없다, pom.xml 에서 라이브러리를 추가함

- dynamic web project 의 WebContent 와 같은 역할

 

pom.xml 파일

- maventest 프로젝트 하위

- Maven 의 환경설정 파일, 필요한 라이브러리를 여기에 추가한다

- 필요한 라이브러리를 pom.xml 추가시 원격 저장소에서 로컬 저장소로 다운받는다

 

- pom.xml 파일을 살펴보자

 

pom.xml 파일

- pom.xml (원하는 라이브러리 추가 전)

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.myhome</groupId>
  <artifactId>maventest</artifactId>
  <packaging>war</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>maventest Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <finalName>maventest</finalName>
  </build>
</project>

- Maven 의 환경설정 파일

- 가장 바깥의 Root Element 가 project 이다, 전체가 <project></project> 로 덮여있다

- Maven Project 생성시 설정했던 내용들이 들어가있다, Group Id, ArtifactId 등

- Dependencies Element 안에 Dependency Element 들이 있다

- 이 Dependencies Element 안에 우리가 원하는 라이브러리를 추가해야함, 의존 라이브러리라고 부른다

- 여기선 junit 라이브러리 추가,  junit 은 테스트 용도로 제공되는 라이브러리

 

라이브러리 가져오기

- 먼저 Maven Repository 에서 의존 라이브러리를 검색한다

- cos 라이브러리를 찾아서 가져와보자

 

Maven Repository: Search/Browse/Explore (mvnrepository.com)

- 이 버전을 클릭

 

- 복사한 코드를 pom.xml 의 dependencies element <dependencies></dependencies> 안에 추가한다

- 추가 후 저장시 원격 저장소에서 로컬 저장소(내 컴퓨터) 로 다운로드를 시작한다

 

- pom.xml (원하는 라이브러리 추가 후, 미완성)

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.myhome</groupId>
	<artifactId>maventest</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>maventest Maven Webapp</name>
	<url>http://maven.apache.org</url>
	<dependencies>
	
		<!-- https://mvnrepository.com/artifact/servlets.com/cos -->
		<dependency>
			<groupId>servlets.com</groupId>
			<artifactId>cos</artifactId>
			<version>05Nov2002</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		
	</dependencies>
	<build>
		<finalName>maventest</finalName>
	</build>
</project>

- 이렇게 여기 dependency element 로 추가되는 라이브러리를 의존 라이브러리 라고 한다

+ Spring 에서는 maven 으로 라이브러리를 관리함

 

로컬 저장소 위치

- C:\Users\admin\.m2\repository

- 내 컴의 로컬 저장소(local repository) 위치이다.

- pom.xml 에 추가되어 원격 저장소에서 로컬 저장소로 다운받아진 라이브러리들이 여기 저장되어 있다.

 

- Windows - Preperences 로 들어와서 Maven ->  Use Settings 선택

- 내 컴의 로컬 저장소 위치이다

- maven 으로 라이브러리 관리 시 기본적으로 이렇게 로컬 저장소가 생성된다

- 특정 라이브러리를 다운하다가 문제가 생기면 로컬 저장소 안의 파일들을 다 지워도 된다, 새로 실행시 처음부터 다시 다운받음

- 만약 특정 라이브러리가 다운이 안된다면, 이미 그 라이브러리를 잘 다운 받은 컴퓨터의 레파지토리를 복사해서 내 컴에 덮어씌운다

 

- 저 주소를 복사해서 탐색기에 쳐보자

- 라이브러리들이 보인다

- 로컬 저장소안의 라이브러리를 지우고 싶으면 이클립스를 종료하고 지워야함

- 이클립스를 종료하고 모두 삭제하면 다시 이클립스를 실행할때 다시 다 다운받는다

 

Oracle JDBC 라이브러리 추가

pom.xml 에 Oracle Repository 추가

- Oracle은 공식 저장소에서 지원되는 라이브러리들이 다운이 안되는 경우가 많이 발생함

- 그래서 비공식적인 오라클 Repository 를 먼저 따로 추가해야함

- pom.xml 의 dependencies Element "위"에 오라클 Repository 를 가져오는 이 코드를 복사

	<!-- 오라클 repository -->
	<repositories>
		<repository>
			<id>codelds</id>
			<url>https://code.lds.org/nexus/content/groups/main-repo</url>
		</repository>
	</repositories>

- 그리고 오라클 JDBC 라이브러리를 dependencies Element 안에 추가

- 이 코드 잘 작동하지 않는다, 이미 다운받은 Repository 를 덮어쓰자

		<!-- 오라클 JDBC Library -->
		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.3</version>
		</dependency>

 

- pom.xml (오라클 Reopository 추가, 오라클 JDBC Library 추가)

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.myhome</groupId>
	<artifactId>maventest</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>maventest Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<!-- 오라클 repository -->
	<repositories>
		<repository>
			<id>codelds</id>
			<url>https://code.lds.org/nexus/content/groups/main-repo</url>
		</repository>
	</repositories>

	<dependencies>
		<!-- 오라클 JDBC Library -->
		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.3</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/servlets.com/cos -->
		<dependency>
			<groupId>servlets.com</groupId>
			<artifactId>cos</artifactId>
			<version>05Nov2002</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>

	</dependencies>
	<build>
		<finalName>maventest</finalName>
	</build>
</project>

- 오라클 JDBC 라이브러리를 다운받을때 문제가 생긴다

 

MySQL JDBC 라이브러리 추가

 

- 지금 설치한 mysql 버전이 8 점대 임을 고려해서 8 점대 중 8.0.28 버전 다운

 

- dependencies element 안에 이 코드를 복붙

 

프레임워크 MyBatis 라이브러리 추가

- 지금은 Spring 을 안쓰므로 MyBatis 만 있으면 된다

- dependencies element 안에 이 코드를 복붙

 

JSTL 라이브러리 추가

- 하나만으로 처리가 안되는 경우가 있으므로 두가지 라이브러리를 가져옴

- 나중엔 JSTL 연결 라이브러리도 필요

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>jstl-impl</artifactId>
<version>1.2</version>
</dependency>

- 이걸 pom.xml 의 dependencies element 안으로 가져옴

 

문제점

- 오라클 JDBC 라이브러리를 가져올때만 문제가 생겼다

 

문제 해결 방법

- 이미 다운을 받은 레파지토리를 복사해서 내 레파지토리로 붙여넣는다

- 이미 오라클 JDBC 라이브러리를 다운받은 레파지토리를 클라우드에서 다운받아서 압축 해제하고 그 안의 내용을 모두 내 로컬 레파지토리로 복붙하자

- 이클립스를 종료하고 내 로컬 레파지토리의 내용은 모두 삭제 하고, 이 repository 압축 풀은 모든 내용을 복붙하기

- 다 삭제하고 복붙 완료한 내 레파지토리

- 다시 이클립스를 켠다

 

- 복붙한 라이브러리 버전과 다른 경우는 다시 pom.xml 에 쓰인 버전으로 다운받는다

- 그럼 오라클 JDBC 라이브러리 코드를 작성해도 에러가 나타나지 않음

- 빨간 에러가 사라졌다

- pom.xml 에 작성된 라이브러리들만 여기에 나타난다

 

- pom.xml (최종)

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.myhome</groupId>
	<artifactId>maventest</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>maventest Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<!-- 오라클 repository -->
	<repositories>
		<repository>
			<id>codelds</id>
			<url>https://code.lds.org/nexus/content/groups/main-repo</url>
		</repository>
	</repositories>

	<dependencies>
		<!-- 오라클 JDBC Library -->
 		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.3</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.28</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.5.7</version>
		</dependency>

		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
		
		<dependency>
			<groupId>org.glassfish.web</groupId>
			<artifactId>jstl-impl</artifactId>
			<version>1.2</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/servlets.com/cos -->
		<dependency>
			<groupId>servlets.com</groupId>
			<artifactId>cos</artifactId>
			<version>05Nov2002</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>

	</dependencies>
	<build>
		<finalName>maventest</finalName>
	</build>
</project>

 

maven 프로젝트 vs spring 프로젝트

- 현재는 maven 프로젝트를 생성했을때 pom.xml 에 의존 라이브러리는 junit 만 있었다

- Spring 프로젝트 만들면 기본적인 라이브러리가 다 들어가있다

+ Spring 프로젝트도 라이브러리 관리는 maven 라이브러리 관리자를 사용

 

Spring 프로젝트 만들기

- Spring 프로젝트는 이클립스 메뉴 없어서 지금은 만들 수 없다

1. 플로그인에 추가

2. STS 란 프로그램 사용, SpringBoot 만들 수 있는 메뉴도 추가되어있다

 

maven 프로젝트 import, export

- maven 프로젝트를 import / export 할때 war 파일로 import / export 하면 안된다

- spring 프로젝트도 import / export 할때 war 파일로 import / export 하면 안된다

- maven 프로젝트를 war 파일로 import / export 하면 Maven Project 가 Dynamic Web Project 로 풀려버림

 

- war 파일로 export -> import 했을때 프로젝트는 Dynamic Web Project 가 된다

 

maven 프로젝트 export 하는 법

- maven 프로젝트를 export 할때는 프로젝트를 통째로 Ctrl + C 로 복사해서 다른 곳에(바탕화면) 붙여넣기 한다

- 이렇게 폴더로 나타난다

 

maven 프로젝트 import 하는 법

- 현재는 이미 이클립스에 maventest 라는 프로젝트가 있으므로 Finish 가 활성화되어있지 않음

- 이클립스의 maventest 프로젝트를 삭제 후 다시 import 하자

- maventest 프로젝트가 maven project 그대로 유지되면서 import 되었다

Controller (Servlet) 클래스

- WebServlet 클래스 등은 Apache Tomcat 의 servlet-api.jar 파일에 있다

 

흐름 정리

- 클라이언트가 요청하면 무조건 Controller 클래스로 가고, 적절한 Service 클래스로 넘겨준다 거기서 DAO 의 메소드를 호출하여 DB와 연동

- DAO 에서 검색한 결과를 Service 클래스에서 공유설정하고 Controller 클래스에서 포워딩해서 View 에서 공유된 값을 가져와서 출력


문제점 1

- 등록 버튼을 눌러서 글을 등록한 뒤 목록페이지에서 F5(새로고침) 를 누르면 똑같은 데이터가 추가된다

* 링크가 잘못되었다, 제목에 링크가 들어가야함, 추후 수정 완료

- 새로고침을 누를때마다 하나씩 추가됨

 

문제점 1 해결

- BoardAddAction.java (원문 글 작성 Service 클래스) 에서 setRedirect(false) 를 true 로 설정해서 Redirect 방식으로 포워딩하면 이 문제가 해결된다

 

문제점 2

- 글 작성 성공 후 글 목록 가져오기를 "/BoardListAction.do" 로 요청할때 get이 나와야하는데 post 방식이 나온다

- 포워딩을 하면 get 방식으로 전달되지만, 콘솔창에는 post 방식으로 뜬다

+ 폼으로 /BoardListAction.do 로 오는게 아니므로 post 가 아닌 get 방식이다

 

문제점 2 이유

- 위는 글 작성 요청 아래는 글 작성이 끝난 후 글 목록을 가져오는 요청

- dispatcher 방식으로 포워딩되면 URL 주소가 바뀌지 않으므로 이전에 글 작성할때 사용했던 post 방식으로 나옴

 

문제점 1 & 문제점 2 해결 방법

- setRedirect 를 true 로 바꿈

- setPath 에서 혹시 경로를 못찾아가면 . 를 앞에 붙여줌

* 링크가 잘못되었다, 제목에 링크가 들어가야함, 추후 수정 완료

- 새로고침해도 같은 데이터가 들어가지 않는다

 

문제점 정리

- Get 방식처럼 단순히 조회하는 요청은 Dispatcher 방식을 사용

- Post, Put 방식처럼 무언가를 변화시키는 요청에는 Redirect 방식을 사용

- Dispatcher 방식이 Redirect 방식보다 성능상 좋다. 

- Post, Put 같은 무언가를 변화시키는 요청에는 Redirect 방식을 사용

- 클라이언트가 Post 요청으로 무언가를 변화시킨 후 새로고침을 눌러버리면 웹 브라우저에서 새로고침은 마지막에 서버에 전송한 데이터를 다시 전송하기 때문에 다시 URL에 접속되면서 Post 요청이 한번 더 전송되기 때문

ex) 상품 등록 요청을 하였는데 재요청을 보내버리면 2개의 상품이 중복 등록가 돼버리는 상황이 발생하게 된다.

 

다른 페이지로 이동

- 아직 페이지를 선택할 수 있는 부분을 만들지 않았으므로 URL 에 입력해서 찾아가보자

- 이 페이지에 맞는 10 개의 글(데이터) 을 뿌려줌

 

파생변수 number

	<c:set var="num" value="${listcount - (page - 1) * 10}"/>

- 각 페이지에 출력될 시작 번호를 저장하고 있다

- 마지막의 10 은 한 페이지에 출력할 데이터 개수 limit 를 의미한다

- 그 번호에서 반복문으로 1씩 감소하여서 출력

 

더미 데이터 만들기

- 페이지 처리 구현시 보기 위해 더미 데이터 여러개를 만든다

* 링크가 잘못되었다, 제목에 링크가 들어가야함, 추후 수정 완료


게시판 프로그램 : 목록 가져오기 (이어서)

- 목록가져오는 것은 구현했고, 공유된 리스트, 기본변수, 파생변수들을 View 에서 가져와서 출력하는 과정에 있다

- 목록은 뿌렸고 페이지 처리, 페이지 선택할 수 있는 것 만들고 있다

- qna_board_list.jsp (최종)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<a href="./BoardForm.do">글작성</a> <br>
글갯수 : ${listcount } 개 <br>

<%
	int count = ((Integer)request.getAttribute("listcount")).intValue();
%>
글갯수 : <%=count %> 개 <br>

<table border=1 width=700 align=center>
	<caption>게시판 목록</caption>
	<tr>
		<td>번호</td>
		<td>제목</td>
		<td>작성자</td>
		<td>날짜</td>
		<td>조회수</td>
	</tr>
	
	<c:set var="num" value="${listcount - (page - 1) * 10}"/>
	<c:forEach var="b" items="${boardlist}">
	<tr>
		<td> ${num}
			<c:set var="num" value="${num-1}"/>
		</td>
		<td>
			<!-- 댓글 제목 앞에 여백 처리 -->
			<c:if test="${b.board_re_lev > 0}">
				<c:forEach var="i" begin="0" end="${b.board_re_lev}">
					&nbsp;
				</c:forEach>
			</c:if>
			<a href="./BoardDetailAction.do?board_num=${b.board_num}&page=${page}">${b.board_subject}</a>
		</td>
		<td>
			${b.board_name}
		</td>
		<td>
			<fmt:formatDate value="${b.board_date}"
							pattern="yyyy-MM-dd HH:mm:ss EEEE"/>
		</td>
		<td>${b.board_readcount}</td>
	</tr>
	</c:forEach>
</table> <br><br>

<!-- 페이지 처리 -->
<center>
<c:if test="${listcount > 0}">

<!-- 1page로 이동 -->
<a href="./BoardListAction.do?page=1" style="text-decoration:none"> << </a>

<!-- 이전 블럭으로 이동 -->
<c:if test="${startPage > 10}">
	<a href="./BoardListAction.do?page=${startPage-10}">[이전]</a>
</c:if>

<!-- 각 블럭에 10개의 페이지 출력 -->
<c:forEach var="i" begin="${startPage}" end="${endPage}">
	<c:if test="${i == page}"> <!-- 현재 페이지 -->
		[${i}]
	</c:if>
	<c:if test="${i != page}"> <!-- 현재 페이지가 아닐때 -->
		<a href="./BoardListAction.do?page=${i}">[${i}]</a>
	</c:if>
</c:forEach>

<!-- 다음 블럭으로 이동 -->
<c:if test="${endPage < pageCount}">
	<a href="./BoardListAction.do?page=${startPage+10}">[다음]</a>
</c:if>

<!-- 마지막 페이지로 이동 -->
<a href="./BoardListAction.do?page=${pageCount}" style="text-decoration:none"> >> </a>

</c:if>
</center>

 

페이지 처리 / 페이지 메뉴 바 설계

각 블럭에 10개의 페이지 출력 (qna_board_list.jsp 부분)

<!-- 각 블럭에 10개의 페이지 출력 -->
<c:forEach var="i" begin="${startPage}" end="${endPage}">
	<c:if test="${i == page}"> <!-- 현재 페이지 -->
		[${i}]
	</c:if>
	<c:if test="${i != page}"> <!-- 현재 페이지가 아닐때 -->
		<a href="./BoardListAction.do?page=${i}">[${i}]</a>
	</c:if>
</c:forEach>

 

- forEach 문으로 startPage 와 endPage 까지 반복문을 돌려서 1 2 3 ... 10 로 페이지 선택하는 걸 만듬

- 현재 페이지가 아닌 경우 클릭한 페이지 번호를 넘기면서 "/BoardListAction.do" 로 요청한다

- 이러면 Controller 클래스를 다시 찾아가서 BoardListAction.java Service 클래스로 가서 다시 목록을 가져온다

- 이때 BoardListAction.java 의 아래 코드에서

		if(request.getParameter("page") != null) {
			page = Integer.parseInt(request.getParameter("page"));
		}

- 사용자가 선택한 페이지가 page 변수에 저장되고 그걸 이용해서 데이터를 가져옴

- 그리고 page 변수로부터 유도된 변수 및 리스트를 BoardListAction.java 아래에서 공유설정함

		// 공유 설정
		request.setAttribute("page", page);

- 그걸 다시 View qna_board_list.jsp 에서 받는다

 

첫 페이지로 이동, 마지막 페이지로 이동 (qna_board_list.jsp 부분)

 

<!-- 1page로 이동 -->
<a href="./BoardListAction.do?page=1" style="text-decoration:none"> << </a>

- 1 page 로 이동

<!-- 마지막 페이지로 이동 -->
<a href="./BoardListAction.do?page=${pageCount}" style="text-decoration:none"> >> </a>

- 총 페이지 수 == 마지막 페이지

- pageCount 는 총 페이지 수이고, 공유되어 받아온 값이다

- >> 클릭시 마지막 페이지로 이동

* 링크가 잘못되었다, 제목에 링크가 들어가야함, 추후 수정 완료

 

이전 블럭 (qna_board_list.jsp)

<!-- 이전 블럭으로 이동 -->
<c:if test="${startPage > 10}">
	<a href="./BoardListAction.do?page=${startPage-10}">[이전]</a>
</c:if>

- 블럭단위로 이동

- startPage 는 본인이 속한 블럭의 첫 페이지 의미

- startPage > 10 은 '1 페이지가 아니면' 이라는 의미, ${startPage - 10} 은 현재 블럭의 첫 페이지에서 -10 을 하면 이전 블럭의 첫 페이지로 이동

ex) 13 페이지에 있다가 [이전] 클릭시 현재 블럭 startPage 인 11 에서 -10 을 하여 1 페이지로 이동, 즉 1 블럭의 startPage 로 이동

 

다음 블럭 이동 (qna_board_list.jsp)

<!-- 다음 블럭으로 이동 -->
<c:if test="${endPage < pageCount}">
	<a href="./BoardListAction.do?page=${startPage+10}">[다음]</a>
</c:if>

- endPage 는 본인이 속한 블럭의 끝 페이지를 의미

- endPage < pageCount 은 '마지막 페이지가 아니면' 이라는 의미, ${startPage + 10} 은 현재 블럭의 첫 페이지에서 +10 을 하면 다음 블럭의 첫 페이지로 이동

 

 

블럭과 파생변수 startPage, endPage

- 두번째 블럭의 startPage 는 11, endPage 는 20

 

- 페이지를 클릭할때마다

- 페이지 번호를 누를때마다 계속 DB와 연동해서 목록을 가져오는 작업, request 로 공유하는 작업, View 에서 받아서 뿌리는 작업이 수행된다

 


+ 같은 페이지 처리를 JSTL 쓰지 않고 했을때

- Model 1 게시판 페이지 메뉴바 만드는 코드이다 : https://laker99.tistory.com/117

<!-- 페이지 링크 설정 -->
<center>
<%
if(count > 0) {
	// pageCount : 총 페이지 수
	int pageCount = count/page_size + ((count%page_size==0) ? 0 : 1);
	System.out.println("pageCount : " + pageCount);
	
	// startPage : 각 블럭의 시작 페이지 번호 : 1, 11, 21 ...
	// endPage : 각 블럭의 끝 페이지 번호 :    10, 20, 30 ...
	int startPage = ((currentPage-1)/10) * 10 + 1;
	int block = 10;	// 1개의 블럭의 크기 : 10개의 페이지로 구성
	int endPage = startPage + block - 1;
	
	// 가장 마지막 블럭에 endPage값을 pageCount로 수정
	if(endPage > pageCount) {	//
		endPage = pageCount;
	}
%>	
	<!-- 1page로 이동 -->
	<a href="list.jsp?page=1" style="text-decoration:none"> << </a>
<%
	// 이전 블럭으로 이동
	if(startPage > 10){	%>
		<a href="list.jsp?page=<%=startPage-10 %>">[이전]</a>
<%	}

	// 각 블럭당 10개의 페이지 출력
	for(int i = startPage; i <= endPage; i++) {
		if(i == currentPage) { %>
			[<%=i %>]
<%		} else {%>
			<a href="list.jsp?page=<%=i %>">[<%=i %>]</a>
<%		}
	}// for end
	
	// 다음 블럭으로 이동
	if(endPage < pageCount) {
%>	
		<a href="list.jsp?page=<%=startPage+10 %>">[다음]</a>	
<%	} %>
	<!-- 마지막 페이지로 이동 -->
	<a href="list.jsp?page=<%=pageCount %>" style="text-decoration:none"> >> </a>	
<%}%>
</center>


게시판 프로그램 : 상세 페이지

- 제목 클릭시 상세 페이지로 넘어간다

- 글번호 num 과 페이지 번호를 전달해야한다 ( 총 3번 전달 중 첫번째)

- 목록페이지 -> 상세페이지 -> 수정/삭제 폼 -> 수정/삭제

- 처음 2 번은 get 방식으로 전달, 마지막은 post 로 전달

- qna_board_list.jsp 부분

<td>
	<a href="./BoardDetailAction.do?board_num=${b.board_num}&page=${page}">${b.board_name}</a>
</td>

- 상세 페이지로 가기 위해 /BoardDetailAction.do 로 요청한다

 

 게시판 프로그램 : 상세 페이지 Service 클래스

- service 패키지 안에 상세 페이지 Service 클래스인 BoardDetailAction.java 를 생성 및 작성하자

- BoardDetailAction.java (수정 전)

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardDetailAction implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardDetailAction");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String page = request.getParameter("page");
		
		BoardDAO dao = BoardDAO.getInstance();
		dao.readcountUpdate(board_num);	// 조회수 1 증가
		BoardBean board = dao.getDetail(board_num);	// 상세정보 구하기
		
		// 글내용 줄바꿈 기능
		String content = board.getBoard_content().replace("\n", "<br>");
		
		// 공유 설정
		request.setAttribute("board", board);
		request.setAttribute("content", content);
		request.setAttribute("page", page);
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false); // dispatcher 방식으로 포워딩
		forward.setPath("./board/qna_board_view.jsp");
		
		return forward;
	}

}

- 목록 페이지에서 제목을 클릭해서 넘어왔고, 글번호와 페이지번호가 넘어왔다, 그 값을 변수 board_num 과 page 로 받음

- 글번호는 int 형으로 형변환한다

<조회수 1 증가>

- 조회수 값을 1 증가시키는 update SQL 을 수행하는 메소드 readcountUpdate() 가 필요하다

- 그 글의 조회수 컬럼을 1 증가시켜야하므로 메소드 호출시 그 글을 특정할 수 있는 글번호 board_num 을 전달함

- 따로 리턴을 받을 필요는 없다, 아무것도 리턴 받지 않고 있음

* DAO 클래스 readcountUpdate() 메소드 작성은 아래에

<상세 정보 구하기>

- 상세 정보를 구해오는 메소드 select SQL 을 수행하는 메소드 getDetail() 이 필요하다

- 그 글의 상세 정보를 구해와야하므로 메소드 호출시 그 글을 특정할 수 있는 글번호 board_num 을 전달함

- 글 1개의 상세정보를 리턴하므로 리턴 자료형은 DTO

* DAO 클래스 getDetail() 메소드 작성은 아래에

<DAO -> Service 로 돌아왔을때> (BoardDetailAction.java 부분)

		// 공유 설정
		request.setAttribute("board", board);
		request.setAttribute("content", content);
		request.setAttribute("page", page);

- View 페이지로 돌아간다, 돌아갈때 2가지 변수 글번호, 페이지번호 뿐 아니라 검색된 결과를 돌려받은 객체 board 도 가져가야한다

- 이때, 객체 board, 줄바꿈 처리를 한 content , 페이지번호 page 3개를 공유설정한다

- 글번호 num 은 board 객체 안의 컬럼 board_num 에 있음

- content 는 board 에서 가져와서 줄바꿈 처리를 한 것이다

- 상세페이지 출력 View 페이지인 qna_board_view.jsp 파일로 포워딩한다.

- 이 Service 클래스를 호출했던 Controller 클래스로 돌아감


Model 1 vs Model 2

- Model 1 에서는 두 기능을 하나의 메소드에 처리했다, Model 2 에서는 따로 메소드를 만듬

+ MyBatis 로 바꿔서 처리하면 1개의 메소드 내에서 2개의 sql 문 사용하기 힘들어지므로 지금부터 다른 메소드로 처리

 

Controller 클래스에서 상세 페이지 Service 클래스 BoardDetailAction로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardDetailAction.do 로 요청이 올때의 경우를 처리

		// 상세 페이지
		} else if(command.equals("/BoardDetailAction.do")) {
			try {
				action = new BoardDetailAction();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();				
			}
		}

- 연결 이후 Service 클래스까지 잘 넘어간다

 

DAO 클래스 조회수 1 증가 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 readcountUpdate() 부분 코드만

	// 조회수 1 증가
	public void readcountUpdate(int board_num) {
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "update model2 set board_readcount = board_readcount + 1 ";
			sql += "where board_num = ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, board_num);
			pstmt.executeUpdate();	// SQL문 실행
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}		
		}
	}

- 데이터를 돌려줄 필요없으므로 리턴 자료형 void

- 그 글의 조회수 컬럼을 1 증가시켜야하므로 그 글을 특정할 수 있는 글번호 board_num 이 전달되고, 그걸로 SQL문 where 절 작성

 

DAO 클래스 상세정보 구하기 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 getDetail() 부분 코드만

	// 상세 페이지
	public BoardBean getDetail(int board_num) {
		BoardBean board = new BoardBean();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from model2 where board_num = ?";
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, board_num);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {
				board.setBoard_num(rs.getInt("board_num"));
				board.setBoard_name(rs.getString("board_name"));
				board.setBoard_pass(rs.getString("board_pass"));
				board.setBoard_subject(rs.getString("board_subject"));
				board.setBoard_content(rs.getString("board_content"));
				board.setBoard_file(rs.getString("board_file"));
				board.setBoard_re_ref(rs.getInt("board_re_ref"));
				board.setBoard_re_lev(rs.getInt("board_re_lev"));
				board.setBoard_re_seq(rs.getInt("board_re_seq"));
				board.setBoard_readcount(rs.getInt("board_readcount"));
				board.setBoard_date(rs.getTimestamp("board_date"));
			}
		} catch (Exception e) {
			e.printStackTrace();			
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return board;
	}

- 그 글의 상세 정보를 구해와야하므로 메소드 호출시 그 글을 특정할 수 있는 글번호 board_num 을 전달함

- 글 1개의 상세정보를 리턴하므로 리턴 자료형은 DTO

- select 문을 작성하므로 ResultSet rs 객체를 생성하고 그 객체로 검색된 데이터들을 받는다
- DTO 객체 board 를 생성하고, 그 board 에 검색된 데이터를 setter 메소드로 세팅한 후 리턴한다

 

- 다음은 board 폴더 하위에 상세 페이지를 출력하는 View 페이지인 qna_board_view.jsp 파일을 생성

- qna_board_view.jsp (수정 전, 버튼 구현이 완성 안됨, 수정/삭제/댓글 버튼 링크 안걸었다)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<table border=1 width=400 align=center>
	<caption>상세 페이지</caption>
	<tr>
		<td>제목</td>
		<td>${board.board_subject}</td>
	</tr>
	<tr>
		<td>내용</td>
		<td>
			<pre>${board.board_content}</pre>
			${content}
		</td>
	</tr>
	<tr>
		<td>첨부파일</td>
		<td>
			<!-- 첨부파일이 있을때 첨부파일 출력 -->
			<c:if test="${board.board_file != null}">
				<a href="./board/file_down.jsp?file_name=${board.board_file}">
					${board.board_file}
				</a>
			</c:if>
		</td>
	</tr>
	<tr>
		<td colspan=2 align=center>
			<input type="button" value="댓글">
			<input type="button" value="수정">
			<input type="button" value="삭제">
			<input type="button" value="목록"
			onClick="location.href='./BoardListAction.do?page=${page}'">
		</td>
	</tr>
</table>
</body>
</html>

<EL>

- Service 클래스인 BoardDetailAction.java 에서 board 객체를 공유설정했으므로 ${board.board_subject} 처럼 네임값.필드명 으로 간단히 출력 가능

<내용 출력>

- 내용 content 을 출력하는 방법 2가지를 모두 썻다

1. 공유된 content 를 바로 EL 로 출력

2. 공유된 board 객체에서 board.board_content 로 내용을 가져와서 <pre></pre> 로 감싸기

<첨부파일> (qna_board_view.jsp 부분)

			<!-- 첨부파일이 있을때 첨부파일 출력 -->
			<c:if test="${board.board_file != null}">
				<a href="./board/file_down.jsp?file_name=${board.board_file}">
					${board.board_file}
				</a>
			</c:if>

- if 태그로 첨부파일이 null 이 아니면, 즉 첨부파일이 있으면 첨부파일명을 출력

- 첨부파일명에 링크를 걸어 클릭시 첨부파일을 다운받도록 한다 첨부파일명을 클릭시 file_down.jsp 로 이동

- file_down.jsp 로 이동하며 다운로드 받을 파일명 ${board.board_file} 을 file_name 변수에 담아 전달한다

<목록 버튼>

- '목록' 버튼 클릭시 "/BoardListAction.do" 요청을 하면서 현제 페이지 번호를 목록 구하는 곳으로 전달한다

- page 변수에 값이 저장되었으므로 원래 페이지로 돌아갈 수 있다

<댓글 버튼>

- '댓글' 버튼 클릭시 "/BoardReplyAction.do" 요청을 하면서 글번호 board_num 과 원래 페이지번호 page 를 전달

- 특정 글 아래에 댓글을 달게되므로 글번호 board_num 이 필요하다

- 댓글 작성 완료 후 원래의 페이지로 돌아가기 위해서 page 번호가 필요하다

- 나머지 버튼들은 나중에 링크 연결하기

 

Q. 왜 파일 다운하기 위해 file_down.jsp 로 갈떄는 원문 글 작성폼으로 갈때와 달리 Controller 안거치고 jsp 로 가는지 모르겠다

A. 원칙은 Controller 클래스 거쳐서 가야하지만 이 file_down.jsp 파일은 예외적으로 바로 JSP 파일로 가기


게시판 프로그램 : 댓글 작성

- 상세페이지에서 '댓글' 버튼 클릭시 "/BoardReplyAction.do" 요청을 get 방식으로 한다

- Controller 클래스에서 이 요청을 처리하고 댓글 작성폼 Service 클래스인 BoardReplyAction.java 로 가야함

 

게시판 프로그램 : 댓글 작성폼 Service 클래스

- 댓글 작성폼으로 가기 전, 부모글의 정보가 필요하고, 부모글의 정보를 가져올떄 DB 연동을 해야하므로 필요한 Service 클래스이다

- 댓글 작성폼으로 이동 전 필요하므로 이름을 "댓글 작성폼 Service 클래스" 로 한다

- service 패키지 안에 BoardReplyAction.java 클래스 생성 및 작성

- BoardReplyAction.java

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardReplyAction implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardReplyAction");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String page = request.getParameter("page");
		
		BoardDAO dao = BoardDAO.getInstance();
		
		// 부모글의 상세 정보 구하기
		BoardBean board = dao.getDetail(board_num);
		
		// 공유 설정
		request.setAttribute("board", board);
		request.setAttribute("page", page);
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false); // dispatcher 방식으로 포워딩
		forward.setPath("./board/qna_board_reply.jsp");
		return forward;
	}

}

- 상세 페이지 View 인 qna_board_view.jsp 에서 넘어온 글번호 board_name 와 페이지번호를 받는다

- 댓글 처리 관련 변수 3개를 처리하기 위해서는 부모글의 상세 정보 (변수 3개)를 먼저 구해야하므로 getDetail() 사용

- getDetail() 의 매개변수로 부모 글의 번호가 될 board_name 를 전달한다

ex) 부모의 board_re_lev 에서 + 1 을 하는 등 부모의 정보 필요

- DB에서 받아온 부모 글의 정보를 저장한 객체 board 와 페이지번호 page 를 공유설정한다.

- 댓글 작성폼 View 페이지 qna_board_reply.jsp 로 포워딩한다, request 로 공유설정했으므로 dispatcher 방식으로 포워딩

 

Controller 클래스에서 댓글 작성폼 Service 클래스 BoardReplyAction로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardReplyAction.do 로 요청이 올때의 경우를 처리

		// 댓글 폼
		} else if(command.equals("/BoardReplyAction.do")) {
			try {
				action = new BoardReplyAction();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

 

- 댓글 작성폼 qna_board_reply.jsp 를 생성 및 작성하자

- qna_board_reply.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>댓글 게시판</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>

<form action="<%=request.getContextPath() %>/BoardReply.do" method="post">
<input type="hidden" name="board_num" value="${board.board_num}">
<input type="hidden" name="page" value="${page}">
<input type="hidden" name="board_re_ref" value="${board.board_re_ref}">
<input type="hidden" name="board_re_lev" value="${board.board_re_lev}">
<input type="hidden" name="board_re_seq" value="${board.board_re_seq}">
<table cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">댓글 게시판</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">글쓴이</div>
		</td>
		<td>
			<input name="board_name" id="board_name" type="text" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">제 목</div>
		</td>
		<td>
			<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100" 
				value="re."/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">내 용</div>
		</td>
		<td>
			<textarea name="board_content" id="board_content" cols="67" rows="15"></textarea>
		</td>
	</tr>
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="댓글">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

- qna_board_write.jsp 의 내용 복붙 후 수정

- 첨부파일 부분을 삭제, enctype 도 삭제하고, action 값을 "/BoardReply.do" 로 수정

- 부모글의 댓글 관련 컬럼 3개와, 글번호, 페이지번호 총 5개를 hidden 으로 값을 전달해야한다

- 그리고 댓글 작성 폼에 입력된 값들도 전달

- 글번호는 부모글의 글 번호를 의미, 그래서 공유된 부모글 정보 객체인 board 에서 부모글의 글번호를 가져옴

 

게시판 프로그램 : 댓글 작성 Service 클래스

- 실제로 댓글을 작성하는 역할을 한다

- service 패키지 안에 BoardReply.java 클래스 생성 및 작성

- BoardReply.java 

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardReply implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardReply");
		
		request.setCharacterEncoding("utf-8");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		int board_re_ref = Integer.parseInt(request.getParameter("board_re_ref"));
		int board_re_lev = Integer.parseInt(request.getParameter("board_re_lev"));
		int board_re_seq = Integer.parseInt(request.getParameter("board_re_seq"));
		String page = request.getParameter("page");
		
		BoardBean board = new BoardBean();
		board.setBoard_num(board_num);
		board.setBoard_re_ref(board_re_ref);
		board.setBoard_re_lev(board_re_lev);
		board.setBoard_re_seq(board_re_seq);
		board.setBoard_name(request.getParameter("board_name"));
		board.setBoard_pass(request.getParameter("board_pass"));
		board.setBoard_subject(request.getParameter("board_subject"));
		board.setBoard_content(request.getParameter("board_content"));
		
		BoardDAO dao = BoardDAO.getInstance();
		int result = dao.boardReply(board); // 댓글 작성
		if(result == 1) System.out.println("댓글 작성 성공");
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(true);
		forward.setPath("./BoardListAction.do?page="+page);
		return forward;
	}

}

- 댓글 작성 폼에서 한글값이 넘어올 수 있으므로 한글값 인코딩

- hidden 으로 넘어온 5개 값과 댓글 작성 입력 양식에서 넘어온 값들도 request.getParameter() 로 받는다

- 생성한 DTO 객체 board에 받아온 값들( 부모 글 정보 + 댓글 작성 입력양식에 사용자가 작성한 값들) 을 setter 메소드로 세팅한다

- 부모 글에 관한 정보를 저장한 변수들은 SQL문 안에서 사용될것이므로 int 형으로 변환한다

- 댓글 작성 메소드인 boardReply() 메소드를 호출하면서 부모 글 정보와 입력한 데이터가 담긴 board 객체를 매개변수로 전달해줌

- 원래 페이지로 돌아가기 위해 목록을 가져오는 요청인 "/BoardListAction.do" 로 포워딩 하면서 get 방식으로 현재 페이지 번호를 넘겨준다, 이로 인해 원래 페이지로 돌아갈 수 있음

* DAO 클래스의 boardReply() 작성은 아래에

 

Controller 클래스에서 댓글 작성 Service 클래스 BoardReply로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardReply.do 로 요청이 올때의 경우를 처리

		// 댓글 작성
		} else if(command.equals("/BoardReply.do")) {
			try {
				action = new BoardReply();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

 

DAO 클래스 댓글 작성 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 boardReply() 부분 코드만

	// 댓글 작성
	public int boardReply(BoardBean board) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		
		// 부모글에 대한 정보
		int re_ref = board.getBoard_re_ref(); // 글 그룹 번호
		int re_lev = board.getBoard_re_lev(); // 댓글의 깊이
		int re_seq = board.getBoard_re_seq(); // 댓글의 출력순서
		
		try {
			con = getConnection();
			
			String sql = "update model2 set board_re_seq = board_re_seq + 1";
			sql += " where board_re_ref = ? and board_re_seq > ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, re_ref);
			pstmt.setInt(2, re_seq);
			pstmt.executeUpdate(); // SQL문 실행
			
			sql = "insert into model2 values(model2_seq.nextval,";
			sql += "?,?,?,?,?,?,?,?,?,sysdate)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getBoard_name());
			pstmt.setString(2, board.getBoard_pass());
			pstmt.setString(3, board.getBoard_subject());
			pstmt.setString(4, board.getBoard_content());
			pstmt.setString(5, ""); // board_file
			pstmt.setInt(6, re_ref); // board_re_ref
			pstmt.setInt(7, re_lev+1); // board_re_lev
			pstmt.setInt(8, re_seq+1); // board_re_seq
			pstmt.setInt(9, 0); // board_readcount
			result = pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();		
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}			
		}
		return result;
	}

- 매개변수로는 부모글의 정보와 댓글 작성 폼에 적힌 데이터가 저장된 DTO 객체 board 가 넘어온다

- 부모글에 대한 3가지 정보를 변수에 저장

- 2가지 SQL 문을 사용해야 한다

<update 문>

- 댓글의 board_re_seq 는 자신의 부모글 보다 seq 값이 큰 글들만 seq 값을 1 씩 증가시킴

1) 부모가 원문이면 그 원문의 모든 댓글의 seq 값을 1 씩 증가

2) 부모가 댓글이면 그 부모보다 먼저 달린 형제 댓글들과 그 형제 댓글들의 대댓글들의 seq 값을 1씩 증가

<insert 문>

- 댓글 번호는 마찬가지로 sequence 로 넣는다

- 댓글의 board_re_ref 는 부모글의 board_re_ref 와 일치해야함

- 댓글의 board_re_lev 는 부모글의 board_re_lev 보다 1 커야함

- board_re_lev 와 board_re_seq 는 부모 글의 board_re_lev 와 board_re_seq 에서 1 증가한 값을 넣음

 

- 이제 상세 페이지에서 '댓글' 을 클릭해서 댓글 폼으로 간 후 댓글을 작성하자

 

문제점

- 안나온다

- 또 get 으로 전송하는데도 post 가 나옴

 

문제 해결

- BoardReply.java 의 setRedirect() 를 true 로 바꾸기

* 링크가 잘못되었다, 제목에 링크가 들어가야함, 추후 수정 완료

- 댓글 작성 후 목록페이지가 잘 나타난다

 

- 이제 댓글과 대댓글을 더 많이 써보자


게시판 프로그램 : 글 수정

- 수정 폼으로 넘어가기 전 DAO 에서 글의 상세정보를 불러와야한다

- 그러므로 "/BoardModifyAction.do" 로 요청함

 

- 상세 페이지 하단에서 '수정' 눌렀을때 수정폼 Service 클래스로 넘어가기 위해 "/BoardModifyAction" 로 요청

- qna_board_view.jsp 부분

<input type="button" value="수정"
onClick="location.href='./BoardModifyAction.do?board_num=${board.board_num}&page=${page}'">

 

- 상세페이지에서 글번호 board_num 과 페이지번호 page 가 넘어온다

 

 게시판 프로그램 : 글 수정폼 Service 클래스

- service 패키지 안에 글 수정폼 Service 클래스인 BoardModifyAction.java 를 생성 및 작성하자

- BoardModifyAction.java

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardModifyAction implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardModifyAction");
		
		int board_num = Integer.parseInt(request.getParameter("board_num"));
		String page = request.getParameter("page");
		
		BoardDAO dao = BoardDAO.getInstance();
		
		// 상세 정보 구하기
		BoardBean board = dao.getDetail(board_num);
		
		// 공유 설정
		request.setAttribute("board", board);
		request.setAttribute("page", page);
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false); // dispatcher 방식으로 포워딩
		forward.setPath("./board/qna_board_modify.jsp");
		return forward;
	}

}

- 수정폼으로 가기 전에 상세정보를 먼저 구해야 한다, 그 후에 수정폼으로 포워딩해야함

- 가장 먼저 상세페이지에서 get 방식으로 넘어온 글번호 board_num 과 페이지번호 page 를 가져온다

- DAO 의 getDetail() 메소드로 board_num 을 사용해서 글의 상세정보를 가져온다

- 가져온 상세정보를 받은 객체 board 를 공유설정하고, 페이지 번호 page 도 공유설정한다

+ 글 번호는 객체 board 안에도 있으므로 따로 공유설정 하지 않음

 

Controller 클래스에서 글 수정폼 Service 클래스 BoardModifyAction로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardModifyAction.do 로 요청이 올때의 경우를 처리

		// 수정 폼
		} else if(command.equals("/BoardModifyAction.do")) {
			try {
				action = new BoardModifyAction();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();				
			}
		}

 

- 다음은 글 수정폼 View 페이지인 qna_board_modify.jsp 파일을 생성하자

- qna_board_modify.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>게시판 수정</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>

<form action="<%=request.getContextPath() %>/BoardModify.do" method="post">
<input type="hidden" name="board_num" value="${board.board_num}">
<input type="hidden" name="page" value="${page}">

<table cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">게시판 수정</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">글쓴이</div>
		</td>
		<td>
			<input name="board_name" id="board_name" type="text" size="10" maxlength="10" 
				value="${board.board_name}"/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">제 목</div>
		</td>
		<td>
			<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100" 
				value="${board.board_subject}"/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">내 용</div>
		</td>
		<td>
			<textarea name="board_content" id="board_content" cols="67" rows="15">${board.board_content}</textarea>
		</td>
	</tr>
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="수정">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

- qna_board_reply.jsp 파일의 내용을 복붙 후 수정

- 수정폼에 수정할 정보를 입력하고 '수정' 버튼 클릭시 글 수정 요청인 "/BoardModify.do" 로 요청이 간다 

- hidden 으로는 글 번호인 board_num 과 페이지번호 page 가 넘어간다

- 글 수정 요청인 "/BoardModify.do" 로 가면서 hidden 으로 2개, 입력양식으로 4개의 값들, 총 6개의 값이 글 수정 Service 클래스 BoardModify.java 로 전달됨

- Service 클래스 BoardModifyAction.java 에서 공유했던 board 객체에서 이름을 "${board.board_name}" 로 가져와서 입력양식에 value 속성으로 넣기

- 이름 뿐 아니라 제목, 내용도 마찬가지로 처리한다

 

- 구현 후 상세페이지에서 '수정' 클릭시 나타나는 수정폼에 정보들이 나타남

 

- 비밀번호를 입력하고 '수정' 버튼 클릭시 /BoardModify.do 로 요청함

 

- 실제 수정을 처리해주는 Service 클래스인 BoardModifyjava 클래스를 생성하자

 

 게시판 프로그램 : 글 수정 Service 클래스

- service 패키지 안에 글 수정 Service 클래스인 BoardModifyjava.java 를 생성 및 작성하자

- BoardModify.java (수정 전 1)

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class BoardModify implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardModify");
		
		ActionForward forward = new ActionForward();
		return forward;
	}
}

- 아직 작성 안했고 여기까지 도착했는지 출력만 하고 있다, 내일 작성

 

Controller 클래스에서 글 수정 Service 클래스 BoardModify로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardModify.do 로 요청이 올때의 경우를 처리

		// 글 수정
		} else if(command.equals("/BoardModify.do")) {
			try {
				action = new BoardModify();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();				
			}	
		}

JSP Model 2 게시판 프로그램 만들기

게시판 프로그램

- 댓글게시판 + 자료실 게시판

 

게시판 프로그램 : 주요 기능 소개

1. Connection Pool

2. 댓글 기능

3. 자료실 – 첨부파일 업로드, 다운로드 : cos 라이브러리 사용

- Model 1, Model 2 에선 주로 cos 라이브러리 사용, Spring 에서는 다른 라이브러리 사용

4. request 객체 공유 설정

- 게시판에서는 request 객체로 공유하는 경우가 많다

5. Controller 클래스 : Java Servlet

6. Model = Service + DAO Service, DTO, DAO 클래스

7. View (화면 인터페이스) : EL , JSTL 사용

 

게시판 프로그램 : 프로그램 구조 설명, 구조도

- Model 2 에서는 DAO, DTO 가 다른 패키지에 있다

- 같은 기능을 하는 클래스들끼리 묶어서 패키지 안에 저장

- controller 패키지 안에 Controller 클래스, dao 패키지 안에 DAO 클래스, model 패키지 안에 DTO 클래스

- service 패키지 안에 Service 클래스들, board 안에 View 페이지들

 

게시판 프로그램 : 프로젝트 생성

- 위의 구조도처럼 만들기 위해 model2board 라는 프로젝트부터 생성

- 이후 update 프로젝트의 WebContent 폴더 하위에 초기에 보여줄 파일인 index.jsp 를 생성

 

게시판 프로그램 : 기초 파일 가져오기

- 클라우드에서 기본 작성 양식, 환경설정 코드가 있는 board 폴더를 이클립스 WebContent 폴더로 가져오기

 

게시판 프로그램 : 몇가지 환경 구축

1. WebContent/WEB-INF/lib 폴더 하위에 라이브러리 3개 넣기

1) cos 라이브러리를 사용하기 위해 cos.jar 파일을 WEB-INF/lib 폴더 안으로 가져오기

2) JSTL 을 사용하기 위한 라이브러리 jstl-1.2.jar 를 WEB-INF/lib 폴더 안으로 가져오기

3) 오라클용 JDBC Driver 인 ojdbc.jar 를 WEB-INF/lib 폴더 안에 가져오기, 다른 프로젝트에서 가져오면 된다

2. board 폴더 안의 context.xml 파일을 META-INF 폴더로 옮기기

- 커넥션 풀의 환경설정 파일이고, 기존에 작성했던 내용

 

게시판 프로그램 : Connection Pool 테스트

- dbcpAPITest.jsp 파일을 실행해서 커넥션 풀에서 커넥션 가져오기 테스트

- context.xml

<Context> 
  <Resource name="jdbc/orcl" 
   auth="Container"
   type="javax.sql.DataSource" 
   username="totoro" 
   password="totoro123"
   driverClassName="oracle.jdbc.driver.OracleDriver"
   factory="org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory"
   url="jdbc:oracle:thin:@localhost:1521:xe"
   maxActive="500"  
   maxIdle="100"/>  
</Context>

<!--  p423 참조
 maxActive="500"  컨넥션풀이 관리하는 컨넥션의 최대 갯수
		          (기본값 : 8)
 maxIdle="100" 	  컨넥션풀이 관리하는 최대 유휴 갯수(기본값 : 8)          
-->

- 테이블을 아직 생성하지 않았지만 계정이 설정되어있으므로 Connection Pool 테스트가 가능하다

 

- dbcpAPITest.jsp

<%@ page language="java" contentType="text/html; charset=EUC-KR"%>
<%@ page import="java.sql.*"%>
<%@ page import="javax.sql.*" %>
<%@ page import="javax.naming.*" %>
<%
 	Connection conn = null; 
	
	try {
  		Context init = new InitialContext();
  		DataSource ds = (DataSource) init.lookup("java:comp/env/jdbc/orcl");
  		conn = ds.getConnection();
  		
  		out.println("<h3>연결되었습니다.</h3>");
	}catch(Exception e){
		out.println("<h3>연결에 실패하였습니다.</h3>");
		e.printStackTrace();
 	}
%>

- 서버를 한번 중지시켰다가 dbcpAPITest.jsp 실행시

- 정상적으로 Connection Pool 에서 Connection을 구해온다

- 커넥션 풀에서 커넥션 가져오기 테스트 성공

+ 추후 DAO 클래스 만들때 커넥션풀에서 커넥션 구하기 3줄 코드를 DAO 클래스에 넣을 것

  		Context init = new InitialContext();
  		DataSource ds = (DataSource) init.lookup("java:comp/env/jdbc/orcl");
  		conn = ds.getConnection();

 

 

게시판 프로그램 : 첨부파일을 저장할 폴더 생성

- 업로드된 첨부파일을 저장할 폴더인 boardupload를 WebContent 하위에 생성

- 실제 저장되는 폴더는 아니지만 여기에 boardupload폴더를 만들어야 실제 서버에 저장되는 위치에도 boardupload폴더가 생성된다

 

게시판 프로그램 : 관련 컬럼

- board_num : 게시판에서는 글 번호가 primary key 가 된다, 글 번호는 sequence 로 입력한다

- board_pass : 글 작성시 작성한 비밀번호를 수정/삭제 시 사용하도록 하기 위한 비밀번호

- board_file : 첨부파일명

- board_re_ref : 글 그룹 번호, 원문은 글번호 num과 같은 숫자가 들어가고, 댓글은 부모와 같은 숫자가 들어간다

- board_re_lev : 댓글 깊이, 원문은 0 이 저장, 1단계 댓글은 1 저장, 2단계 댓글은 2 저장

- board_re_seq : 댓글을 출력 순서 제어, 원문은 0 으로 고정, 나머지는 오름차순 정렬되며 계속해서 바뀌는 컬럼

 

게시판 프로그램 : 테이블 및 시퀀스 생성

- 이전에 회원관리 프로그램에서 사용했던 오라클계정인 totoro 계정에 model2 테이블 생성

- 아래 양식으로 테이블을 생성한다

create table model2(
	board_num number primary key,
	board_name varchar2(20),
	board_pass varchar2(15),
	board_subject varchar2(50),
	board_content varchar2(2000),
	board_file varchar2(50), -- 첨부 파일명
	board_re_ref number, -- 글 그룹번호
	board_re_lev number, -- 댓글 깊이 : 원문(0), 1, 2...
	board_re_seq number, -- 댓글 출력 순서 : 원문(0) 오름차순 정렬
	board_readcount number,
	board_date timestamp );
    
create sequence model2_seq
	start with 1
	increment by 1
	nocache;

- WebContent 하위에 sql 폴더를 만들고 안에 board.sql 파일 생성

- 커넥션 프로파일을 설정 : Oracle_11, New Oracle(totoro), xe 로 해준다

- totoro 계정으로 커넥션을 연결하고

- 테이블과 시퀀스를 생성한다

- model2 테이블과 model2_seq 테이블 생성 확인

- 생성확인 완료

 

게시판 프로그램 : DAO 와 DTO 클래스 만들기

- 확장자가 .java 인 파일은 src 폴더 안에 있어야 정상동작한다

- Model 2 에선 기능에 따라 세분화하므로 DAO, DTO 가 다른 패키지에 들어감

- 해당 클래스를 사용할때 반드시 import 를 해야하고, 접근제어자는 public 이어야한다

- 나중에 회원관리, 게시판이 같은 프로젝트 안에 들어갈땐, 같은 기능을 하는 파일끼리 같은 폴더에 넣음

 

- 프로그램 구조도 대로 src안에 model 패키지를 만들고 그 안에 DTO 클래스를 만든다

- 프로그램 구조도 대로 src안에 dao 패키지를 만들고 그 안에 DAO 클래스를 만든다

 

게시판 프로그램 : DTO 클래스 작성

- 테이블 model2 의 create 문에서 가져와서 DTO 클래스에 복붙 후 수정

- varchar2 는 String 으로, number 는 Int 로, timestamp 는 Timestamp로 바꿔서 자바 변수(프로퍼티) 만들기

+ 프로퍼티의 접근제어자는 private

+ java.sql 의 Timestamp import

- 이후 getter / setter 메소드 추가

 

- DTO 클래스 완성 코드

- BoardBean.java

// DTO (Data Transfer Object)
package model;

import java.sql.Timestamp;

public class BoardBean {
	private int board_num;
	private String board_name;
	private String board_pass;
	private String board_subject;
	private String board_content;
	private String board_file;
	private int board_re_ref;
	private int board_re_lev;
	private int board_re_seq;
	private int board_readcount;
	private Timestamp board_date;
	
	public int getBoard_num() {
		return board_num;
	}
	public void setBoard_num(int board_num) {
		this.board_num = board_num;
	}
	public String getBoard_name() {
		return board_name;
	}
	public void setBoard_name(String board_name) {
		this.board_name = board_name;
	}
	public String getBoard_pass() {
		return board_pass;
	}
	public void setBoard_pass(String board_pass) {
		this.board_pass = board_pass;
	}
	public String getBoard_subject() {
		return board_subject;
	}
	public void setBoard_subject(String board_subject) {
		this.board_subject = board_subject;
	}
	public String getBoard_content() {
		return board_content;
	}
	public void setBoard_content(String board_content) {
		this.board_content = board_content;
	}
	public String getBoard_file() {
		return board_file;
	}
	public void setBoard_file(String board_file) {
		this.board_file = board_file;
	}
	public int getBoard_re_ref() {
		return board_re_ref;
	}
	public void setBoard_re_ref(int board_re_ref) {
		this.board_re_ref = board_re_ref;
	}
	public int getBoard_re_lev() {
		return board_re_lev;
	}
	public void setBoard_re_lev(int board_re_lev) {
		this.board_re_lev = board_re_lev;
	}
	public int getBoard_re_seq() {
		return board_re_seq;
	}
	public void setBoard_re_seq(int board_re_seq) {
		this.board_re_seq = board_re_seq;
	}
	public int getBoard_readcount() {
		return board_readcount;
	}
	public void setBoard_readcount(int board_readcount) {
		this.board_readcount = board_readcount;
	}
	public Timestamp getBoard_date() {
		return board_date;
	}
	public void setBoard_date(Timestamp board_date) {
		this.board_date = board_date;
	}
	
}

 

게시판 프로그램 : DAO 클래스 작성

DAO 에 들어갈 내용

1. 싱글톤

2. 정적메소드 getInstance() 생성

3. Connection Pool 에서 커넥션 구해오는 메소드

4. 그 이후 게시판 CRUD 관련 메소드

 

1. 싱글톤

- 외부 접근 불가능하게 private, 공유하기 위해 static 해서 자기자신의 클래스로 객체 생성을 한번만 하기

 

2. 정적메소드 getInstance() 생성

 

3. Connection Pool 에서 커넥션 구해오는 메소드 getConnection() 생성

- 메소드 호출시 Connection Pool 에서 커넥션을 구해주도록 함

- DBCP 방식으로 DB 와 연결하므로 이 DB 연결시 커넥션을 구해오는 이 메소드를 사용해야한다

- 커넥션 구할때 예외처리를 해야한다, 여기선 throws 로 Exception 던지기

+ Connection 클래스 import

- 테스트할때 사용했던 dbcpAPITest.jsp 3줄을 복사해서 넣는다

 

- 단축키 Ctrl + Shift + O 로 import 시키기

- javax.naming.Context 를 선택후 Next, 다음은 javax.sql.DataSource 선택후 Finish

+ Context 와 DataSource 둘 다 Interface 이다

 

- getConnection() 메소드 완성

- 가져온 커넥션을 리턴하는 코드로 바꿈

+ 리턴자료형을 private 으로 해서 DAO 클래스 내에서만 호출가능하도록 함

 

- 현재까지 DAO 클래스 BoardDAO.java 코드

// DAO (Data Access Object)
package dao;

import java.sql.Connection;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class BoardDAO {
	
	// 싱글톤 : 객체 생성을 1번만 수행하는 것
	private static BoardDAO instance = new BoardDAO();
	
	public static BoardDAO getInstance() {	// 정적 메소드
		return instance;
	}
	
	// 커넥션 풀에서 커넥션을 구해오는 메소드
	private Connection getConnection() throws Exception {
  		Context init = new InitialContext();
  		DataSource ds = (DataSource) init.lookup("java:comp/env/jdbc/orcl");
  		return ds.getConnection();
	}
	
	// 글 작성 : 원문작성
}

 

4. 그 이후 게시판 CRUD 관련 메소드

- 가장 먼저 원문 글 작성 메소드를 만들어야한다, 그 후 다양한 메소드들을 DAO에 작성

 

게시판 프로그램 : Controller 클래스

- Java Servlet 클래스로 만든다

- 모든 요청의 진입점

- src에서 오른마우스를 클릭해서 Servlet 선택

- controller 패키지, BoardFrontController 클래스 를 만든다

- Controller 클래스 생성 완료

 

- BoardFrontController.java (수정 전 1 , 기본적인 것만 작성했음)

package controller;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class BoardFrontController
 */
@WebServlet("*.do")	// do 확장자로 요청하는 요청을 받는다는 의미
public class BoardFrontController extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	// doGet(), doPost() 메소드에서 공통적인 작업을 처리하는 메소드
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI : " + requestURI); // /model2board/BoardAddAction.do
		System.out.println("contextPath : " + contextPath); // /model2board
		System.out.println("command : " + command); // /BoardAddAction.do
		
	} // doProcess() end
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {	
		System.out.println("get");
		doProcess(request, response); // doProcess() 메소드 호출
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("post");
		doProcess(request, response); // doProcess() 메소드 호출
	}
}

 

- 오류 생겼을때 어느정도까지 실행되었는지 확인하기 위해서 System.out.println(); 으로 브라우저에 출력

- doGet() 과 doPost() 에 같은 내용을 써야한다, 요청 방식에 따라 서로 다른 일을 하는게 더 이상함

- doGet() 과 doPost() 에서 처리할 내용은 동일해야하므로 따로 공통적인 작업을 처리하는 메소드 doProcess() 를 만듬

- 그 메소드 doProcess() 를 doGet(), doPost() 에서 호출하도록 함

- doGet(), doPost() 는 자동으로 호출되는 메소드이므로 doGet(), doPost() 의 매개변수 request, response 를 doProcess() 호출시 전달

- doProcess() 안의 내용은 아래에

 

WebServlet 어노테이션 이름값으로 "*.do" 로 임의로 설정

@WebServlet("*.do")

- "*.do" 는 do 확장자로 요청하는 모든 요청을 받는다는 의미

- "*.do" 는 앞쪽의 파일명은 어떤 이름이어도 상관없고, 확장자가 do 인 모든 요청을 받는다는 의미

- 이 확장자는 임의로 설정한다, 주로 회사명

 

- @WebServlet("/") 로 하면 아무 이름으로 찾아가도 무조건 여기로 찾아온다

- 지금은 특정 확장자로 요청할때만 찾아오도록 확장자를 사용하자

+ 아파치 톰캣 버전 6 이하에는 @WebServlet 어노테이션 대신 web.xml 에 관련 내용들이 들어갔었다

+ Spring 프로젝트에서는 어노테이션 대신 과거 방식 처럼 Controller 클래스 이름과 위치, Servlet 매핑을 잡는 내용을 web.xml 안에서 설정함

+ Spring 에선 web.xml 안의 servlet  class (controller class) 관련 내용, 매핑 잡는 내용(요청 이름값 등) 들이 들어가있다

 

doProcess() 안의 내용 (BoardFrontController.java 부분)

	// doGet(), doPost() 메소드에서 공통적인 작업을 처리하는 메소드
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI : " + requestURI); // /model2board/BoardAddAction.do
		System.out.println("contextPath : " + contextPath); // /model2board
		System.out.println("command : " + command); // /BoardAddAction.do
		
	} // doProcess() end

- request 객체에서 제공되는 6개의 메소드 중 getRequestURI() 와 getContextPath() 를 쓰고 있다

- Controller 클래스는 요청을 어떤 서비스 클래스로 넘겨줄지 결정함

- getRequestURI() : 요청 URI 를 구해줌 ex) /model2board/BoardAddAction.do

- getContextPath() : 현재 프로젝트 명을 구해줌 ex) /model2board

- substring() 으로 contextPath 변수(현재 프로젝트명) 의 길이 숫자부터 시작해서 추출

- 추출시 요청 URI 만 남게되고, 이 요청 URI 가 command 변수에 저장됨 ex) /BoardAddAction.do

 

Controller 클래스에서 맞는 Service 클래스로 요청을 넘기는 방법 (BoardFrontController.java 부분)

- /model2board 의 길이는 12, /model2board/BoardAddAction.do 에서 0번 인덱스부터 시작하므로 12번 인덱스는 /, 즉 /BoardAddAction.do 가 command 가 된다

 

요청을 보내는 방법

- 게시판에 원문 글을 작성하는 양식 WebContent/board/qna_board_write.jsp 파일을 보자

- qna_board_write.jsp 에서 form 의 action 값을 확인해보자

- 요청이름 확장자가 .do 이므로 Controller 클래스로 찾아간다

+ 요청 이름값과 Service 클래스명을 일치시킬 것 ex) 원문 글 작성 Service 클래스는 BoardAddAction.java 로 만들 것

 

- qna_board_write.jsp 를 실행시켜서 아무 값이나 입력하고 '등록' 을 누른 후 콘솔창을 보자

- 콘솔창 확인

- 요청이름값이 command 변수에 저장됨

 

만약 qna_board_write.jsp 에서 form 태그 action에 이렇게 패키지명을 쓰지 않고 ./ 로 쓰면

- requestURI 에 다른 하위폴더도 나오게 되어서 경로가 잘 맞지 안게 됨

 

게시판 프로그램 : Service 클래스의 부모 인터페이스

- Service 클래스를 통일성 있게 설계하기 위해 부모가 될 인터페이스 Action 먼저 만들기

- 인터페이스 Action 을 만들고 안에는 추상메소드 execute() 만들기

- Action.java

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface Action {
	
	// 추상 메소드
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

- 리턴자료형은 ActionForward 로 한다

- Controller 클래스의 doProcess() 의 매개변수 request, response 를 그대로 execute() 의 매개변수에 복사

- 추상메소드 execute() 는 형식만 있다

 

게시판 프로그램 : ActionForward 클래스

- service 패키지 안에 ActionForward 클래스 만들기

- ActionForward.java

package service;

public class ActionForward {
	
	private boolean redirect;	// 포워딩 방식 설정
	private String path;		// 포워딩 페이지명 설정
	
	public boolean isRedirect() {
		return redirect;
	}
	public void setRedirect(boolean redirect) {
		this.redirect = redirect;
	}
	public String getPath() {
		return path;
	}
	public void setPath(String path) {
		this.path = path;
	}
	
}

- 포워딩 방식과 포워딩 페이지명을 저장하는 클래스

1. 포워딩 방식을 결정할 변수 redirect 정의,  dispatcher, redirect 방식 중 어느 것으로 할지 true, false 로 결정

+ request 객체로 공유가 되었을때는 반드시 dispatcher 방식으로 포워딩되어야 뷰페이지에 출력 가능

2. 포워딩할 페이지명이 저장될 변수 path 를 정의

- 두 변수는 private 으로 설정

3. getter, setter 메소드 만들기

- Service 클래스에서 값을 처리하고 View 페이지로 가기 전에 이 ActionForward 로 포워딩 방식, 포워딩 페이지명을 결정

- setter 메소드는 Service 클래스 하단에서 호출해서 설정하고, getter 메소드는 Controller 하단에서 포워딩 할때 호출한다

 

- 다시 Controller 클래스로 돌아와서 Service 클래스로 넘겨주는 작업 및 포워딩 작업 코드를 쓰자

- BoardFrontController.java  (수정 전 2)

package controller;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import service.Action;
import service.ActionForward;

/**
 * Servlet implementation class BoardFrontController
 */
@WebServlet("*.do")	// do 확장자로 요청하는 요청을 받는다는 의미
public class BoardFrontController extends HttpServlet {
	private static final long serialVersionUID = 1L;
	
	// doGet(), doPost() 메소드에서 공통적인 작업을 처리하는 메소드
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI : " + requestURI); // /model2board/BoardAddAction.do
		System.out.println("contextPath : " + contextPath); // /model2board
		System.out.println("command : " + command); // /BoardAddAction.do
		
		Action action = null;
		ActionForward forward = null;
		
		// 글 작성 (원문작성)
		if(command.equals("/BoardAddAction")) {
			
		}
		
		// 포워딩 처리
		if(forward != null) {
			if(forward.isRedirect()) {	// redirect 방식으로 포워딩
				response.sendRedirect(forward.getPath());
			} else {	// dispatcher 방식으로 포워딩
				RequestDispatcher dispatcher = 
						request.getRequestDispatcher(forward.getPath());
				dispatcher.forward(request, response);
			}
		}
		
	} // doProcess() end
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {	
		System.out.println("get");
		doProcess(request, response); // doProcess() 메소드 호출
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("post");
		doProcess(request, response); // doProcess() 메소드 호출
	}
}

- 모두 doProcess() 안에서 작성한다

- service 패키지 안의 Action 인터페이스 객체 action 를 만들고 null 로 초기화

- service 패키지 안의 ActionForward 클래스 객체 forward 를 만들고 null 로 초기화

- 원문 글 작성 요청이름값인 "/BoardAddAction.do" 일때의 처리를 해줌. Service 클래스 BoardAddAction.java 로 넘기기

- 마지막 부분에서 포워딩 처리를 한다

- forward != null 로 인해 DAO -> Service -> Controller 로 돌아올때만 여기서 포워딩을 해줌

 

포워딩 처리 (BoardFrontController.java 부분)

		// 포워딩 처리
		if(forward != null) {
			if(forward.isRedirect()) {	// redirect 방식으로 포워딩
				response.sendRedirect(forward.getPath());
			} else {	// dispatcher 방식으로 포워딩
				RequestDispatcher dispatcher = 
						request.getRequestDispatcher(forward.getPath());
				dispatcher.forward(request, response);
			}
		}

- Controller 클래스에서는 포워딩 처리도 한다

- forward 객체는 한번 Service 클래스로 갔다오면 null 이 아니게 됨

- forward.isRedirect() 는 true, false 를 돌려줌, true면 redirect 방식 포워딩, false 면 diepacher 방식으로 포워딩 하기로 함

- getPath() 메소드로 포워딩할 페이지 불러오기

 


게시판 프로그램 : 원문 글 작성

- 이제 잠시 Controller 클래스는 두고, 원문 글 작성 기능을 먼저 구현하자

- 먼저 원문 글 작성 폼 qna_board_write.jsp 를 보자

<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
<head>
	<title>MVC 게시판</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/board/script.js"></script>
</head>
<body>

<form action="<%=request.getContextPath() %>/BoardAddAction.do" method="post" 
	  enctype="multipart/form-data">
<table cellpadding="0" cellspacing="0" align=center border=1>
	<tr align="center" valign="middle">
		<td colspan="5">MVC 게시판</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">글쓴이</div>
		</td>
		<td>
			<input name="board_name" id="board_name" type="text" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">비밀번호</div>
		</td>
		<td>
			<input name="board_pass" id="board_pass" type="password" size="10" maxlength="10" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12" height="16">
			<div align="center">제 목</div>
		</td>
		<td>
			<input name="board_subject" id="board_subject" type="text" size="50" maxlength="100" 
				value=""/>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">내 용</div>
		</td>
		<td>
			<textarea name="board_content" id="board_content" cols="67" rows="15"></textarea>
		</td>
	</tr>
	<tr>
		<td style="font-family:돋음; font-size:12">
			<div align="center">파일 첨부</div>
		</td>
		<td>
			<input name="board_file" type="file"/>
		</td>
	</tr>
	<tr bgcolor="cccccc">
		<td colspan="2" style="height:1px;">
		</td>
	</tr>
	<tr><td colspan="2">&nbsp;</td></tr>
	<tr align="center" valign="middle">
		<td colspan="5">			
			<input type=submit value="등록">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

 

원문 글 작성 폼 form 태그 보기 (qna_board_write.jsp)

<form action="<%=request.getContextPath() %>/BoardAddAction.do" method="post" 
	  enctype="multipart/form-data">

- 개발환경이 아닐땐 프로젝트 명이 빠지므로, 프로젝트 명을 명시하지 말고 request.getContextPath() 메소드 사용하기

- 첨부파일을 포함한 데이터 전송시에는 method 는 post 방식으로만 가능하다

- 첨부파일을 전송하기 위해 반드시 enctype="multipart/form-data" 가 필요하다

- action 을 .do 확장자로 작성했으므로 Controller 클래스로 잘 찾아감

 

 

테이블 컬럼과 폼에서 전달되는 값 비교

- board_name, board_pass, board_subject, board_content, board_file 값만 폼에서 넘어간다

- model2 테이블 컬럼에서 댓글처리 3개 컬럼, 조회수 컬럼, 작성시간 컬럼은 form 에서 넘어가지 않는다

- 원문 글이므로 board_re_ref 컬럼은 board_num 과 똑같이 sequence 로 들어감, board_re_lev 컬럼은 0이 들어감

+ 가입 양식의 name 값, DTO 의 프로퍼티명, 테이블의 컬럼명을 일치시켜야편함, MyBatis 사용시 반드시 일치시켜야함

 

게시판 프로그램 : 원문 글 작성 Service 클래스 (미완성, 연결 확인만 하기)

- 실제 원문 글 작성 요청을 처리하는 BoardAddAction.java 파일을 만들자

- 원문 글 작성 폼의 요청이름값과 동일하게 설정

- service 패키지에 BoardAddAction.java 클래스 생성

- BoardAddAction.java (수정 전, Controller 연결 코드 아래에 완성 BoardAddAction.java 클래스가 있다)

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class BoardAddAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardAddAction");
		
		ActionForward forward = new ActionForward();
		
		return forward;
	}

}

 

Controller 클래스에서 원문 글 작성 Service 클래스 BoardAddAction 로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardAddAction.do 로 요청이 올때의 경우를 처리

		// 글 작성 (원문작성)
		if(command.equals("/BoardAddAction.do")) {
			try {
				action = new BoardAddAction();
				forward = action.execute(request, response);
			} catch(Exception e) {
				e.printStackTrace();
			}
		}

- Controller 클래스는 어떤 Service 클래스로 넘겨줄건지 결정

- command 값, 즉 요청 이름값이 "/BoardAddAction" 면 회원가입을 처리하는 BoardAddAction 클래스로 객체 action 생성

- 왼쪽은 부모 인터페이스, 오른쪽은 상속받는 구현 클래스가 왔다, 업캐스팅을 하고 있음

- action 객체를 통해 BoardAddAction () 클래스의 오버라이딩된 메소드인 execute() 를 실행하고 ActionForward 객체 forward 로 리턴받음

- 해당 클래스 BoardAddAction 의 객체를 만들고 execute() 를 실행하므로써 그 Service 클래스 BoardAddAction 로 가는 것임

+ 메소드 오버라이딩이 되면 오버라이딩 된 메소드만 실행된다

- execute() 를 호출하며 매개변수로는 doGet(), doPost() 의 매개변수인 request, response 를 전달함

+ try-catch 예외처리 해야한다

- DB 연동을 할 때만 Service 클래스로 넘어가고, DB 연동을 하지 않으면 바로 포워딩 객체 생성 후 포워딩 경로, 방식 설정 후 아래의 포워딩 코드로 포워딩하면 된다

* BoardAddAction 클래스는 원문 글 작성 Service 클래스이다.

 

- 이제 View 페이지, Service 클래스, Controller 에서 연결까지 했으니 원문 글 작성 폼인 qna_board_write.jsp 실행

- '등록' 을 클릭하고 콘솔창을 보자 

- BoardAddAction 이 출력되는 것을 보아, 폼 -> Controller -> Service 까지 왔음을 확인 가능

 

게시판 프로그램 : 원문 글 작성 Service 클래스 (이어서)

- BoardAddAction.java (수정 후)

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.oreilly.servlet.MultipartRequest;
import com.oreilly.servlet.multipart.DefaultFileRenamePolicy;

import dao.BoardDAO;
import model.BoardBean;

public class BoardAddAction implements Action {

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardAddAction");
		
		String path = request.getRealPath("boardupload");
		System.out.println("path : " + path);
		
		int size = 1024 * 1024; // 1MB
		
		// 첨부파일은 MultipartRequest 클래스로 객체를 생성하면서 파일 업로드가 수행된다
		MultipartRequest multi =
				new MultipartRequest(request, 
						path,	// 업로드 디렉토리
						size,	// 업로드 파일의 크기 (1MB)
						"utf-8",	// 한글 인코딩
						new DefaultFileRenamePolicy());	// 중복파일 문제 해결
		
		BoardBean board = new BoardBean();
		board.setBoard_name(multi.getParameter("board_name"));
		board.setBoard_pass(multi.getParameter("board_pass"));
		board.setBoard_subject(multi.getParameter("board_subject"));
		board.setBoard_content(multi.getParameter("board_content"));
		board.setBoard_file(multi.getFilesystemName("board_file"));
		
		BoardDAO dao = BoardDAO.getInstance();
		int result = dao.insert(board);
		if(result == 1) System.out.println("글 작성 성공");
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/BoardListAction.do");
		
		return forward;
	}

}

- 첨부파일이 원문 글 작성 폼 에서 넘어왔으므로 request 객체로 getParamter() 해서 값을 받을 수 없다

- 넘어온 첨부파일을 받아서 서버로 전송해야한다

 

첨부파일을 서버로 업로드 (BoardAddAction.java 부분)

		String path = request.getRealPath("boardupload");
		System.out.println("path : " + path);
		
		int size = 1024 * 1024; // 1MB
		
		// 첨부파일은 MultipartRequest 클래스로 객체를 생성하면서 파일 업로드가 수행된다
		MultipartRequest multi =
				new MultipartRequest(request, 
						path,	// 업로드 디렉토리
						size,	// 업로드 파일의 크기 (1MB)
						"utf-8",	// 한글 인코딩
						new DefaultFileRenamePolicy());	// 중복파일 문제 해결

1. 실제 업로드할 위치 경로를 getRealPath() 로 구해서 path 변수에 저장

- 폴더 boardupload 에 저장할 것

2. 첨부파일의 크기 구하기 (단위 : Byte)

3. MultipartRequest 객체를 생성하면 자동으로 업로드 된다

- DefaultFileRenamePolicy 객체를 생성해서 중복문제도 해결해줌

- cos 라이브러리에서 지원되는 2가지 클래스이다

- 한글 인코딩을 여기서 했으므로 따로 할 필요 없다

- Model 1 자료실 게시판, 첨부파일 업로드 : https://laker99.tistory.com/120?category=1080281 

 

전달된 나머지 값 받기 (BoardAddAction.java 부분)

		BoardBean board = new BoardBean();
		board.setBoard_name(multi.getParameter("board_name"));
		board.setBoard_pass(multi.getParameter("board_pass"));
		board.setBoard_subject(multi.getParameter("board_subject"));
		board.setBoard_content(multi.getParameter("board_content"));
		board.setBoard_file(multi.getFilesystemName("board_file"));

- 첨부파일을 제외한 나머지 값들은 request 객체로 값을 받을 수 없다, multi 객체로 받아야함

- java 파일이거나 첨부파일 전송이 포함되어있을때는 useBean, setProperty action tag 사용 불가능

- 일일히 setter 메소드로 객체 board에넣어주기

- 클라이언트가 업로드한 파일명, 실제 서버에 저장된 파일명 2개가 있다

- 그 중 여기선 실제 서버에 저장된 파일명을 getFilesystemName("board_file") 메소드로 가져와서 board 객체에 저장

- 매개변수에 작성하는 "board_file" 은 첨부파일 업로드 양식의 name 값이다

 

DB 의 메소드 호출해서 DB 와 연동 (BoardAddAction.java 부분)

		BoardDAO dao = BoardDAO.getInstance();
		int result = dao.insert(board);
		if(result == 1) System.out.println("글 작성 성공");

- 그 후 DAO 객체를 생성하고 DAO 의 insert() 메소드로 사용자가 입력한 값들을 DB에 삽입함

- 글 작성 이후 목록페이지

 

포워딩 방식, 포워딩 페이지 설정 (BoardAddAction.java 부분)

		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("/BoardListAction.do");

- 목록페이지로 가는 요청이 아니라 DB에서 목록을 가져오는 요청이다

- 목록을 가져오기 위한 Service 클래스와 목록을 출력하는 View 페이지가 따로 있다

- View 페이지로 가는게 아닌 목록을 가져오기 위한 요청인 "/BoardListAction.do" 로 목록을 가져와야함

- 글 작성 성공 후 목록페이지로 돌아가야한다,그냥 목록페이지로 가면 아무것도 없음

- 목록을 끄집어내는 요청 "/BoardAddAction" 을 먼저 해야한다, 다시 Controller 클래스로 감

 

- 이제 Service -> DAO 로 가야한다

- 작성된 원문글을 DB에 삽입하는 메소드 insert() 를 DAO 클래스에 작성

 

DAO 클래스 원문 글 작성 기능 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 insert() 부분 코드만

	// 글 작성 : 원문작성
	public int insert(BoardBean board) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		
		try {
			con = getConnection();
			
			String sql = "insert into model2 values(model2_seq.nextval,?,?,?,?,?,";
			sql += "model2_seq.nextval,?,?,?,sysdate)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, board.getBoard_name());
			pstmt.setString(2, board.getBoard_pass());
			pstmt.setString(3, board.getBoard_subject());
			pstmt.setString(4, board.getBoard_content());
			pstmt.setString(5, board.getBoard_file());
			pstmt.setInt(6, 0);	// board_re_lev
			pstmt.setInt(7, 0); // board_re_seq
			pstmt.setInt(8, 0); // board_readcount
			
			result = pstmt.executeUpdate(); // SQL문 실행
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return result;
	}

- 사용자가 원문 글 작성폼에 입력한 정보를 저장하고 있는 DTO 객체 board 가 매개변수로 넘어옴

- board_re_ref 값은 board_num 과 같은값이 들어가야하므로 같은 시퀀스 mode2_seq 사용하면 같은 값 들어감

- 원문이므로 board_re_lev, board_re_seq, readcount 는 0 이 들어갸아한다

- insert 수행 후 Service 클래스인 BoardAddAction.java 로 돌아간다

 

- 이제 DB와 연동이 끝났으니 원문 글 작성 폼 qna_board_write.jsp 를 실행해서 글을 작성해보자

- 아직 뒤는 구현하지 않았으므로 나오지 않음, 콘솔창 보기

- 글 작성 성공 메세지가 나타났으므로 insert 가 잘 되었음을 알 수 있다

- path : 에 찍혀진 경로가 실제 첨부파일이 업로드되는 폴더이다

C:\Users\admin\eclipse-workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\model2board\boardupload

- 탐색기에 이 경로를 쳐보자

- 사용자가 첨부한 첨부파일도 잘 업로드 되었다

+ 빨간 선 아래는 BoardAddAction.java 에서 요청한 "/BoardListAction.do" 이다,

- dispatcher 방식 포워딩 시 URL 주소가 바뀌지 않으므로 post 방식으로 찍혀나오므로 빨간 선 아래도 post 로 나왔음


게시판 프로그램 : 목록 가져오기

흐름

- BoardAddAction.java(글 작성) 에서 "/BoardListAction.do" 로 포워딩 페이지를 설정했다, Controller 클래스로 돌아감

- BoardFrontController.java 부분

- 아래 포워딩 처리 코드에서 "/BoardListAction.do" 로 포워딩하므로 다시 Controller 클래스의 WebContent 어노테이션으로 찾아감

- 그리고 다시 Controller 에서 command.equals("/BoardListAction.do") 인 조건문으로 와서 BoardListAction.java Service 클래스로 찾아감

- 요청이 /BoardListAction.do 로 온 것을 처리하는 코드를 추가 한 후이다.

 

Model 1 vs Model 2 목록 페이지

- Model 1 에선 list.jsp 파일에서 모두 작업했다

- Model 2 에선 목록을 가져오는 파일 (Service 클래스) 과 화면에 뿌려주는 파일 (JSP 파일) 이 따로 있다

 

- service 클래스 안에 BoardListAction.java 생성

 

게시판 프로그램 : 목록 가져오기 Service 클래스 (미완성, 연결 확인만 하기)

- service 클래스 안에 BoardListAction.java 생성

- BoardListAction.java (수정 전, Controller 연결 코드 아래에 완성 BoardListAction.java 클래스가 있다)

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class BoardListAction implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardListAction");

		ActionForward forward = new ActionForward();
		
		return forward;
	}

}

 

Controller 클래스에서 목록 가져오기 Service 클래스 BoardListAction 로 전달 (BoardFrontController.java 부분)

- BoardFrontController.java 전체 코드는 나중에

- BoardFrontController.java 부분

- BoardListAction.do 로 요청이 올때의 경우를 처리

		// 글 목록
		} else if(command.equals("/BoardListAction.do")) {
			try {
				action = new BoardListAction();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

 

게시판 프로그램 : 목록 가져오기 Service 클래스 (이어서)

- 페이징 처리를 해서 목록을 가져와야한다, 가져온 목록을 request 객체로 공유godigksek

- View 에서 그 공유값을 가져와서 목록을 뿌린다 (화면에 출력)

- BoardListAction.java (수정 후)

package service;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.BoardDAO;
import model.BoardBean;

public class BoardListAction implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("BoardListAction");

		int page = 1; 	// 현재 페이지 번호
		int limit = 10;	// 한 페이지에 출력할 데이터 개수
		
		if(request.getParameter("page") != null) {
			page = Integer.parseInt(request.getParameter("page"));
		}
		
		// page=1 : startRow=1, endRow=10
		// page=2 : startRow=11, endRow=20
		// page=3 : startRow=21, endRow=30
		int startRow = (page - 1) * limit + 1;
		int endRow = page * limit;
		
		BoardDAO dao = BoardDAO.getInstance();
		int listcount = dao.getCount(); // 총 데이터 갯수
		System.out.println("listcount : " + listcount);
		
		List<BoardBean> boardlist = dao.getList(startRow, endRow);
		System.out.println("boardlist : " + boardlist);
		
		// 총 페이지 수
		int pageCount = listcount/limit + ((listcount % limit == 0) ? 0 : 1);
		
		int startPage = ((page - 1) / 10) * limit + 1;	// 1, 11, 21
		int endPage = startPage + 10 - 1;			// 10, 20, 30
		
		if(endPage > pageCount) endPage = pageCount;
		
		// 공유 설정
		request.setAttribute("page", page);
		request.setAttribute("listcount", listcount);
		request.setAttribute("boardlist", boardlist);
		request.setAttribute("pageCount", pageCount);
		request.setAttribute("startPage", startPage);
		request.setAttribute("endPage", endPage);
		
		// request 객체로 공유한 경우에는 dispatcher 방식으로 포워딩되어야,
		// view 페이지에서 공유한 값에 접근 가능
		ActionForward forward = new ActionForward();
		forward.setRedirect(false); // dispatcher 방식으로 포워딩
		forward.setPath("./board/qna_board_list.jsp");
		return forward;
	}

}

1. 기본변수 page : 사용자가 클릭함으로서 넘어온 페이지값이 있다면 그 페이지값을 page 변수에 저장, 없다면 1 이 저장됨

ex) 하단의 페이지 들 중 3 페이지를 클릭하면 다시 BoardListAction.java 로 오게되어 page 변수에 3이 들어간다

2. 기본변수 limit : 한 화면에 출력할 데이터 개수, 현재 10개로 설정

3. 파생변수 startRow / endRow : 목록을 limit (10) 개씩 잘라주기 위한 변수, 페이지 번호에 따라 결정됨

ex) page가 3이면, startRow 는 21, endRow 는 30 으로서 21 ~ 30번쨰 글을 가져옴

4. 기본변수 listcount : 총 데이터 개수, DB와 연동하여 getCount() 메소드로 가져옴

5. 다음에는 DB와 연동하여 실제로 목록을 자르고 목록을 List 로 구해오는 getList() 메소드를 호출하고 결과를 리스트 boardlist 로 받자

- 나중에 출력시 DTO 객체 (주솟값) 가 콘솔창에 찍혀나온다

- DAO 에서 다시 여기로 돌아와서 돌려받은 리스트 boardlist 에 저장된 데이터를 출력해야한다

6. 파생변수 pageCount : 총 페이지 수를 구해서 저장, 총 데이터개수을 limit 으로 나누고, 나누어 떨어지지 않는다면 1을 더함

7. 파생변수 startPage / endPage : 블럭의 시작 페이지와 끝페이지

- 여기서 블럭은 페이지 10개를 출력하도록 했으므로 startPage, endPage 를 만들때 10 으로 나누고 더하고 있다

ex) 두번째 블럭은 시작페이지가 11 페이지, 끝 페이지가 20 페이지, 세번째 블럭은 시작페이지가 21 페이지

- 가장 마지막 블럭에서는 무조건 10개의 페이지 출력을 하면 안되므로 마지막 페이지를 총 페이지수로 설정함

		if(endPage > pageCount) endPage = pageCount;

8. 마지막 파생변수인 화면 출력 번호는 목록을 출력하는 View 페이지에서 만들기

- 블럭에 있는 1 2 3 ... 10 처럼 페이지를 클릭할 수 있는 것은 View 페이지에서 만들 것

9 . View 페이지에서 목록을 출력해야하므로 여기서 모든 기본변수, 파생변수, 검색결과(데이터)를 저장한 리스트를 request 객체로 공유설정해야함

- 공유되는 값의 형태를 잘 봐야함

10. ActionForward 객체 forward 를 생성 하고 포워딩 방식과 포워딩 페이지를 설정해줌

- request 공유설정 했으므로 반드시 dispatcher 방식으로 포워딩한다

- 목록(게시판) View 페이지인 qna_board_list.jsp 로 포워딩 한다

- 즉 여기 BoardListAction Service 클래스에서 DB에서 목록을 먼저 가져온 후 공유가 선행되어야 한다

- 그 다음에 목록 View 페이지로 포워딩해야 공유된 값을 가져와서 View 에서 출력 가능

11. 다음으로는 Controller 클래스로 돌아가서 forward 객체를 받아서 Controller 하단 포워딩 코드로 qna_board_list.jsp 로포워딩한다

 

값의 형태에 따라 View 페이지에서 출력하는 방법이 다르다

- page, listcount, pageCount, startPage, endPage 는 기본자료형 공유값이므로 바로 ${네임값} 으로 출력 가능

- boardlist 는 리스트이므로 forEach 의 items 안에 들어간다

 

- Model 1 게시판 목록 가져오기 부분 : https://laker99.tistory.com/123?category=1080281 

* DAO 클래스 getCount() 메소드 작성은 아래에

* DAO 클래스 getList() 메소드 작성은 아래에

 

DAO 클래스 총 데이터 개수 구하는 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 getCount() 부분 코드만

	// 총 데이터 갯수 구하기
	public int getCount() {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select count(*) from model2";
			
			pstmt = con.prepareStatement(sql);
			rs = pstmt.executeQuery();
			
			if(rs.next()) {
//				result = rs.getInt(1); 아래와 같다
				result = rs.getInt("count(*)");
			}
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return result;
	}

- select 문이므로 ResultSet 객체 rs 가 필요

- 그룹함수 count(*) 를 사용해서 총 데이터 개수를 구한 후 그걸 result 변수에 저장해서 돌려준다

 

DAO 클래스 목록을 자르고 목록을 구해주는 메소드 작성

- BoardDAO.java 전체 코드는 모두 구현 후 올리기

- BoardDAO.java 추가된 getList() 부분 코드만

	// 글 목록
	public List<BoardBean> getList(int start, int end) {
		List<BoardBean> list = new ArrayList<BoardBean>();
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from ( select rownum rnum, board.* from ";
			sql += " (select * from model2 order by board_re_ref desc, ";
			sql += " board_re_seq asc) board ) ";
			sql += " where rnum >= ? and rnum <= ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, start);
			pstmt.setInt(2, end);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			while(rs.next()) {
				BoardBean board = new BoardBean();
				
				board.setBoard_num(rs.getInt("board_num"));
				board.setBoard_name(rs.getString("board_name"));
				board.setBoard_pass(rs.getString("board_pass"));
				board.setBoard_subject(rs.getString("board_subject"));
				board.setBoard_content(rs.getString("board_content"));
				board.setBoard_file(rs.getString("board_file"));
				board.setBoard_re_ref(rs.getInt("board_re_ref"));
				board.setBoard_re_lev(rs.getInt("board_re_lev"));
				board.setBoard_re_seq(rs.getInt("board_re_seq"));
				board.setBoard_readcount(rs.getInt("board_readcount"));
				board.setBoard_date(rs.getTimestamp("board_date"));
				
				list.add(board);
			}
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		
		return list;
	}

- 여러개의 데이터를 구해야하므로 리턴자료형은 List 이다

- 먼저 리스트를 만들고 그 리스트에 검색한 데이터들을 저장한 다음 그 리스트를 반환

- DTO 객체 board 생성 후, 검색결과를 컬럼단위로 잘라서 setter 로 board 에 저장 후 그 board 를 리스트 list 에 저장 

<SQL문>

- 첫번째 서브쿼리문에서는 rownum 컬럼의 별칭을 rnum 으로 설정하고, 두번째 서브쿼리에서 검색한 모든 컬럼을 다 사용 (board.*)

- 두번째 서브쿼리문에서 원하는 방식으로 정렬을 한다. 두번째 서브쿼리의 별칭이 board

- board_re_ref 기준 내림차순으로 한번 정렬을 한다

- 부모와 그 부모의 댓글들은 모두 같은 ref 값을 가지고 있으므로 두번째 정렬을 board_re_seq 기준 오름차순으로 한다

- 전체 데이터를 원하는 방식으로 정렬한 곳에서 where 문으로 rnum >= startRow and rnum <= endRow 로 글들 가져오기

 

- getAttribute() 사용 후 다운캐스팅 하고 표현식 태그로 출력하는 대신 EL 로 쉽게 처리

 

- 이제 포워딩 할 페이지인 qna_board_list.jsp 에서 request 객체로 공유한 리스트, 변수들을 여기서 뿌려줘야함

- WebContent/board 폴더 안에 qna_board_list.jsp 생성 및 작성

- qna_board_list.jsp (수정 전, 아직 작성 안함)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
게시판 목록
</body>
</html>

 

- model 1 처럼 이 파일 qna_board_list.jsp 를 그냥 실행해선 안된다, 바로 실행 시 공유된 데이터가 없다

- 현재는 글 작성 후 목록 가져오기 요청을 통해 Controller 클래스로 가서 필요한 목록을 가져온 후 이 페이지로 와야함

- 매번 글 작성 후 목록으로 가기 힘드므로 개발 중간에 목록을 확인하면서 개발을 하기 위해 index.jsp 파일에 목록을 가져오기 요청을 하는 코드를 써서 쉽게 목록 페이지로 갈 수 있도록 하자

- index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
모델2 게시판

<script>
	location.href = "./BoardListAction.do";
</script>
</body>
</html>

- index.jsp 에서 목록 가져오기 요청을 한다

- index.jsp 실행 시 목록을 가져오는 요청을 Controller 클래스에 하고 Service -> DAO -> Service 로 목록을 가져왔다가 qna_board_list.jsp 로 포워딩 됨

- index.jsp 실행 시

- 목록 구하는 요청 부터 qna_board_list.jsp 까지 온 것 이다, "게시판 목록" 출력은 qna_board_list.jsp 의 코드이다

- boardlist 가 찍힌 것을 봐서 Controller -> Service -> DAO -> 다시 Service 까지 왔다

 

- 다시 qna_board_list.jsp 로 돌아오자

- qna_board_list.jsp (수정 후 1, 목록 뿌리고 있다, 아직 블럭관련은 안함, 미완성)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<a href="./BoardForm.do">글작성</a> <br>
글갯수 : ${listcount } 개 <br>

<%
	int count = ((Integer)request.getAttribute("listcount")).intValue();
%>
글갯수 : <%=count %><br>

<table border=1 width=700 align=center>
	<caption>게시판 목록</caption>
	<tr>
		<td>번호</td>
		<td>제목</td>
		<td>작성자</td>
		<td>날짜</td>
		<td>조회수</td>
	</tr>
	
	<c:set var="num" value="${listcount - (page - 1) * 10}"/>
	<c:forEach var="b" items="${boardlist}">
	<tr>
		<td> ${num}
			<c:set var="num" value="${num-1}"/>
		</td>
		<td>${b.board_subject}</td>
		<td>${b.board_name}</td>
		<td>
			<fmt:formatDate value="${b.board_date}"
							pattern="yyyy-MM-dd HH:mm:ss EEE요일"/>
		</td>
		<td>${b.board_readcount}</td>
	</tr>
	</c:forEach>
</table>

 

공유 값 가져오기 (qna_board_list.jsp 부분)

- BoardListAction 에서 공유한 값들을 사용해서 출력만 시켜야한다, 테이블을 이용해서 출력

- 공유된 값이 기본자료형이라면 바로 ${네임값} 으로 출력 가능하다

- 공유된 값이 리스트일 때는 JSTL forEach 태그의 items 속성에 공유 네임이 들어가야한다

	<c:forEach var="b" items="${boardlist}">
	<tr>
		<td> ${num}
			<c:set var="num" value="${num-1}"/>
		</td>
		<td>${b.board_subject}</td>
		<td>${b.board_name}</td>
		<td>${b.board_date}</td>
		<td>${b.board_readcount}</td>
	</tr>
	</c:forEach>

- 그 forEach 태그 안에서 리스트의 값(글)들을 변수 b 로 받아서 반복문으로 출력

- 그 글의 제목, 작성자 들을 출력하기 위해서 ${b.board_subject}, ${b.board_writer} 사용

- 필드들은 private 임, 직접 접근이 아니라 이 EL ${b.board_subject}, ${b.board_writer} 은 내부적으로 b.getBoard_subject , b.getBoard_writer() 과 같은 의미임

- 주의 : 표현식 태그 <%=number --%> 와 달리 EL 에서는 증감연산자 지원안되므로 ${num --} 불가능, 화면 출력시 ${num} 출력 후 set 태그로 재정의해서 1 을 감소시켜야함

 

화면 출력 번호 (qna_board_list.jsp 부분)

	<c:set var="num" value="${listcount - (page - 1) * 10}"/>

- 화면에 출력되는 번호는 마지막 파생변수로서 여기서 JSTL set 태그로 그 변수 num 을 만들어준다

+ 10 은 limit 값

 

원문 작성 폼으로 가는 링크 (qna_board_list.jsp 부분)

<a href="./BoardForm.do">글작성</a> <br>

- '글작성' 에 링크를 걸어서 원문 글 작성 폼으로 가도록 하자, 바로 갈 수 없고 먼저 Controller 클래스로 요청해야함

- Controller 클래스에 "/BoardForm.do" 로 요청시 원문 글 작성 폼으로 가는 코드를 작성해야한다

* Controller 클래스 코드는 아래에

* qna_board_list.jsp 모두 작성 후 실행 해서 확인하는 것은 아래에

 

원하는 포맷으로 날짜 출력 (qna_board_list.jsp 부분)

			<fmt:formatDate value="${b.board_date}"
							pattern="yyyy-MM-dd HH:mm:ss EEEE"/>

- JSTL 국제화 라이브러리 사용

- EEEE 는 자동으로 "요일" 까지 뒤에 붙여줌

- EL 과 SimpleDateFormat 은 같이 사용 불가, 이제부터는 JSTL 국제화 라이브러리로 원하는 포맷으로 날짜 출력하기

 

댓글인 경우에는 제목 앞에 여백주기 (qna_board_list.jsp 부분)

		<td>
			<!-- 댓글 제목 앞에 여백 처리 -->
			<c:if test="${b.board_re_lev > 0}">
				<c:forEach var="i" begin="0" end="${b.board_re_lev}">
					&nbsp;
				</c:forEach>
			</c:if>
			${b.board_subject}
		</td>

- 댓글을 구별하기 위해 여백주기

- 공유로 넘어온 리스트에서 forEach 로 가져온 글 하나의 board_re_lev 가 0 보다 크면 댓글이라는 의미

- 댓글인 경우 forEach 루프를 돌리며 댓글 깊이(board_re_lev) 만큼 여백을 주기

 

제목 클릭시 링크로 상세페이지로 넘어가기 (qna_board_list.jsp 부분)

<td>
	<a href="./BoardDetailAction.do?board_num=${b.board_num}&page=${page}">${b.board_name}</a>
</td>

- 상세페이지로 갈때의 요청은 "/BoardDetailAction.do" 로 하자

+ 상세페이지 Service 클래스명도 BoardDetailAction.java

- 요청하면서 현재 글 번호와 페이지 번호를 가져가야한다

- ?board_num=${b.board_num}&page=${page} 를 뒤에 붙여서 상세페이지로 넘어갈때 get 방식으로 상세페이지로 이 값들을 전달한다

 

- 공유 설정을 했던 BoardListAction.java 부분 (글 목록 가져오는 Service 클래스)

- 현재 페이지 번호 page 는 BoardListAction.java (글 목록 가져오기 Service 클래스) 에서 기본자료형으로 공유설정되었으므로 ${page} 로 가져올 수 있다

- 현재 글 번호는 BoardListAction.java 에서 기본자료형으로 공유설정되지 않았고, 공유설정된 리스트 boardlist 에서 가져와야하므로 ${b.board_name} 으로 가져왔다


EL 을 쓰지 않고 공유된 값을 출력하려면?

글갯수 : ${listcount } 개 <br>

- 이 코드를 EL 로 작성 (아래)

<%
	int count = ((Integer)request.getAttribute("listcount")).intValue();
%>
글갯수 : <%=count %>

- getAttribute() 를 사용해야하고, 다운캐스팅 명시와 언박싱을 해야한다

- EL을 사용하는 것이 훨씬 간편하다


Controller 클래스에서 원문 글 작성폼으로 전달 (이동)

		// 글 작성폼
		} else if(command.equals("/BoardForm.do")) {
			try {
				forward = new ActionForward();
				forward.setRedirect(false);
				forward.setPath("./board/qna_board_write.jsp");
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

- DB 연동을 하지 않으므로 Service 클래스로 가지 않고 바로 폼으로 감

- ActionForward 객체 foward 를 여기서 생성 후 바로 포워딩 방식, 포워딩 페이지(원문 글 작성폼) 설정

 

- index.jsp 를 실행해서 목록인 qna_board_list.jsp 로 이동 한 후 '글작성' 클릭해보자

- 글 작성폼으로 잘 넘어간다


- qna_board_list.jsp 작성이 끝난 후 확인을 위해 index.jsp 를 실행해서 목록을 가져온 뒤 View qna_board_list.jsp 로 오자

* 아직 블럭에서 페이지 1 2 3 ... 10 중 페이지를 선택하는 건 구현하지 않았음

- '글작성' 을 눌러서 원문 글 작성도 한번 더 해보자

- 최신순으로 정렬되어있다, 화면의 번호는 글 번호 board_num 이 아니다

- 현재 페이지의 가장 위 게시물의 번호를 구하고, 그 번호를 1씩 감소시켜서 나타낸 번호이다

앞으로 학습할 내용

- Model 2 회원관리 프로그램, 게시판 프로그램

- lib 폴더 안에 라이브러리 저장하는 대신 maven 으로 라이브러리 관리

- DB 접속 방법 중 MyBatis 사용해서 Model 1, Model 2 와의 연동

- (기존 DAO 클래스 안의 내용이 xml 로 처리됨)

- Spring 수업

 

+ JSP 와 오라클 연동 3가지 방법

 


 

+ session : 회원관리 부분에서만 사용, 로그인 ~ 로그아웃

 

프로젝트 구성도

- 회원가입폼, ID중복검사, 로그인 폼, 로그아웃 구현했다

- 이제 수정, 삭제(탈퇴) 기능을 해야함


회원관리 프로그램 : 정보수정

- 먼저 main.jsp 에서 '회원정보 수정' 에 링크를 걸어주자

- main.jsp (수정 전 4)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<!-- 세션이 있는 경우 -->
<c:if test="${!empty sessionScope.id }">
<%-- <c:if test="${sessionScope.id != null }"> --%>
	${sessionScope.id} 님 환영 합니다. <br><br>
	
	<a href="./UpdateMember.do">회원정보 수정</a> <br>
	<a href="./Logout.do">로그아웃</a> <br>
	회원탈퇴 <br>
</c:if>
<!-- 세션이 없는 경우 -->
<c:if test="${empty sessionScope.id }">
<%-- <c:if test="${sessionScope.id == null}"> --%>
	<a href="<%=request.getContextPath() %>/MemberForm.do">회원가입 </a> <br>
	<a href="<%=request.getContextPath() %>/LoginForm.do">로그인 </a> <br>
</c:if>

- 요청이름값을 "/UpdateMember.do" 로 하자

- a href="./UpdateMember.do" 로 해주었다, 만약 안되면 앞에 프로젝트 명도 써야한다

+ 나중에 Service 클래스도 이름을 맞춰서 UpdateMember.java 로 만들기

- '회원정보 수정' 을 누르면 수정폼으로 이동한다, 수정폼에 사용자의 상세정보를 뿌려주기 위해 DB 연동 필요, Controller -> Service 로 가야한다

 

- Controller 에 연결하기 전에 Service 클래스인 UpdateMember.java 를 먼저 만들자

 

회원관리 프로그램 : 회원정보 수정폼 Service 클래스

- 회원 정보 수정폼을 처리하는 UpdateMember.java 파일을 만들자

- '회원정보 수정' 요청이름값과 동일하게 설정

- service 패키지에 UpdateMember.java 클래스 생성 및 작성

- UpdateMember.java

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import dao.MemberDAO;
import model.MemberDTO;

public class UpdateMember implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("UpdateMember");
		
		HttpSession session = request.getSession();
		String id = (String)session.getAttribute("id");
		
		MemberDAO dao = MemberDAO.getInstance();
		MemberDTO member = dao.getMember(id);	// 1명의 상세정보 구하기
		System.out.println("수정폼:" + member);
		
		String hobby = member.getHobby();	// hobby = "공부-게임-등산-"
		String[] h = hobby.split("-");		// h[0] = "공부", h[1] = "게임", h[2] ="등산"
		
		// 공유 설정
		request.setAttribute("member", member);
		request.setAttribute("h", h);
		
		// request 객체로 공유를 한 경우에는 dispatcher 방식으로 포워딩을 해야 view 페이지에서 공유한 값에 접근 가능
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);	// dispatcher 방식으로 포워딩
		forward.setPath("./member/updateform.jsp");
		return forward;
	}

}

- 진짜 수정 전에 id 로 1명의 상세정보를 구한 후 수정폼에 뿌려주는 역할을 해야한다

<회원 정보수정 Service 클래스 형식, 전체적인 흐름>

- 부모 인터페이스인 Action 인터페이스를 상속받기, 이클립스 자동 기능으로 execute() 를 메소드 오버라이딩 하기

- request 객체는 Controller 클래스의 getPost() -> getProcess() -> Service 클래스의 execute() 로 넘어온다

- ActionForward 객체 forward 생성 후 필드값을 설정해서 execute() 를 호출한 Controller 클래스에 돌려줌

- 진짜 수정 전에 id 로 1명의 상세정보를 구한 후 수정폼에 뿌려주는 역할을 해야한다

 

<회원 정보수정 Service 클래스 작성 순서>

1. id 를 통해 현재 로그인된 한명의 상세정보를 구해와야하므로 세션 값을 구해와야한다

- 먼저, session 객체를 생성하고 "id" 네임으로 세션에 공유된 id 를 가져온다

2. DAO 객체를 생성하고 회원 정보를 구해오는 하는 DAO 의 getMember() 메소드 호출

- getMember() 는 1명에 대한 정보를 검색하므로 리턴자료형은 DTO

3. getMember() 메소드로부터 리턴받은 member 객체와 취미를 request 객체로 공유설정

- 아래에 설명 : <DAO 에서 getMember() 로 회원 정보를 구한 뒤 다시 Service 클래스로 돌아왔을때>

4. 포워딩 방식과 포워딩 페이지 설정

- request 객체로 공유했으므로 포워딩 2가지 방법 중 dispatcher 방식으로 포워딩 해야 view 페이지에서 공유된 값을 가져올 수 있음

 

<DAO 에서 getMember() 로 회원 정보를 구한 뒤 다시 Service 클래스로 돌아왔을때>

* 아래의 DAO 를 작성 한 후 여기로 돌아왔다

- DAO로 부터 받아온 1명의 상세 정보를 저장하는 객체 member 전체를 공유설정해야한다

- 취미는 공유설정 전에, 객체 member 로 받은 값 중 취미를 가져와서 split() 로 잘라야한다, 그래야 수정폼에서 체크가 되어있게 할 수 있다

- 취미를 자른 후 각각의 취미를 배열 h에 저장하고 그 배열 h를 공유설정함

- 이후 포워딩 방식과 포워딩 페이지를 설정하고 객체 forward 를 리턴하며 다시 Controller 클래스로 돌아가서 updateform.jsp로 포워딩함

- request 객체로 공유했으므로 포워딩 2가지 방법 중 dispatcher 방식으로 포워딩 해야 view 페이지에서 공유된 값을 가져올 수 있음

 

* 아래에 DAO getMember() 메소드 작성

- 로그인 후 main.jsp 에서 '회원정보 수정' 클릭시

- Controller 클래스까지 잘 도착했고 command 에 알맞은 값이 들어갔음을 확인 가능

 

- Controller 클래스에서 요청을 Service 클래스로 연결시켜주자

 

Controller 클래스에서 회원정보 수정폼 Service 클래스로 가기

- MemberController.java 전체 코드는 나중에

- MemberController.java 부분

- /UpdateMember.do 로 요청이 올때의 경우를 처리

		// 회원정보 수정폼
		} else if(command.equals("/UpdateMember.do")) {
			try {
				action = new UpdateMember();
				forward = action.execute(request, response);
			} catch(Exception e) {
				e.printStackTrace();
			}
		}

 

- command 값, 즉 요청 이름값이 "/UpdateMember" 면 회원 정보 수정을 처리하는 UpdateMember 클래스로 객체 action 생성

- action 객체를 통해 UpdateMember() 클래스의 오버라이딩된 메소드인 execute() 를 실행하고 ActionForward 객체 forward 로 리턴받음

- execute() 를 호출하며 매개변수로는 doGet(), doPost() 의 매개변수인 request, response 를 전달함

- 나중에 execute() 가 실행된 후 Service 클래스에서 돌아올때는 Service 클래스에서 값이 설정된 forward 객체를 받아서 이 코드 아래의 포워딩 코드에서 포워딩 처리된다.

 

DAO 클래스 회원 1명 상세 정보 구하기 기능 메소드 작성

- MemberDAO.java 전체 코드는 모두 구현 후 올리기

- MemberDAO.java 추가된 getMember() 부분 코드만

	// 회원 1명 상세 정보 구하기 : 수정폼, 수정, 삭제
	public MemberDTO getMember(String id) {
		MemberDTO member = new MemberDTO();
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from member2 where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			rs = pstmt.executeQuery(); // SQL문 실행
			
			if(rs.next()) {
				member.setId(rs.getString("id"));
				member.setPasswd(rs.getString("passwd"));
				member.setName(rs.getString("name"));
				member.setJumin1(rs.getString("jumin1"));
				member.setJumin2(rs.getString("jumin2"));
				member.setMailid(rs.getString("mailid"));
				member.setDomain(rs.getString("domain"));
				member.setTel1(rs.getString("tel1"));
				member.setTel2(rs.getString("tel2"));
				member.setTel3(rs.getString("tel3"));
				member.setPhone1(rs.getString("phone1"));
				member.setPhone2(rs.getString("phone2"));
				member.setPhone3(rs.getString("phone3"));
				member.setPost(rs.getString("post"));
				member.setAddress(rs.getString("address"));
				member.setGender(rs.getString("gender"));
				member.setHobby(rs.getString("hobby"));
				member.setIntro(rs.getString("intro"));
				member.setRegister(rs.getTimestamp("register"));
			}
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return member;
	}

- 수정폼 뿐만 아니라, 수정, 삭제시에도 사용될 메소드이다

- 1명에 대한 정보, 즉 데이터 1개이므로 리턴 자료형은 DTO, 매개변수는 사용자의 id

- select문이므로 ResultSet 객체 rs 도 만들었음

- 이제 DAO -> Service 로 리턴되고, 넘어온 값들을 Service 에서 request 객체로 공유해야한다, dispatcher 방식 포워딩해야함

 

- 이제 포워딩될 수정폼 View 페이지인 updateform.jsp 를 생성해야한다

- member 폴더 안에 수정폼 updateform.jsp 를 생성 및 작성

- updateform.jsp

<%@page import="model.MemberDTO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원 수정 폼</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>

<script src="http://dmaps.daum.net/map_js_init/postcode.v2.js"></script>
<script>
	function openDaumPostcode() {
		new daum.Postcode({
			oncomplete : function(data) {
				// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
				// 우편번호와 주소 정보를 해당 필드에 넣고, 커서를 상세주소 필드로 이동한다.
//				document.getElementById('join_zip1').value = data.postcode1;
//				document.getElementById('join_zip2').value = data.postcode2;
				document.getElementById('post').value = data.zonecode;
				document.getElementById('address').value = data.address;
				
			}
		}).open();
	}
</script>


<!-- 외부 자바스크립트 파일 불러오기 -->
<script src="<%=request.getContextPath() %>/member/member.js"></script>


<script>
$(document).ready(function(){
	/* 취미 체크되도록 하기 */
	<c:forEach var="s" items="${h}">
		$("input:checkbox[name='hobby'][value='${s}']").attr("checked", true);
	</c:forEach>
	
	/* 라디오 버튼 체크  */
	$("input:radio[name='gender'][value='${member.gender}']").attr("checked", true);
});
</script>
<!-- 휴대폰 번호 첫번째 자리 선택-->
<script>
$(document).ready(function(){
	
	$("#phone1 option[value=${member.phone1 }]").attr("selected", true);
});
</script>
</head>
<body>

<form method="post" action="<%=request.getContextPath() %>/Update.do">
<input type="hidden" name="id" value="${member.id }">
<table border=1 width=500 align=center>
	<caption>회원 수정</caption>
	<tr><td>ID</td>
		<td>
		<%
			MemberDTO mem = (MemberDTO)request.getAttribute("member");
		%>
		<%=mem.getId() %> <br>
		${member.id}
		</td>
	</tr>
	<tr><td>비밀번호</td>
		<td><input type=password id="passwd" name="passwd"></td>
	</tr>
	<tr><td>성명</td>
		<td>
			<input type=text id="name" name="name" value="${member.name }">
		</td>
	</tr>
	<tr><td>주민번호</td>
		<td><input type=text size=6 maxlength=6 id="jumin1" 
		name="jumin1" value="${member.jumin1 }">-
			<input type=text size=7 maxlength=7 id="jumin2" 
		name="jumin2" value="${member.jumin2 }">
		</td>
	</tr>
	<tr><td>E-Mail</td>
		<td><input type=text size=10 id="mailid" 
		name="mailid" value="${member.mailid }">@
		    <input type=text size=10 id="domain" 
		name="domain" value="${member.domain }">
		    <select id="email">
		    	<option value="">직접입력</option>
		    	<option value="naver.com">네이버</option>
		    	<option value="daum.net">다음</option>
		    	<option value="nate.com">네이트</option>
		    	<option value="gmail.com">gmail</option>
		    </select>		    
		 </td>
	</tr>
	<tr><td>전화번호</td>
		<td><input type=text size=4 id="tel1" name="tel1" 
		maxlength=4 value="${member.tel1 }">-
			<input type=text size=4 id="tel2" name="tel2" 
		maxlength=4 value="${member.tel2 }">-
			<input type=text size=4 id="tel3" name="tel3" 
		maxlength=4 value="${member.tel3 }">
		</td>
	</tr>
	<tr><td>핸드폰</td>
		<td><select id="phone1" name="phone1">
				<option value="">번호선택</option>
				<option value="010">010</option>
				<option value="011">011</option>
				<option value="016">016</option>
				<option value="018">018</option>
				<option value="019">019</option>
			</select>-
			<input type=text size=4 id="phone2" name="phone2" 
			maxlength=4 value="${member.phone2 }">-
			<input type=text size=4 id="phone3" name="phone3" 
			maxlength=4 value="${member.phone3 }">
		</td>
	</tr>
	<tr><td>우편번호</td>
		<td><input type=text size=5 id="post" name="post" value="${member.post }">
			<input type=button value="우편번호검색" 
			       onClick="openDaumPostcode()">
		</td>
	</tr>
	<tr><td>주소</td>
		<td>
		<input type=text size=45 id="address" name="address" value="${member.address }">
		</td>
	</tr>
	<tr><td>성별</td>
		<td>
			<input type=radio id="male" name="gender" value="남자">남자
			<input type=radio id="female" name="gender" value="여자">여자
		</td>
	</tr>
	<tr><td>취미</td>
		<td>
			<input type="checkbox" id="h1" name="hobby" value="공부" checked>공부
			<input type="checkbox" id="h2" name="hobby" value="게임">게임
			<input type="checkbox" id="h3" name="hobby" value="등산">등산
			<input type="checkbox" id="h4" name="hobby" value="낚시">낚시
			<input type="checkbox" id="h5" name="hobby" value="쇼핑">쇼핑
		</td>
	</tr>	
	<tr><td>자기소개</td>
		<td>
			<textarea id="intro" name="intro" rows="5" cols="50" 
			placeholder="자기소개를 100자 이내로 입력하세요">${member.intro }
			</textarea>
		</td>
	</tr>
	<tr><td colspan=2 align=center>
			<input type=submit value="회원수정">
			<input type=reset value="취소" onClick="history.go(-1)">
		</td>
	</tr>		
</table>
</form>


</body>
</html>

0. memberform.jsp 파일 내용 복붙 후 수정

1. JSTL 코어 라이브러리를 불러오기

2. 수정폼에서 취미가 체크되도록 하는 처리를 한다 * 아래에 설명

3. 수정 시 요청하는 요청이름값을 /Update.do 로 하자, 폼태그의 action 값을 /Update.do 로 수정, 

4. id 값은 회원을 식별하는 기준이 되므로 id 값은 수정해선 안됨, id 입력양식 지우고 단순 출력하기 * 아래에 설명

5. 비밀번호를 제외한 입력양식 태그에 value 속성값들을 설정해서 뿌려준다

- 이름, 이메일 등 value 속성에서 EL 태그 사용해서 출력, textarea 는 태그 사이에 출력

- 휴대폰 첫번째 자리, 라디오박스 은 다른방식으로 처리한다 * 아래에 설명

6. '취소' 를 누르면 다시 main.jsp 로 돌아가도록 만들기

7. id 값은 Update.do 로 넘어가지 않기 때문에 hidden 으로 id 값을 전달


* 값 뿌리기 : 취미 체크 설정 (updateform.jsp 부분)

<!-- 취미 체크되도록 하기 -->
<script>
$(document).ready(function(){
	<c:forEach var="s" items="${h}">
		$("input:checkbox[name='hobby'][value='${s}']").attr("checked", true);
	</c:forEach>
})
</script>

- 공유 설정되어있는 취미를 저장한 배열 h 를 가져옴

- items 에 "${h}" 로 공유설정된 h 네임값의 값인 배열을 가져와서 forEach 태그로 각각의 원소(취미)를 s로 가져옴

- input type 이 checkbox 이고 name 값이 hobby이고, 그 취미 s를 value 로 가지고 있는 체크박스를 반복문에서 하나씩 선택해서 체크함

- 그럼 수정폼에서 체크표시가 되어 나타난다


* 값 뿌리기 : id 단순 출력하기 (updateform.jsp 부분)

- 공유설정되어있는 MemberDTO 객체 member 를 가져와야한다

- getAttribute() 를 할때 MemberDTO 로 다운캐스팅해야함

- EL 을 사용하지 않고 출력하는 방법, EL 로 출력하는 방법 둘 다 해보자 

 

- EL 을 사용하지 않고 출력하는 방법

		<%
			MemberDTO mem = (MemberDTO)request.getAttribute("member");
		%>
		<%=mem.getId() %>

- EL 을 사용해서 출력하는 방법

		${member.id}

- member 는 request 객체로 공유된 네임

- ${member} 는 member 라는 네임으로 공유된 객체 를 의미하고, ${member.id} 는 그 객체의 id 값을 출력하라는 의미

- .필드명을 쓴다고해서 id 필드에 직접 접근하는 것이 아님, 표기만 그럴뿐 실제로는 member.getId() 처럼 getter 메소드로 가져오는 것이다. 

 

- 다시 수정폼에 들어가보면 두 경우 다 id 를 잘 출력하고 있다

 

공유 시 주의

- setAttribute(A,B) 에서 A 는 네임이고 B는 값인데, B는 Object 형이다, 즉 모든 자료형이 다 올 수 있음

- getAttribute(A) 는 리턴 자료형이 Object 이다, 원래 저장되었던 자료형으로 다운캐스팅 해야한다


request 공유 값 크게 3가지 형태가 들어감, 값의 형태에 따라 공유값을 구해오는 방법

1. 기본자료형 변수 가 공유되었을때

- 그냥 공유네임 으로 구해오면 된다

ex) setAttribute("name","Lay") 였을때 "Lay" 를 가져오기

${name}

 

2. DTO 객체 가 공유되었을때

- 구해올때는 공유네임.필드명 으로 구해오면 된다

ex) 객체 member 에 대해 setAttribute("member",member) 를 했을때, 그 객체에서 id 값을 가져오고 싶다면

		${member.id}

ex) 객체 member 에 대해 setAttribute("member",member) 를 했을때, 그 객체에서 name 값을 가져오고 싶다면

			<input type=text id="name" name="name" value="${member.name }">

 

3. 배열, 리스트 가 공유되었을때

- 구해올 때는 공유네임이 forEach 태그의 items 값으로 들어가야한다

ex) String 배열  h 에 대해 setAttribute("h",h) 를 했을때, 그 배열의 원소들을 차례로 가져오고 싶다면

<!-- 취미 체크되도록 하기 -->
<script>
$(document).ready(function(){
	<c:forEach var="s" items="${h}">
		$("input:checkbox[name='hobby'][value='${s}']").attr("checked", true);
	</c:forEach>
})
</script>

+ forEach 태그는 더이상 원소값이 없을때까지 돌아간다


* 값 뿌리기 : 라디오버튼 체크 설정 (updateform.jsp 부분)

<!-- 라디오 버튼 체크 -->
<script>
$(document).ready(function(){
	$("input:radio[name='gender'][value='${member.gender}']").attr("checked", true);
	
})
</script>

- if 조건문을 사용해서 처리할 수도 있지만 jQuery 로 처리해보자

- gender 라는 컬럼에 "남자" 또는 "여자" 가 들어가 있다

- input type 이 radio 이고, name 값이 gender 이고, value 로 member 객체의 gender 필드값과 같은 값을 가지고 있는 라디오버튼 태그를 선택

 

- 그럼 수정폼에서 라디오버튼에 표시가 잘 나타난다


* 값 뿌리기 : 휴대폰 번호 첫번째 자리 (updateform.jsp 부분)

<!-- 휴대폰 번호 첫번째 자리 선ㄱ-->
<script>
$(document).ready(function(){
	
	$("#phone1 option[value=${member.phone1 }]").attr("selected", true);
});
</script>

- jQuery 로 처리하지 않으면 코드가 복잡해진다

- 가장 먼저 select 의 id 값으로 select 태그를 가져와야한다. #phone1 으로 id 값을 구하기

- 한칸 띄우면 자식 태그를 의미하므로 #phone option 으로 설정

- 그 option 태그들 중 value 가 ${"member.phone1"} 인 태그를 selected 한다

 

- updateform.jsp 작성 완료했다

- 이제 로그인 후 '회원정보 수정' 을 눌러서 수정폼으로 가자

 

- "/Update.do" 가 요청이름값이다

 

회원관리 프로그램 : 회원 정보수정 Service 클래스

- 실제 수정을 수행하는 Update.java 클래스를 service 패키지 안에 생성하자

- Update.java 파일에서 비번이 틀리면 다시 수정폼으로 돌아가도록 하고 비번이 맞으면 수정하자

- Update.java

package service;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.MemberDAO;
import model.MemberDTO;

public class Update implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("Update");
		
		response.setContentType("text/html; charset=utf-8");
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		
		MemberDTO member = new MemberDTO();
		member.setId(request.getParameter("id"));
		member.setPasswd(request.getParameter("passwd"));
		member.setName(request.getParameter("name"));
		member.setJumin1(request.getParameter("jumin1"));
		member.setJumin2(request.getParameter("jumin2"));
		member.setMailid(request.getParameter("mailid"));
		member.setDomain(request.getParameter("domain"));
		member.setTel1(request.getParameter("tel1"));
		member.setTel2(request.getParameter("tel2"));
		member.setTel3(request.getParameter("tel3"));
		member.setPhone1(request.getParameter("phone1"));
		member.setPhone2(request.getParameter("phone2"));
		member.setPhone3(request.getParameter("phone3"));
		member.setPost(request.getParameter("post"));
		member.setAddress(request.getParameter("address"));
		member.setGender(request.getParameter("gender"));
		
		String[] hobby = request.getParameterValues("hobby");
		String h = "";
		for(String h1 : hobby) {
			h += h1 + "-";	// h = "공부-게임-"
		}
		member.setHobby(h);
		member.setIntro(request.getParameter("intro"));
		
		MemberDAO dao = MemberDAO.getInstance();
		MemberDTO old = dao.getMember(member.getId()); // 1명의 상세정보 구하기
		
		// 비번 비교
		if(old.getPasswd().equals(member.getPasswd())) { // 비번 일치시
			int result = dao.update(member);	// update SQL문 실행
			if(result == 1) System.out.println("회원 수정 성공");
		} else { // 비번 불일치시
			out.println("<script>");
			out.println("alert('비번이 일치하지 않습니다');");
			out.println("history.go(-1);");
			out.println("</script>");
			out.close();
			
			return null;
		}

		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("./member/main.jsp");
		return forward;
	}

}

- 수정 폼에서 넘어온 값들을 처리하기 위한 Service 클래스

 

<회원 정보수정 Service 클래스 형식, 전체적인 흐름>

- 부모 인터페이스인 Action 인터페이스를 상속받기, 이클립스 자동 기능으로 execute() 를 메소드 오버라이딩 하기

- request 객체는 Controller 클래스의 getPost() -> getProcess() -> Service 클래스의 execute() 로 넘어온다

- ActionForward 객체 forward 생성 후 필드값을 설정해서 execute() 를 호출한 Controller 클래스에 돌려줌

- 수정 전에 id 로 DB에서 사용자의 비밀번호를 가져온 후 일치되어야 수정한다

 

<회원 정보수정 Service 클래스 작성 순서>

1. 한글값 인코딩, 한글 문서 인코딩

- 수정폼에서 한글값이 넘어오므로 한글값 인코딩 request.setCharacterEncoding()

- 한글 문서(값) 깨지지 않도록 인코딩 response.setContentType()

2. memberInsert.java 의 일부 내용을 가져와서 붙여넣기

3. DB에 저장된 비밀번호와 사용자가 입력한 비밀번호(member 객체 안에 저장되어있음)를 비교한다

- 수정폼에서 사용했던 1명의 상세정보를 구하는 getMember() 메소드를 사용해서 1명의 상세정보를 구해옴

- 그 상세정보에서 비밀번호만 가져오면 된다 old.passwd

4. 사용자가 입력한 비밀번호가 DB의 비밀번호와 일치시 수정을 하는 DAO 메소드 Update() 를 호출

* DAO 클래스는 아래에 작성

5. 비밀번호가 불일치시 out 객체를 사용해서 자바스크립트로 메세지 출력

- 먼저 out 객체를 생성, 출력하기, out 객체 닫기

- return null 로 해야 아래를 실행하지 않기때문에 history.go(-1) 에 의해 다시 수정폼으로 돌아갈때 에러나지 않음

+ out 객체 안에 자바스크립트 코드 작성 가능, 바깥에 " " 면 안은 ' '

6. forward 객체에 포워딩 방식, 포워딩 페이지 설정

- 아직 session 이 끊어지지 않았으므로 redirect, dispatcher 방식 포워딩 둘 다 사용 가능

- 수정이 끝나면 main.jsp 로 돌아가도록 한다

 

- 이후 Controller 클래스에서 수정폼에서 들어온 "/Update.do" 요청을 Update.java Service 클래스로 넘겨주는 처리를 하자

- 즉 Controller 클래스에서 연결시켜주자

 

Controller 클래스에서 회원정보 수정 Service 클래스로 가기

- MemberController.java 전체 코드는 나중에

- MemberController.java 부분

- /Update.do 로 요청이 올때의 경우를 처리

		// 회원정보 수정
		} else if(command.equals("/Update.do")) {
			try {
				action = new Update();
				forward = action.execute(request, response);
			} catch (Exception e){
				e.printStackTrace();
			}
		}

- 이러면 "/Update.do" 요청을 Controller 클래스에서 Update.java Service 클래스로 보내줌

 

DAO 클래스 회원정보 수정 기능 메소드 작성

- MemberDAO.java 전체 코드는 모두 구현 후 올리기

- MemberDAO.java 추가된 Update() 부분 코드만

	// 회원정보 수정
	public int update(MemberDTO member) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;

		try {
			con = getConnection();
			
			String sql = "update member2 set name=?,jumin1=?,jumin2=?,mailid=?,";
			sql += "domain=?,tel1=?,tel2=?,tel3=?,phone1=?,phone2=?,phone3=?,";
			sql += "post=?,address=?,gender=?,hobby=?,intro=? where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, member.getName());
			pstmt.setString(2, member.getJumin1());
			pstmt.setString(3, member.getJumin2());
			pstmt.setString(4, member.getMailid());
			pstmt.setString(5, member.getDomain());
			pstmt.setString(6, member.getTel1());
			pstmt.setString(7, member.getTel2());
			pstmt.setString(8, member.getTel3());
			pstmt.setString(9, member.getPhone1());
			pstmt.setString(10, member.getPhone2());
			pstmt.setString(11, member.getPhone3());
			pstmt.setString(12, member.getPost());
			pstmt.setString(13, member.getAddress());
			pstmt.setString(14, member.getGender());
			pstmt.setString(15, member.getHobby());
			pstmt.setString(16, member.getIntro());
			pstmt.setString(17, member.getId());

			result = pstmt.executeUpdate();	// SQL문 실행
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return result;
	}

 

- 다 완성되었다

- 로그인폼에서 로그인 한 후 main.jsp 에서 '회원정보 수정' 으로 수정폼으로 가서 수정해보자

- 맞는 비밀번호 입력시

- 회원정보가 잘 수정된 후 main.jsp 로 왔다

- 다시 수정폼에 들어가보면 정보가 수정되었음을 확인 가능

- 콘솔창


회원관리 프로그램 : 회원 탈퇴

- 먼저 main.jsp 에서 '회원탈퇴' 에 링크를 걸어주자

- main.jsp (수정 후, 최종, 수정 전 4 다음 코드)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<!-- 세션이 있는 경우 -->
<c:if test="${!empty sessionScope.id }">
<%-- <c:if test="${sessionScope.id != null }"> --%>
	${sessionScope.id} 님 환영 합니다. <br><br>
	
	<a href="./UpdateMember.do">회원정보 수정</a> <br>
	<a href="./Logout.do">로그아웃</a> <br>
	<a href="./DeleteMember.do">회원탈퇴</a> <br>
</c:if>
<!-- 세션이 없는 경우 -->
<c:if test="${empty sessionScope.id }">
<%-- <c:if test="${sessionScope.id == null}"> --%>
	<a href="<%=request.getContextPath() %>/MemberForm.do">회원가입 </a> <br>
	<a href="<%=request.getContextPath() %>/LoginForm.do">로그인 </a> <br>
</c:if>

- 회원탈퇴 클릭시 콘솔창

- "/DeleteMember.do" 로 요청해서 회원 탈퇴폼으로 갈 것

- 회원탈퇴 폼으로 갔을때는 DB연동이 필요하지 않으므로 Service 클래스로 갈 필요 없다

 

- Controller 클래스에서 "/DeleteMember.do" 요청을 처리하는 코드를 작성하자

 

Controller 클래스에서 회원 탈퇴폼 Service 클래스로 가기-

- MemberController.java 전체 코드는 나중에

- MemberController.java 부분

- /DeleteMember.do 로 요청이 올때의 경우를 처리

		// 회원탈퇴 폼
		} else if(command.equals("/DeleteMember.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("./member/deleteform.jsp");
		}

- DB연동하지 않으므로 Service 클래스로 가지 않아도 된다

- ActionForward 객체 foward 를 생성하고 forward 객체 값 설정

- 그리고 deleteform.jsp 로 포워딩

 

- 이제 member 폴더 하위에 삭제폼 deleteform.jsp 를 생성 및 작성

- deleteform.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원 삭제 폼</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>

<!-- 외부 자바스크립트 파일 불러오기 -->
<script src="<%=request.getContextPath() %>/member/member.js"></script>

</head>
<body>

<form method="post" action="<%=request.getContextPath() %>/Delete.do">
<input type="hidden" name="id" value="${sessionScope.id }">
<table border=1 width=500 align=center>
	<caption>회원 삭제</caption>
	<tr><td>비밀번호</td>
		<td><input type=password id="passwd" name="passwd"></td>
	</tr>
	<tr><td colspan=2 align=center>
			<input type=submit value="회원삭제">
			<input type=reset value="취소" onClick="history.go(-1)">
		</td>
	</tr>		
</table>
</form>

</body>
</html>

- updateform.jsp 내용을 복붙 후 많은 내용 삭제 및 수정

- 비번만 맞으면 탈퇴시키기 위해 삭제 폼에서 비번을 입력받아야함

- 아이디와 비밀번호만 "/Delete.do" 로 넘기면 된다

- 아이디를 넘길때는 sessionScope.id 로 공유된 세션에서 id 를 가져와서 hidden 으로 /Delete.do 로 넘김

- form 태그 action 을 "Delete.do" 로 바꿈, 즉 삭제를 할때 "/Delete.do" 로 요청할 것

- 이제 삭제폼에서 비번 입력 후 '회원삭제' 클릭 시 "/Delete.do" 로 요청한다

- 삭제폼 까지 완료했다, main.jsp 에서 '회원 탈퇴' 클릭시 콘솔창

 

회원관리 프로그램 : 회원 탈퇴 Service 클래스

- 실제 삭제 수행하는 Delete.java 클래스를 service 패키지 안에 생성하자

- Delete.java 파일에서 비번이 틀리면 다시 수정폼으로 돌아가도록 하고 비번이 맞으면 수정하자

- Delete.java

package service;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import dao.MemberDAO;
import model.MemberDTO;

public class Delete implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("Delete");
		
		response.setContentType("text/html; charset=utf-8");
		request.setCharacterEncoding("utf-8");
	
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();
		
		String id = request.getParameter("id");
		String passwd = request.getParameter("passwd");
		
		MemberDAO dao = MemberDAO.getInstance();
		MemberDTO old = dao.getMember(id);	// 1명의 상세 정보 구하기
		
		// 비번 비교
		if(old.getPasswd().equals(passwd)) {	// 비번 일치시
			int result = dao.delete(id);
			if(result == 1) System.out.println("회원탈퇴 성공");
			
			session.invalidate(); // 세션 삭제
		} else {	// 비번 불일치시
			out.println("<script>");
			out.println("alert('비번이 일치하지 않습니다.');");
			out.println("history.go(-1);");
			out.println("</script>");
			out.close();
			
			return null;
		}
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false);
		forward.setPath("./member/loginform.jsp");
		return forward;
	}

}

1. 현재 문서의 한글값 인코딩, 한글값이 넘어올 수 있으므로 한글값 인코딩

2. out 객체(출력스트림 객체) 생성, 회원 삭제를 하면 세션을 끊어야하므로 session 객체 생성

3. hidden 으로 넘어온 id 값과, 비번 입력양식에 넘어온 비번값을 받아서 변수에 저장

4. DB에서 비번을 가져와서 사용자가 삭제폼에 입력한 비번과 일치하는지 확인

- 수정폼에서 사용했던 1명의 상세정보를 구하는 메소드인 getMember() 로 상세 정보를 구해서 거기서 비번 가져오기

5. 비번이 일치하면 DAO 의 delete() 메소드를 호출해서 회원 삭제, 세션을 invalidate() 메소드로 강제 삭제

6. 비번 불일치시 out 객체를 사용해서 자바스크립트로 메세지 박스를 뿌리고, 이전 페이지인 main.jsp 로 이동

- return null 로 빠져나가서 아래의 forward 관련 코드 실행 못하도록 하기

7. ActionForward 객체 forward 로 포워딩 방식, 포워딩 페이지 설정

- return null 때문에 이 코드는 비번이 틀리면 수행하지 않음, 비번이 맞고, 회원탈퇴에 성공한 경우에만 실행됨

- 회원 탈퇴 성공 후 포워딩 될 페이지는 loginform.jsp

- 이후 이 Service 클래스의 execute() 를 부른 Controller 클래스로 다시 돌아감

 

- "/Delete.do" 로 요청이 왔을때 이 Service 페이지로 오도록  Controller 클래스에 연결해주는 코드를 작성하자

* DAO delete() 메소드 작성은 Controller 클래스 작성 아래에

 

Controller 클래스에서 회원 탈퇴 Service 클래스로 가기

- MemberController.java 전체 코드는 나중에

- MemberController.java 부분

- /Delete.do 로 요청이 올때의 경우를 처리

		// 회원 탈퇴
		} else if(command.equals("/Delete.do")) {
			try {
				action = new Delete();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

 

DAO 클래스 회원 탈퇴 (삭제) 기능 메소드 작성

	// 회원 탈퇴
	public int delete(String id) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;

		try {
			con = getConnection();
			
			String sql = "delete from member2 where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			result = pstmt.executeUpdate(); // SQL문 실행
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return result;
	}

- id 를 받아서 그 한명의 정보를 삭제 하면 된다

- 이걸 실행 후 DAO -> Service 클래스 -> Controller 클래스 로 돌아간다

 

회원관리 프로그램 : 초기 페이지

- index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
모델2 회원관리

<script>
	location.href = "./LoginForm.do"
</script>
</body>
</html>

- 실행 시 바로 로그인 폼으로 넘어가도록 한다

- "./LoginForm.do" 로 요청 시 Controller 클래스에서 로그인 폼으로 넘어감

 

- 삭제 기능이 잘 구현되었는지 확인해보자

- 현재 프로젝트 model2member 를 선택하고 오른쪽 마우스 : Run As -> Run on Server 선택시 실행된다

1. 자동으로 index.html 로 가서 로그인 페이지를 띄움

2. 로그인 후 main.jsp 로 가기

3. '회원탈퇴' 클릭 후 삭제 폼에 맞는 비번 입력

 

4. 삭제시 로그인 폼으로 다시 넘어간다

- DB 에서 삭제 확인

- 삭제 되었다, 삭제 기능 잘 구현되었음

 

문제점

- 가입 / 로그인 / 수정 / 삭제 성공 시 '수정 성공' , '삭제 성공' 와 같은 메세지가 뜨지 않는다

- out 객체를 MemberInsert.java 에 생성해서 out.println("alert();") 로 alert 창을 띄울 수 없다

 

문제 해결

- MemberInsert.java 에서 회원가입 성공 후 '회원가입' 성공 alert 메세지를 띄워보자

- out 객체를 MemberInsert.java 에 생성해서 out.println("alert()") 로 alert 창을 띄울 수 없으므로

- 따로 출력을 위한 jsp 파일을 만들어서 location 객체를 사용해서 이동해야한다

- 그리고 아래 이 코드 주석 처리한다, 성공 후 Controller 에서 포워딩 되지 않고 위처럼 location.href 로 result.jsp 로 가므로 아래에 return null; 을 해준다

- 이제 member 폴더 하위에 result.jsp 를 만들자

 

- result.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>

<script>
alert("회원가입 성공");
location.href = "<%=request.getContextPath()%>/LoginForm.do"
</script>

 

- 이제 회원가입을 시키면 성공 메세지가 뜨고 로그인 폼으로 넘어간다

 

- 이제 '회원가입 성공' 메시지가 제대로  뜬다

 


Controller 클래스 완성본

- MemberController.java (최종, 전체 코드)

package controller;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import service.Action;
import service.ActionForward;
import service.Delete;
import service.IdCheck;
import service.Login;
import service.MemberInsert;
import service.Update;
import service.UpdateMember;

/**
 * Servlet implementation class MemberController
 */
@WebServlet("*.do")	// do 확장자
public class MemberController extends HttpServlet {
	private static final long serialVersionUID = 1L;

	// doGet(), doPost() 메소드에서 공통적인 작업을 처리하는 메소드
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI : " + requestURI); // requestURI : /model2member/Login.do
		System.out.println("contextPath : " + contextPath); // contextPath : /model2member
		System.out.println("command : " + command); // command : /Login.do
		
		Action action = null;
		ActionForward forward = null;
		
		// 회원가입
		if(command.equals("/MemberInsert.do")) {
			try {
				action = new MemberInsert();
				forward = action.execute(request, response);
				
			} catch (Exception e){
				e.printStackTrace();
			}
		// ID중복 검사 (ajax)
		} else if (command.equals("/Idcheck.do")) {
			try {
				action = new IdCheck();
				forward = action.execute(request, response);
			}
			catch (Exception e){
				e.printStackTrace();
			}
		// 회원가입 폼
		} else if (command.equals("/MemberForm.do")){
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("./member/memberform.jsp");

		// 로그인 (회원 인증)
		} else if(command.equals("/Login.do")) {
			try {
				action = new Login();
				forward = action.execute(request, response);
			}
			catch (Exception e){
				e.printStackTrace();
			}
		// 로그인 폼	
		} else if(command.equals("/LoginForm.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("./member/loginform.jsp");
			
		// 로그아웃
		} else if(command.equals("/Logout.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("./member/logout.jsp");
			
		// 회원정보 수정폼
		} else if(command.equals("/UpdateMember.do")) {
			try {
				action = new UpdateMember();
				forward = action.execute(request, response);
			} catch(Exception e) {
				e.printStackTrace();
			}
		// 회원정보 수정
		} else if(command.equals("/Update.do")) {
			try {
				action = new Update();
				forward = action.execute(request, response);
			} catch (Exception e){
				e.printStackTrace();
			}
		// 회원탈퇴 폼
		} else if(command.equals("/DeleteMember.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("./member/deleteform.jsp");
		// 회원 탈퇴
		} else if(command.equals("/Delete.do")) {
			try {
				action = new Delete();
				forward = action.execute(request, response);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		// 포워딩 처리
		if(forward != null) {
			if(forward.isRedirect()) {	// redirect 방식으로 포워딩
				response.sendRedirect(forward.getPath());
			} else {				// dispatcher 방식으로 포워딩
				RequestDispatcher dispatcher = 
						request.getRequestDispatcher(forward.getPath());
				dispatcher.forward(request, response);
			}
		}
		
		
	} // doProcess() end

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("get");
		doProcess(request, response);	// doProcess() 호출
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("post");
		doProcess(request, response);	// doProcess() 호출
	}

}

DAO 클래스 완성본

- MemberDAO.java (최종, 전체 코드)

// DAO (Date Access Object)
package dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

import model.MemberDTO;

public class MemberDAO {

	// 싱글톤 : 객체 생성을 한번만 수행 하는 것.
	private static MemberDAO instance = new MemberDAO();
	
	public static MemberDAO getInstance() {
		return instance;
	}
	
	// 커넥션풀에서 커넥션을 구해오는 메소드
	private Connection getConnection() throws Exception {
 		Context init = new InitialContext();
  		DataSource ds = (DataSource) init.lookup("java:comp/env/jdbc/orcl");
  		return ds.getConnection();
	}
	
	// 회원가입
	public int insert(MemberDTO member) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		try {
			con = getConnection();
			
			String sql = "insert into member2 ";
			sql += "values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,sysdate)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, member.getId());
			pstmt.setString(2, member.getPasswd());
			pstmt.setString(3, member.getName());
			pstmt.setString(4, member.getJumin1());
			pstmt.setString(5, member.getJumin2());
			pstmt.setString(6, member.getMailid());
			pstmt.setString(7, member.getDomain());
			pstmt.setString(8, member.getTel1());
			pstmt.setString(9, member.getTel2());
			pstmt.setString(10, member.getTel3());
			pstmt.setString(11, member.getPhone1());
			pstmt.setString(12, member.getPhone2());
			pstmt.setString(13, member.getPhone3());
			pstmt.setString(14, member.getPost());
			pstmt.setString(15, member.getAddress());
			pstmt.setString(16, member.getGender());
			pstmt.setString(17, member.getHobby());
			pstmt.setString(18, member.getIntro());

			result = pstmt.executeUpdate(); // SQL문 실행
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return result;
	}
	
	// ID중복 검사 (ajax)
	public int idcheck(String id) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from member2 where id = ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {
				result = 1;	// 중복 ID
			} else {
				result = -1; // 사용 가능한 ID
			}
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return result;
	}
	
	// 로그인 (회원인증)
	public int memberAuth(String id, String passwd) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from member2 where id = ? and passwd=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			pstmt.setString(2, passwd);
			rs = pstmt.executeQuery();	//SQL문 실행
			
			if(rs.next()) {	// 회원인증 성공
				result = 1;
			} else {		// 회원인증 실패
				result = -1;
			}
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
	return result;
	}
	
	// 회원 1명 상세 정보 구하기 : 수정폼, 수정, 삭제
	public MemberDTO getMember(String id) {
		MemberDTO member = new MemberDTO();
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from member2 where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			rs = pstmt.executeQuery(); // SQL문 실행
			
			if(rs.next()) {
				member.setId(rs.getString("id"));
				member.setPasswd(rs.getString("passwd"));
				member.setName(rs.getString("name"));
				member.setJumin1(rs.getString("jumin1"));
				member.setJumin2(rs.getString("jumin2"));
				member.setMailid(rs.getString("mailid"));
				member.setDomain(rs.getString("domain"));
				member.setTel1(rs.getString("tel1"));
				member.setTel2(rs.getString("tel2"));
				member.setTel3(rs.getString("tel3"));
				member.setPhone1(rs.getString("phone1"));
				member.setPhone2(rs.getString("phone2"));
				member.setPhone3(rs.getString("phone3"));
				member.setPost(rs.getString("post"));
				member.setAddress(rs.getString("address"));
				member.setGender(rs.getString("gender"));
				member.setHobby(rs.getString("hobby"));
				member.setIntro(rs.getString("intro"));
				member.setRegister(rs.getTimestamp("register"));
			}
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return member;
	}
	// 회원정보 수정
	public int update(MemberDTO member) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;

		try {
			con = getConnection();
			
			String sql = "update member2 set name=?,jumin1=?,jumin2=?,mailid=?,";
			sql += "domain=?,tel1=?,tel2=?,tel3=?,phone1=?,phone2=?,phone3=?,";
			sql += "post=?,address=?,gender=?,hobby=?,intro=? where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, member.getName());
			pstmt.setString(2, member.getJumin1());
			pstmt.setString(3, member.getJumin2());
			pstmt.setString(4, member.getMailid());
			pstmt.setString(5, member.getDomain());
			pstmt.setString(6, member.getTel1());
			pstmt.setString(7, member.getTel2());
			pstmt.setString(8, member.getTel3());
			pstmt.setString(9, member.getPhone1());
			pstmt.setString(10, member.getPhone2());
			pstmt.setString(11, member.getPhone3());
			pstmt.setString(12, member.getPost());
			pstmt.setString(13, member.getAddress());
			pstmt.setString(14, member.getGender());
			pstmt.setString(15, member.getHobby());
			pstmt.setString(16, member.getIntro());
			pstmt.setString(17, member.getId());

			result = pstmt.executeUpdate();	// SQL문 실행
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return result;
	}
	// 회원 탈퇴
	public int delete(String id) {
		int result = 0;
		
		Connection con = null;
		PreparedStatement pstmt = null;

		try {
			con = getConnection();
			
			String sql = "delete from member2 where id=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			result = pstmt.executeUpdate(); // SQL문 실행
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return result;
	}
}

 

복습

전체 흐름

 

Service 클래스

- 공통적인 내용들을 Action 이라는 인터페이스에 작성, 추상메소드 execute() 가 있다

- 각각의 클래스들은 인터페이스 Action 을 상속함, 반드시 추상메소드 execute() 를 오버라이딩 해야함

- 클라이언트의 요청을 실제로 처리

- DAO 와 함께 실제 DB 쪽에 데이터를 처리해주는 Model 이라고 불린다

 

- DB에서 검색한 결과를 DAO 에서 Service 클래스로 리턴함

- Service 클래스에서 돌려받은 객체를 공유설정함

- View 페이지에서 공유설정된 값을 EL + JSTL 으로 출력한다

 

Controller 클래스

- HttpServlet 클래스를 상속받는다, doPost(), doGet() 메소드를 오버라이딩 해야함

- WebServlet 어노테이션 이름값이 하나면 한곳에서 온 요청만 받을 수 있으므로 Controller 클래스를 여러개 만들어야한다

- WebServlet 어노테이션 이름값을 패턴을 지정해서 패턴에 맞으면 다 여기로 오도록 함

- 그 후 폼의 action 값들을 패턴에 맞게 지정한다, 그럼 Contorller 클래스로 찾아갈 수 있음

- 다른 웹사이트의 URL 주소를 보면 확장자가 보인다 ex) .do, .naver


회원관리 프로그램 : Controller 클래스 (이어서)

- MemberController.java (수정 전 1)

package controller;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class MemberController
 */
@WebServlet("*.do")	// do 확장자
public class MemberController extends HttpServlet {
	private static final long serialVersionUID = 1L;

	// doGet(), doPost() 메소드에서 공통적인 작업을 처리하는 메소드
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI : " + requestURI); // requestURI : /model2member/Login.do
		System.out.println("contextPath : " + contextPath); // contextPath : /model2member
		System.out.println("command : " + command); // command : /Login.do
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("get");
		doProcess(request, response);	// doProcess() 호출
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("post");
		doProcess(request, response);	// doProcess() 호출
	}

}

- Controller 클래스에서 매개변수로 받은 request, response 객체를 Service 클래스로 넘어갈때도 전달한다

- doPost(), doGet() 에서 받은 request, response -> doProcess() 호출시 넘겨줌 -> Service 클래스로 갈때도 넘겨줌

- doProcess() 에도 넘겨줘야 거기서 request.getRequestURI() 와 request getContextPath() 메소드를 사용가능

 

doProcess() 안의 내용 (MemberController.java 부분)

	// doGet(), doPost() 메소드에서 공통적인 작업을 처리하는 메소드
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI : " + requestURI); // requestURI : /model2member/Login.do
		System.out.println("contextPath : " + contextPath); // contextPath : /model2member
		System.out.println("command : " + command); // command : /Login.do
	}

- command 에 저장되는 값에 구체적으로 어떤 요청이름기값으로 Controller 클래스로 왔는지 확인 가능

- 즉, command 에 저장된 값으로 어디서 요청이 왔는지 확인 가능하다

- command 값을 보고 어떤 서비스 클래스로 보낼지 결정한다

+ substring(13) 은 13 인덱스부터 끝까지를 자른다

 

- 결과 확인 1

- loginform.jsp form 태그

	<form method="post" action="<%=request.getContextPath() %>/Login.do">

 

- loginform.jsp 파일을 실행해서 '로그인' 을 눌러보자

+ 해당 Service 클래스 이름도 요청 이름값과 똑같이 작성할 것, Login.java

 

- 결과 확인 2

- memberform.jsp form 태그

<form method="post" action="<%=request.getContextPath() %>/MemberInsert.do">

- memberform.jsp 파일을 실행해서 '회원가입' 을 눌러보자

- 요청 이름값이 command 변수에 저장된다

+ 해당 Service 클래스 이름도 요청 이름값과 똑같이 작성할 것, MemberInsert.java

 

실행 순서

1. loginform.jsp 또는 memberform.jsp 에서 action 태그로 지정된 곳을 따라 Controller 클래스 도착

2. post 방식으로 요청했으므로 오버라이딩 된 doPost() 실행됨, doProcess() 가 다음으로 실행됨

3. 3개의 변수값이 구해짐

- 최종적으로는 요청 이름값이 저장된 command 변수의 값이 필요하다

- 어떤 서비스 클래스로 보낼지 command 값을 보고 결정

 

회원관리 프로그램 : Service 클래스 1

- Service 클래스를 통일성 있게 설계하기 위해 부모가 될 인터페이스 Action 먼저 만들기

- 인터페이스 Action 을 만들고 안에는 추상메소드 execute() 만들기

- Service 에서 DAO 로 넘어가야한다

 

+ ActionForward 클래스

- 이 클래스에서 어떤 방식으로 포워딩 할지, 어디로 포워딩 할지 결정해줌

- redirect 변수는 포워딩 방식을 설정, path 는 포워딩할 페이지명 설정

- request 객체로 공유가 되면 포워딩 하는 방법 2가지가 있다

- request 객체로 공유가 되었을때는 반드시 dispatcher 방식으로 포워딩되어야 뷰페이지에 출력 가능

 

부모 인터페이스 만들기

- 이제 service 패키지를 만들고 인터페이스 Action 을 만들자

- 현재까지 Action 인터페이스

- Action.java

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface Action {
	
	// 추상 메소드
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

- 리턴자료형은 아직 만들지 않은 ACtionForward 로 한다

- Controller 클래스의 doProcess() 의 매개변수 request, response 를 그대로 execute() 의 매개변수에 복사

- 추상메소드 execute() 는 형식만 있다

 

ActionForward 클래스 만들기

- ActionForward.java

package service;

public class ActionForward {
	
	private boolean redirect;	// 포워딩 방식 설정
	private String path;	// 포워딩 페이지명 설정
	
	public boolean isRedirect() {
		return redirect;
	}
	public void setRedirect(boolean redirect) {
		this.redirect = redirect;
	}
	public String getPath() {
		return path;
	}
	public void setPath(String path) {
		this.path = path;
	}
	
}

- Service 패키지 안에 ActionForward 클래스 만들기

1. 포워딩 방식을 결정할 변수 redirect 정의,  dispatcher, redirect 방식 중 어느 것으로 할지 true, false 로 결정

2. 포워딩할 페이지명이 저장될 변수 path 를 정의

- 두 변수는 private 으로 설정

3. getter, setter 메소드 만들기

+ 이클립스가 redirect 변수의 getter 메소드를 자동으로 만들때 getRedirect() 가 아니라 isRedirect() 로 만들었음을 유심히 보기

- Service 클래스에서 값을 처리하고 View 페이지로 가기 전에 이 ActionForward 로 포워딩 방식, 포워딩 페이지명을 결정

 

- 다시 Controller 클래스로 돌아와서 Service 클래스로 넘겨주는 작업을 하자

- MemberController.java  (수정 전 2)

package controller;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import service.Action;
import service.ActionForward;
import service.MemberInsert;

/**
 * Servlet implementation class MemberController
 */
@WebServlet("*.do")	// do 확장자
public class MemberController extends HttpServlet {
	private static final long serialVersionUID = 1L;

	// doGet(), doPost() 메소드에서 공통적인 작업을 처리하는 메소드
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI : " + requestURI); // requestURI : /model2member/Login.do
		System.out.println("contextPath : " + contextPath); // contextPath : /model2member
		System.out.println("command : " + command); // command : /Login.do
		
		Action action = null;
		ActionForward forward = null;
		
		// 회원가입
		if(command.equals("/MemberInsert.do")) {
			try {
				action = new MemberInsert();
				forward = action.execute(request, response);
				
			} catch (Exception e){
				e.printStackTrace();
			}
		}
		
		// 포워딩 처리
		if(forward != null) {
			if(forward.isRedirect()) {	// redirect 방식으로 포워딩
				response.sendRedirect(forward.getPath());
			} else {				// dispatcher 방식으로 포워딩
				RequestDispatcher dispatcher = 
						request.getRequestDispatcher(forward.getPath());
				dispatcher.forward(request, response);
			}
		}
		
	} // doProcess() end

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("get");
		doProcess(request, response);	// doProcess() 호출
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("post");
		doProcess(request, response);	// doProcess() 호출
	}

}

- 모두 doProcess() 안에서 작성한다

- service 패키지 안의 Action 인터페이스 객체 action 를 만들고 null 로 초기화

- service 패키지 안의 ActionForward 클래스 객체 forward 를 만들고 null 로 초기화

- 다음은 요청에 따라 적절한 Service 클래스로 넘기기

- 마지막 부분에서 포워딩 처리를 한다, DAO -> Service -> Controller 로 돌아올때 포워딩 처리함

 

Service 클래스로 전달 (MemberController.java 부분)

		// 회원가입
		if(command.equals("/MemberInsert.do")) {
			try {
				action = new MemberInsert();
				forward = action.execute(request, response);
				
			} catch (Exception e){
				e.printStackTrace();
			}
		}

- Controller 클래스는 어떤 Service 클래스로 넘겨줄건지 결정

- command 값, 즉 요청 이름값이 "/MemberInsert" 면 회원가입을 처리하는 MemberInsert 클래스로 객체 action 생성

- 왼쪽은 부모 인터페이스, 오른쪽은 상속받는 구현 클래스가 왔다, 업캐스팅을 하고 있음

- action 객체를 통해 MemberInsert() 클래스의 오버라이딩된 메소드인 execute() 를 실행하고 ActionForward 객체 forward 로 리턴받음

- 해당 클래스 MemberInsert 의 객체를 만들고 execute() 를 실행하므로써 그 Service 클래스 MemberInsert 로 가는 것임

+ 메소드 오버라이딩이 되면 오버라이딩 된 메소드만 실행된다

- execute() 를 호출하며 매개변수로는 doGet(), doPost() 의 매개변수인 request, response 를 전달함

+ try-catch 예외처리 해야한다

* MemberInsert 클래스는 아래에서 구현한 회원가입 처리 Service 클래스이다.

 

포워딩 처리 (MemberController.java 부분)

		// 포워딩 처리
		if(forward != null) {
			if(forward.isRedirect()) {	// redirect 방식으로 포워딩
				response.sendRedirect(forward.getPath());
			} else {				// dispatcher 방식으로 포워딩
				RequestDispatcher dispatcher = 
						request.getRequestDispatcher(forward.getPath());
				dispatcher.forward(request, response);
			}
		}

- Controller 클래스에서는 포워딩 처리도 한다

- forward 객체는 한번 Service 클래스로 갔다오면 null 이 아니게 됨

- forward.isRedirect() 는 true, false 를 돌려줌, true면 redirect 방식 포워딩, false 면 diepacher 방식으로 포워딩 하기로 함

- getPath() 메소드로 포워딩할 페이지 불러오기

 


회원관리 프로그램 : 회원가입 기능

- 이제 잠시 Controller 클래스는 두고, 회원가입 기능을 먼저 구현하자

- 먼저 회원가입 폼 memberform.jsp 를 보자

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원 가입 폼</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>

<script src="http://dmaps.daum.net/map_js_init/postcode.v2.js"></script>
<script>
	function openDaumPostcode() {
		new daum.Postcode({
			oncomplete : function(data) {
				// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
				// 우편번호와 주소 정보를 해당 필드에 넣고, 커서를 상세주소 필드로 이동한다.
//				document.getElementById('join_zip1').value = data.postcode1;
//				document.getElementById('join_zip2').value = data.postcode2;
				document.getElementById('post').value = data.zonecode;
				document.getElementById('address').value = data.address;
				
			}
		}).open();
	}
</script>


<!-- 외부 자바스크립트 파일 불러오기 -->
<script src="<%=request.getContextPath() %>/member/member.js"></script>

</head>
<body>

<form method="post" action="<%=request.getContextPath() %>/MemberInsert.do"> 
<table border=1 width=500 align=center>
	<caption>회원 가입</caption>
	<tr><td>ID</td>
		<td><input type=text autofocus="autofocus" id="id" name="id">
			<input type=button value="ID중복검사" id="idcheck">
			<div id="myid"></div>
		</td>
	</tr>
	<tr><td>비밀번호</td>
		<td><input type=password id="passwd" name="passwd"></td>
	</tr>
	<tr><td>성명</td>
		<td><input type=text id="name" name="name"></td>
	</tr>
	<tr><td>주민번호</td>
		<td><input type=text size=6 maxlength=6 id="jumin1" name="jumin1">-
			<input type=text size=7 maxlength=7 id="jumin2" name="jumin2">
		</td>
	</tr>
	<tr><td>E-Mail</td>
		<td><input type=text size=10 id="mailid" name="mailid">@
		    <input type=text size=10 id="domain" name="domain">
		    <select id="email">
		    	<option value="">직접입력</option>
		    	<option value="naver.com">네이버</option>
		    	<option value="daum.net">다음</option>
		    	<option value="nate.com">네이트</option>
		    	<option value="gmail.com">gmail</option>
		    </select>		    
		 </td>
	</tr>
	<tr><td>전화번호</td>
		<td><input type=text size=4 id="tel1" name="tel1" maxlength=4>-
			<input type=text size=4 id="tel2" name="tel2" maxlength=4>-
			<input type=text size=4 id="tel3" name="tel3" maxlength=4>
		</td>
	</tr>
	<tr><td>핸드폰</td>
		<td><select id="phone1" name="phone1">
				<option value="">번호선택</option>
				<option value="010">010</option>
				<option value="011">011</option>
				<option value="016">016</option>
				<option value="018">018</option>
				<option value="019">019</option>
			</select>-
			<input type=text size=4 id="phone2" name="phone2" maxlength=4>-
			<input type=text size=4 id="phone3" name="phone3" maxlength=4>
		</td>
	</tr>
	<tr><td>우편번호</td>
		<td><input type=text size=5 id="post" name="post">
			<input type=button value="우편번호검색" 
			       onClick="openDaumPostcode()">
		</td>
	</tr>
	<tr><td>주소</td>
		<td><input type=text size=45 id="address" name="address"></td>
	</tr>
	<tr><td>성별</td>
		<td>
			<input type=radio id="male" name="gender" value="남자">남자
			<input type=radio id="female" name="gender" value="여자">여자
		</td>
	</tr>
	<tr><td>취미</td>
		<td>
			<input type="checkbox" id="h1" name="hobby" value="공부" checked>공부
			<input type="checkbox" id="h2" name="hobby" value="게임">게임
			<input type="checkbox" id="h3" name="hobby" value="등산">등산
			<input type="checkbox" id="h4" name="hobby" value="낚시">낚시
			<input type="checkbox" id="h5" name="hobby" value="쇼핑">쇼핑
		</td>
	</tr>	
	<tr><td>자기소개</td>
		<td>
			<textarea id="intro" name="intro" rows="5" cols="50" placeholder="자기소개를 100자 이내로 입력하세요"></textarea>
		</td>
	</tr>
	<tr><td colspan=2 align=center>
			<input type=submit value="회원가입">
			<input type=reset value="취소">
		</td>
	</tr>		
</table>
</form>


</body>
</html>

 

유효성 검사를 하는 자바스크립트 불러오기 경로 (memberform.jsp 부분)

<!-- 외부 자바스크립트 파일 불러오기 -->
<script src="<%=request.getContextPath() %>/member/member.js"></script>

- Controller 클래스에 의해 왔다갔다 하면 상대경로이기 때문에 이 경로를 잘 못찾아갈 수도 있다

- 이땐 위의 코드처럼 프로젝트명을 적고 나머지는 WebContent 폴더 기준으로 하위폴더를 모두 쓰고 파일명을 적기

+ request.getContextPath() 메소드는 프로젝트 명을 구해줌 ex) /model2member

 

form 의 action 태그 (memberform.jsp 부분)

<form method="post" action="<%=request.getContextPath() %>/MemberInsert.do">

- post 방식으로 입력양식의 값들을 전송한다

- model 2 에서는 파일이 아닌 무조건 Controller 클래스로 찾아가야한다, 프로젝트명을 앞에 붙이면 Controller 클래스를 잘 찾아감

- 배포가 되면 프로젝트가 URL 경로상에서 빠지므로 프로젝트명을 직접 적는 것이 아닌 request.getContextPath() 사용

 

회원관리 프로그램 : 회원가입 Service 클래스

- 실제 회원가입 요청을 처리하는 MemberInsert.java 파일을 만들자

- 회원가입 폼의 요청이름값과 동일하게 설정

- service 패키지에 MemberInsert.java 클래스 생성

package service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.MemberDAO;
import model.MemberDTO;

public class MemberInsert implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		System.out.println("MeberInsert");
		
		request.setCharacterEncoding("utf-8");
		
		MemberDTO member = new MemberDTO();
		member.setId(request.getParameter("id"));
		member.setPasswd(request.getParameter("passwd"));
		member.setName(request.getParameter("name"));
		member.setJumin1(request.getParameter("jumin1"));
		member.setJumin2(request.getParameter("jumin2"));
		member.setMailid(request.getParameter("mailid"));
		member.setDomain(request.getParameter("domain"));
		member.setTel1(request.getParameter("tel1"));
		member.setTel2(request.getParameter("tel2"));
		member.setTel3(request.getParameter("tel3"));
		member.setPhone1(request.getParameter("phone1"));
		member.setPhone2(request.getParameter("phone2"));
		member.setPhone3(request.getParameter("phone3"));
		member.setPost(request.getParameter("post"));
		member.setAddress(request.getParameter("address"));
		member.setGender(request.getParameter("gender"));
		
		String[] hobby = request.getParameterValues("hobby");
		String h = "";
		for(String h1 : hobby) {
			h += h1 + "-";	// h = "공부-게임-"
		}
		member.setHobby(h);
		member.setIntro(request.getParameter("intro"));
		
		MemberDAO dao = MemberDAO.getInstance();
		int result = dao.insert(member);	// 회원가입
		
		if(result == 1) System.out.println("회원가입 성공");
		
		ActionForward forward = new ActionForward();
		forward.setRedirect(false); // dispatcher 방식으로 포워딩
		forward.setPath("./member/loginform.jsp");	// 포워딩할 파일명
		
		return forward;
	}
}

<회원가입 Service 클래스 형식, 전체적인 흐름>

- 부모 인터페이스인 Action 인터페이스를 상속받기, 이클립스 자동 기능으로 execute() 를 메소드 오버라이딩 하기

- request 객체는 Controller 클래스의 getPost() -> getProcess() -> Service 클래스의 execute() 로 넘어왔다

- 매개변수를 통해 Controller 클래스의 request 객체가 그대로 넘어왔으므로, 폼에서 Controller 클래스로 넘겨준 form 의 각종 입력양식의 값들을 여기 Service 클래스에서 request.getParamter() 로 가져올 수 있다

- 필드값을 설정한 ActionForward 객체를 execute() 를 호출한 Controller 클래스에 돌려줌

 

<회원가입 Service 클래스 작성 순서>

1. 먼저 post 방식으로 한글값이 넘어올 수 있으므로 한글값 인코딩을 설정한다

2. DTO 객체를 만들고 setter 메소드로 값을 세팅

- jsp 에서 useBean, setProperty 로 DTO 객체 생성하고 값을 세팅했던 코드에 해당함

- 이때 DTO 개체에 설정하는 값은 폼-> 컨트롤러-> 서비스 클래스로 넘어온 request 객체를 사용해서 request.getParamter() 로 폼 입력양식의 입력값을 가져와서 설정

- 취미는 2개이상 선택해야하는 양식이므로 값을 getParamterValues() 로 받고 여러개의 값을 하이픈으로 결합해서 저장

+ Spring 에선 일일히 값을 전달하지 않아도 어노테이션으로 한번에 처리 가능

3. DAO 객체를 생성하고 회원가입 insert 를 하는 DAO 의 insert() 메소드 호출

- 매개변수로 값을 설정한 DTO 객체를 넘긴다

- insert() 가 잘 수행되었으면 1 을 리턴한다

4. insert() 가 끝난다음엔 Service -> 다시 execute() 를 호출한 Controller 로 돌아가야한다

- 이때 Controller 로 ActionForward 객체를 리턴해야한다

- 리턴하기 전에 어떤 방식으로 포워딩할지, 어느 파일로 포워딩할지를 setRedirect(), setPath() 로 설정

- 지금은 request 객체로 공유를 설정하지 않았으므로 dispatcher, redirect 두개의 포워딩 방식 모두 사용 가능, 현재는 dispatcher 방식으로 포워딩하자

- setPath() 메소드 안에는 WebContent 폴더를 기준으로 하위 폴더를 쓰면서 포워딩할 파일명을 적어주기

- 회원가입이 끝나면 loginform 으로 갈 것이므로 loginform.jsp 로 포워딩하자

 

- 그 후 객체 forward 를 리턴하면서 Controller 클래스의 이곳으로 간다

- MemberController.java 부분

- 요청을 받을때는 forward == null 이므로 만족하지 않았던 포워딩 처리의 조건문

- 이번엔 객체를 돌려받으므로 forward !=null 이 되어 포워딩 처리함

5. 이후 DAO 클래스를 작성 해야함

 

콘솔창 확인

- memberform.jsp 에서 '회원가입' 클릭시

- 여태까지 회원가입 폼 -> Controller 클래스  -> MemberInsert Service 클래스까지 넘어왔음을 알 수 있음

 

- 다음으로 DAO 클래스에서 insert() 메소드를 작성하자

- DAO 클래스의 코드는 Model 1 와 거의 같다

 

DAO 클래스 회원가입 기능 메소드 작성

- MemberDAO.java 전체 코드는 모두 구현 후 올리기

- MemberDAO.java 추가된 insert() 부분 코드만

	// 회원가입
	public int insert(MemberDTO member) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		try {
			con = getConnection();
			
			String sql = "insert into member2 ";
			sql += "values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,sysdate)";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, member.getId());
			pstmt.setString(2, member.getPasswd());
			pstmt.setString(3, member.getName());
			pstmt.setString(4, member.getJumin1());
			pstmt.setString(5, member.getJumin2());
			pstmt.setString(6, member.getMailid());
			pstmt.setString(7, member.getDomain());
			pstmt.setString(8, member.getTel1());
			pstmt.setString(9, member.getTel2());
			pstmt.setString(10, member.getTel3());
			pstmt.setString(11, member.getPhone1());
			pstmt.setString(12, member.getPhone2());
			pstmt.setString(13, member.getPhone3());
			pstmt.setString(14, member.getPost());
			pstmt.setString(15, member.getAddress());
			pstmt.setString(16, member.getGender());
			pstmt.setString(17, member.getHobby());
			pstmt.setString(18, member.getIntro());

			result = pstmt.executeUpdate(); // SQL문 실행
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return result;
	}

- Model 1 에서 DAO의 insert() 메소드를 작성했던 것과 같은 방식으로 작성하면 된다

- Model1 DAO 회원가입 insert() 메소드 설명 : https://laker99.tistory.com/114?category=1080281

 

다음 흐름

- DB 에 insert 수행 후 DAO -> Service 클래스(MemberInsert.java) 로 간다

- Service 클래스에서 forward 객체를 생성 후 어디로 포워딩 할지, 어떻게 포워딩 할지 값만 설정한 후 forward 객체 리턴

- Service 클래스의 execute() 메소드를 호출한 Controller 클래스로 돌아가서 실제 포워딩을 수행한다

- Controller 클래스의 이곳으로 다시 돌아간다

- Controller 클래의 객체 forward 가 execute() 에서 리턴으로 ActionForward 객체를 받았으므로 더이상 fowrad 는 null 이 아님

- 처음에는 null 이었기에 실행되지 않았던 포워딩 코드를 이제는 null 이 아니므로 실행함

- getPath() 로 포워딩할 페이지를 불러와서 dispatcher 방식 또는 redirect 방식으로 포워딩 하면 된다

- 여기서는 dispatcher 방식으로 포워딩 했다

 

실행 캡처

- 먼저 memberform.jsp 회원가입 폼에서 회원가입 시키자

- 회원가입을 하면 로그인 페이지로 포워딩 된다, dispacher 방식 포워딩이므로 URL 변화 없음

- sql 파일에서 DB insert 되었는지 확인

- 삽입되었음을 확인 가능

 

- 콘솔창 보기

- Controller 클래스("post" 출력) -> Service 클래스("MemberInsert" 출력) -> DAO 클래스에서 DB에 삽입 -> Service 클래스("회원가입 성공" 출력) -> Controller 클래스 -> loginform.jsp 포워딩 을 콘솔창에서 찍혀진 결과로 확인 가능

 

회원관리 프로그램 : ID 중복검사

- memberform.jsp 파일을 보면

- 비동기적으로 입력창 아래에 사용 가능한 ID 메세지 띄우기

- Ajax는 만들어져 있으므로 저렇게 글자는 뜬다

- 해당 서비스 클래스를 만들어야 실제로 ID중복검사를 하고, 제대로 동작한다

 

- 이제 member.js 파일에서 Ajax 로 비동기적으로 서버창에 요청하는 코드를 보자

- Idcheck.do 로 요청한다, Controller 클래스로 찾아가서 doPost() 가 실행됨, 이때 URL 에는 /Idcheck.do 가 찍힌다

- 'ID중복검사' 버튼 클릭시 요청 이름값인 Idcheck.do 가 콘솔창에 나온다

- 요청을 했고 그 요청이 Controller 클래스까지 갔다는 의미이다

 

- 이제 요청을 실제로 처리하는 Service 클래스를 만들어야한다

 

회원관리 프로그램 : ID중복검사 Service 클래스

- 실제 ID중복검사 요청을 처리하는 Idcheck.java 파일을 만들자

- 회원가입 폼의 요청이름값과 동일하게 설정

- service 패키지에 IdCheck.java 클래스 생성

- IdCheck.java

package service;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import dao.MemberDAO;

public class IdCheck implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("IdCheck");
		
		// 출력 스트림 객체 생성
		PrintWriter out = response.getWriter();
		
		String id = request.getParameter("id");
		System.out.println("id:" + id);
		
		MemberDAO dao = MemberDAO.getInstance();
		int result = dao.idcheck();	// ID중복검사
		System.out.println("result:" + result); // 1 : 중복 ID //-1 : 사용 가능한 ID
		
		// 웹브라우저에 출력되는 값이 callback 함수로 리턴됨
		out.println(result);
		
		return null;
	}

}

* 아래의 Controller 클래스에서 이 Service 클래스로 가는 코드를 먼저 작성 한 후 다시 이 코드로 돌아와 작성했다

- 먼저 Action 인터페이스를 상속하고 execute() 메소드 오버라이딩

- 지금은 비동기로 처리하므로 Controller 클래스로 돌아가지 않는다! 그래서 return null을 함

- 돌아갈때 member.js 로 돌아가야한다, 이때 브라우저에 출력되는 값이 callback 함수로 리턴되므로 1 또는 -1 이 돌아감

- member.js 에서 요청되면서 넘어온 값을 request.getParamter("id") 로 받는다

- DAO 객체 생성해서 메소드 idcheck() 호출하며 사용자의 id 를 넘기기, 중복이면 1 리턴, 사용가능한 아이디면 -1 리턴

- 브라우저에 출력되는 값이 콜백함수로 리턴되므로, 이 IdCheck.java 에서 출력해야함, 출력위해 out 객체 생성

- out 객체로 DAO 의 idcheck() 에서 돌려받은 값 result 를 출력, 그럼 member.js 에서 data 로 돌려받음

+ Model 2 에선 JSP 의 내장객체 없다, request, response 만 받아서 사용 가능하고 나머지 객체는 직접 만들어야함

 

- 다음은 다시 Controller 클래스로 돌아가서 ID중복검사에서 요청이 올경우 IdCheck.java Service 클래스로 보내주는 코드 작성

 

Controller 클래스에서 ID 중복검사 Service 클래스로 가기

- MemberController.java 전체 코드는 나중에

- MemberController.java 부분

		// ID중복 검사 (ajax)
		} else if (command.equals("/Idcheck.do")) {
			try {
				action = new IdCheck();
				forward = action.execute(request, response);
			}
			catch (Exception e){
				e.printStackTrace();
			}
		}

- IdCheck() 클래스로 객체 생성하고, 그 객체에서 execute() 실행하기

- 이 코드 작성 후 회원가입 폼에서 'ID중복검사' 버튼 클릭시 콘솔창 확인하면

- IdCheck 가 찍히는 걸 보아, 요청하는 곳 -> Controller -> Service 클래스까지 잘 도착했음을 확인 가능

- id: lay99 이 찍히는 걸 보아, Ajax로 요청하면서 넘어온 값이 Service 클래스에서 잘 받았음을 확인 가능

 

- 이제 DAO 클래스에 ID중복검사를 하기 위한 메소드 idcheck() 작성하기

 

DAO 클래스 ID중복검사 메소드 작성

- MemberDAO.java 전체 코드는 모두 구현 후 올리기

- MemberDAO.java 추가된 idchcek() 부분 코드만

- MemberDAO.java (추가)

	// ID중복 검사 (ajax)
	public int idcheck(String id) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from member2 where id = ?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			rs = pstmt.executeQuery();	// SQL문 실행
			
			if(rs.next()) {
				result = 1;	// 중복 ID
			} else {
				result = -1; // 사용 가능한 ID
			}
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
		return result;
	}

- select 문이므로 ResultSet 객체도 생성

- select SQL문 실행 후 rs.next 가 true 인 경우엔 DB 에 중복아이디가 있다는 의미이다

- 중복 ID 면 1 을 리턴, 사용 가능한 ID 면 -1 을 Service 클래스 IdCheck.java 로 돌아가며 리턴

 

- memberform.jsp 를 실행해서 ID중복검사 를 해보자

- 이미 가입된 ID 입력시

- 콘솔창 확인

- Service 클래스에서 DAO 로 부터 받은 값을 Service 클래스에서 result 로 받아 출력하고 있다, 중복 ID 이므로 1 이 출력

 

- 가입되지 않은 ID 입력시

- 콘솔창 확인

- Service 클래스에서 DAO 로 부터 받은 값을 Service 클래스에서 result 로 받아 출력하고 있다, 사용 가능한 ID 이므로 -1 이 출력


회원관리 프로그램 : 로그인 기능

- 이제 로그인 기능을 구현하자

- 먼저 로그인 폼인 loginform.jsp 파일을 보자

- loginform.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
	<!DOCTYPE html>
	<html>
	<head>
	<meta charset="UTF-8">
	<title>로그인 폼</title>
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	
	<!-- 외부 자바스크립트 파일 불러오기 -->
	<script src="<%=request.getContextPath() %>/member/login.js"></script>
	
	</head>
	<body>
	
	<form method="post" action="<%=request.getContextPath() %>/Login.do">
	<table border=1 width=350 align=center>
		<caption>로그인</caption>
		<tr>
			<td>ID</td>	
			<td><input type=text size=30 id="id" name="id" autofocus="autofocus"></td>	
		</tr>
		<tr>
			<td>비밀번호</td>
			<td><input type="password" size=30 id="passwd" name="passwd"></td>
		</tr>
		<tr>
			<td colspan=2 align=center>
				<input type="button" value="회원가입" 
				       onClick="location.href='<%=request.getContextPath()%>/MemberForm.do' ">
				<input type="submit" value="로그인">
				<input type="reset" value="취소">
			</td>
		</tr>
		
	</table>
	</form>
	
	
	</body>
	</html>

- 외부 자바스크립트 파일을 불러올때 request.getContext() 로 프로젝트명을 구하고. WebContent 기준 하위 폴더와 파일명 적기

+ '로그인' 버튼만 submit 버튼으로서 클릭시 입력된 값을 post 방식으로 전송, Controller 클래스로 간다

 

'회원가입' 으로 넘어갈때 주의

- '회원가입' 을 클릭시 바로 memberform.jsp 로 가는 것이 아님

- Model 2 이기 때문에 Controller 클래스를 갔다가 다시 가입 폼 (View)으로 가야함

- '회원가입' 클릭시 Controller 클래스로 먼저 가기 위해 memberform.jsp 대신 MemberForm.do 로 do 확장자로 요청해서 어노테이션으로 Controller 클래스로 찾아가게함

- Controller 클래스의 doGet() 메소드가 실행됨

- 로그인폼에서 '회원가입' 클릭시 콘솔창

- Controller 클래스로 간것을 확인 가능, 요청 이름값은 MemberForm.do

 

- Controller 클래스에서 MemberForm.do 로 요청이 올때 어떻게 처리할지를 작성해야한다

 

Controller 클래스에서 회원가입 폼으로 가기

- MemberController.java 전체 코드는 나중에

- MemberController.java 부분

- MemberForm.do 로 요청이 올때의 경우를 처리

		// 회원가입 폼
		} else if (command.equals("/MemberForm.do")){
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("./member/memberform.jsp");
		}

- DB 연동을 하는 경우가 아니므로 Service 클래스로 가지 않고 바로 회원가입 폼으로 가면 된다

- 바로 forward 객체를 생성하고 setRedirect(), setPath() 로 값을 설정해줌, 원래 이 작업은 Service 클래스 하단에서 해주는 거지만 여기선 Service 클래스로 가지 않으므로 여기서 값 설정

- 여기서 forward 객체는 null 이 아니게 되므로 아래의 코드인 포워딩 코드에서 포워딩되어 회원가입폼으로 간다

+ 모두 Service 클래스로 가는 것이 아님, DB연동 없으면 Service 클래스로 넘어가지 않는다

 

- 지금까지 로그인 폼에서 '회원가입' 클릭시 회원가입 폼으로 넘어가도록 처리함

- 이제 진짜 로그인 기능을 구현하자

- loginform.jsp 에서 아이디, 비밀번호 입력 후 '로그인' 클릭 시 콘솔창

- Controller 클래스가서 doPost() 를 실행했음을 알 수 있다.

 

앞으로 해야할 일

1. Service 클래스 명도 맞추서 Login.java 파일로 하자

2. Controller 클래스에서 "/Login.do" 인 경우를 Login.java 로 보내는 것을 처리하자

3. Service 와 DAO 부분 구현

 

- 로그인 기능 Service 클래스인 Login.java 파일 생성부터 하자

 

회원관리 프로그램 : 로그인 Service 클래스

- 실제 로그인 요청을 처리하는 Login.java 파일을 만들자

- 로그인 폼의 요청이름값과 동일하게 설정

- service 패키지에 Login.java 클래스 생성

- Login.java (수정 전, 한글 인코딩 전, 한글 인코딩 빼고는 다 작성함)

package service;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import dao.MemberDAO;

public class Login implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("Login");
		
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();
		
		String id = request.getParameter("id");
		String passwd = request.getParameter("passwd");
		
		MemberDAO dao = MemberDAO.getInstance();
		int result = dao.memberAuth(id, passwd); // 회원인증
		if(result == 1) System.out.println("회원인증 성공");
		
		if(result == 1) {	// 회원인증 성공
			session.setAttribute("id", id);
		} else {	// 회원인증 실패
			out.println("<script>");
			out.println("alert('로그인 실패');");
			out.println("history.go(-1);");
			out.println("</script>");
			out.close();
			
			return null;
		}
		ActionForward forward = new ActionForward();
		forward.setRedirect(false); // dispatcher 방식으로 포워딩
		forward.setPath("./member/main.jsp");
		
		return forward;
	}

}

<로그인 Service 클래스 형식, 전체적인 흐름>

- 부모 인터페이스인 Action 인터페이스를 상속받기, 이클립스 자동 기능으로 execute() 를 메소드 오버라이딩 하기

- request 객체는 Controller 클래스의 getPost() -> getProcess() -> Service 클래스의 execute() 로 넘어왔다

- 매개변수를 통해 Controller 클래스의 request 객체가 그대로 넘어왔으므로, 폼에서 Controller 클래스로 넘겨준 form 의 각종 입력양식의 값들을 여기 Service 클래스에서 request.getParamter() 로 가져올 수 있다

- 필드값을 설정한 ActionForward 객체를 execute() 를 호출한 Controller 클래스에 돌려줌

 

<로그인 Service 클래스 작성 순서>

1. 먼저 post 방식으로 한글값이 넘어올 수 있으므로 한글값 인코딩을 설정한다

2. 회원 인증 성공시 공유설정을 해야하므로 session 객체 생성, out 객체도 생성해주자

3. 로그인 폼에서 전달한 id, passwd 를 받아온다

- 다음으로는 이 id, passwd 와 DB의 id, passwd 가 같으면 공유설정해야함

4. DAO 객체를 생성하고 회원 인증 select 를 하는 DAO 의 memberAuth() 메소드 호출

- 매개변수로는 id, passwd 를 전달

- 회원 인증에 성공시 결과값을 1로 받기로 하자

5. 회원 인증 성공시 "id" 를 네임값으로 회원의 id 를 공유 설정을 한다

- 파일을 계속 넘어가도 session 값은 유지됨

6. 회원 인증 실패시 out 객체를 통해 실패 메세지를 뿌린다

- 실패시 알림창을 띄우고 이전 페이지로 돌아간다

- return null 이 없으면 콘솔창에 에러가 뜬다, return null 을 통해 이 메소드를 완전히 빠져나가며 아래 내용 실행 안함

- out 객체를 닫아줌

- 회원 인증 성공시, return null 로 빠져나가지 않고 아래 코드가 실행됨

7. 회원 인증 성공시 Service -> 다시 execute() 를 호출한 Controller 로 돌아가야한다

- 이때 Controller 로 ActionForward 객체를 리턴해야한다, ActionForward 객체 forward 생성

- 리턴하기 전에 어떤 방식으로 포워딩할지, 어느 파일로 포워딩할지를 setRedirect(), setPath() 로 설정

- 지금은 request 객체로 공유를 설정하지 않았으므로 dispatcher, redirect 두개의 포워딩 방식 모두 사용 가능, 현재는 dispatcher 방식으로 포워딩하자

- setPath() 메소드 안에는 WebContent 폴더를 기준으로 하위 폴더를 쓰면서 포워딩할 파일명을 적어주기

- 회원가입이 끝나면 main 페이지로 갈 것이므로 main.jsp 로 포워딩하자

8. 이후 DAO 클래스를 작성 해야함

 

+ out 객체와 session 객체 만들기

		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();

- DAO 클래스로 가서 회원 인증 (로그인) 을 해주는 memberAuth() 메소드를 작성하자

 

DAO 클래스 회원인증 (로그인) 기능 메소드 작성

- MemberDAO.java 전체 코드는 모두 구현 후 올리기

- MemberDAO.java 추가된 memberAuth() 부분 코드만

	// 로그인 (회원인증)
	public int memberAuth(String id, String passwd) {
		int result = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			con = getConnection();
			
			String sql = "select * from member2 where id = ? and passwd=?";
			
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, id);
			pstmt.setString(2, passwd);
			rs = pstmt.executeQuery();	//SQL문 실행
			
			if(rs.next()) {	// 회원인증 성공
				result = 1;
			} else {		// 회원인증 실패
				result = -1;
			}
			
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(rs != null) try { rs.close(); } catch(Exception e) {}
			if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
			if(con != null) try { con.close(); } catch(Exception e) {}
		}
	return result;
	}

- select * from member2 where id =? and passwd = ? 에서 결과를 가져온다는 것은 아이디, 비번이 모두 일치하는 결과가 있다는 의미

- 회원 인증 성공시 1 리턴, 실패시 -1 리턴

- 이후 이 메소드를 호출한 Service 클래스로 이동 -> Controller 클래스로 이동 -> main.jsp 로 포워딩하게 된다

- Service 클래스 -> Controller 클래스로 갈때 forward 객체를 리턴했음

- Controller 클래스에서 main.jsp 로 포워딩 한다

 

- 아직 Controller 클래스에서 "/Login.do" 가 요청이름값일때의 Login.java 로 보내는 처리를 하지 않았음

- Controller 클래스에서 Service 클래스로 연결하는 것은 

 

Controller 클래스에서 로그인 Service 클래스로 가기

- MemberController.java 전체 코드는 나중에

- MemberController.java 부분

- Login.do 로 요청이 올때의 경우를 처리

		// 로그인 (회원 인증)
		} else if(command.equals("/Login.do")) {
			try {
				action = new Login();
				forward = action.execute(request, response);
			}
			catch (Exception e){
				e.printStackTrace();
			}
		}

 

- 이제 모든게 연결되었으므로 loginform.jsp 에서 로그인을 시도해보자

- 맞는 비밀번호 입력시

- 아직 main.jsp 가 만들어지지 않았으므로 오류 발생

- 콘솔창을 보면 로그인 폼 -> Controller 클래스("post" 출력) -> Service 클래스("Login" 출력) -> DAO 에서 DB 연결 -> Service 클래스("회원가입 성공" 출력) -> Controller 클래스 -> main.jsp 로 포워딩 잘 되었음을 확인 가능

- 틀린 비밀번호 입력시

- 로그인 실패 라는 메세지 대신 깨진 한글값이 나온다

 

깨진 한글값 문제 처리

- Login.java 파일에서 회원가입 실패시 출력하는 메세지가 한글이 깨져서 나타난다

- 위에서 "한글값" 을 인코딩하는 setCharacterEncoding("utf-8") 은 있지만 현재 문서의 한글 인코딩은 안했다

- 그래서 alert 로 출력되는 한글이 깨졌던 것

- JSP 에서는 자동으로 생성되었던 현재 문서의 한글을 인코딩하는 코드

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

- 이 코드를 JSP 가 아니라 Java 클래스 내에서 JAVA 에 맞게 작성해야함

		response.setContentType("text/html; charset=utf-8");

- 이 한줄을 Login.java 에 추가해야함

 

+ 이 코드는 한글값 인코딩, 한글 문서 인코딩과 관련없다

		request.setCharacterEncoding("utf-8");

 

- Login.java (수정 후, 한글 인코딩 완료)

package service;

import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import dao.MemberDAO;

public class Login implements Action{

	@Override
	public ActionForward execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		System.out.println("Login");
		
		response.setContentType("text/html; charset=utf-8");
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		HttpSession session = request.getSession();
		
		String id = request.getParameter("id");
		String passwd = request.getParameter("passwd");
		
		MemberDAO dao = MemberDAO.getInstance();
		int result = dao.memberAuth(id, passwd); // 회원인증
		if(result == 1) System.out.println("회원인증 성공");
		
		if(result == 1) {	// 회원인증 성공
			session.setAttribute("id", id);
		} else {	// 회원인증 실패
			out.println("<script>");
			out.println("alert('로그인 실패');");
			out.println("history.go(-1);");
			out.println("</script>");
			out.close();
			
			return null;
		}
		ActionForward forward = new ActionForward();
		forward.setRedirect(false); // dispatcher 방식으로 포워딩
		forward.setPath("./member/main.jsp");
		
		return forward;
	}
}

 

- 다시 loginform.jsp 를 실행하고 틀린 아이디나 비밀번호를 입력 후 '로그인' 을 클릭하자

- 문서의 한글 인코딩이 잘 처리 되었다!

 

- 맞는 비밀번호를 입력했을때도 한글이 깨지는 문제 해결됨

- main.jsp 파일이 아직 생성되지 않아서 아직 404 에러가 뜬다

 

중간 정리

+ 현재 공유된 값 : 로그인 성공시 session 으로 공유된 id 값

- 그래도 main.jsp 로 포워딩하는 것까지 Controller 클래스에서 다 처리했다

- Service 클래스를 거쳐서 돌아올때는 forward 객체가 null 이 아니므로 조건식을 만족해서 포워딩함

- session 으로 공유를 설정할때는 어느 방식으로 포워딩해도 상관없다

- 지금 로그인 페이지는 session 으로만 공유설정했으므로 상관없음, 나중에 request 객체로 공유 설정시에는 Dispatcher 방식으로 포워딩 해야함

 

- main.jsp 파일에서는 세션 유무에 따라 보이는 화면이 달라진다


회원관리 프로그램 : 중간 페이지 main.jsp

세션 유무(로그인 성공 유무) 에 따라 다른 화면 보여주기

- WebContent/member 폴더 하위에 main.jsp 파일 생성 및 작성

- main.jsp (수정 전 1)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<!-- 세션이 있는 경우 -->
<c:if test="${sessionScope.id != null }">
	${sessionScope.id} 님 환영 합니다. <br><br>
	
	회원정보 수정 <br>
	로그아웃 <br>
	회원탈퇴 <br>
</c:if>
<!-- 세션이 없는 경우 -->
<c:if test="${sessionScope.id == null}">
	회원가입 <br>
	로그인 <br>
</c:if>

- EL / JSTL 사용하기

- JSTL 코어 라이브러리를 불러온다

- main.jsp 파일에서는 세션 유무에 따라 보이는 화면이 달라진다

- session 값이 있는경우, 없는 경우 두가지 경우로 나눠서 처리, JSTL 의 if 태그 사용

- 조건식은 EL 로 만들고 sessionScope 내장객체를 사용함, sessionScope.id 는 session 으로 공유된 네임 "id" 의 값 의미

 

- 세션 값이 있을때 (즉, 로그인 (회원인증) 하고 main.jsp 로 왔을때)

- 세션 값이 없을때 (바로 main.jsp 를 그냥 실행했을때)

 

- 이제 세션이 없는 경우 나타나는 텍스트인 '회원가입' 과 '로그인' 에 링크걸기

- main.jsp (수정 전 2)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<!-- 세션이 있는 경우 -->
<c:if test="${sessionScope.id != null }">
	${sessionScope.id} 님 환영 합니다. <br><br>
	
	회원정보 수정 <br>
	로그아웃 <br>
	회원탈퇴 <br>
</c:if>
<!-- 세션이 없는 경우 -->
<c:if test="${sessionScope.id == null}">
	<a href="<%=request.getContextPath() %>/MemberForm.do">회원가입 </a> <br>
	<a href="<%=request.getContextPath() %>/LoginForm.do">로그인 </a> <br>
</c:if>

<회원가입 링크>

- href = "./MemberForm.do" 만을 설정시 못 찾아간다

- 앞에 프로젝트 명을 붙여서 이동시켜줘야함

- 콘솔창

<로그인 링크>

- 아직 Controller 클래스에 로그인 폼으로 넘겨주는 코드는 없다

- 요청이름값을 "LoginForm.do" 로 하자

- LoginForm.do 로 요청 했으므로 Controller 클래스로 찾아가긴 했다

- 마찬가지로 프로젝트 명을 앞에 써주기 위해 request.getContextPath() 사용

- Controller 클래스에서 /LoginForm.do 로 올때를 처리해주기

- 폼이 아니므로 무조건 Get 방식으로 전달한다,

- 콘솔창

- command 변수에 요청 이름값이 저장되어있다

- Controller 클래스에서 command 가 LoginForm.do 일때 로그인 폼으로 가는 코드를 작성

 

Controller 클래스에서 로그인 폼으로 가기

- MemberController.java 전체 코드는 나중에

- MemberController.java 부분

- LoginForm.do 로 요청이 올때의 경우를 처리

		// 로그인 폼	
		} else if(command.equals("/LoginForm.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("./member/loginform.jsp");
		}

- 지금은 DB연동을 안하므로 Service 클래스로 갈 필요 없다

- 회원가입 폼으로 가는 것과 마찬가지로 처리한다

- ActionForward 객체 forward 를 생성후 바로 값을 세팅해줌

- View 페이지는 member 폴더 하위 loginform.jsp 이다

- dispatcher 방식으로 포워딩 한다

 

- 그 후에 아래의 포워딩 코드에서 forward != null 을 만족하므로 포워딩한다

 

- main.jsp 실행 후 '로그인' 누르기

+ 세션값이 없으므로 아래 화면이 뜬다

- 로그인 포믕로 잘 이동했음을 확인 가능

 

Model 1 vs Model 2

- Model 1 에서는 Loginform.jsp 로 바로 갈 수 있다

- Model 2 에서는 Controller 로 갔다가 다시 View (Loginform.jsp) 로 가도록 설정해야한다

 

- 다시 main.jsp 를 보자

- sessionScope.id 는 EL 내장객체, session.getAttribute("id") 를 대신한다

- sessionScope.id 대신 다른 방법이 있다, empty 연산자

- 위의 if 조건식을 empty 연산자로 바꿔서 처리해보자

 

empty 연산자

- '비어있는지', '없는지' 를 묻는 EL연산자

- 비어있으면 true, 비어있지 않으면 false 값이 된다

 

empty 연산자 코드로 바꾸기 1

- main.jsp 의 '세션이 있는 경우' if 태그 코드를 empty 연산자 코드로 바꾸기

<!-- 세션이 있는 경우 -->
<c:if test="${sessionScope.id != null }">

- 아래 코드로 바꿀 수 있다

<!-- 세션이 있는 경우 -->
<c:if test="${!empty sessionScope.id }">

- empty 연산자는 없으면 true 이므로 세션이 있는 경우는 ! 를 앞에 붙여야함

- empty 연산자는 == null 과 같은 의미, '없으면' 의 의미이다

 

empty 연산자 코드로 바꾸기2

- 마찬가지로 main.jsp 의 '세션이 없는 경우' if 태그 아래 코드는 이렇게 바꾼다

<!-- 세션이 없는 경우 -->
<c:if test="${sessionScope.id == null}">

- 아래코드로 바꾼다

<!-- 세션이 없는 경우 -->
<c:if test="${empty sessionScope.id }">

- 또한 이렇게 sessionScope 를 빼고 id 만 적어도 똑같이 처리된다

<!-- 세션이 없는 경우 -->
<c:if test="${empty id }">

- 똑같이 처리된다

- sessionScope 를 적는게 원칙이지만 빼도 처리된다

 

- main.jsp (수정 전 3)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<!-- 세션이 있는 경우 -->
<c:if test="${!empty sessionScope.id }">
<%-- <c:if test="${sessionScope.id != null }"> --%>
	${sessionScope.id} 님 환영 합니다. <br><br>
	
	회원정보 수정 <br>
	로그아웃 <br>
	회원탈퇴 <br>
</c:if>
<!-- 세션이 없는 경우 -->
<c:if test="${empty sessionScope.id }">
<%-- <c:if test="${sessionScope.id == null}"> --%>
	<a href="<%=request.getContextPath() %>/MemberForm.do">회원가입 </a> <br>
	<a href="<%=request.getContextPath() %>/LoginForm.do">로그인 </a> <br>
</c:if>

회원관리 프로그램 : 로그아웃

- main.jsp 세션있는 경우에서 나타나는 차에서 '로그아웃' 을 눌렀을때 로그아웃을 수행하는 기능을 만들자

 

- main.jsp 에서 링크를 연결하자

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<!-- 세션이 있는 경우 -->
<c:if test="${!empty sessionScope.id }">
<%-- <c:if test="${sessionScope.id != null }"> --%>
	${sessionScope.id} 님 환영 합니다. <br><br>
	
	회원정보 수정 <br>
	<a href="./Logout.do">로그아웃</a> <br>
	회원탈퇴 <br>
</c:if>
<!-- 세션이 없는 경우 -->
<c:if test="${empty sessionScope.id }">
<%-- <c:if test="${sessionScope.id == null}"> --%>
	<a href="<%=request.getContextPath() %>/MemberForm.do">회원가입 </a> <br>
	<a href="<%=request.getContextPath() %>/LoginForm.do">로그인 </a> <br>
</c:if>

- 수정한 부분 코드만 캡처

- 요청 이름값은 "/Logout.do" 로 하자

- 로그아웃 처리는 DB연동을 안하므로 Service 클래스로 갈 필요 없음

- 로그인 하고 main.jsp 로 와서 '로그아웃' 을 눌러보자

- command 에 요청 이름값 "/Logout.do" 이 출력되고 있다

- Controller 클래스의 doGet(), doProcess() 메소드 안까지 잘 찾아 간 것

 

- Controller 클래스로 가서 요청이 "/Logout.do" 로 온 경우에 View 페이지로 넘겨주는 처리를 하자

 

Controller 클래스에서 회원가입 폼으로 가기

- MemberController.java 전체 코드는 나중에

- MemberController.java 부분

- Logout.do 로 요청이 올때의 경우를 처리

		// 로그아웃
		} else if(command.equals("/Logout.do")) {
			forward = new ActionForward();
			forward.setRedirect(false);
			forward.setPath("./member/logout.jsp");
		}

- 지금은 DB연동 안하므로 Service 클래스 갈 필요 없다

- 로그아웃 View 페이지인 logout.jsp 로 보내주기만 하면 된다.

- 그래서 회원가입 폼으로 갈 때와 비슷하게 처리한다

- ActionForward 객체 forward 를 생성하고 바로 값을 설정함

 

- 이제 member 폴더 하위에 logout.jsp 파일을 생성하자

- logout.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<%
	// 세션 삭제
	session.invalidate();
%>

<script>
	alert("로그 아웃");
	location.href="./LoginForm.do"
</script>

logout.jsp 에서 해야할 일

1. session 삭제

- 브라우저 창이 닫히지 않아도 강제로 session 끊기

- session.invalidate() 메소드 사용

2. 로그아웃 되었다는 메세지를 뿌린 후 다시 로그인 폼으로 가기(포워딩)

- 로그인 폼으로 가는 요청이름인 "/LoginForm" 을 사용해서 로그인 폼으로 간다

 

- 로그인 폼에서 로그인 한 후 main.jsp 에서 '로그아웃' 클릭하자

- 로그인 폼으로 잘 넘어간다.

- Controller 클래스에서 View 페이지로 포워딩되었다

+ 그 상태에서 main.jsp 실행시 이 화면이 뜨는걸 보아 세션이 삭제되었다

- 콘솔창

- 로그아웃 기능 구현 완료

 

다음에 만들 Model 2 게시판 프로그램

- 이후에 만들 프로그램

- request 객체로 공유를 많이 한다

- lib 폴더에 라이브러리를 저장하는 대신 maven 을 사용한다

복습

Model 2 전체 흐름 정리

- Controller 클래스가 클라이언트의 요청을 받음, WebServlet 어노테이션으로 Controller 클래스로 찾아감

- Service 클래스는 여러개를 만드는데 공통적인 내용을 부모 인터페이스로 만들고 그것을 상속받아서 사용함

- DAO 클래스에서 검색한 결과를 View 페이지에 출력할땐, select 를 수행후 결과를 Service 클래스로 다시 돌아가고, View 페이지에 결과를 출력

- 돌려줄 값이 많을때는 Service 클래스에서 돌려줄 값을 request 객체로 공유 설정 해야한다.

- 공유 설정 후 포워딩 함, 포워딩 된 JSP 파일에서 공유된 값을 가져와서 표현 언어로 출력


JSTL (JSP Standard Tag Library)

- 널리 사용되는 커스텀 태그를 표준으로 만든 태그 라이브러리

- 태그 중 일종, 사용자 정의 태그의 표준

- View 페이지에서 결과를 출력할때 EL, JSTL 이 같이 사용된다.

- 라이브러리가 필요하다

* 사용자 정의 태그 = 개발자가 만들어 쓰는 태그

 

JSTL 환경 구축

- JSTL 을 쓰기 위해서는 라이브러리가 필요하므로 가장 먼저 라이브러리를 구한다.

 

JSTL 라이브러리 구하기

https://tomcat.apache.org/taglibs/standard/

 

Apache Taglibs - Apache Standard Taglib: JSP[tm] Standard Tag Library (JSTL) implementations

<!-- Copyright 1999-2011 The Apache Software Foundation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/

tomcat.apache.org

- JSTL 1.2 버전을 다운로드

- 해당 프로젝트의 WebContent/WEB-INF/lib 폴더에 jstl-1.2.jar 를 저장한다

 

JSTL 라이브러리

- 5개 종류의 JSTL 라이브러리가 있다

- prefix 에 접두어를 쓰면 사용할 수 있다.

- 이 라이브러리를 관리하는 기관의 URI 주소를 써야한다, 주소를 설정해야 그 라이브러리를 불러온다는 의미

+ JSP 태그 중 사용자 정의 태그이다.

 

JSTL 예제

실습 준비

- 한글이름은 문제가 생길수 있으므로 '사용자 정의태그(JSTL)' 을 'JSTL' 로 이름 수정

- JSTL/lib/jstl-1.2.jar 파일을 WEB-INF/lib 안으로 복사

 

JSTL 예시

- JSTL/Core/ex1/setTag.jsp

<%@ page contentType = "text/html; charset=utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<c:set var="num1" value="${20}" />
<c:set var="num2">
10.5
</c:set>
<c:set var="today" value="<%= new java.util.Date() %>" />

<html>
	<head>
		<title>set 태그와 remove 태그</title>
	</head>

	<body>
		변수 num1 = ${num1} <br>
		변수 num2 = ${num2} <br>
		num1 + num2 = ${num1 + num2} <br>
		오늘은 ${today} 입니다.

		<c:remove var="num1" scope="page" />

		<p>
		삭제한 후의 num1 = ${num1} <br>
		삭제한 후의 num1 + num2 = ${num1 + num2}
	</body>
</html>

 

라이브러리 불러오기 (setTag.jsp 부분)

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

- 라이브러리는 5개 종류가 있다

- 그 중 하나인 코어 라이브러리, 이때 라이브러리를 불러오는 형식

- 어떤 라이브러리를 사용하냐에 따라 prefix 가 정해져 있다

- 코어 라이브러리를 쓸때는 접두어를 "c" 를 쓴다

- 또한 코어에 해당하는 uri 값을 불러온다

- 이 코드가 JSP의 사용자 정의 태그(tag libary tag) 이다.

 

JSTL 지원 라이브러리

라이브러리 하위 기능 접두어 관련 URI
코어 변수 지원
흐름 제어
URL 처리
c http://java.sun.com/jsp/jstl/core
XML XML 코어
흐름 제어
XML 변환
x http://java.sun.com/jsp/jstl/xml
국제화 지역
메세지 형식
숫자 및 날짜 형식
fmt http://java.sun.com/jsp/jstl/fmt
데이터베이스 SQL sql http://java.sun.com/jsp/jstl/sql
함수 콜렉션 처리
String 처리
fn http://java.sun.com/jsp/jstl/functions

- 90% 이상 코어 라이브러리만 사용, 10% 는 국제화 라이브러리 사용

- 코어 라이브러리는 preifx값을 "c" 라고 쓰고 코어 라이브러리를 관리하는 기관 uri 를 쓴다

- 코어 라이브러리의 주요 기능은 변수 생성, 변수 삭제, 조건식, 반복식, URL 처리

- 국제화 라이브러리는 주로 날짜 시간을 년월일시분초 등의 패턴을 지정할때 사용된다, Model 2에서 SimpleDateFormat 의 역할

 

JSP 라이브러리 : 코어 라이브러리

코어 라이브러리 지원 태그

- set 태그 : 변수를 정의(생성)할때 사용

- remove 태그 : 변수를 제거할때 사용

- if 태그 : if문과 유사

- choose - when - otherwise 태그 : JSLT 에선 if-else 가 없으므로 if-else 처럼 쓰고 싶을때 사용

- forEach 태그 : for문과 유사

 

태그 사용법

set 태그 사용법

- 태그들이 어떤 라이브러리에 포함되어있냐에 따라 접두어를 가장 먼저 쓴다

ex) set 태그는 코어 라이브러리이므로 접두어 c 를 쓴다

- 각 태그마다의 속성을 알아야 한다

 

JSTL 예제 1

코어 라이브러리

set 태그 / EL 출력

- JSTL/Core/ex1/setTag.jsp (중복)

<%@ page contentType = "text/html; charset=utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<c:set var="num1" value="${20}" />
<c:set var="num2">
10.5
</c:set>
<c:set var="today" value="<%= new java.util.Date() %>" />

<html>
	<head>
		<title>set 태그와 remove 태그</title>
	</head>

	<body>
		변수 num1 = ${num1} <br>
		변수 num2 = ${num2} <br>
		num1 + num2 = ${num1 + num2} <br>
		오늘은 ${today} 입니다.

		<c:remove var="num1" scope="page" />

		<p>
		삭제한 후의 num1 = ${num1} <br>
		삭제한 후의 num1 + num2 = ${num1 + num2}
	</body>
</html>

- 한글 인코딩을 가장 위에서 하고 있다

- 사용자 정의 태그로 코어(Core) 라이브러리를 불러옴

 

코어 라이브러리 불러오기 (setTag.jsp 부분)

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

- JSTL 태그를 쓰기 위해서는 그 태그가 포함된 라이브러리를 먼저 불러와야한다

- 이 코드가 없으면 에러는 없지만 변수 값 등이 출력이 되지 않음

- 해당 라이브러리의 uri 를 모르면 Ctrl + Space 중 선택한다

 

태그 사용하기 (setTag.jsp 부분)

- 태그앞에 접두어를 붙여야한다

 

set 태그 : 변수 정의 (setTag.jsp 부분)

<c:set var="num1" value="${20}" />

- num1 변수를 정의하면서 num1 에 20 을 할당하고 있다

- 아래와 똑같다

<c:set var="num1" value="20" />

- 또는 set 태그 사이에 값을 입력해도 값이 저장된다.

<c:set var="num2">
10.5
</c:set>

- 변수 선언 하고 객체를 저장할 수도 있다

<c:set var="today" value="<%= new java.util.Date() %>" />

- today 변수에는 Date 객체가 저장되었다

 

변수 값 출력 (setTag.jsp 부분)

		변수 num1 = ${num1} <br>
		변수 num2 = ${num2} <br>
		num1 + num2 = ${num1 + num2} <br>
		오늘은 ${today} 입니다.

- 출력시 JSTL 과 EL 이 같이 쓰인다 , 지금은 EL 로 출력하고 있다.

- 첫 줄은 num1 변수안의 값을 출력하고 있다

- 일반적인 JSP 변수를 ${} (EL) 안에 쓰면 출력되지 않지만 JSTL 로 정의된 변수를 ${} (EL)안에 쓰면 출력된다

- num1, num2, today 는 JSTL 로 정의된 변수이므로 ${} (EL) 안에서 출력된다

- today 변수 출력시 영미권 날짜 시간 형식으로 출력된다.
- JSTL 로 정의된 변수는 표현식 태그로 출력할 수 없고, EL 로 출력해야한다

+ EL 안 에서 산술연산을 포함한 각종 연산이 된다.

 

변수 삭제 (제거) (setTag.jsp 부분)

		<c:remove var="num1" scope="page" />

- remove 태그는 코어(Core) 라이브러리에 포함된 라이브러리 이므로 접두어 쓰기

- var = "삭제할 변수명" scope ="4가지 영역 중 하나"

		삭제한 후의 num1 = ${num1} <br>
		삭제한 후의 num1 + num2 = ${num1 + num2}

- num1 이 삭제되었으므로 아무값도 나타나지 않음, 오류는 발생하지 않고 나타나지 않음

- num1 이 삭제되더라도 num1 + num2 에서 오류가 발생하는 것이 아니라 num2 값만 나옴

 

- 실행 결과 캡처


EL 에서 출력 가능한 변수

- 일반적인 변수는 ${} 에서 출력불가

- 일반적인 변수더라도 request 로 공유한 변수는 ${} 에서 출력 가능

- JSTL 로 정의한 변수는 ${} 에서 출력 가능

+ JSTL 로 정의한 변수는 EL 태그로만 출력 가능하다!

 

1. 일반적인 JSP 변수를 EL 로 출력시 출력되지 않음

		<% 
			String str = "JSP변수";
		%>
		변수 : str1 = <%=str %> <br> <!-- "JSP변수" 출력 -->
		변수 : str2 = ${str} <br> <!-- 출력 안됨 -->

- 출력되지 않음, 오류가 발생하진 않음

 

2. 일반적인 JSP 변수를 EL 로 출력하고 싶으면 공유 설정해야함

		<% 
			String str = "JSP변수";
			request.setAttribute("st", str);	// 공유 설정
		%>
		변수1 : str1 = <%=str %> <br> <!-- "JSP변수" 출력 -->
		변수2 : str2 = ${str} <br> <!-- 출력 안됨 -->
		변수3 : str3 = ${st} <br> <!-- "JSP변수" 출력-->

- 현재는 현재 페이지에서 공유 설정하므로 page 부터 모든 4개 영역 공유 설정해도 유효 영역이므로 EL 로 출력 가능

- 지금은 현재 request 로 공유 설정

- 공유된 값은 EL로 출력됨

 

+ EL을 쓰지 않고 str3 와 같은 결과를 출력하려면?

- 아래는 EL 태그를 써서 간략하게 공유된 값을 출력하고 있다

		변수3 : str3 = ${st} <br> <!-- "JSP변수" 출력-->

- EL 태그를 쓰지 않고 이것과 같은 코드는 아래

		<%
			String s = (String) request.getAttribute("st");
		%>
		변수4 : str4 = <%=s %> <br>	<!-- "JSP변수" 출력 -->

- EL 태그를 쓰지 않고 공유된 값을 출력하고 싶으면 먼저 getAttribute() 로 다운캐스팅해서 변수에 저장

- 이후 그 변수를 표현식 태그로 출력

+ getAttribute() 는 Object 를 돌려주므로 안의 값의 자료형인 String 형으로 다운캐스팅 명시해야함

3. JSTL set 태그로 정의된 변수

<c:set var="num1" value="${20}" />

- JSTL set 태그로 정의된 변수 num1

		변수 num1 = ${num1} <br>

- EL 태그로 출력 가능하다

 

EL 로 출력가능 한 것

1. set 태그로 정의한 변수

2. 현재 공유 설정된 변수

- 이 두가지 밖에 없다


if 태그

- if문과 같은 역할

- JSTL 에는 if 태그만 있다, if-else, if-else if 없음

- 형식 :

- 조건식이 참이면 if 태그 안의 내용이 실행되고, 조건식이 거짓이면 if 태그 안의 내용이 실행되지 않음

 

JSTL 예제 2

코어 라이브러리

if 태그

- JSTL/Core/ex2/if/ifTagForm.jsp

<%@ page contentType = "text/html; charset=utf-8" %>

<html>	
	<body>
	<form method=post action=ifTag.jsp>
	이름 <input type=text name=name><br>
	나이<input type=text name=age><br>
	<input type=submit value="전송">
	</form>
	</body>
</html>

 

- action으로 지정된 ifTag.jsp 파일을 생성하자

- JSTL/Core/ex2/if/ifTag.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<c:if test="true">
	무조건 실행 <br>
</c:if>

<c:if test="${param.name == 'toto'}">
	당신의 이름은 ${param.name } 입니다. <br>
</c:if>

<c:if test="${param.age >= 20}">
	당신의 나이는 20세 이상 입니다.
</c:if>

- EL 에선 request.getParamter() 대신 객체 param 을 사용 가능

 

taglib 추가 : 코어 라이브러리 추가 (ifTag.jsp 부분)

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

 

if 태그 사용 1 (ifTag.jsp 부분)

<c:if test="true">
	무조건 실행 <br>
</c:if>

 

if 태그 사용 2 / EL 객체 param (ifTag.jsp 부분)

<c:if test="${param.name == 'toto'}">
	당신의 이름은 ${param.name } 입니다. <br>
</c:if>

- param 은 EL 에 쓸 수 있는 객체

- test 속성은 바깥쪽에 " ", 안쪽엔 ' ' 를 써야함

- EL에서는 equals() 가 아닌 비교연산자 == 를 써야함

- EL로 조건식을 쓰고 있다

- param.name 은 request.getParameter("name") 과 같다

- 즉 이름 입력양식에 사용자가 'toto' 를 입력한 경우에만 아래의 문장이 출력된다.

 

if 태그 사용 3 / EL 객체 param (ifTag.jsp 부분)

<c:if test="${param.age >= 20}">
	당신의 나이는 20세 이상 입니다.
</c:if>

- param.name 은 request.getParameter("age") 과 같다

- EL 안에서 비교연산자를 사용할 수 있다

- 나이 입력 양식에 20 이상의 값을 입력한 경우에 if 태그 안의 문장이 실행되어 아래처럼 출력됨


choose - when - otherwise 태그

- if만 있고 if-else 나 if-else if 기능을 하는 태그는 없다

- 다만 if-else if 와 비슷한 역할을 수행하는 태그가 있다, 조건식을 여러개 쓸 때 사용하는 태그가 있다

- choose - when - otherwise 태그를 써서 조건식을 여러개 쓸 수 있다

- switch-case 문이나 if-else if문과 비슷

- 형식 : 

- 조건식을 쓸 때는 if 태그가 아닌 when 태그로 조건식을 만듬, if 대신 when 이 사용됨

- 조건식이 참이면 when 태그 안의 내용을 실행함

- when 태그를 여러개 써서 조건식을 여러개 사용한다

- 맨 마지막에 쓰이는 otherwise 태그는 switch-case문의 default 와 같은 역할, otherwise는 쓰지 않아도 됨

 

choose - when - otherwise 태그 주의

- 조건식들이 여러개 오는 경우 여러개 조건식을 모두 만족한다고 다 실행되지 않음

- if-else if 문과 마찬가지로 가장 먼저 만족하는 위의 조건 태그 안의 내용 1개만 실행하고 choose 태그를 빠져나감

- 즉, if 문이 여러개 있는게 아니라 if-else if 문과 같은 방식으로 동작

- 위의 조건을 다 만족하지 않을때는 otherwise 태그 안의 내용을 실행

 

JSTL 예제 3

코어 라이브러리

choose - when - otherwise 태그

- JSTL/Core/ex3/choose/ifTagForm.jsp

<%@ page contentType = "text/html; charset=utf-8" %>

<html>	
	<body>
	<form method=post action=chooseTag.jsp>
	이름 <input type=text name=name><br>
	나이<input type=text name=age><br>
	<input type=submit value="전송">
	</form>
	</body>
</html>

 

- action으로 지정된 chooseTag.jsp 파일을 생성하자

 

- JSTL/Core/ex3/choose/chooseTag.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

<ul>
	<c:choose>
		<c:when test="${param.name == 'toto'}">
			<li>당신의 이름은 ${param.name } 입니다.</li>
		</c:when>
		<c:when test="${param.age >= 20}">
			<li>당신의 나이는 20세 이상 입니다.</li>
		</c:when>
		<c:otherwise>
			<li>당신의 이름은 'toto'가 아니고, 나이는 20세 이상이 아닙니다.</li>
		</c:otherwise>
	</c:choose>
</ul>

</body>
</html>

- EL 로 조건식을 쓰고 있다

- 필요하면 when 태그를 계속 추가해서 조건식을 추가하면 된다

- if-else if 처럼 가장 먼저 만족한 조건의 내용을 실행하고 빠져나감

- 마지막은 otherwise 태그로 끝난다,어느 when 태그의 조건식도 만족하지 않으면 실행됨

 

- 모든 when 태그를 만족할 때

- 모든 2개의 when 조건절을 만족하지만 가장 위의 조건을 만족하고, 빠져나가기 때문에 첫번째 when 태그 안의 내용만 실행됨

 

- 모든 when 태그를 만족하지 않을때

- otherwise 태그 안의 내용이 실행됨


JSTL 예제 4

코어 라이브러리

catch 태그, out 태그

- JSTL/Core/ex33/jstl_core_ex1.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>JSTL core 라이브러리 사용 예제 1</title>
</head>
<body>
<c:set var="test" value="Hello JSTL!"/>
<h3>&lt;c:set&gt; 사용 후 : <c:out value="${test}"/></h3>
<h3>&lt;c:set&gt; 사용 후 : ${test}</h3>

<c:remove var="test"/>
<h3>&lt;c:remove&gt; 사용 후 : <c:out value="${test}"/></h3>

<c:catch var="err">
<%=10/0%>
</c:catch>
<h3>&lt;c:catch&gt;로 잡아낸 오류 : <c:out value="${err}"/></h3>

<c:if test="${5<10}">
<h3>5는 10보다 작다.</h3>
</c:if>
<c:if test="${6+3==9}">
<h3>6 + 3 은 9이다.</h3>
</c:if>

<c:choose>
	<c:when test="${5+10==50}">
		<h3>5+10은 50이다.</h3>
	</c:when>
	
	<c:otherwise>
		<h3>5+10은 50이 아니다.</h3>
	</c:otherwise>
</c:choose>
</body>
</html>

- 코어 라이브러리를 불러오고 있다

- body 태그 안에서 set 태그로 test 란 변수 정의

 

< , > 출력 (jstl_core_ex1.jsp 부분)

<h3>&lt;c:set&gt; 사용 후 : <c:out value="${test}"/></h3>

- 그냥 < 나 > 를 쓰면 동작하므로 부등호기호를 만들어서 <, > 를 출력하기 위해서 사용

- $lt; 는 '<' 를 만들어줌 (less than)

- $gt; 는 '>' 를 만들어줌 (greater than)

 

out 태그로 브라우저 출력 (jstl_core_ex1.jsp 부분)

<h3>&lt;c:set&gt; 사용 후 : <c:out value="${test}"/></h3>

- EL로 출력시켜도 되지만 이렇게 JSTL out 태그로 브라우저에 출력시켜도 된다

- value 속성값으로 출력시킬 값을 적는다

 

- 아래와 같은 역할

<h3>&lt;c:set&gt; 사용 후 : ${test}</h3>

- 변수 test는 JSTL set 태그로 정의한 변수 이므로 EL을 써서도 출력 가능

 

+ remove 태그로 test 변수 삭제 후 out 객체로 출력시 아무런 값이 출력되지 않음

<c:remove var="test"/>
<h3>&lt;c:remove&gt; 사용 후 : <c:out value="${test}"/></h3>

 

catch 태그로 예외처리 (jstl_core_ex1.jsp 부분)

<c:catch var="err">
<%=10/0%>
</c:catch>
<h3>&lt;c:catch&gt;로 잡아낸 오류 : <c:out value="${err}"/></h3>

- 0 으로 나눌때 발생하는 아리스매틱 예외를 catch 태그를 사용해서 err 변수로 잡는다

- catch 태그 안에 예외가 발생할 수 있는 가능성이 있는 문장을 넣음

- 그 err 변수를 출력하면 어떤 예외가 발생했는지 확인 가능

 

- 전체 출력 캡처


forEach 태그

- for문과 같은 역할을 하는 태그

- 크게 2가지 형식이 있다

- 첫번째 형식 :

- var 속성으로 forEach 에서 사용할 변수 지정

- begin 속성으로 시작값을 지정, end 속성으로 끝 값을 지정

- step 속성으로 증가치 설정, step 속성 생략시 기본 증가치는 1

- 반드시 값 좌우에는 " " 가 있어야 정상 동작한다, 숫자더라도 속성값은 " " 붙여야함 

- 반복문이 돌아가며 forEach 태그 안의 내용이 출력됨

 

- 두번째 형식 : 

- items 속성값으로는 순차적인 자료구조가 온다. ex) 배열, 리스트

- 이 배열, 리스트를 forEach문 바깥 위에서 정의하고 바로 itmes 자리에 들어가면 안됨

- 배열과 리스트를 먼저 request 객체로 공유하고, 공유한 값이 이 items 자리에 들어가야한다

- 또는 set 태그로 정의된 배열, 변수명 등도 items 속성값 자리에 올 수 있다

 

- 두가지 형식이 모두 자주 사용됨

 

JSTL 예제 5

코어 라이브러리

forEach 태그

- JSTL/Core/ex4/forEach/forEachTag.jsp

<%@page import="java.util.ArrayList"%>
<%@page import="java.util.List"%>
<%@ page contentType = "text/html; charset=utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
    java.util.HashMap mapData = new java.util.HashMap();
    mapData.put("name", "최범균");
    mapData.put("today", new java.util.Date());
%>
<c:set var="intArray" value="<%= new int[] {1,2,3,4,5} %>" />
<c:set var="map" value="<%= mapData %>" />
<html>
<head><title>forEach 태그</title></head>
<body>
<h4>1부터 100까지 홀수의 합</h4>
<c:set var="sum" value="0" />
<c:forEach var="i" begin="1" end="100" step="2">
<c:set var="sum" value="${sum + i}" />
</c:forEach>
결과 = ${sum}

<h4>구구단: 4단</h4>
<ul>
<c:forEach var="i" begin="1" end="9">
   <li>4 * ${i} = ${4 * i}
</c:forEach>
</ul>

<h4>int형 배열</h4>

<c:forEach var="i" items="${intArray}" begin="2" end="4">
    [${i}]
</c:forEach>

<h4>Map</h4>

<c:forEach var="i" items="${map}">
    ${i.key} = ${i.value}<br>
</c:forEach>
<br><br>

<%
	List list = new ArrayList();
	list.add("자바");
	list.add("웹표준");
	list.add("JSP");
	list.add("오라클");
	list.add("스프링");
	list.add("파이썬");
	list.add("텐서플로우");
	
	request.setAttribute("slist",list);	// 공유설정
%>
<!-- 방법1. -->
<c:forEach var="s" items="${slist}">
	${s} <br>
</c:forEach>
<br><br>

<%
	// 기존 코드로 처리
	List li = (List) request.getAttribute("slist");
	for(int i=0; i<li.size(); i++) {
		String str = (String) li.get(i);
		out.println(str + "<br>");
	}
%>
<br><br>

<!-- 방법2. -->
<c:set var="s1" value="<%=list %>"/>
<c:forEach var="s2" items="${s1}">
	${s2} <br>
</c:forEach>

</body>
</html>

- 코어 라이브러리를 taglib 로 불러옴

- 스크립틀릿 태그로 Map 객체를 생성하고, 키, 밸류 쌍으로 2개의 데이터를 넣음

ex) 키 "name", 밸류 "최범균"

- 1 부터 5까지 원소를 가진 int 형 배열을 가지고 있는 변수 intArray 정의

- Map 객체 mapData 를 가지고 있는 변수 map 정의 

 

forEach 태그 사용 1 / forEach 첫번째 형식 (forEachTag.jsp 부분)

<h4>1부터 100까지 홀수의 합</h4>
<c:set var="sum" value="0" />
<c:forEach var="i" begin="1" end="100" step="2">
<c:set var="sum" value="${sum + i}" />
</c:forEach>
결과 = ${sum}

- set 태그로 합을 저장할 변수 sum 을 정의하고 누적에 영향을 주지 않는 0 으로 초기화

- forEach 태그에서 루프를 돌릴 변수 i , 시작값은 1, 끝값은 100, 증가치는 2, 홀수만 누적되는 반복문임

- sum 변수에 누적시키기 위해 sum 변수를 set 태그로 다시 정의하고 sum + i 로 값 누적, sum = sum + i 와 같다


forEach 태그 사용 2 / forEach 첫번째 형식 (forEachTag.jsp 부분)

<h4>구구단: 4단</h4>
<ul>
<c:forEach var="i" begin="1" end="9">
   <li>4 * ${i} = ${4 * i}
</c:forEach>
</ul>

- 1 부터 9 까지를 i 변수에 하나씩 전달해서 구구단 4단 출력

- forEach 에서 루프를 돌릴 변수인 i 를 출력할때는 그냥 출력해선 안되고 EL 로 출력해야한다.

 

forEach 태그 사용 3 / forEach 두번째 형식 (forEachTag.jsp 부분)

<h4>int형 배열</h4>

<c:forEach var="i" items="${intArray}" begin="2" end="4">
    [${i}]
</c:forEach>

- items 속성도 쓰고 있고, begin, end 속성도 쓰고 있다

<items 속성>

- items 속성값으로는 배열, 리스트가 주로 들어감

- 바로 배열, 리스트가 들어갈 수는 없고 "set 태그로 정의한 변수"가 들어가든지, "request 로 공유한 변수"가 들어가야함

- 지금은 위에서 "set 태그로 정의한 변수"인 intArray 가 items 로 들어감

- set 태그로 정의되었고, intArray 라는 변수가 배열이므로 items 속성값으로 들어갈 수 있다.

- intArray 에서 하나씩 원소를 가져와서 변수 i 에게 전달한다.

<begin 속성, end 속성>

- begin, end 속성이 없을때는 intArray 배열 안의 원소들을 모두 i 로 넘어가서 출력된다

- begin, end 속성이 있을때는 intArray 배열 안의 원소 중 배열 index 가 2부터 4인 원소가 i 로 넘어간다

 

+ 배열에서 첫번째 인덱스는 0

 

forEach 태그 사용 4 / forEach 두번째 형식 (forEachTag.jsp 부분)

<h4>Map</h4>

<c:forEach var="i" items="${map}">
    ${i.key} = ${i.value}<br>
</c:forEach>

- 맵 객체를 가지고 있는 변수 map 이 items 속성으로 들어갔다, 주로 배열, 리스트가 들어가지만 여기선 맵이 들어갔다

- 맵을 통해서 첫번째 데이터부터 i 로 전달되서 key 와 value 값을 출력

- set 태그로 정의한 변수이므로 items 속성에 들어갈 수 있다

 

forEach 태그 사용 5 / forEach 두번째 형식 (forEachTag.jsp 부분)

 

- List 가 items 속성에 들어갔다

- list 에 들어있는 원소값들을 forEach 태그로 출력시키고 있다

+ 이렇게 List 를 공유설정하고 포워딩 한 다음, 포워딩된 곳에서 foEach로 List 를 출력하는 경우가 많다

 

JSP 에서 정의한 list 를 바로 items 에 넣으면?

- list 를 바로 사용하면 출력되지 않음

 

forEach items 속성 사용 방법 2가지

1. 배열이나 리스트를 request 객체로 공유하고 공유 네임값이 items 자리에 와야한다. (forEachTag.jsp 부분)

<%
	List list = new ArrayList();
	list.add("자바");
	list.add("웹표준");
	list.add("JSP");
	list.add("오라클");
	list.add("스프링");
	list.add("파이썬");
	list.add("텐서플로우");
	
	request.setAttribute("slist",list);	// 공유설정
%>
<!-- 방법1. -->
<c:forEach var="s" items="${slist}">
	${s} <br>
</c:forEach>

- itmes 에 공유 네임값인 "slist" 를 적는다

- slist 로부터 원소가 하나씩 s 로 넘어와서 하나씩 출력된다

- 잘 출력된다.

 

+ 위와 같은 기능, forEach 태그를 쓰지 않는 경우

리스트 안의 모든 원소를 출력하는 것을 기존 코드로 처리시 (forEachTag.jsp 부분)

<%
	// 기존 코드로 처리
	List li = (List) request.getAttribute("slist");
	for(int i=0; i<li.size(); i++) {
		String str = (String) li.get(i);
		out.println(str + "<br>");
	}
%>

- 똑같이 출력된다.

 

2. 배열이나 리스트를 set 태그로 정의한 후, 그변수가 items 자리에 와야 한다. (forEachTag.jsp 부분)

<!-- 방법2. -->
<c:set var="s1" value="<%=list %>"/>
<c:forEach var="s2" items="${s1}">
	${s2} <br>
</c:forEach>

- set 태그로 정의된 변수는 items 속성값으로 올 수 있다

- 잘 출력된다.

 

- 전체 출력 캡처

 

실제 활용

- 목록을 가져오는 Service 클래스에서 목록을 가져오기 위한 6개 변수와 리스트를을 request 객체로 공유설정

- 이후 그 리스트 안 DTO 객체들을 View 페이지에서 출력하기 위해 forEach 문의 items 에 리스트 공유 네임값을 쓴다 


 

forTokens 태그

- 자바의 StringTokenizer 과 비슷

- 특정 구분기호로 파싱함

 

JSTL 예제 6

코어 라이브러리

forTokens 태그

- JSTL/Core/ex5/forTokens/forTokensTag.jsp

<%@ page contentType = "text/html; charset=utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>
<head><title>forTokens 태그</title></head>
<body>

콤마와 점을 구분자로 사용:<br>
<c:forTokens var="token" 
             items="빨강색,주황색.노란색.초록색,파랑색,남색.보라색"
             delims=",.">
${token} 
</c:forTokens>

</body>
</html>

-  코어 라이브러리를 taglib 태그로 불러옴

- forTokens 태그를 사용

- var 속성값 : 값들을 받기 위한 임의의 변수명

- itmes 속성값 : 어떤 문자열이 있다

- delims 속성값 : 구분기호가 들어감, 여러개 구분기호 사용 가능

- 여기서는 , 와 . 으로 잘라서 token 변수로 전달하고, 그걸 EL로 출력한다

+ forEach, forTokens 에서 값들을 받는 변수는 그 태그 사이에서 EL 로 출력함


JSTL 예제 7

코어 라이브러리

forEach 태그, forTokens 태그

- JSTL/Core/ex44/jstl_core_ex2.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>JSTL core 라이브러리 사용 예제 2</title>
</head>
<body>
<c:forEach var="test" begin="1" end="10" step="2">
	<b>${test}</b>&nbsp;
</c:forEach>
<br>
<c:forTokens var="alphabet" items="a,b,c,d,e,f,g,h,i,j,k,l,m,n" delims=",">
	<b>${alphabet}</b>&nbsp;
</c:forTokens>
<br>
<c:set var="data" value="홍길동,김길동,고길동"/>
<c:forTokens var="varData" items="${data}" delims=",">
	<b>${varData}</b>&nbsp;
</c:forTokens>
</body>
</html>

- forEach 태그로 1 ~ 10까지 2씩 증가된 값을 출력하고 있다, 1 3 5 7 9 가 출력됨

- forTokens 태그로 itmes 의 문자들을 , 로 구분해서 파싱해서 alphabet 변수에 저장 후 하나씩 출력

+ forTokens 의 itmes 에 바로 파싱할 문자열을 넣을 수도 있다

- set 태그로 data 변수를 만들어서 "홍길동,김길동,고길동" 값을 할당하고, forTokens itmes 로 data 를 써서 그 문자열을 구분기호 , 로 파싱해서 varData 에 저장 후 하나씩 출력

+ 변수 data 는 set 태그로 정의한 변수이므로 items 로 들어갈 수 있다.

 

+ &nbsp : 수평간격을 벌려줌

+ <b> : bold


기타 코어 라이브러리 태그

- import 태그 : 다른 문서를 불러올 수 있다.


JSTL 라이브러리 : 국제화 라이브러리

- 숫자나 날짜 출력에 사용하는 라이브러리

- SimpleDateFormat 이 사용불가능할때 사용

- formatDate 태그 : 자바의 SimpldDateFormat 과 비슷한 역할

- formatDate 태그를 가장 많이 사용, 원하는 포맷으로 날짜 포맷팅

- formatNumber 태그 : 자바의 DecimalFormat 과 비슷한 역할

- timeZone 태그 : 특정 타임존(시간대)을 지정

 

JSTL 예제 8

국제화 라이브러리

formatDate 태그 / 날짜 포맷 지정

- JSTL/fmt/ex6/use_DateFormat.jsp

<%@ page contentType = "text/html; charset=utf-8" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>
<head><title>numberFormat 태그 사용</title></head>
<body>

<c:set var="now" value="<%= new java.util.Date() %>" />
${now } <br>
<c:out value="${now }"/> <br><br>

<fmt:formatDate value="${now}" type="date" dateStyle="full" /> <br>
<fmt:formatDate value="${now}" type="date" dateStyle="short" /> <br>
<fmt:formatDate value="${now}" type="time" /> <br>
<fmt:formatDate value="${now}" type="both" 
                dateStyle="full" timeStyle="full" /> <br>
<fmt:formatDate value="${now}" pattern="z a h:mm" /> <br>
<fmt:formatDate value="${now}" pattern="yyyy-MM-dd a hh:mm:ss EEE요일"/> <!-- 12시간제 --> <br>
<fmt:formatDate value="${now}" pattern="yyyy-MM-dd a HH:mm:ss EEE요일"/> <!-- 24시간제 -->

</body>
</html>

- 코어 라이브러리와 국제화 라이브러리를 불러오고 있다.

 

국제화 라이브러리 불러오기 (use_DateFormat.jsp 부분)

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

 

- prefix 는 "fmt" 이다

 

- now 변수에 Date 객체 생성해서 저장, now 변수는 현재시간을 가지고 있다.

 

now 변수를 그냥 출력한다면?

- EL 또는 out 객체로 출력

${now } <br>

- 위는 EL 출력, 아래는 out 객체로 출력

<c:out value="${now }"/> <br>

- 영미권 날짜 형식으로 나온다

 

- Model 2 에서는 SimpleDateFormat 과 EL 을 동시 사용 불가

- JSTL 국제화 라이브러리의 DateFormat 태그로 원하는 포맷으로 바꿔야한다

 

formatDate 태그 속성

- 앞에 fmt 란 prefix 를 쓰고 있다, value 속성값이 날짜 시간이 들은 변수가 된다.

- type 속성값으로 "date" 는 연월일(날짜) "time" 은 시분초(시간) "both"는 날짜와 시간이 모두 출력됨

- dateStyle 속성값으로 "full" 이면 복잡, "short" 이면 간략하게 나옴

 

dateStyle = "full" 일때 :날짜가 자세히 출력

dateStyle = "short" 일때 : 날짜가 간략히 출력

type = "time" 일때

- 시간만 출력됨

type = "both" 이고, dateStyle = "full" 이고, timeStyle = "full" 일때

- 날짜, 시간 둘 다 나오고, 둘 다 자세하게 나옴

 

formatDate 태그 패턴 지정

<fmt:formatDate value="${now}" pattern="z a h:mm" /> <br>

- pattern 속성으로 패턴 지정

- z :타임존, a : 오전/오후, h : 시간, mm : 분

- hh 는 12시간제 시간, HH 는 24시간제 시간

<fmt:formatDate value="${now}" pattern="yyyy-MM-dd a hh:mm:ss EEE요일"/> <!-- 12시간제 --> <br>
<fmt:formatDate value="${now}" pattern="yyyy-MM-dd a HH:mm:ss EEE요일"/> <!-- 24시간제 -->


JSTL 예제 9

국제화 라이브러리

timeZone 태그 / 타임존 지정

- JSTL/fmt/ex6/use_timezone_tag.jsp

<%@ page contentType = "text/html; charset=utf-8" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>
<head><title>timeZone 태그 사용</title></head>
<body>

<c:set var="now" value="<%= new java.util.Date() %>" />

<fmt:formatDate value="${now}" type="both" 
                dateStyle="full" timeStyle="full" />

<br>
<fmt:timeZone value="Hongkong">
<fmt:formatDate value="${now}" type="both" 
                dateStyle="full" timeStyle="full" />
</fmt:timeZone>

</body>
</html>

- Date 객체를 가지고 있는 now 변수, 현재 시간 가지고 있다

- formatDate로 날짜, 시간 둘 다 출력하고 둘 다 자세히 출력함

 

- timeZone 태그의 value 로 특정 시간대(타임존)를 지정 가능, 여기선 "Hongkong" 으로 지정

- timeZone 을 홍콩으로 설정하고 날짜를 출력하면 홍콩 타임존의 날짜와 시간이 출력됨

 

- 타임존을 특정 지역으로 설정 후, 날짜 출력하면 특정 타임존의 날짜와 시간 출력 가능

- 위는 한국 시간, 아래는 홍콩 시간


formatNumber 태그

- Java 에서 소수점 자리를 제어할때 사용했던 DecimalFormat 클래스와 유사

- 형식 :

- 여러 속성들이 있다

 

JSTL 예제 10

국제화 라이브러리

formatNumber 태그 / 숫자 포맷 지정

- JSTL/fmt/ex6/use_number_tag.jsp

<%@ page contentType = "text/html; charset=utf-8" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>
<head><title>formatNumber 태그 사용</title></head>
<body>

<c:set var="price" value="10000" />
<fmt:formatNumber value="${price}" type="number" var="numberType" />
숫자: ${numberType} <br>

통화: <fmt:formatNumber value="${price}" type="currency" currencySymbol="$" /><br>
통화: <fmt:formatNumber value="${price}" type="currency" currencySymbol="\\" /><br>

퍼센트: <fmt:formatNumber value="${price}" type="percent" groupingUsed="true" /> <br>
퍼센트: <fmt:formatNumber value="${price}" type="percent" groupingUsed="false" /> <br>

패턴: <fmt:formatNumber value="${price}" pattern="00000000.00"/>

</body>
</html>

- set 태그로 price 변수를 정의하고 10000 으로 초기화

- formatNumber 태그로 이 10000 을 어떻게 출력할지 지정

- type 이 "number" 면 숫자 단위로 출력하겠다는 의미

- var 속성값은 변수명

 

숫자로 출력

<fmt:formatNumber value="${price}" type="number" var="numberType" />
숫자: ${numberType} <br>

- price 에 저장된 값을 숫자단위로 받아서 numberType 이라는 변수에 저장

- 이 numbeType 변수를 출력하면 일반적인 숫자가 출력됨

 

통화 단위로 출력

통화: <fmt:formatNumber value="${price}" type="currency" currencySymbol="$" /><br>
통화: <fmt:formatNumber value="${price}" type="currency" currencySymbol="\\" /><br>

- currencySymbol 속성으로 통화 단위 지정

+ 원(won) 출력하려면 "\\" 으로 역슬래시를 두번

 

퍼센트 % 출력

퍼센트: <fmt:formatNumber value="${price}" type="percent" groupingUsed="true" /> <br>
퍼센트: <fmt:formatNumber value="${price}" type="percent" groupingUsed="false" /> <br>

- true 설정시 3자리씩 끊어서 출력, false 설정시 끊어져 있지 않음

 

숫자 패턴 지정

패턴: <fmt:formatNumber value="${price}" pattern="00000000.00"/>

- pattern 속성에 포맷 작성

- 0 으로 패턴 지정 : 자리값이 맞지 않으면 0 을 채워서 출력


JSP 라이브러리 : 함수 라이브러리

- 쉽게 말하면 함수이다

- Java 의 String 클래스의 메소드와 유사한 메소드들이 많다

 

JSTL 예제 11

함수 라이브러리

여러가지 태그들

- JSTL/fn/use_function.jsp

<%@ page contentType = "text/html; charset=utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

<html>
<head><title>함수 사용</title></head>
<body>
<c:set var="str1" value="Functions <태그>를 사용합니다. " />
<c:set var="str2" value="사용" />
<c:set var="tokens" value="1,2,3,4,5,6,7,8,9,10" />

length(str1) = ${fn:length(str1)} <br>
toUpperCase(str1) = "${fn:toUpperCase(str1)}" <br>
toLowerCase(str1) = "${fn:toLowerCase(str1)}" <br>
substring(str1, 3, 6) = "${fn:substring(str1, 3, 6)}" <br>
substringAfter(str1, str2) = "${fn:substringAfter(str1, str2)}" <br>
substringBefore(str1, str2) = "${fn:substringBefore(str1, str2)}" <br>
trim(str1) = "${fn:trim(str1)}" <br>
replace(str1, src, dest) = "${fn:replace(str1, " ", "-")}" <br>
indexOf(str1, str2) = "${fn:indexOf(str1, str2)}" <br>
startsWith(str1, str2) = "${fn:startsWith(str1, 'Fun')}" <br>
endsWith(str1, str2) = "${fn:endsWith(str1, "합니다.")}" <br>
contains(str1, str2) = "${fn:contains(str1, str2)}" <br>
containsIgnoreCase(str1, str2) = "${fn:containsIgnoreCase(str1, str2)}" <br>

<c:set var="array" value="${fn:split(tokens, ',')}" />

join(array, "-") = "${fn:join(array, "-")}" <br>
escapeXml(str1) = "${fn:escapeXml(str1)}" <br>

</body>
</html>

- 코어 라이브러리와 함수 라이브러리를 불러오고 있다

- set 태그로 str1,str2,str3 을 정의하며 각각 문자열로 초기화 한다

 

함수 라이브러리 불러오기 (use_function.jsp 부분)

<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

- prefix 가 "fn" 이다

 

함수 사용 형식 (use_function.jsp 부분)

length(str1) = ${fn:length(str1)} <br>

- 함수 라이브러리를 쓸 때는 EL 안 (${}) 에서 쓴다

- prefix 인 fn 을 쓰고 : 다음 함수명을 써서 사용한다

 

함수 종류

- legnth(문자열) : 문자열의 길이 구해줌

- toUpperCase(문자열) : 문자열을 대문자로 변환

- toLowerCase(문자열) : 문자열을 소문자로 변환

- subString(문자열,시작,끝) :문자열 추출

- 인덱스 3 ~ 5까지 str1 문자열을 추출

- substringAfter(문자열 str1, 문자열 str2) : str1의 문자를 str2문자 다음의 문자를 추출

- str1에서 str2 인 '사용' 이라는 문자 다음의 것을 추출

- substringBefor(문자열 str1, 문자열 str2) : str1의 문자를 str문자 이전의 문자를 추출

- trim(문자열) : 공백을 없애줌

- replace(str1, " ", "-") : str1 의 공백을 "-" 으로 치환

 

- 전체 출력 캡처


JSTL 라이브러리 : 데이터베이스 라이브러리

- DB 연동이 되어야한다

- 실습 위해 테이블도 만들어야함

- INSERT, UPDATE, DELETE 는 update 태그, SELECT 는 query 태그 사용

 

JSTL 예제 12

실습 준비

- totoro 계정을 사용할 것이므로 오라클 totoro 계정을 연결해야한다

- 테이블명이 test 로 되어있다, 컬럼은 2개

create table test(
	num number,
	name varchar2(10),
	primary key(num) );

- totoro 계정으로 들어가서 테이블을 생성하자

- WebContent/sql/myoracle.jsp 에서 연결 후 테이블 생성

- 생성 후 확인

- test 테이블이 생성되었다

- num 컬럼, name 컬럼이 있고, num 컬럼이 기본키이다

 

JSTL 예제 12

데이터베이스 라이브러리

여러가지 태그들

- JSTL/sql/ex7/jstl_sql_ex.jsp

<%@ page language="java" contentType="text/html; charset=EUC-KR"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>
<html>
<head>
<title>JSTL sql 라이브러리 사용 예제</title>
</head>
<body>

<sql:setDataSource var="conn" driver="oracle.jdbc.driver.OracleDriver" 
				url="jdbc:oracle:thin:@localhost:1521:xe"
				user="totoro" 
				password="totoro123"/>

<sql:update dataSource="${conn}">
	INSERT INTO test (num, name) VALUES (1, '홍길동')
</sql:update>
<sql:update dataSource="${conn}">
	INSERT INTO test (num, name) VALUES (2, '조준동')
</sql:update>
<sql:update dataSource="${conn}">
	INSERT INTO test (num, name) VALUES (3, '홍길동')
</sql:update>
<sql:update dataSource="${conn}">
	INSERT INTO test (num, name) VALUES (4, '홍길순')
</sql:update>

<sql:query var="rs" dataSource="${conn}">
	SELECT * FROM test WHERE name=?
	<sql:param>홍길동</sql:param>
</sql:query>

<c:forEach var="data" items="${rs.rows}">
	<c:out value="${data['num']}"/>
	<c:out value="${data['name']}"/>
	<br>
</c:forEach>

</body>
</html>

- 연결하고, insert 수행하고, select 수행하고 있다

- INSERT, UPDATE, DELETE 는 update 태그, SELECT 는 query 태그 사용

 

데이터베이스 라이브러리 부르기

<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>

- prefix 는 "sql"

 

DB 접속 설정

<sql:setDataSource var="conn" driver="oracle.jdbc.driver.OracleDriver" 
				url="jdbc:oracle:thin:@localhost:1521:xe"
				user="totoro" 
				password="totoro123"/>

- user 속성과 password 를 실제 오라클 계정과 유저네임과 비밀번호로 설정

- 정보가 모두 일치하면 실행 시 하나의 Connection 객체가 생성된다

 

INSERT 수행

<sql:update dataSource="${conn}">
	INSERT INTO test (num, name) VALUES (1, '홍길동')
</sql:update>
<sql:update dataSource="${conn}">
	INSERT INTO test (num, name) VALUES (2, '조준동')
</sql:update>
<sql:update dataSource="${conn}">
	INSERT INTO test (num, name) VALUES (3, '홍길동')
</sql:update>
<sql:update dataSource="${conn}">
	INSERT INTO test (num, name) VALUES (4, '홍길순')
</sql:update>

- update 태그로 INSERT 수행

- dateSource 속성에 아까 만든 Connection 객체 conn 을 넣는다

- 삽입 확인

 

SELECT 수행

<sql:query var="rs" dataSource="${conn}">
	SELECT * FROM test WHERE name=?
	<sql:param>홍길동</sql:param>
</sql:query>

<c:forEach var="data" items="${rs.rows}">
	<c:out value="${data['num']}"/>
	<c:out value="${data['name']}"/>
	<br>
</c:forEach>

- ? 자리에 값을 설정할때 param 태그 사용,

- 즉 name 이 "홍길동" 인 데이터를 검색

- rs 가 검색결과를 받는다, Java의 ResultSet 과 비슷

- forEach 문의 itmes 에 rs.rows 로 한 행씩 가져와서 한 행의 'num' 컬럼의 값, 'name' 컬럼의 값을 출력하고 있다

 

- 1번만 실행해야한다, 결과 캡처

 

 


- Java Servlet, EL, JSTL 끝났다

- 이제 Model 2 로 완성된 프로그램 만들 것

- Controller 클래스 1개 Java Servlet 클래스로 만들기

- Service 클래스 여러개 만들기, 상속을 통해 통일성 있게 구현

- DTO, DAO 는 기존 Model 1 의 DAO, DTO 와 같다


 

JSP Model 2 회원관리 프로그램 만들기

 

회원관리 프로그램 : 주요 기능 소개

1. Connection Pool

2. request, session 객체 공유 설정

- 로그인 성공시 session 공유 시작, 로그아웃시 session 강제로 삭제

- 출력할 것들을 request 객체로 공유 설정

3. Controller 클래스 : Java Servlet

4. Model = Service + DAO

- Service, DTO, DAO 클래스

5. View (화면 인터페이스)

- 공유된 값을 EL , JSTL 사용해서 출력

 

회원관리 프로그램 : 프로그램 구조 설명, 구조도

- 이렇게 구조를 만들기 위해

- Model 2 에서는 DAO, DTO 가 다른 패키지에 있다

- 같은 기능을 하는 클래스들끼리 묶어서 패키지 안에 저장

- controller 패키지 안에 Controller 클래스, dao 패키지 안에 DAO 클래스, model 패키지 안에 Model 클래스(DTO)

- service 패키지 안에 Service 클래스들

 

<환경 설정>

- WebContent 에 폼, 유효성 검사 되어있는 파일이 있는 폴더 member 넣기

- META-INF 에 context.xml

- 자료실 위해서 cos.jar, JSTL 쓰려면 jstl, 오라클 쓰려면 ojdbc6.jar

<Controller 클래스>

- Controller 클래스는 Java Servlet 클래스로 만듬

- Controller 클래스 WebServlet 어노테이션에 패턴 지정, .do 하면 무조건 여기로 찾아옴

<Service 클래스>

- Service 클래스의 부모 인터페이스는 Action.java, 추상메소드 execute() 만을 가짐

- 그 인터페이스를 상속받은 많은 구현 클래스들을 만듬

- 어떤 방식으로 포워딩할지 결정하는 Service 클래스 : ActionForward.java

 

회원관리 프로그램 : 프로젝트 생성

- model2member 라는 이름으로 새로운 프로젝트 만들기

- 초기에 보여줄 파일인 index.jsp 를 WebContent 폴더 안에 생성

 

회원관리 프로그램 : HTML, Javascript 파일 가져오기

- 폼과 유효성 검사가 되어있는 파일을 가져오기

- 이 기본 파일들을 바탕으로 프로그램 구현

 

회원관리 프로그램 : 몇가지 환경 구축

1) WebContent/WEB-INF/lib 폴더 하위에 라이브러리 3개 넣기

- 자료실 위해서 cos.jar, JSTL 쓰려면 jstl-1.2.jar, 오라클 쓰려면 ojdbc6.jar 라이브러리

 

2) member 폴더 안의 context.xml 파일을 META-INF 폴더로 옮기기

- 커넥션 풀의 환경설정 파일이다

- context.xml

<Context> 
  <Resource name="jdbc/orcl" 
   auth="Container"
   type="javax.sql.DataSource" 
   username="totoro" 
   password="totoro123"
   driverClassName="oracle.jdbc.driver.OracleDriver"
   factory="org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory"
   url="jdbc:oracle:thin:@localhost:1521:xe"
   maxActive="500"  
   maxIdle="100"/>  
</Context>

<!--  p423 참조
 maxActive="500"  컨넥션풀이 관리하는 컨넥션의 최대 갯수
		          (기본값 : 8)
 maxIdle="100" 	  컨넥션풀이 관리하는 최대 유휴 갯수(기본값 : 8)          
-->

- 500 개의 커넥션이 미리 연결되어있다

 

회원관리 프로그램 : Connection Pool 테스트

- dbcpAPITest.jsp 파일을 실행해서 커넥션 풀에서 커넥션 가져오기 테스트

<%@ page language="java" contentType="text/html; charset=EUC-KR"%>
<%@ page import="java.sql.*"%>
<%@ page import="javax.sql.*" %>
<%@ page import="javax.naming.*" %>
<%
 	Connection conn = null; 
	
	try {
  		Context init = new InitialContext();
  		DataSource ds = (DataSource) init.lookup("java:comp/env/jdbc/orcl");
  		conn = ds.getConnection();
  		
  		out.println("<h3>연결되었습니다.</h3>");
	}catch(Exception e){
		out.println("<h3>연결에 실패하였습니다.</h3>");
		e.printStackTrace();
 	}
%>

- 커넥션 풀에서 커넥션 가져오기 테스트 성공

+ 추후 DAO 클래스 만들때 커넥션풀에서 커넥션 구하기 3줄 코드를 DAO 클래스에 넣을 것

  		Context init = new InitialContext();
  		DataSource ds = (DataSource) init.lookup("java:comp/env/jdbc/orcl");
  		conn = ds.getConnection();

 

회원관리 프로그램 : member2 테이블 생성

- totoro 오라클 계정에 member2 테이블을 생성

- Model 1 시간에 만들었던 회원 관리 프로그램과 같은 테이블명만 다르다

create table member2(
id varchar2(20) primary key,
passwd varchar2(20) not null,
name varchar2(20) not null,
jumin1 varchar2(6) not null,
jumin2 varchar2(7) not null,
mailid varchar2(30), 
domain varchar2(30), 
tel1 varchar2(5),
tel2 varchar2(5),
tel3 varchar2(5),
phone1 varchar2(5),
phone2 varchar2(5),
phone3 varchar2(5),
post varchar2(10),
address varchar2(200),
gender varchar2(20),
hobby varchar2(50),
intro varchar2(2000),
register timestamp );

- WebContent 하위에 sql 폴더를 만들고 안에 member.sql 파일 생성

- 커넥션 프로파일을 Oracle_11, New Oracle(totoro), xe 로 해준다

- member2 테이블을 생성

- member2 테이블 생성 확인

 

회원관리 프로그램 : DAO 와 DTO 클래스 만들기

- Model 2 에서는 DAO, DTO 의 패키지가 달라진다

- 해당 클래스를 사용할때 반드시 import 를 해야하고, 접근제어자는 public 이어야한다

 

- 프로그램 구조도 대로 src안에 model 패키지를 만들고 그 안에 DTO 클래스를 만든다
- 프로그램 구조도 대로 src안에 dao 패키지를 만들고 그 안에 DAO 클래스를 만든다

 

회원관리 프로그램 : DTO 클래스 작성

- 테이블 member2 create 문에서 가져와서 DTO 클래스에 복붙 후 수정

- varchar2 는 String 으로, number 는 Int 로, timestamp 는 Timestamp로 바꿔서 자바 변수(프로퍼티) 만들기

+ 프로퍼티의 접근제어자는 private

+ java.sql 의 Timestamp import

- 이후 getter / setter 메소드 추가

 

- DTO 클래스 완성 코드

- MemberDTO.java

// DTO (Date Transfer Object)
package model;

import java.sql.Timestamp;

public class MemberDTO {
	private String id;
	private String passwd;
	private String name;
	private String jumin1;
	private String jumin2;
	private String mailid; 
	private String domain;
	private String tel1;
	private String tel2;
	private String tel3;
	private String phone1;
	private String phone2;
	private String phone3;
	private String post;
	private String address;
	private String gender;
	private String hobby;
	private String intro;
	private Timestamp register;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPasswd() {
		return passwd;
	}
	public void setPasswd(String passwd) {
		this.passwd = passwd;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getJumin1() {
		return jumin1;
	}
	public void setJumin1(String jumin1) {
		this.jumin1 = jumin1;
	}
	public String getJumin2() {
		return jumin2;
	}
	public void setJumin2(String jumin2) {
		this.jumin2 = jumin2;
	}
	public String getMailid() {
		return mailid;
	}
	public void setMailid(String mailid) {
		this.mailid = mailid;
	}
	public String getDomain() {
		return domain;
	}
	public void setDomain(String domain) {
		this.domain = domain;
	}
	public String getTel1() {
		return tel1;
	}
	public void setTel1(String tel1) {
		this.tel1 = tel1;
	}
	public String getTel2() {
		return tel2;
	}
	public void setTel2(String tel2) {
		this.tel2 = tel2;
	}
	public String getTel3() {
		return tel3;
	}
	public void setTel3(String tel3) {
		this.tel3 = tel3;
	}
	public String getPhone1() {
		return phone1;
	}
	public void setPhone1(String phone1) {
		this.phone1 = phone1;
	}
	public String getPhone2() {
		return phone2;
	}
	public void setPhone2(String phone2) {
		this.phone2 = phone2;
	}
	public String getPhone3() {
		return phone3;
	}
	public void setPhone3(String phone3) {
		this.phone3 = phone3;
	}
	public String getPost() {
		return post;
	}
	public void setPost(String post) {
		this.post = post;
	}
	public String getAddress() {
		return address;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	public String getGender() {
		return gender;
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	public String getHobby() {
		return hobby;
	}
	public void setHobby(String hobby) {
		this.hobby = hobby;
	}
	public String getIntro() {
		return intro;
	}
	public void setIntro(String intro) {
		this.intro = intro;
	}
	public Timestamp getRegister() {
		return register;
	}
	public void setRegister(Timestamp register) {
		this.register = register;
	}
	
}

 

회원관리 프로그램 : DAO 클래스 작성

DAO 에 들어갈 내용

1. 싱글톤

2.  정적메소드 getInstance() 생성

3. Connection Pool 에서 커넥션 구해오는 메소드

4. 그 이후 회원가입, 중복검사, 탈퇴 등의 메소드

 

1. 싱글톤

- 자기자신의 클래스로 객체 생성을 한번만 하고 static 을 붙여 공유 , private 설정

 

2. 정적메소드 getInstance() 생성

 

3. Connection Pool 에서 커넥션 구해오는 메소드

- 메소드 호출시 Connection Pool 에서 커넥션을 구해주도록 함

- 커넥션 구할때 예외처리를 해야한다, 여기선 throws 로 Exception 던지기

+ Connection 클래스 import

- 테스트할때 사용했던 이 3줄을 복사해서 넣는다

- 단축키 Ctrl + Shift + O 로 import 시키기

- javax.naming.Context 를 선택후 Next, 다음은 javax.sql.DataSource 선택후 Finish

+ Context 와 DataSource 둘 다 Interface 이다

- getConnection() 메소드 완성

- 가져온 커넥션을 리턴하는 코드로 바꿈

+ 리턴자료형을 private 으로 해서 DAO 클래스 내에서만 호출가능하도록 함

 

- 현재까지 DAO 클래스 BoardDBBean.java 코드

// DAO (Date Access Object)
package dao;

import java.sql.Connection;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class MemberDAO {

	// 싱글톤 : 객체 생성을 한번만 수행 하는 것.
	private static MemberDAO instance = new MemberDAO();
	
	public static MemberDAO getInstance() {
		return instance;
	}
	
	// 커넥션풀에서 커넥션을 구해오는 메소드
	private Connection getConnection() throws Exception {
 		Context init = new InitialContext();
  		DataSource ds = (DataSource) init.lookup("java:comp/env/jdbc/orcl");
  		return ds.getConnection();
	}
	
	// 회원가입
}

 

4. 그 이후 회원가입, 중복검사, 탈퇴 등의 메소드

- 그 이후 회원가입, 중복검사, 탈퇴 등의 메소드가 오면 된다

- 이 DAO 메소드를 "Service 클래스"에서 호출해서 SQL문 실행할 것

- 그래서 값이 Service -> DAO 로 넘어간다

 

회원관리 프로그램 : Controller 클래스

- Java Servlet 클래스로 만든다

- 모든 요청의 진입점

- src에서 오른마우스를 클릭해서 Servlet 선택

- controller 패키지, MemberController 클래스 를 만든다

 

- MemberController.java (수정 전)

package controller;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class MemberController
 */
@WebServlet("*.do")	// do 확장자
public class MemberController extends HttpServlet {
	private static final long serialVersionUID = 1L;

	// doGet(), doPost() 메소드에서 공통적인 작업을 처리하는 메소드
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI : " + requestURI); // requestURI : /model2member/Login.do
		System.out.println("contextPath : " + contextPath); // contextPath : /model2member
		System.out.println("command : " + command); // command : /Login.do
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("get");
		doProcess(request, response);	// doProcess() 호출
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println("post");
		doProcess(request, response);	// doProcess() 호출
	}

}

- 오류 생겼을때 어느정도까지 실행되었는지 확인하기 위해서 System.out.println(); 으로 브라우저에 출력

- doGet() 과 doPost() 에 같은 내용을 써야한다

- 요청 방식에 따라 서로 다른 일을 하는게 더 이상함

- doGet() 과 doPost() 에서 처리할 내용은 동일해야하므로 따로 공통적인 작업을 처리하는 메소드 doProcess() 를 만듬

- 그 메소드 doProcess() 를 doGet(), doPost() 에서 호출하도록 함

- doGet(), doPost() 는 자동으로 호출되는 메소드이므로 doGet(), doPost() 의 매개변수 request, response 를 doProcess() 호출시 전달

- doProcess() 안의 내용은 아래에

 

WebServlet 어노테이션 이름값으로 "*.do" 로 임의로 설정

@WebServlet("*.do")	// do 확장자

- "*.do" 는 do 확장자로 요청하는 요청을 받는다는 의미

- "*.do" 는 앞쪽의 파일명은 어떤 이름이어도 상관없고, 확장자가 do 인 모든 요청을 받는다는 의미

- 이 확장자는 임의로 설정한다, 주로 회사명

 

ex) 네이버에서 확장자는 .naver 사용

 

+ WebContent/memer/memberform.jsp 에서 form 의 action 값을 확인해보자

- 앞의 이름과 상관없이 확장자가 do 이므로 무조건 MemberController 클래스로 찾아감

 

+ WebContent/memer/loginform.jsp 에서 form 의 action 값을 확인해보자

- 앞의 이름과 상관없이 확장자가 do 이므로 무조건 MemberController 클래스로 찾아감

 

- Model 2에서 모든 클라이언트의 요청은 하나의 Controller 클래스로 가야한다

- WebServlet 어노테이션을 "*.do" 로 설정시 클라이언트(폼)에서 do 확장자로 요청하면 무조건 그 요청을 받음

- 이 패턴 *.do 에 맞으면 그곳으로 찾아감

 

doProcess() 안의 내용 (MemberController.java 부분)

	// doGet(), doPost() 메소드에서 공통적인 작업을 처리하는 메소드
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		System.out.println("requestURI : " + requestURI);
		System.out.println("contextPath : " + contextPath);
		System.out.println("command : " + command);
	}

- request 객체에서 제공되는 6개의 메소드 중 getRequestURI() 와 getContextPath() 를 쓰고 있다

- Controller 클래스는 요청을 어떤 서비스 클래스로 넘겨줄지 결정함

- getRequestURI() : 현재 프로젝트명과 요청 URI 를 구해줌 ex) /model12member/Login.do

- getContextPath() : 프로젝트 명을 구해줌 ex) /model12member

- substring() 으로 contextPath 변수(현재 프로젝트명) 의 길이 숫자부터 시작해서 추출

- 추출시 요청 URI 만 남게되고, 이 요청 URI 가 command 변수에 저장됨

 

- loginform.jsp 에서 Controller 클래스로 요청을 해보자

- loginform.jsp 실행하기

- 아무 값이나 로그인 폼에 입력하고 '로그인' 누르기

- 그럼 "/login.do" 로 요청, 확장자가 do 이므로 무조건 Controlelr 클래스로 찾아간다

- 값 전송방식이 post 이므로 doPost() 가 실행됨, doProcess() 호출됨

 

- 콘솔창 확인

- post  : doPost() 가 호출되었다는 의미

- requestURI 는 /model2member/Login.do 가 출력됨

- contextPath 는 /model2memer 가 출력됨

- command 는 /Login.do 가 출력됨

 

Controller 클래스에서 맞는 Service 클래스로 요청을 넘기는 방법 (MemberController.java 부분)

		String command = requestURI.substring(contextPath.length());

- contextPath.length() 는 13 이고, requestURI.substring(13) 은 requestURI 의 인덱스 13부터 쭉 출력하라는 의미

- requestURI는 /model2member/Login.do 이므로 13부터는 /Login.do 이고 이게 command 변수에 저장됨

- command 변수에는 최종적으로 요청 이름값이 저장됨 , 즉 action 값으로 지정된 "/Login.do" 가 저장됨

- 이제 어디서 요청이 왔는지 확인이 가능하다

ex) command 가 "/Login.do" 이면 로그인 페이지 와서 왔음을 알 수 있음

 

- 조건문과 command.equals() 를 사용해서 어디서 온 요청이냐에 따라 전달될 Service 클래스를 지정함

ex) command.equals("/Login.do")

 

+ Controller 클래스는 또한 포워딩 2가지 방법 중 어떤 방식으로 포워딩 될지도 처리함

- Dispatcher 방식으로 포워딩할지, Ridirect 방식으로 포워딩할지 Controller 클래스에서 정한다

 

 

 

 

Model 1 설계 방식

- 클라이언트가 브라우저를 통해 요청

- 서버측 JSP 에서 요청을 받아서 변수에 저장하고, JavaBean (DTO) 객체 생성해서 값을 저장

- DAO로 DB 연동을 처리

- 많은 부분을 JSP 코드로 처리


Model 1 설계 특징

- JSP 만 이용하여 개발하는 경우

- JSP + Java Bean (DTO)을 이용하여 개발하는 경우

- Model2의 Controller 개념이 모호함

 

Model 1 장점

- 개발속도가 빠름

- 개발자의 기술적인 숙련도가 낮아도 배우기 쉬워서 빠르게 적용이 가능함

 

Model 1 단점
- JSP 페이지에서 프레젠테이션 로직과 비즈니스 로직이 혼재되어서 복잡함

- 로직의 혼재(한 파일안에 Java, JSP, HTML 혼용)로 인해서 개발자와 디자이너의 작업 분리가 어려움

- JSP 코드의 복잡도로 인해서 수정, 유지보수가 어려워짐

- 단점들로 인해 Model 1 방식을 잘 사용하지 않음


Model 2 설계 방식 1

- 클라이언트가 브라우저를 통해 요청

- 3가지 영역으로 기능을 분리시켜 개발

- 모든 요청의 진입점 역할을 하는 Controller Class, 중간의 흐름을 제어함, 요청을 어떤 클래스로 넘겨줄지를 처리

- JavaBean 사이에, 즉 2번에 Service Class 를 만들것

+ 위 사진은 대략적인 그림임

 

+ Java Servlet

- Java Servlet 으로 Controller Class 를 만든다

- Java Servlet : 자바로 만들어진 웹 프로그램, HTML, JS 코드도 포함됨, 웹 상에서 사용가능한 서버 기능도 가지고 있다

+ 자바시간에 만든 자바는 Java Application, 내 컴, 자바 가상먼신 에서만 실행된다

 

Model 2 설계 특징

- GUI 개발모델인 MVC를 웹 애플리케이션에 적용하여 구현한 방식

- Application의 역할을 Model – View – Controller로 분리시켜서 개발하는 방식

 

Model 2 장점

- 비즈니스 로직(개발)과 프리젠테이션(디자인)의 분리로 인해 애플리케이션이 명료해지며 유지보수와 확장이 용이함

- 디자이너와 개발자의 작업을 분리해 줌

 

Model 2 단점

- 개발 초기에 아키텍처 디자인을 위한 시간의 소요로 개발 기간이 늘어남

- MVC 구조에 대한 개발자들의 이해가 필요


MVC 패턴

- MVC는 Model / View / Controller 의 약자로 애플리케이션을 세 역할로 나누어서 개발하는 개발 방법론

- View 페이지는 디자이너가 처리, Model 과 Controller Class 는 개발자가 처리

- Spring 등의 프레임워크들도 모두 MVC 패턴으로 되어있다

 

Model

- 애플리케이션의 데이터 처리를 담당함

- Service 클래스 + DAO 클래스로 구현함

 

View

- 화면에 출력되는 페이지

- 사용자 인터페이스를 처리함

- JSP 를 이용해서 구현함 : EL(Expression Language, 표현언어) + JSTL(JSP Standard Tag Library)

 

Controller

- 클라이언트의 요청을 받아 Model과 View사이에서 흐름을 제어한다.

- Java Servlet으로 구현

 

+ Model 2 보다 빠르게 개발하기 위한 프레임 워크 : 쟝고, Spring

+ Model, Controller 는 Java 로 구현


Model 2 MVC 패턴 아키텍처

- 클라이언트가 서버측에 요청함

 

Controller Class

- 모든 요청의 진입점 역할을하는 Controller 클래스가 요청을 받음

- Controller 클래스는 중간의 흐름을 제어

- Java Servlet 으로 Controller Class 를 만든다

- 어떤 Service Class 로 요청을 넘길건지 결정, Controller 에서 Service 로 넘어간다

- 또한 어떤 VIew 페이지로 포워딩 할 것인지 결정

 

Service Class

- 자바로 만들어짐

- Service 클래스를 만들때 상속 사용

- Service 클래스는 여러개를 만드는데 공통적인 내용을 부모 인터페이스로 만들고 나머지는 공통적인 내용을 부모 인터페이스로부터 상속받아서 서비스 클래스를 여러개 만듬

- Service Class에서 DAO Class로 넘어간다

 

DAO

- Model 1 의 코드와 같다

- DB 처리는 모두 DAO 에서 한다

- Service Class + DAO Class = Model = 실제 요청을 처리해줌

 

DTO

- Model 1 의 코드와 같다

- Controller -> Service -> DAO 에서 이동할때 값의 전달은 DTO 객체를 사용함

- 반대로 DAO -> Service -> Controller 로 값을 DTO 객체로 돌려줄 수도 있다

 

공유 설정

- DB에서 검색한 데이터들 DAO에서 Service 로 리턴하고, Service Class 에서 그 데이터를 request  영역 객체로 공유 설정

- 공유한 값들은 Controller 클래스, View 페이지에서 사용(출력)한다

- 이때 표현언어(EL), JSTL 로 출력하면 다운캐스팅을 할 필요 없는 장점이 있다

+ 표현식 태그 대신 표현언어와 JSTL 사용

 

값의 흐름

- 값이 넘어가는 순서 : Controller -> Service -> DAO

- 값을 리턴할때 ( DAO 에서 검색결과를 돌려줄때 ) : DAO -> Service -> Controller -> View에서 출력

 

Model 2 개발을 위해 학습 할 것

- Java Servlet 으로 Controller 클래스 만드므로 Java Servlet

- 결과 출력시 표현식 태그를 대신하는 표현 언어(EL)

- JSTL 도 같이 사용되므로 JSTL

+ EL, JSTL 은 Model 2 뿐 아니라 Spring, Spring Boot 에도 똑같이 사용됨


Java Servlet 클래스

- Java로 작성된 웹프로그램을 의미함

- Java Servlet 클래스에는 HTML, JavaScript 코드를 포함할 수 있다

- Java Servlet 클래스는 웹브라우저로 실행 결과를 출력할 수 있다

- JSP 파일이 내부적으로 Java Servlet 으로 변환됨, 이젠 직접 Java Servlet 을 작성하는 것

- Controller 클래스를 만들때 사용함

- 모든 요청의 진입점 역할

 

Java Servlet 클래스 특징

- 확장자가 java 이므로 src 폴더 안에 넣어야한다.

- 실행시 웹 브라우저에 결과가 바로 나타난다

 

+ 실습 준비

- 새 프로젝트 jsppro 생성

- 이 프로젝트 내에서 Java Servlet, EL, JSTL 실습할 것

- 클라우드의 servlet 폴더를 jsppro/WebContnet 로 복사

- .java 파일은 src 파일에 생성해야함, WebContent 안에선 정상작동하지 않음

- src 폴더에 직접 Java Servlet 파일을 만들어보자

- Perspective 가 Java EE 여야 Java Servlet Class 생성 가능

 

Java Servlet 클래스 생성 / 실습 준비

- Java 이므로 Class 명이 파일명이 된다

- Superclass : 부모 클래스가 된다, HttpServlet가 부모클래스로 자동으로 만들어짐

- Helloworld Class 는 HttpServlet Class 를 내부적으로 상속받음

- Constructors from superclass 체크 해제 : 부모 클래스로 생성자가 만들어지는데, 필요하지 않으므로 체크 해제

- Inherited abstract methods 체크시 : 상속받을 메소드인 부모클래스 HttpServlet 중에서 필요한 메소드만 체크 가능

- 그 메소드 중 doGet 메소드와 doPost 메소드가 필요하므로 체크, 체크시 오버라이딩 됨

- doGet 메소드 체크 시 : 클라이언트가 get방식 요청시 자동으로 실행

- doPost 메소드 체크 시 : 클라이언트가 post방식 요청시 자동으로 실행

- Finish 누르면 src 폴더 내에 java 파일이 완성됨


- 예제 ex111 폴더

 

- HelloWorld.java

package send;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class Helloworld
 */
@WebServlet(description = "처음 작성하는 자바 서블릿", urlPatterns = { "/Helloworld" })
public class Helloworld extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.getWriter().append("Served at: ").append(request.getContextPath());
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}

}

<상속>

- Servlet Class 들은 기본적으로 HttpServlet Class 를 상속함

- 이 클래스는 HttpServlet Class의 doGet, doPost 메소드를 오버라이딩 해서 만들어짐

- extends 로 HttpServlet 클래스를 상속받아서 사용함, import javax.servlet.http.HttpServlet; 를 import 해야함

- import 되어있는 HttpServlet, WebServlet, HttpServletRequest Class 들은 Java EE 클래스로서 Apache Tomcat 에 저장되어있다

- Apache Tomcat 의 servlet-api.jar 라이브러리에 있는 클래스들임

- Java Servlet 클래스 생성 및 실행을 위해서는 반드시 아파치 톰캣이 있어야함

<메소드>

- doGet 메소드 : 클라이언트가 Get 방식 요청시 오버라이딩 메소드가 자동으로 실행됨

- doPost 메소드 : 클라이언트가 Post 방식 요청시 오버라이딩 메소드가 자동으로 실행됨

- 이 클래스 HelloWorld 그냥 실행시, Get 방식이 디폴트이므로, doGet 메소드만 자동 실행됨, doGet 메소드 안의 코드가 브라우저에 출력됨

 

@WebServlet

- 웹 서블릿 어노테이션이라고 부름

- 웹 서블릿 어노테이션, 실행시 이것 덕분에 찾아올 수 있다

- import javax.servlet.annotation.WebServlet; 클래스를 import 해서 사용가능

 

urlPatterns

- 현재 클래스명과 동일하게 자동으로 만들어짐

- 반드시 클래스명과 일치할 필요는 없음, 나중엔 값을 바꿀것

 

description

- 아까 생성시 작성함

 

- HelloWorld.java 실행시

- 기본전송방식이 get 이므로 doGet 메소드 안의 내용이 실행되어서 출력되었다

 

doGet() 오버라이딩 메소드

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.getWriter().append("Served at: ").append(request.getContextPath());
	}

<형식>

- 이 형식을 그대로 사용해야함

- 매개변수가 request 와 response 객체로 되어있고 throws 로 예외를 던짐

- Java Servlet 에는 JSP 와 달리 내장객체가 없다

- 객체 request 와 객체 response 만 받아서 사용 가능하고, out 등의 다른 내장 객체는 없으므로 만들어서 사용해야한다

- 이 HttpServletRequest, HttpServletResponse 클래스를 사용하고 있다, 찾아보면 메소드를 찾아볼 수 있음

<내용>

1. 매개변수로 받은 response 객체의 response.getWriter() 로 out 객체를 구해와서 출력스트림 객체를 만듬

2. 내용(Served at:)을 append 시키고, request.getContextPath() 로 현재 프로젝트명(jsppro)을 구해서 append 로 추가해서 출력시키고 있음

 

- doGet() 메소드를 수정해보자

- WebContent에 있던 HelloWorld.java 의 내용을 src의 HelloWorld.java 로 일부 복사

- HelloWorld.java (doGet() 메소드 수정)

package send;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class Helloworld
 */
@WebServlet(description = "처음 작성하는 자바 서블릿", urlPatterns = { "/Helloworld" })
public class Helloworld extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		
		// 현재 문서의 한글값을 인코딩(utf-8)을 시켜준다.
        response.setContentType("text/html;charset=utf-8");
        
        PrintWriter out = response.getWriter();	// 출력 스트림 객체 생성
        
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>안녕 하세요!!</h1>");
        out.println("<h1>안녕 하세요!!</h1>");
        out.println("</body>");
        out.println("</html>");
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}

}

+ 주의 : @WebServlet 때문에 WebContent 의 HelloWorld.java 가 실행되어있으면 src의 HellowWorld.java 가 실행 안됨

- 현재 클래스를 그냥 실행시 기본 전송방식이 get 방식이므로 오버라이딩 된 doGet() 메소드가 자동 실행됨

 

doGet() 형식

- 이 형식을 그대로 사용해야함

- 매개변수가 request 와 response 객체로 되어있고 throws 로 예외를 던짐

- Java Servlet 에는 JSP 와 달리 내장객체가 없다

- 객체 request 와 객체 response 만 받아서 사용 가능하고, out 등의 다른 내장 객체는 없으므로 만들어서 사용해야한다

- 객체 request 와 객체 response 는 HttpServletRequest, HttpServletResponse 클래스를 사용하고 있다, 찾아보면 메소드를 찾아볼 수 있음

 

+ JSP의 내장객체

- Java Servlet 에는 내장객체가 없다, 쓸 수 있는 것은 doGet 과 doPost 에서 지원되어 매개변수로 전달되는 request, response 만 사용 가능

- 나머지 객체들은 만들어서 사용해야함

- 브라우저에 출력하고자 하면, out 객체를 생성해야함

 

out 객체 생성 방법

        PrintWriter out = response.getWriter();

- response.getWriter() 로 출력스트림 객체를 만듬, 이름을 out 으로 함

- 결과를 돌려줄때는 PrintWriter 클래스로 돌려줌

+ PrintWriter 클래스는 java.io 패키지 안에 있다, import 되어있음

- 브라우저에 출력할때는 out 객체를 생성해야함, JSP 가 아니므로 표현식 태그 사용 불가

 

화면 출력

        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>안녕 하세요!!</h1>");
        out.println("</body>");
        out.println("</html>");

- out.print 나 out.println() 사용해서 출력

- 소스코드에 HTML 코드도 들어갈 수 있고 Javascript 코드도 들어갈 수 있다

+ 한글이 들어가므로 한글 인코딩을 시켜야함

 

한글 인코딩

- 한글이 깨지지 않게 하기 위해 여태껏 JSP 에서는 page 태그의 이 코드를 사용했음

- contentType 속성의 charset 으로 인코딩

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

- 이 역할을 Java Servlet Class 에서는 이 코드를 사용해서 인코딩

	// 현재 문서의 한글값을 인코딩(utf-8)을 시켜준다.
        response.setContentType("text/html;charset=utf-8");

- 이 코드가 있어야 현재 문서의 한글값이 깨지지 않고 utf-8 로 인코딩되어 출력됨

- 이 코드가 없으면 한글값이 깨짐

 

+ get 방식과 post 방식 두가지 중 한가지로 요청이 들어오므로 각각의 경우 실행될 내용을 doGet() 과 doPost() 메소드 안에 작성

 

만들어서 사용할 객체

- 출력을 위해 out 객체 (출력스트림 객체) 만들기 (검색어 : out 객체 생성 방법)

- 회원관리에서 사용하기 위해 session 객체 만들기

 

session 객체 생성 방법

        HttpSession session = request.getSession();	// 세션 객체 생성

- request 객체로 만든다

- HttpSession 클래스로 결과를 돌려준다

- 로그인 성공 후 공유 설정할때 session 객체를 주로 사용


- 예제 ex222 폴더

 

form 을 통해서 요청하기

- 클라이언트가 html 에서 폼을 통해서 get/post 방식으로 Java Servlet 클래스로 찾아간다

- WebContent/ex222 의 Method.html 파일에서 src 폴더의 Method.java Java Servlet Class 로 찾아갈 것

 

- src 폴더에 Method.java 를 만들자

 

실습 준비

- Method.java (수정 전)

- 웹 서블릿 어노테이션이 만들어져있고 이 이름값 "/Method" 로 찾아오는 것임

- 기본적으로 HttpServlet Class 를 상속함

 

- 이제 WebContent/ex222 안의 Method.java doGet(), doPost() 안의 내용을 복붙해서 src 안의 Method.java 로 복사

- Method.java (수정 후)

package send;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class Method
 */
@WebServlet("/Method")
public class Method extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		out.println("<h1>get 방식으로 처리됨</h1>");
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		out.println("<h1>post 방식으로 처리됨</h1>");
	}

}

- 클라이언트에서 get 방식 요청시 오버라이딩 된 doGet() 메소드 자동 실행

- 클라이언트에서 get 방식 요청시 오버라이딩 된 doPost() 메소드 자동 실행

- 두 메소드의 내용에서 공통적으로 한글 인코딩, 출력스트림 객체 out 생성, 출력 을 하고 있다 

 

웹 서블렛 어노테이션

@WebServlet("/Method")

- 클라이언트가 폼에서 요청할 때 이 이름값 "/Method"을 가지고 찾아옴

- 이름값은 기본으로는 자기 클래스와 같은 이름, 이름 바꿔도 된다

@WebServlet(description = "값전달 연습", urlPatterns = { "/QueryString" })

- 또는 이런 형식도 있다

- 이름값은 urlPatterns 안의 값 "/QueryString" 이다

 

- 이제 폼에서 Servlet Class 로 요청해보자

- WebContent/ex222/Method.html

<html lang="ko">
<head>
	<meta charset="utf-8">
</head>
<body>
	<!-- action = "/프로젝트명/서블릿파일명"  
		 패키지명은 기술안함.
	-->
	<form method="get" action="/jsppro/Method">
		<input type="submit" value="get 방식으로 호출하기">
	</form>

	<form method="post" action="/jsppro/Method">
		<input type="submit" value="post 방식으로 호출하기">
	</form>
</body>
</html>

- 폼 태그가 2개이고, 첫번째 form 태그는 get 방식, 두번째 form 태그는 post 방식

+ 한 파일에 여러개의 form 태그 쓸 수 있다, 중첩은 되지 않는다

- 버튼 2개가 있고 첫번째 버튼 클릭시 해당 Servlet 클래스로 get 방식 요청, 두번째 버튼 클릭시 해당 Servlet 클래스로 post 방식 요청함

- 이때 찾아가는 방법은 action 에 적힌 어노테이션 이름값으로 찾아간다

- Model 1에서는 action 값에 jsp 파일을 입력했지만, 지금 Model 2에서는 WebServlet 어노테이션 이름값으로 찾아감

 

ex)

- 사용자가 'post 방식으로 호출하기' 버튼을 클릭시 두번째 폼이 post 방식으로 전송됨

- 이때 어노테이션이 "/Method" 인 곳으로 찾아감

- post 방식으로 요청했으므로 그 클래스 Method Class 에서 doPost() 메소드가 실행됨

 

WebServlet 어노테이션으로 찾아가기 (Method.html 부분)

	<form method="get" action="/jsppro/Method">
		<input type="submit" value="get 방식으로 호출하기">
	</form>
    
	<form method="post" action="/jsppro/Method">
		<input type="submit" value="post 방식으로 호출하기">
	</form>

- /jsppro 는 프로젝트명 으로 인식, 이름값만으로 잘 못찾아가는 경우가 있으므로 현재 프로젝트 명을 붙임

- /Method 가 이름값 으로 인식

- action 값 지정시 패키지명은 경로상에 포함되지 않음.

+ 나중엔 현재 프로젝트명인 /jsppro 를 직접 쓰지 않고 request 객체 제공 메소드 현재 프로젝트명을 구해주는request.getContextPath() 사용함

+ 이 파일은 확장자가 HTML 이라서 JSP 코드 못쓰므로 직접 /jsppro 썼다

 

WebServlet 어노테이션 이름값 변경

- 원래 이름값은 클래스명과 동일하지만 이름값을 임의로 변경 가능하다

- Method.java 의 WebServlet 어노테이션을 "/test" 로 변경

@WebServlet("/test")

- 그럼 form 태그 안의 action 도 "/jsppro/test" 로 바꿔야함

	<form method="get" action="/jsppro/test">
		<input type="submit" value="get 방식으로 호출하기">
	</form>

	<form method="post" action="/jsppro/test">
		<input type="submit" value="post 방식으로 호출하기">
	</form>

- 이름값만 맞으면 찾아간다

 

- Method.html 실행시 잘 처리된다

 

+ Java Servlet 클래스 여러개 만들때 WebServlet 어노테이션 이름값이 다른 값이어야함


- 예제 ex333 폴더

- QueryString.html : 회원가입 양식

- QueryString.java : Java Servlet 클래스 명

 

- ex333/QueryString.java 를 src 폴더의 send 패키지 안으로 복붙

 

- QueryString.html

<html lang="ko">
 <head>
  <meta charset="utf-8">
 </head>
 <body>

<form method="get" action="/jsppro/QueryString">
 아 이 디 : <input type="text" name="id"  size="20"> <br>
 비밀번호 : <input type="password" name="pw"  size="20"> <br>
 회원이름 : <input type="text" name="name"  size="20"> <br>
 회원구분 : <input type="radio" name="class"  value="일반회원"> 일반회원
           <input type="radio" name="class"  value="교수님"> 교수님<br>
 전화번호 : <select name="phone1">
                 <option value="010" selected> 010 </option>    
                 <option value="011"> 011 </option>    
                 <option value="016"> 016 </option> 
                 <option value="017"> 017 </option>    
                 <option value="018"> 018 </option>    
                 <option value="019"> 019 </option>
           </select>              
    -      <input type="text" name="phone2" size="4" maxlangth="4">
    -
			<input type="text" name="phone3" size="4" maxlangth="4"><br>
			<input type="submit" value="전송">
		</form>
	</body>
</html>

- action 으로 지정된 어노테이션으로 이동뿐 아니라 값까지 전달한다

- WebServlet 어노테이션 이름값이 "/QueryString" 인 곳 으로 찾아가고 값을 전달

- get 방식으로 전송하고 있다

- name 값은 사용자가 입력한 값을 전달하기 위한 변수역할을 한다

+ 받는 쪽에서는 request.getParamter() 로 값을 받음

- 라디오 값의 value 값이 한글값임

 

- 가입을 시켜보자

+ 서버 중지 후 실행해야함

+ '다시' 누르면 다시 폼으로 돌아감

- 한글 값이 넘어가고 있다, 한글 인코딩이 어떻게 되는지도 자세히 보자

- Model 1 때 처럼 get 방식으로 "한글 값" 전달시 아파치 톰캣에서 자동으로 utf-8 로 인코딩 시킴

 

- QueryString.java

- src 폴더 안에 있다

package send;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class QueryString
 */
@WebServlet(description = "값전달 연습", urlPatterns = { "/QueryString" })
public class QueryString extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// TODO Auto-generated method stub

		response.setContentType("text/html;charset=utf-8");
		
		PrintWriter out = response.getWriter();
		String id = "", name = "", vclass = "", phone1 = "", phone2 = "", phone3 = "";
		id = request.getParameter("id");
		name = request.getParameter("name");
		vclass = request.getParameter("class");
		phone1 = request.getParameter("phone1");
		phone2 = request.getParameter("phone2");
		phone3 = request.getParameter("phone3");
		
		out.println("<html><head></head><body>");
		out.println("당신이 입력한 정보(get방식)입니다.<br> 아 이 디 : <b>");
		out.println(id);
		out.println("</b><br> 이름 : <b>");
		out.println(name);
		out.println("</b><br> 구분 : <b>");
		out.println(vclass);
		out.println("</b><br> 전화번호 : <b>");
		out.println(phone1);
		out.println("-");
		out.println(phone2);
		out.println("-");
		out.println(phone3);
		out.println("</b><br><a href='javascript:history.go(-1)'>다시</a>");
		out.println("</body></html>");
		out.close();
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// TODO Auto-generated method stub
		
		response.setContentType("text/html;charset=utf-8");

		// 한글값이 post 방식으로 전송될 때 인코딩
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		String id = "", name = "", vclass = "", phone1 = "", phone2 = "", phone3 = "";
		String pw = "";
		id = request.getParameter("id");
		id = request.getParameter("pw");
		name = request.getParameter("name");
		vclass = request.getParameter("class");
		phone1 = request.getParameter("phone1");
		phone2 = request.getParameter("phone2");
		phone3 = request.getParameter("phone3");
		
		out.println("<html><head></head><body>");
		out.println("당신이 입력한 정보(post방식)입니다.<br> 아 이 디 : <b>");
		out.println(id);
		out.println("</b><br> 이름 : <b>");
		out.println(name);
		out.println("</b><br> 구분 : <b>");
		out.println(vclass);
		out.println("</b><br> 전화번호 : <b>");
		out.println(phone1);
		out.println("-");
		out.println(phone2);
		out.println("-");
		out.println(phone3);
		out.println("</b><br><a href='javascript:history.go(-1)'>다시</a>");
		out.println("</body></html>");
		out.close();
		
		QueryDTO query = new QueryDTO();
		
	}

}

- form 에서 get 방식으로 요청했으므로 doGet() 메소드가 자동 호출됨

- form 에서 넘어온 값을 request.getParameter() 로 양식의 name 값을 사용해서 받는다

- 받은 값을 출력하고 있음

+ doGet(), doPost() 메소드들은 오버라이딩되었으므로 이름 및 형식은 바꿀 수 없다, 내용은 바꿀 수 있다

 

+

		out.println("</b><br><a href='javascript:history.go(-1)'>다시</a>");

- 여기선 HTML 코드도 쓸 수 있고 JS 코드도 쓸 수 있음

 

현재 문서 한글 인코딩 (QueryString.java 부분)

		response.setContentType("text/html;charset=utf-8");

- 이 코드는 "한글 값"과는 상관없이 "현재 문서의 한글"을 인코딩 시키는 것임

 

- QueryString.html 에서 form 의 method 를 "post" 로 변경시 QueryString.java 의 doPost() 메소드가 실행됨

- post 방식으로 한글 값이 전달될 때는 아파치 톰캣이 자동으로 한글 인코딩 해주지 않는다!

- response.setContentType() 으로 직접 인코딩 해주면 된다

 

한글 값 인코딩

		// 한글값이 post 방식으로 전송될 때 인코딩
		request.setCharacterEncoding("utf-8");

- "한글 값" 인코딩은 request 객체 사용

- post 방식으로 한글값이 전송될때 인코딩 해야한다

 

- DTO 클래스 및 DAO 클래스도 다 만들어져 있다

- WebContent/ex333 폴더 안에 있는 QueryDTO.java, QueryDAO.java 를 src 의 send 패키지 안으로 넣기

- QueryDTO.java

package send;

public class QueryDTO {

	private String id;
	private String pw;
	private String name;
	private String vclass;
	private String phone;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getPw() {
		return pw;
	}
	public void setPw(String pw) {
		this.pw = pw;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getVclass() {
		return vclass;
	}
	public void setVclass(String vclass) {
		this.vclass = vclass;
	}
	public String getPhone() {
		return phone;
	}
	public void setPhone(String phone) {
		this.phone = phone;
	}
	
}

- QueryDAO.java

package send;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;

public class QueryDAO {

	private static QueryDAO instance = new QueryDAO();
	
	public static QueryDAO getInstance(){
		return instance;
	}
	
	public void insert(QueryDTO dto){
		Connection conn = null;
		PreparedStatement pstmt = null;
		String url = "jdbc:oracle:thin:@localhost:1521:xe";
		String sql="";
		
		try{
			Class.forName("oracle.jdbc.driver.OracleDriver");
			conn = DriverManager.getConnection(url,"totoro","totoro123");
			
			sql="insert into query values(?,?,?,?,?)";
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, dto.getId());
			pstmt.setString(2, dto.getPw());
			pstmt.setString(3, dto.getName());
			pstmt.setString(4, dto.getVclass());
			pstmt.setString(5, dto.getPhone());
			pstmt.executeUpdate();		
			
		}catch(Exception e){
			
		}finally{
			if(pstmt != null){
				try{
					pstmt.close();
				}catch(Exception e){					
				}
			}
			if(conn != null){
				try{
					conn.close();
				}catch(Exception e){					
				}
			}			
		}
		
	}
	
}

- 다 만들어져있으므로 테이블 생성 후 실행하면 연동처리 되고 실행됨

- DAO의 insert() 메소드를 사용해보자

 

- src/send 로 옮겨진 DTO, DAO 클래스를 QueryString.java 에서 사용해 보자

 

- QueryString.java 의 doPost() 안에서 DTO 객체 생성후 폼에서 넘어온 값을 저장 (메모리상에 저장)

- 저장 후, 그 DTO 객체를 DAO 클래스의 삽입 메소드 매개변수로 전달해서 insert 시도 (DB 테이블에 저장)

 

- QueryString.java (수정, doPost() 아래 코드 추가됨)

package send;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class QueryString
 */
@WebServlet(description = "값전달 연습", urlPatterns = { "/QueryString" })
public class QueryString extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// TODO Auto-generated method stub

		response.setContentType("text/html;charset=utf-8");
		
		PrintWriter out = response.getWriter();
		String id = "", name = "", vclass = "", phone1 = "", phone2 = "", phone3 = "";
		id = request.getParameter("id");
		name = request.getParameter("name");
		vclass = request.getParameter("class");
		phone1 = request.getParameter("phone1");
		phone2 = request.getParameter("phone2");
		phone3 = request.getParameter("phone3");
		
		out.println("<html><head></head><body>");
		out.println("당신이 입력한 정보(get방식)입니다.<br> 아 이 디 : <b>");
		out.println(id);
		out.println("</b><br> 이름 : <b>");
		out.println(name);
		out.println("</b><br> 구분 : <b>");
		out.println(vclass);
		out.println("</b><br> 전화번호 : <b>");
		out.println(phone1);
		out.println("-");
		out.println(phone2);
		out.println("-");
		out.println(phone3);
		out.println("</b><br><a href='javascript:history.go(-1)'>다시</a>");
		out.println("</body></html>");
		out.close();
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// TODO Auto-generated method stub
		
		response.setContentType("text/html;charset=utf-8");

		// 한글값이 post 방식으로 전송될 때 인코딩
		request.setCharacterEncoding("utf-8");
		
		PrintWriter out = response.getWriter();
		String id = "", name = "", vclass = "", phone1 = "", phone2 = "", phone3 = "";
		String pw = "";
		id = request.getParameter("id");
		pw = request.getParameter("pw");
		name = request.getParameter("name");
		vclass = request.getParameter("class");
		phone1 = request.getParameter("phone1");
		phone2 = request.getParameter("phone2");
		phone3 = request.getParameter("phone3");
		
		out.println("<html><head></head><body>");
		out.println("당신이 입력한 정보(post방식)입니다.<br> 아 이 디 : <b>");
		out.println(id);
		out.println("</b><br> 이름 : <b>");
		out.println(name);
		out.println("</b><br> 구분 : <b>");
		out.println(vclass);
		out.println("</b><br> 전화번호 : <b>");
		out.println(phone1);
		out.println("-");
		out.println(phone2);
		out.println("-");
		out.println(phone3);
		out.println("</b><br><a href='javascript:history.go(-1)'>다시</a>");
		out.println("</body></html>");
		out.close();
		
		QueryDTO query = new QueryDTO();
		query.setId(id);
		query.setPw(pw);
		query.setName(name);
		query.setVclass(vclass);
		query.setPhone(phone1 + "-" + phone2 + "-" + phone3); // 결합해서 저장
		
		QueryDAO dao = QueryDAO.getInstance();
		dao.insert(query);	// 회원 가입
	}

}

 

- 수정한 부분만

- 이제 폼에서 넘어온 값을 QueryString.java 에서 받고 그 값들을 DB 에 삽입 insert 함

 

- 폼 실행 전에 query 라는 테이블을 먼저 생성해야함

- WebContent 하위에 sql 폴더를 만들고 그 하위에 myoracle.sql 파일 생성

- myoracle.sql 에서 커넥션 프로파일 선택 후 테이블 create 문 작성

+ 이때 DAO 에 작성되어있는 insert 문을 참고하면

+ 컬럼이 5개여야함

- myoracle.sql

-- 모델2와 연동
select * from tab;
select * from query;

create table query(
	id varchar2(20),
	pw varchar2(20),
	name varchar2(20),
	vclass varchar2(20),
	phone varchar2(30)
);

- 테이블 생성하기

 

- 이제 최종 연동을 확인하기 위해 QueryString.html 을 실행해서 회원가입 시키기

- 이건 그냥 출력이고 가입이 DB 에 저장되었는지는 sql 파일에서 확인

- 지금은 Java Servlet 클래스 내에서 DTO, DAO 사용했다

- 나중엔 중간에 Service 클래스를 만들어서 처리한다

 


- 예제 ex555 폴더

 

- multiPara.html

<html lang="ko">
 <head>
  <meta charset="UTF-8">
</head>
<body>
<form method="post" action="/jsppro/multiPara">
<h2>액사서리</h2><br>
관심항목을 선택하세요...<br>
<hr>
 <input type="checkbox" name="item" value="신발"> 신발
 <input type="checkbox" name="item" value="가방"> 가방
 <input type="checkbox" name="item" value="벨트"> 벨트<br>
 <input type="checkbox" name="item" value="모자"> 모자
 <input type="checkbox" name="item" value="시계"> 시계
 <input type="checkbox" name="item" value="쥬얼리"> 쥬얼리<br>  
 <input type="submit" value="전송"> 
</form>
</body>
</html>

- 체크박스를 통해 하나의 name 값에 여러개의 value 값이 넘어감

- name 인 "item" 에 value 값들이 여러개 넘어간다, 이때 value 값들이 한글값이므로 받는쪽에서 한글값 인코딩해야함

+ 값을 받는 쪽에선 Model 1 때 처럼 request.getParamteterValues() 를 사용하고 배열에 저장하면 된다

- jsppro 프로젝트의 WebServlet 어노테이션 이름값이 multiPara 인 곳으로 찾아가서 값을 전달함

 

- WebContent/ex555 안의 multiPara.java 를 src 의 send 패키지 안으로 복사

 

- multiPara.html 파일을 실행

 

- multiPara.java

- scr 폴더 안에 있다

package send;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class multiPara
 */
@WebServlet(description = "값전달 연습", urlPatterns = { "/multiPara" })
public class multiPara extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		String[] item;

		item = request.getParameterValues("item");
		out.println("선택된 항목이");
		
		try {
			for (int i = 0; i < item.length; i++)
				out.println(" : " + item[i]);
//				out.println(" : " + HanConv.toKor(item[i]));
			out.println("입니다.");
		} catch (Exception e) {
			out.println(" 없습니다.");
		}
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.setContentType("text/html;charset=UTF-8");
		
		request.setCharacterEncoding("UTF-8");
		
		PrintWriter out = response.getWriter();
		String[] item;

		item = request.getParameterValues("item");
		out.println("선택된 항목이");
		
		try {
			for (int i = 0; i < item.length; i++)
				out.println(" : " + item[i]);
//				out.println(" : " + HanConv.toKor(item[i]));
			out.println("입니다.");
		} catch (Exception e) {
			out.println(" 없습니다.");
		}
	}

}

- post 방식으로 넘어왔으므로 doPost() 메소드 내용이 실행됨

 

현재 문서 한글 인코딩 vs 한글 값 인코딩

현재 문서 한글 인코딩 (multiPara.java 부분)

		response.setContentType("text/html;charset=UTF-8");

값(value)이 post 방식으로 넘어왔을때 인코딩 (multiPara.java 부분)

		request.setCharacterEncoding("UTF-8");

 

하나의 name 값에 여러개 value 가 넘어왔을때 처리 (multiPara.java 부분)

		String[] item;

		item = request.getParameterValues("item");

- request.getParamterValues() 로 값을 받아서 1차원 배열에 저장

			for (int i = 0; i < item.length; i++)
				out.println(" : " + item[i]);

- 출력하고 싶다면 반복문으로 출력

 

+ 서버 재구동 하는 방법 : java 파일에 아무곳에서 스페이스바, 한깐 띄우기, 다시 컴파일해야하므로 서버 재구동됨


- 예제 ex666 폴더

 

- src 폴더 하위에 login 패키지 생성

- WebContent/ex666 폴더의 LoginServlet.java 파일을 src의 login 패키지에 복사

 

- LoginServlet.java

package login;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet implementation class LoginServlet
 */
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public LoginServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();	// 출력 스트림 객체 생성
		
		
		String id=request.getParameter("id");
		String passwd = request.getParameter("passwd");
		
		if(id.equals("java")&& passwd.equals("java")){	// 회원 인증 성공
			
			// 세션 객체 생성
			HttpSession session = request.getSession();
			session.setAttribute("id", id);	// 세션 공유 설정
			
			// 1. Dispatcher 방식으로 포워딩
//			RequestDispatcher dispatcher = 
//					request.getRequestDispatcher("./servlet/ex666/loginSuccess.jsp");
//			dispatcher.forward(request, response);
			
			// 2. Redirect 방식으로 포워딩
			response.sendRedirect("./servlet/ex666/loginSuccess.jsp");
		}
		else{	// 회원 인증 실패
			out.println("<script>");
			out.println("alert('아이디나 비밀번호가 일치하지 않습니다.')");
			out.println("history.back()");
			out.println("</script>");
		}
	}

}

- WebServlet 어노테이션 이름값이 "/login"

- 아이디가 "java" 고 비밀번호가 "java"면 로그인 성공, 로그인 성공시 session 으로 id 를 공유함

 

포워딩

			RequestDispatcher dispatcher = 
					request.getRequestDispatcher("/ex666/loginSuccess.jsp");
			dispatcher.forward(request, response);

- 아이디 비번 일치시 loginSuccess.jsp 로 포워딩 시킨다

 

- ex666/index.html 을 보자

- index.html

<html>
	<head>
	<meta charset="utf-8">
	<title>Insert title here</title>
	</head>
	<frameset cols="30%,*">
		<frame src="/jsppro/servlet/ex666/menu.jsp" name="leftFrame" />
		<frame src="/jsppro/servlet/ex666/login.jsp" name="rightFrame" />
	</frameset>
	<body>

	</body>
</html>

- frameset 태그로 프레임을 나눔, cols 속성은 세로방향으로 프레임을 나누겠다는 의미, 좌측 30% 우측은 나머지

- fram src 의 경로는 패키지명을 적고, WebContent 를 기준으로 파일까지 경로를 적기

- index.html 실행시

- 좌측엔 menu.jsp, 우측엔 login.jsp 가 나타남

 

- menu.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>

<html>
	<head>
	<meta charset="utf-8">
	<title>Insert title here</title>
	</head>
<%
	String id = (String)session.getAttribute("id");
%>
<body>
<%
	if(id == null){
%>
   <a href="login.jsp" target="rightFrame" />로그인</a>
<% 
	}
	else{
%>
	<%=id %> 님 환영합니다.
<%
	}
%>

</body>
</html>

- 세션 id 값을 구해옴

- 로그인을 하지 않으면 session 값이 null 이므로 로그인 링크만 뜬다

- 로그인을 하면 session 값이 null 이 아니므로 ~님 환영합니다 가 나옴

 

- login.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>

<html>
	<head>
	<meta charset="utf-8">
	<title>Insert title here</title>
	</head>
	<body>
	<form action="/jsppro/login" method="post">
		아이디 : <input type="text" name="id"/>
		비밀번호 : <input type="password" name="passwd"/><br>
		<input type="submit" value="로그인"/>
	</form>
</body>
</html>

- 아이디, 비번 입력양식이 있고 로그인이 가능한 페이지

- form 을 통해 WebServlet 어노테이션 이름값이 "/login" 인 곳으로 찾아가서 post 방식으로 값을 전달한다

- 어노테이션 이름값이 "/login"인 LoginServlet.java 에서 doPost()가 실행됨

 

- LoginServlet.java (중복)

package login;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet implementation class LoginServlet
 */
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public LoginServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();	// 출력 스트림 객체 생성
		
		
		String id=request.getParameter("id");
		String passwd = request.getParameter("passwd");
		
		if(id.equals("java")&& passwd.equals("java")){	// 회원 인증 성공
			
			// 세션 객체 생성
			HttpSession session = request.getSession();
			session.setAttribute("id", id);	// 세션 공유 설정
			
			// 1. Dispatcher 방식으로 포워딩
//			RequestDispatcher dispatcher = 
//					request.getRequestDispatcher("./servlet/ex666/loginSuccess.jsp");
//			dispatcher.forward(request, response);
			
			// 2. Redirect 방식으로 포워딩
			response.sendRedirect("./servlet/ex666/loginSuccess.jsp");
		}
		else{	// 회원 인증 실패
			out.println("<script>");
			out.println("alert('아이디나 비밀번호가 일치하지 않습니다.')");
			out.println("history.back()");
			out.println("</script>");
		}
	}

}

- WebServlet 어노테이션으로 요청을 받음

- doPost() 메소드가 자동으로 실행됨

<doPost() 내용>

- 한글 값 인코딩, 현재 문서 한글 인코딩

- 아이디나 비번이 틀렸을때 브라우저에 메세지 출력위한 out 객체(출력스트림 객체) 생성

- 전달된 값 받기

 

전달된 값 받기 (LoginServlet.java 부분)

		String id=request.getParameter("id");
		String passwd = request.getParameter("passwd");

- form 에서 넘어온 데이터를 request.getParamter() 로 받고 있다

 

- 여기선 DB연동 대신 아이디, 비밀번호 값이 "java" 인 경우 로그인 성공으로 간주

- 로그인(회원 인증) 성공시 세션으로 공유 설정

 

세션 객체 생성 & 세션 공유 설정 (LoginServlet.java 부분)

			HttpSession session = request.getSession();
			session.setAttribute("id", id);

- session 이 내장 객체가 아니므로 session 객체를 만들어서 사용해야함

- 로그인 성공시 session 객체를 만들어서 공유해야함

- session 으로 setAttribute() 로 공유설정함

- 여기서부터 세션 영역 시작, 로그인 성공 부터 세션 영역 시작

- session value 값이 id 값이다, 즉 "java"

 

포워딩 (LoginServlet.java 부분)

			RequestDispatcher dispatcher = 
					request.getRequestDispatcher("./servlet/ex666/loginSuccess.jsp");
			dispatcher.forward(request, response);

- 포워딩하는 2가지 방법 중 Dispatcher 를 통한 포워딩 방법이다

- request.getRequestDispatcher() 로 경로 설정 후 포워딩할 파일을 불러오고 dispatcher.forward() 사용

- 즉, loginSuccess.jsp 로 포워딩하겠다는 의미, 이동하겠다는 의미

 

- loginSuccess.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>

<html>
	<head>
	<meta charset="utf-8">
	<title>Insert title here</title>
	<script>
	top.leftFrame.location.href="/jsppro/servlet/ex666/menu.jsp";
	</script>
	</head>
	<body>
		로그인 성공
	</body>
</html>

- index.html 의 좌측 프레임 name 값이 "LeftFrame" 이었다

- 즉 loginSuccess.jsp 의 코드는 좌측 프레임 쪽(menu.jsp)으로 이동하겠다는 의미

 

- menu.jsp 로 이동

- menu.jsp 에서 이젠 session 값을 받아올 수 있으므로 더이상 null 이 아님

- java님 환영합니다가 나타나게 된다

 

- 이제 index.html 실행해보자

- 우측 화면에서 로그인해보자

- 맞는 아이디, 비밀번호 입력시

- 좌측에 ~님 환영하였습니다. 가 나타난다.

- menu.jsp 파일에도 세션이 잘 공유되었음을 확인 가능

 

- 틀린 아이디나 비밀번호 입력시

 

포워딩 2가지 방법

- Java Servlet 클래스에서 JSP페이지로 포워딩 방법

1. Dispatcher 방식

RequestDispatcher dispatcher = request.getRequestDispatcher("dispatcher.jsp");
dispatcher.forward(request, response);

- 경로 설정 후 dispatcher 객체로 forward() 함

 

2. Redirect 방식

response.sendRedirect("redirect.jsp");

 

포워딩과 공유 주의

- 세션의 경우엔 어느방식으로 포워딩 되더라도 포워딩된 페이지에서도 세션으로 공유한 값이 유지됨

- request 객체로 공유했을때는, dispatcher 방식으로 포워딩 되어야만 포워딩 된 곳에서 request 객체로 공유한 값을 구해올 수 있다

request.setAttribute("request","requestValue"); // 공유 설정

- request 객체로 공유 설정시 포워딩 방법 2가지 중 Dispatcher 방식의 포워딩만 가능

RequestDispatcher dispatcher = request.getRequestDispatcher("dispatcher.jsp");
dispatcher.forward(request, response);

 

- session 으로 공유 설정시 포워딩 방법 2가지 모두 사용 가능

			// 세션 객체 생성
			HttpSession session = request.getSession();
			session.setAttribute("id", id);	// 세션 공유 설정
			// 1. Dispatcher 방식으로 포워딩
			RequestDispatcher dispatcher = 
					request.getRequestDispatcher("./servlet/ex666/loginSuccess.jsp");
			dispatcher.forward(request, response);
			// 2. Redirect 방식으로 포워딩
			response.sendRedirect("./servlet/ex666/loginSuccess.jsp");

 

- Java Servlet 클래스에서 2번 Redirect 방식으로 포워딩 한 후 index.html 실행

- session 으로 공유설정했으므로 어떤 방식으로 포워딩되더라도 session 공유값이 계속 유지됨

 

 

- request 객체로 공유했을때도 두가지 방법으로 포워딩했을때 되는지 실험해보자

- request 객체로 공유시 Dispatcher 방식의 포워딩 방법만 가능

- 그렇게 포워딩해서 View 페이지에 출력하는 것

 

예제 전체 흐름 정리

- index.html 을 실행해서 오른쪽 프레임인 login.jsp 에서 로그인 시도 (왼쪽 프레임 menu.jsp 에는 세션이 없으므로 '로그인'만뜸)

- login.jsp 의 form 의 action 을 통해 "/login" 이 WebServlet 어노테이션 이름값인 곳으로 찾아감 (LoginServlet.java 이다)

- LoginServlet.java 에서 로그인 성공시(id 와 pw 가 "java"일때) loginSuccess.jsp 로 포워딩함

- loginSuccess.jsp 에서 좌측 프레임을 menu.jsp 로 이동함(location.href 로 이동)

- menu.jsp 로 가서 이번엔 세션값이 있으므로 'java 님 환영합니다' 메세지를 띄움


- 예제 ex777 폴더

- DispatcherServlet.java 와 RedirectServlet.java 를 src의 send 패키지로 복사

 

- DispatcherServlet.java

- src 폴더 안에 있다

package send;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class DispatcherServlet
 */
@WebServlet("/DispatcherServlet")
public class DispatcherServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public DispatcherServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		
		request.setAttribute("request","requestValue"); // 공유 설정
		
		RequestDispatcher dispatcher = 
				request.getRequestDispatcher("./servlet/ex777/dispatcher.jsp");
		dispatcher.forward(request, response);
	}

}

- 바로 이 DispatcherServlet.java 실행시 디폴트가 get 방식이므로 doGet() 메소드 실행됨

- request 영역으로 공유를 설정하고, dispatcher.jsp 로 포워딩을 하고 있다

- 포워딩시 작성하는 경로값은 WebContent 폴더 하위폴더부터 작성해야한다

- request 영역으로 공유 설정했으므로 포워딩 2가지 방법 중 Dispatcher 방법으로만 포워딩 가능

- forward action tag 대신 Model 2에서는 Dispatcher 방식으로 포워딩 시킴

- 여기서부터 request 영역이 시작된다, 일반적으로 request 영역은 포워딩된 다음페이지까지

+ 생성자는 필요 없다

 

dispatcher 방식으로 포워딩 특징
1. 포워딩된 페이지에서는 request 객체로 공유한 값을 사용할 수 있다.
2. 포워딩이 되면 브라우저의 URL주소가 변경되지 않는다.

- 포워딩을 해도 현재 문서명 그대로 나타나고 url 이 dispatcher.jsp 로 바뀌지 않음

 

- ex777/dispatcher.jsp 에서 공유되는 값을 받아서 잘 출력하고 있다

- dispatcher.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>

<html>
	<head>
	<meta charset="utf-8">
	<title>Insert title here</title>
	</head>
	<body>
	request 속성 값 : <%=request.getAttribute("request") %>
	</body>
</html>

- request.getAttribute("request") 로 공유값을 잘 받아서 출력하고 있다

 

- DispatcherServlet.java 실행시 doGet()이 실행되고 포워딩하면서 request 로 공유설정함

- DispatcherServlet.java 실행해보자

- 공유값을 잘 가져와서 출력한다

- dispatcher.jsp 로 포워딩(이동)되었지만 URL주소가 dispatcher.jsp 로 바뀌어 있지 않음

 

- 다음은 RedirectServlet.java

package send;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class RedirectServlet
 */
@WebServlet("/RedirectServlet")
public class RedirectServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public RedirectServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		
		// request 객체로 공유 설정
		request.setAttribute("request", "requestValue");
		
		response.sendRedirect("./servlet/ex777/redirect.jsp");
	}

}

 

- 이번에는 request 객체로 공유 설정 후 Redirect 방식으로 포워딩 하고 있다

- 포워딩은 잘 되지만, request 객체로 공유 설정했을때 Redirect 방식으로 포워딩했다면, 포워딩 된 페이지에서 공유 설정한 값을 가져올 수 없다

- 즉 포워딩은 되지만 포워딩 된 페이지에서 공유값을 가져올 수 없는 문제 생김

- request 객체로 공유 설정시 Dispatcher 방식으로 포워딩해야 포워딩된 페이지에서 공유값 사용 가능

 

- redirect.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>

<html>
	<head>
	<meta charset="utf-8">
	<title>Insert title here</title>
	</head>
	<body>
	request 속성 값 : <%=request.getAttribute("request") %>
	</body>
</html>

 

redirect 방식으로 포워딩 특징
1. 포워딩된 페이지에서는 request 객체로 공유한 값을 사용할 수 없다.
2. 포워딩된 JSP페이지에서는 자바 서블릿 클래스에서 request 영역의 공유한 속성값에 접근 할 수 없다.

- 공유 영역을 벗어나게되기 때문이다.
3. 포워딩이 되면 브라우저의 URL주소가 포워딩된 JSP 파일명으로 바뀐다.

 

- RedirectServlet.java 파일 실행하자

- 포워딩은 되지만 공유값을 가져오지 못해 null 임

- URL주소가 redirect.jsp 로 변경되어있음

 

MVC 패턴 흐름 정리

- MVC의 Controller 클래스를 Java Servlet Class 로 만들어야한다

- 요청이 찾아오는 것은 WebServlet 어노테이션으로 찾아온다

- DAO 에서 DB에서 검색하고 검색한 값을 Service 클래스에서 공유 수행

- 이때 request 방식으로 공유설정했다면 Dispatcher 방식으로 포워딩 해야함

 

ex)

- Service 클래스에서 페이징에 필요한 6개 변수를 request 객체로 공유 설정

- View 페이지에서 결과를 EL, JSTL 로 출력

 


표현 언어 (Express Language)

- 기존 Model 1 표현식 태그의 역할을 대신함

- JSP의 표현식 태그와 비슷한 역할을 수행한다

- Spring, Spring Boot 에서도 사용

 

- 표현언어는 ${ } 기호를 사용한다, 이 사이에 코드를 씀

- 표현언어에 JSP의 네가지 영역 객체로 공유한 값을 구해올 수 있다.

ex) ${ sessionScope.id } // sessionScope는 내장 객체 중 하나

- 표현언어에 산술연산자(+,-,*,/,%), 비교연산자(>,>=,<,<=,==,!=), 논리연산자(&&, ||, !), 조건연산자, empty 연산자 등을 사용할 수 있다. ex) ${ 10 + 20 }

- 표현언어에 자바빈 클래스의 getter 메소드를 호출할 수 있다.

ex) ${ article.id } // article 은 DTO 객체, article.getId() 와 같은 의미

 

- Service 클래스에서 request 객체로 공유 설정한 뒤,

- Controller 클래스를 통해 View 페이지로 출력할때 getParameter() 이나, 형변환 등을 하지 않아도바로 결과를 EL로 출력 가능

- EL 과 JSTL 이 View 페이지에서 결합되어 결과를 출력함

 

EL 지원되는 내장 객체

- EL 안에서만 사용 가능, 즉 ${} 안에서만 사용 가능

 

1. param

${param.name}

- name 은 값을 전달하기위한 변수명

- 아래의 코드와 같다

<%=request.getParamter("name")%>

 

2. sessionScope

${sessionScope.id}

- id 는 세션 네임

- 아래의 코드와 같다

<%= session.getAttribute("id")%>

 

표현 언어 예제

실습 준비

- 클라우드의 el 폴더를 jsppro 의 WebContent 폴더에 복붙

 

표현 언어 예제 1

- el/ex11 폴더의 eLEx1.jsp 파일을 보자

- eLEx1.jsp

<%@ page contentType="text/html;charset=utf-8"%>

<HTML>
<HEAD>
	<TITLE>간단한 표현언어(EL)예제</TITLE>
</HEAD>

<BODY>

<H3>간단한 표현언어(EL)예제</H3>
<P>
연산자를 사용한 예와 내장객체의 사용한 예:

<TABLE BORDER="1">
  <THEAD>
    <TD><B>표현식</B></TD>
    <TD><B></B></TD>
  </THEAD>
  <TR>
    <TD>\${2 + 5}</TD>
    <TD>${2 + 5}</TD>
  </TR>
  <TR>
    <TD>\${4/5}</TD>
    <TD>${4/5}</TD>
  </TR>
  <TR>
    <TD>\${5 div 6}</TD>
    <TD>${5 div 6}</TD>
  </TR>
  <TR>
    <TD>\${5 mod 7}</TD>
    <TD>${5 mod 7}</TD>
  </TR>
  <TR>
    <TD>\${2 < 3}</TD>
    <TD>${2 < 3}</TD>
  </TR>
  <TR>
    <TD>\${2 gt 3}</TD>
    <TD>${2 gt 3}</TD>
  </TR>
  <TR>
    <TD>\${3.1 le 3.2}</TD>
    <TD>${3.1 le 3.2}</TD>
  </TR>
  <TR>
    <TD>\${(5 > 3) ? 5 : 3}</TD>
    <TD>${(5 > 3) ? 5 : 3}</TD>
  </TR>
  <TR>
    <TD>\${header["host"]}</TD>
    <TD>${header["host"]}</TD>
  </TR>
  <TR>
    <TD>\${header["user-agent"]}</TD>
    <TD>${header["user-agent"]}</TD>
  </TR>
</TABLE>
</BODY>
</HTML>

- EL 안에서 각종 연산자를 사용하는 내용이다

- EL ${} 안에 산술연산자, 비교연산자, 논리연산자, 조건연산자, empty 연산자 등을 쓸 수 있다

 

브라우저에 그대로 출력 vs 연산해서 출력 (eLex1.jsp 부분)

  <TR>
    <TD>\${2 + 5}</TD>
    <TD>${2 + 5}</TD>
  </TR>

- \(역슬래시) 가 있으면 \ 다음의 내용을 특수문자로 인식해서 브라우저에 그대로 출력함

- ${2 + 5} 면 연산결과 7 이 출력됨

 

나누기 연산시 결과 주의 1 (eLex1.jsp 부분)

  <TR>
    <TD>\${4/5}</TD>
    <TD>${4/5}</TD>
  </TR>

- 자료형이 따로 없기떄문에 4/5 연산을 수행시 정수형으로 결과가 나오지 않고, 0.8 실수로 출력됨

 

나누기 연산시 결과 주의 2 (eLex1.jsp 부분)

  <TR>
    <TD>\${5 div 6}</TD>
    <TD>${5 div 6}</TD>
  </TR>

- / 기호 대신 div 를 적어도 같은 역할, 5/6 과 같다

- 마찬가지로 0.83333.. 실수로 결과가 나옴

 

비교 연산 1 (eLex1.jsp 부분)

  <TR>
    <TD>\${2 < 3}</TD>
    <TD>${2 < 3}</TD>
  </TR>

- 결과는 true 또는 false

 

비교 연산 2 (eLex1.jsp 부분)

  <TR>
    <TD>\${2 gt 3}</TD>
    <TD>${2 gt 3}</TD>
  </TR>
  <TR>
    <TD>\${3.1 le 3.2}</TD>
    <TD>${3.1 le 3.2}</TD>
  </TR>

- 부등호 모양의 비교연산자를 사용해도 되고, gt (greater than) 또는 lt(less than) 을 사용해도 된다

- 2 gt 3 은 2 > 3 과 같은 의미임

 

삼항 연산자 (eLex1.jsp 부분)

  <TR>
    <TD>\${(5 > 3) ? 5 : 3}</TD>
    <TD>${(5 > 3) ? 5 : 3}</TD>
  </TR>

- 첫번째 식이 참이면 5가 , 거짓이면 3 이 결과가 됨

 

header["user-agent"] (eLex1.jsp 부분)

  <TR>
    <TD>\${header["user-agent"]}</TD>
    <TD>${header["user-agent"]}</TD>
  </TR>

- 브라우저에 대한 정보를 가져옴

 

- eLEx1.jsp 실행시

- 연산자들의 연산결과가 출력된다.

 

표현 언어 예제 2

- WebContent/el/ex22/eLEx2.jsp

<%@ page contentType="text/html;charset=utf-8"%>
<% request.setCharacterEncoding("utf-8");%>

<HTML>
<HEAD>
<TITLE>표현언어의 사용예제2</TITLE>
</HEAD>

<BODY>

<H3>표현언어의 사용예제2 -파라미터값 처리</H3>
<P>
<FORM action="eLEx2.jsp" method="post">
   이름 : <input type="text" name="name" value="${param['name']}">
          <input type="submit" value="확인">
</FORM>

<P>
이름은: <%=request.getParameter("name") %> <br>
이름은: ${param.name} 입니다. <br>
이름은: ${param['name']} 입니다.

</BODY>
</HTML>

- action 이 자기 자신으로 되어있다

- 원래 이름값을 받을때는 request.getParameter() 로 받아야하지만, EL 을 사용해서 %{param.name} 으로 더 간단하게 값을 받고 출력까지 시켜줌

+ 한글값 깨지지 않도록 가장 위에서 한글값 인코딩을 하고 있다

- 실행시 이름 입력 양식이 나타나고 '확인'누르면 자기 자신의 파일을 다시 실행해서 다시 입력창을 띄움, 그리고 받은 이름값을 아래에 출력

 

EL 내장 객체 param

이름은: <%=request.getParameter("name") %> <br>
이름은: ${param.name} 입니다. <br>
이름은: ${param['name']} 입니다.

- 세 줄이 모두 같은 코드

- EL 은 표현식 태그 대신이므로 출력까지 시킴

 

+ EL 내장 객체 sessionScope

+ Session 영역 공유 설정시에는 기존처럼 한다, setAttribute() 사용

- Session 영역 공유값 받아오기는 sessionScope 내장객체로 쉽게 가능

${sessionScope.id}

- 아래와 같다

<%=session.getAttribute("id")%>

 

+ EL 내장 객체 requestScope

- Request 영역 공유값 받아오기를 requestScope 내장객체로 쉽게 가능

${requestScope.page}

- 이렇게 써도 되지만 이렇게 requestScope 를 생략해도 바로 ${page} 로 출력 가능 하므로 잘 쓰지 않음

${page}

- 두 코드는 아래와 같다

<%=request.getAttribute("page")%>

 

+ EL 은 JSTL 과 결합되어 사용된다

+ 지금 다 설명하지 못한 EL 기능도 JSTL 하면서 같이 할 것

 

표현 언어 예제 3

실습 준비

- WebContent/el/ex33 의 Product.java 는 자바파일이므로 src 폴더 하위에 jspbook 패키지를 만들어서 그곳으로 복사

 

표현 언어 예제 3

- src/jspbook/Product.java

package jspbook;

public class Product {

	// 상품 목록을 저장할 배열
	private String[] productList = {"item1","item2","item3","item4","item5"};
	
	// 변수 설정
	private int num1 = 10;
	private int num2 = 20;
	
	public int getNum1() {
		return num1;
	}

	public int getNum2() {
		return num2;
	}

	public String[] getProductList() {
		return productList;
	}
}

- 메소드를 통해서 Product 클래스의 필드들을 돌려주고 있음

- 일종의 DTO 클래스이다.

 

- WebContent/el/ex33/productList.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
%>

<html>

	<head>
		
<meta charset="utf-8">

		<title>EL 예제</title>

	</head>

	<body>

	<center>
		<H2>EL 예제-상품목록</H2>
		<HR>
	<form method="post" action="productSel.jsp">
		<jsp:useBean id="product" class="jspbook.Product" scope="session"/>
		<select name="sel">
			<%
			// session 에서 가져온 Product 객체로 부터 저장되어 있는 목록을 가져와 출력함.
			for(String item : product.getProductList()) {
				out.println("<option>"+item+"</option>");
			}
			%>
		</select>
		<input type="submit" value="선택"/>
	</form>
	</center>
</body>

</html>

- Model 1 으로 되어있다, action 에서 productSel.jsp 파일로 값을 전달함

- useBean action tag 를 써서 Product 객체 product 를 생성, 생성된 객체를 scope 속성을 통해서 사용 범위 지정

- scope 속성으로는 영역명이 들어가는데, 여기선 session 이 적혀있으므로, 세션이 끊기기 전까지 이 객체가 계속 공유됨

- scope 속성값 생략시 기본값이 page, 여기서 session 이므로 다음 페이지 productSel.jsp 로 넘어가도 세션이 끊기기 전까지 이 객체 product 를 계속 쓸 수 있다.

- 객체 product 에서 getProductList() 메소드를 사용해서 String형 배열을 가져옴

- 향상된 for 문으로 getProductList() 에서 "item1", "item2", "item3".. 를 차례로 가져와서 item 변수에 저장, 그 item 변수를 차례로 출력하고 있음

- option 태그 안에 value 속성이 없으면사이의 값이 select 의 name 인 sel 에 저장되어 넘어감

- 객체의 scope 값이 session 으로 되어있으면 session 으로 공유된 것과 같다

+ 향상된 for 문 오른쪽에는 배열이나 List 가 온다

 

- productList.jsp 실행시

- 'item3' 선택 후 '선택' 버튼 클릭시

 

- WebContent/el/ex33/productSel.jsp

<%@page import="jspbook.Product"%>
<%@ page language="java" contentType="text/html; charset=utf-8"%>

<html>
	<head>		
		<meta charset="utf-8">
		<title>EL 예제</title>
	</head>
	<body>
		<center>
		<H2>EL 예제-상품선택</H2>
		<HR>
		<!-- 표현 언어로 출력 -->
		1. 선택한 상품은 : ${param.sel} <br>
		
		<!-- 표현식 태그로 출력 -->
		1. 선택한 상품은 : <%=request.getParameter("sel") %> <br>
		
		<!-- 표현 언어로 출력 -->
		2. num1 + num2 = ${product.num1 + product.num2} <br>
		
		<!-- 표현식 태그로 출력 -->
<%
		Product pro = (Product) session.getAttribute("product");
%>
		2. num1 + num2 = <%=pro.getNum1() + pro.getNum2()%>
		
		</center>
	</body>
</html>

- 페이지가 넘어왔지만 세션이 끊어지기전까지 객체 product 는 계속 사용가능하므로 이 페이지에서도 Product 객체 product 사용 가능

- ${param.sel} 과 request.getParamteter("sel") 로 select 옵션의 선택된 값을 구해올 수 있다, 같은 의미이다.

+ productList.jsp 에서 option 태그 안에 value 속성이 없으므로 사이의 값인 "item1", "item2", "item3" ..중 선택한 것이 sel 변수에 저장되어서 넘어옴

 

주의 1 (productSel.jsp 부분)

		<!-- 표현 언어로 출력 -->
		2. num1 + num2 = ${product.num1 + product.num2} <br>

- 객체 product 의 num1, num2 값을 product.num1, product.num2 로 구해오는데, Product 클래스에서 num1, num2 는 private 이다

- 표기만 product.num1 으로 필드명으로 표기하고, 의미는 product.getNum1() 을 리턴받아 출력한다는 의미

- EL 안에서는 메소드를 표기하지 않음

- 즉 product.num1 은 표기일 뿐 실질적으로는 product.getNum1() 을 의미한다.

- 아래의 코드와 위의 코드는 같다.

 

주의 2 (productSel.jsp 부분)

		<!-- 표현식 태그로 출력 -->
<%
		Product pro = (Product) session.getAttribute("product");
%>
		2. num1 + num2 = <%=pro.getNum1() + pro.getNum2()%>

- 위의 코드와 같다

- 객체의 scope 값이 session 으로 되어있으면 session 으로 공유된 것과 같다

- 공유 네임값도 그 객체 이름으로 되어있다.

- getAttribute("product") 로 가져올때 다운캐스팅을 해야한다

 

- productList.jsp 에서

		<jsp:useBean id="product" class="jspbook.Product" scope="session"/>

- 이렇게 product 객체를 만들면서 scope 를 "session" 으로 설정했다

- EL 태그에서는 바로 ${product.num1} 으로 사용 가능하다

- EL 이 아닌 표현식 태그로 다른 페이지에서 이 객체 product 를 사용하려면 session 에 공유된 값으로부터 객체를 가져와서 사용해야 한다.

- 위의 useBean action tag 중 scope="session" 부분은 아래 코드와 같다

session.setAttribute("product");

- 객체 product 가 session 영역에 공유 설정되어있는 것이므로, EL 에서는 바로 사용 가능

 

표현 언어 (EL) 정리

- 표현 언어는 현재 공유된 값을 출력하는 것이다!

- 현재 페이지에서 공유된 값을 사용할 수 있으면 EL 태그로 쉽게 출력할 수 있다.

ex) 이전 페이지에서 session 영역에 어떤 값을 공유 설정해둠, 다음 페이지에서 그 값을 구해올 수 있다, 이때 EL 태그로 쉽게 출력 가능

 

String str = "jsp";

표현식 태그  :  <%=str %>

	      <%=article.getId()%>

	     <%=session.getAttribute("test")%>

	     <%=request.getParameter("name")%>

	     <%=request.getParameterValues("choice")%>


request.setAttribute("str",str);

표현언어(EL) : ${str}

	      ${article.id}

	      ${sessionScope.test}

	      ${param.name}

	      ${paramValues.choice}

<표현식 태그>

- 단순히 String str = "jsp" 일때, ${str} 로 "jsp" 를 출력하는 것도 가능하다.

<표현언어(EL)>

- 표현 언어는 현재 공유된 값을 출력하는 것이다!

- 단순히 String str = "jsp" 일때, ${str} 로 "jsp" 를 출력하는 것은 불가능하다

 

- str 와 article 이 request 객체로 공유가 되어야만 ${str}, ${article.id} 을 EL로 이렇게 출력 가능하다

ex) request.setAttribute("str",str) 로 공유되어있어야만 공유 네임 "str" 로 ${str} 사용 가능

- article 또한 request 객체로 공유되어있어야만 ${article.id} 사용 가능

- ${article.id} 는 <표현식 태그>의 <%=article.getId()%> 와 같은 의미

- ${sessionScope.test} 는 표현식 태그의 <%=session.getAttribute("test")%> 와 같다

- sessionScope 내장객체로 쉽게 session 의 공유값을 가져오는 것이다.

 

+ 또한 get/post 방식으로 전달되는 값을 받을때도 param 내장객체를 이용해서 쉽게 받을 수 있다.


+ Session 이 끊어지는 때

1. 세션을 강제로 invalid()로 끊을때

2. 브라우저 창을 닫을때 Session 끊김

+ Recent posts