게시판 직접 만들어보기 (이어서)
환경 설정 파일 작성(세팅) 순서
1) pom.xml
2) web.xml
3) servlet-context.xml
4) configuration.xml
5) board.xml
6) root-context.xml
- 4), 5), 6) 은 DB 연동 관련 내용
- configuration.xml, board,xml(Mapper 파일) 환경설정 파일은 resources 폴더 안에 넣는다
- configuration.xml 에서 DTO 개수에 따라 typeAlias 설정이 늘어남
게시판 직접 만들어보기 (이어서)
상세 페이지
- 저번에 만든 목록 페이지 boardlist.jsp 에서 제목을 클릭하면 상세페이지로 넘어간다

<a href="boardcontent.do?no=${b.no}&page=${page}">${b.subject}</a>
- "boardContent.do" 로 요청하며 글 번호 no 와 페이지 번호 page 를 가져간다
- 글 번호와 페이지 번호를 최대 3번까지 전달하게 되는데 여기가 전달하는 출발점이다
ex) 목록 페이지 -> 상세 페이지 -> 수정/삭제 폼
- Controller 클래스 BoardController.java 에서 "boardContent.do" 요청 부분을 처리하자
// 상세 페이지 : 조회수 1 증가 + 상세 정보 구하기
@RequestMapping("boardcontent.do")
public String boardcontent(int no, int page, Model model) {
bs.updatecount(no); // 조회수 1 증가
Board board = bs.getBoard(no); // 상세 정보 구하기
String content = board.getContent().replace("\n", "<br>");
model.addAttribute("board", board);
model.addAttribute("content", content);
model.addAttribute("page", page);
return "board/boardcontent";
}
- 앞에서 넘어온 글 번호, 페이지 번호의 변수명과 같은 이름을 써서 바로 값이 들어가게끔 한다, @RuequestParam 생략
- 앞에서 받아온 Service 객체 bs 를 사용해서 updatecount() , getBoard() 메소드를 호출한다
상세 페이지 Controller 처리에서 해야할 DB 연동 2가지
1. updatecount() : 조회수 증가 Update 문
- 글 번호 no 를 전달한다
- 리턴 받지 않기로 함
2. getBoard() : 상세 정보 구하기 Select 문
- 글 번호 no 를 전달한다
- 리턴은 DTO 클래스 Board 형으로 받는다
- 이 후 내용 content 의 줄바꿈 처리를 하기 위해 내용을 따로 구하고 replace() 메소드를 써서 바꾼 후 내용을 따로 Model 객체에 저장한다
View 페이지로 가져갈 값 3가지
1. getBoard() 에서 리턴받은 상세 정보 DTO 객체 board
2. 줄이 바뀐 내용 content
3. 목록 페이지에서 넘어온 페이지 번호 page
- 글 번호는 객체 board 안에 들어있으므로 페이지 번호만 따로 가져간다
- 이후 board 폴더 하위에 boardcontent.jsp 를 생성해야함
<조회수 증가 Update 문>
- Service 클래스 BoardService.java 에서 updatecontent() 메소드 작성
public void updatecount(int no) {
dao.updatecount(no);
}
- 리턴 받지 않으므로 return 을 쓰지 않는다, 그냥 DAO의 메소드 updatecount() 만 호출함
- DAO 클래스 BoardDao.java 에서 updatecontent() 메소드 작성
public void updatecount(int no) {
session.update("hit", no);
}
- Mapper 파일에서 id 가 "hit" 인 SQL문을 불러옴, 전달하는 값은 글 번호인 no
- Mapper 파일 board.xml 에서 id 가 "hit" 인 조회수 증가 Update SQL문 작성
<!-- 조회수 1 증가 -->
<update id="hit" parameterType="int">
update myboard set readcount=readcount+1 where no=#{no}
</update>
- 넘어온 값이 글 번호 no 이므로 parameterType 은 "int", where 절에는 #{no} 를 사용
<상세 정보 구하기 Select 문>
- Service 클래스 BoardService.java 에서 getBoard() 메소드 작성
public Board getBoard(int no) {
return dao.getBoard(no);
}
- 위에서 주입받은 DAO 객체 dao 로 getBoard() 메소드 호출, 글 번호 전달
- DAO 클래스 BoardDao.java 에서 getBoard() 메소드 작성
public Board getBoard(int no) {
return session.selectOne("content", no);
}
- Mapper 파일에서 id 가 "content" 인 SQL문을 불러옴, 전달하는 값은 글 번호인 no
- 글 1개 에 대한 상세정보를 가져오므로 selectOne() 메소드 사용
- Mapper 파일 board.xml 에서 id 가 "content" 인 조회수 증가 SelectSQL문 작성
<!-- 상세정보 구하기 -->
<select id="content" parameterType="int" resultType="board">
select * from myboard where no = #{no}
</select>
- View 페이지인 boardcontent.jsp 를 WEB-INF/views/board/ 폴더 하위에 생성하기

- boardcontent.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>상세 페이지</title>
</head>
<body>
<table border=1 width=400 align=center>
<caption>상세 페이지</caption>
<tr>
<td>작성자</td>
<td>${board.writer}</td>
</tr>
<tr>
<td>날짜</td>
<td>
<fmt:formatDate value="${board.register}"
pattern="yyyy-MM-dd HH:mm:ss"/>
</td>
</tr>
<tr>
<td>조회수</td>
<td>${board.readcount}</td>
</tr>
<tr>
<td>제목</td>
<td>${board.subject}</td>
</tr>
<tr>
<td>내용</td>
<td>
<pre>${board.content}</pre>
${board.content}
</td>
</tr>
<tr>
<td colspan=2 align=center>
<input type="button" value="목록"
onClick="location.href='boardlist.do?page=${page}'"/>
<input type="button" value="수정"
onClick="loacation.href='boardupdateform.do?no=${board.no}&page=${page}'"/>
<input type="button" value="삭제"
onClick="loacation.href='boarddeleteform.do?no=${board.no}&page=${page}'"/>
</td>
</tr>
</table>
</body>
</html>
- 날짜 시간 패턴을 지정하기 위해 국제화 라이브러리 fmt 를 불러옴
- 내용의 줄바꿈을 처리할때는 <pre></pre> 사용 또는 앞에서 replace() 를 써서 바꿔서 저장한 내용 ${content} 를 불러옴


- 현재는 내용이 한 줄이라 줄바꿈이 제대로 되었는지 확인이 어려움
버튼 처리 : 목록 버튼 (boardcontent.jsp 부분)

<input type="button" value="목록"
onClick="location.href='boardlist.do?page=${page}'"/>
- 상세 페이지에서 '목록' 버튼을 누르면 다시 목록 페이지로 돌아간다
- 돌아갈때 페이지번호를 전달해서 원래 페이지로 돌아가게끔 한다



- 다시 돌아옴
버튼 처리 : 수정 버튼 (boardcontent.jsp 부분)
<input type="button" value="수정"
onClick="location.href='boardupdateform.do?no=${board.no}&page=${page}'"/>
- 수정 폼으로 가도록 "boardupdateform.do" 로 요청한다, 요청하면서 글 번호, 페이지 번호를 전달
- 글 번호는 객체 board 안에 있으므로 ${board.no} 로 가져오기
버튼 처리 : 삭제 버튼 (boardcontent.jsp 부분)
<input type="button" value="삭제"
onClick="location.href='boarddeleteform.do?no=${board.no}&page=${page}'"/>
- 삭제 폼으로 가도록 "boarddeleteform.do" 로 요청한다, 요청하면서 글 번호, 페이지 번호를 전달
- 글 번호는 객체 board 안에 있으므로 ${board.no} 로 가져오기
글 수정 폼
<input type="button" value="수정"
onClick="location.href='boardupdateform.do?no=${board.no}&page=${page}'"/>
- 상세 페이지에서 '수정'을 누르면 "boardupdateform.do" 로 요청한다, 요청하면서 글 번호, 페이지 번호를 전달
- Controller 클래스 BoardController.java 에서 "boardupdateform.do" 요청 부분을 처리하자
// 수정 폼
@RequestMapping("boardupdateform.do")
public String boardupdateform(int no, int page, Model model) {
Board board = bs.getBoard(no); // 상세 정보 구하기
model.addAttribute("board", board);
model.addAttribute("page", page);
return "board/boardupdateform";
}
- 앞에서 넘어온 글 번호, 페이지 번호의 변수명과 같은 이름을 써서 바로 값이 들어가게끔 한다, @RuequestParam 생략
- 앞의 상세 페이지를 처리할때 만든 상세정보를 가져오는 메소드 getBoard() 를 호출해서 상세 정보 객체를 구해온다
- 객체 board 와 페이지 번호 page 를 Model 객체에 저장
- getBoard() 메소드에 대해서는 이전에 설명했으므로 생략
- View 페이지 boardupdateform.jsp 를 board 폴더 하위에 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>글수정</title>
</head>
<body>
<form method=post action="boardupdate.do">
<input type="hidden" name="no" value="${board.no}"/>
<input type="hidden" name="page" value="${page}"/>
<table border=1 width=400 align=center>
<caption>글수정</caption>
<tr><th>작성자명</th>
<td><input type=text name="writer" required="required"
value="${board.writer}" autofocus="autofocus"></td>
</tr>
<tr><th>비밀번호</th>
<td><input type=password name="passwd" required="required"></td>
</tr>
<tr><th>제목</th>
<td><input type=text name="subject" required="required"
value="${board.subject}"></td>
</tr>
<tr><th>내용</th>
<td><textarea cols=40 rows=5 name="content" required="required">
${board.content}</textarea></td>
</tr>
<tr><td colspan=2 align=center>
<input type=submit value="글수정">
<input type=reset value="취소">
</td>
</tr>
</table>
</form>
</body>
</html>
- boardform.jsp 파일 내용을 복붙 후 수정
- value 속성을 이용해서 앞에서 넘어온 객체 board 안의 값을 뿌려준다
- 수정 폼 boardupdateform.jsp 에서 정보 입력 후 '수정' 을 누르면 "boardupdate.do" 로 요청한다
- hidden 객체를 사용해서 글 번호와 페이지 번호를 "boardupdate.do" 를 요청하며 전달한다
- 수정 위해 글 번호 필요, 수정 후 원래 페이지로 돌아가기 위해 페이지 번호가 필요
- 수정폼에서 넘어간 값들은 받을 때 DTO 객체에 저장해야한다, 페이지 번호 page 만 DTO 안에 들어갈 수 없으므로 따로 받아야한다
- 비밀번호 값이 맞는 경우에만 수정할 것
글 수정
- 앞의 글 수정 폼 boardupdateform.jsp 에서 입력 후 "글수정" 을 누르면 "boardupdate.do" 료 요청
- Controller 클래스 BoardController.java 에서 "boardupdate.do" 요청 부분을 처리하자
// 글 수정
@RequestMapping("boardupdate.do")
public String boardupdate(Board board, int page, Model model) {
int result = 0;
Board old = bs.getBoard(board.getNo());
// 비번 비교
if(old.getPasswd().equals(board.getPasswd())) { // 비번 일치시
result = bs.update(board); // 글 수정
} else { // 비번 불일치시
result = -1;
}
model.addAttribute("result", result);
model.addAttribute("board", board);
model.addAttribute("page", page);
return "board/updateresult";
}
- 넘어온 값들은 DTO 객체에 저장해야한다, @ModelAttribute 를 써서 (생략) 넘어온 값들을 바로 DTO 객체 board 에 저장
- 페이지 번호 page 만 DTO 안에 들어갈 수 없으므로 @RequestParam("page") (생략) 을 써서 따로 받는다
- 수정 성공 메세지를 뿌릴 것이므로 result 값을 Model 객체에 저장해서 넘겨줄 것, Model 객체도 선언함
글 수정 요청에서 해야할 DB연동 2가지
1. 비번 비교 Select SQL
- 비번 비교를 위해 hidden 으로 넘어온 값인 글 번호 no 를 써서 DB에 저장된 해당 글의 상세 정보를 가져옴
- 이때, 상세 정보를 가져오는 메소드인 getBoard() 를 호출해서 상세정보를 구함
2. 글 수정 Update SQL
- Service 클래스의 메소드 update() 호출
- 전달하는 값은 수정할 데이터인 board
- 돌려받는 값은 글 수정 성공 개수, 즉 성공시 1 을 result 에 돌려받음
<View 와 관련>
- 이후 View 에서 수정 성공 / 실패 메세지 처리를 할 것이므로 Model 객체에 result 를 저장해서 전달
- View 에서 원래 페이지로 돌아가기 위해 Model 객체에 페이지 번호 page 를 저장해서 전달
- 수정 완료 후 상세페이지로 갈 때는 글 번호 no 와 페이지 번호 page 를 가져가야하므로 Model 객체에 글 번호를 가지고 있는 객체 board 를 Model 객체에 저장해서 전달
- 만약, 목록페이지로 갈 때는 Model 에 객체 board 를 가져갈 필요없다
- Service 클래스 BoardService.java 에서 update() 메소드 생성
public int update(Board board) {
return dao.update(board);
}
- DAO 클래스 BoardDao.java 에서 update() 메소드 생성
public int update(Board board) {
return session.update("update", board);
}
- Mapper 파일에서 id 가 "update" 인 SQL문을 불러옴, 전달하는 값은 수정할 데이터인 객체 board
- 수정 성공시 1 을 자동으로 돌려준다
- Mapper 파일 board.xml 에서 id 가 "update" 인 Update SQL문 작성
<!-- 글 수정 -->
<update id="update" parameterType="board">
update myboard set writer=#{writer},subject=#{subject},
content=#{content},register=sysdate where no=#{no}
</update>
- Mapper 파일의 SQL문에선 SQL문 끝에 ; 를 찍으면 안된다
- View 페이지 updateresult.jsp 를 board 폴더 하위에 생성 및 작성
<%@ 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>
<c:if test="${result == 1}">
<script>
alert("글 수정 성공");
location.href="boardlist.do?page=${page}"; // 목록 페이지
// location.href="boardcontent.do?no=${board.no}&page=${page}"; // 상세 페이지
</script>
</c:if>
<c:if test="${result != 1}">
<script>
alert("글 수정 실패");
history.go(-1);
</script>
</c:if>
</body>
</html>
- if 태그를 사용하기 위해 JSTL core 라이브러리를 불러온다
- 수정 성공 후 목록 페이지로 가기 위해서 페이지 번호 page 를 전달해야 한다
- 수정 성공 후 상세 페이지로 가기 위해서 글 번호 no, 페이지 번호 page 를 전달해야한다
- 수정 실패 시 result 는 -1 이므로 "글 수정 실패" 메세지 출력 후 다시 이전 페이지인 수정 폼으로 돌아간다



글 삭제폼
<input type="button" value="삭제"
onClick="location.href='boarddeleteform.do?no=${board.no}&page=${page}'"/>
- 상세 페이지에서 '삭제'를 누르면 "boarddeleteform.do" 로 요청한다, 요청하면서 글 번호, 페이지 번호를 전달
- 글 번호는 객체 board 안에 있으므로 ${board.no} 로 가져오기
- Controller 클래스 BoardController.java 에서 "boarddeleteform.do" 요청 부분을 처리하자
- 앞에서 넘어온 글 번호, 페이지 번호의 변수명과 같은 이름을 써서 바로 값이 들어가게끔 한다, @RuequestParam 생략
- 글 삭제 폼에서는 여기서 글 번호, 페이지 번호 값을 받아서 Model 객체에 저장해서 가져가도 되지만, 글 삭제 폼에서 param 으로 바로 받아도 된다
- 그러므로 여기서는 아무값도 받아서 Model 객체에 저장해서 전달하지 않는다
- Model 2 에서도 View 에서 View 로 갈때 Controller 클래스를 거치지만 Contorller 에서 값을 중간 저장하고 전달하지 않았다, 바로 View 에서 전달한 값을 이동한 View 에서 getParameter() 로 받아왔었음, 여기서도 같은 방법을 사용 가능함
- View 페이지 boarddeleteform.jsp 를 생성 및 작성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>글삭제</title>
</head>
<body>
<form method=post action="boarddelete.do">
<input type="hidden" name="no" value="${param.no}"/>
<input type="hidden" name="page" value="${param.page}"/>
<table border=1 width=400 align=center>
<caption>글삭제</caption>
<tr><th>비밀번호</th>
<td><input type=password name="passwd" required="required"></td>
</tr>
<tr><td colspan=2 align=center>
<input type=submit value="글삭제">
<input type=reset value="취소">
</td>
</tr>
</table>
</form>
</body>
</html>
- boardupdateform.jsp 파일 내용을 복붙 후 수정
상세 페이지에서 전달된 글 번호, 페이지 번호 받기
- 상세 페이지에서 GET 방식으로 넘어온 값인 글 번호, 페이지 번호를 여기 View 에서 바로 param 으로 받을 수 있다
- 중간에 Controller 클래스를 거치지만 Contorller 에서 중간 저장 후 다시 전달할 필요 없다
- request.getParameter("네임") 은 ${param.네임} 과 같은 의미
<input type="hidden" name="no" value="${param.no}"/>
<input type="hidden" name="page" value="${param.page}"/>
- 이렇게 param 으로 받은 값을 다시 hidden 객체로 전달한다
- 비밀번호를 입력 후 "글삭제" 클릭시 "boarddelete.do" 로 요청한다
- 넘어가는 값 : 글 번호, 페이지 번호, 비밀번호


글 삭제
- 글 삭제 폼 boarddeleteform.jsp 에서 비밀번호 입력 후 "글삭제" 클릭시 "boarddelete.do" 로 요청
- Controller 클래스 BoardController.java 에서 "boarddelete.do" 요청 부분을 처리하자
// 글 삭제
@RequestMapping("boarddelete.do")
public String boarddelete(Board board, int page, Model model) {
int result = 0;
Board old = bs.getBoard(board.getNo()); // 상세 ㅈ어보 구하기
// 비번 비교
if(old.getPasswd().equals(board.getPasswd())) { // 비번 일치시
result = bs.delete(board.getNo()); // 글 삭제
} else { // 비번 불일치시
result = -1;
}
model.addAttribute("result", result);
model.addAttribute("page", result);
return "board/deleteresult";
}
- 넘어온 값들은 DTO 객체에 저장해야한다, @ModelAttribute 를 써서 (생략) 넘어온 값들을 바로 DTO 객체 board 에 저장
- 페이지 번호 page 만 DTO 안에 들어갈 수 없으므로 @RequestParam("page") (생략) 을 써서 따로 받는다
- 삭제 성공 메세지를 뿌릴 것이므로 result 값을 Model 객체에 저장해서 넘겨줄 것, Model 객체도 선언함
글 삭제 요청에서 해야할 DB연동 2가지
1. 비번 비교 Select SQL
- 비번 비교를 위해 hidden 으로 넘어온 값인 글 번호 no 를 써서 DB에 저장된 해당 글의 상세 정보를 가져옴
- 이때, 상세 정보를 가져오는 메소드인 getBoard() 를 호출해서 상세정보를 구함
2. 글 수정 Update SQL
- Service 클래스의 메소드 delete() 호출
- 전달하는 값은 삭제할 글 번호인 no
- 돌려받는 값은 글 수정 성공 개수, 즉 성공시 1 을 result 에 돌려받음
- 삭제 성공 이후 View deleteresult.jsp 에서 목록 페이지로 갈 것이므로 페이지 번호 page 를 View 로 전달줘야함
+ 상세 페이지로 갈때는 글 번호도 전달해야한다
- Service 클래스 BoardService.java 에서 delete() 메소드 생성
public int delete(int no) {
return dao.delete(no);
}
- DAO 클래스 BoardDao.java 에서 update() 메소드 생성
public int delete(int no) {
return session.delete("delete", no);
}
- Mapper 파일에서 id 가 "delete" 인 SQL문을 불러옴, 전달하는 값은 수정할 글의 글 번호 no
- 삭제 성공시 1 을 자동으로 돌려준다
- Mapper 파일 board.xml 에서 id 가 "delete" 인 DeleteSQL문 작성
<!-- 글 삭제 -->
<delete id="delete" parameterType="int">
delete from myboard where no = #{no}
</delete>
- View 페이지 deleteresult.jsp 를 board 폴더 하위에 생성 및 작성
<%@ 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>
<c:if test="${result == 1}">
<script>
alert("글 삭제 성공");
location.href = "boardlist.do?page=${page}";
</script>
</c:if>
<c:if test="${result != 1}">
<script>
alert("글 삭제 실패");
history.go(-1);
</script>
</c:if>
</body>
</html>
- if 태그를 사용하기 위해 JSTL core 라이브러리를 불러온다
- 삭제 성공 후엔 상세페이지로는 가지 못함, 목록페이지로 이동하자, 이동하면서 페이지 번호를 전달
- 삭제 실패 시 result 는 -1 이므로 "글 수정 실패" 메세지 출력 후 다시 이전 페이지인 수정 폼으로 돌아간다




- 삭제 성공시 "boardlist.do"로 요청하며 페이지번호를 전달하므로 원래 페이지로 돌아온다
메일 보내기
- 이메일 보내기 위한 메일 서버를 구축해야 이메일을 보낼 수 있다
- Window 계열에선 Exchange Server 서버 (유료) 사용, Linux 계열에선 Qmail 서버 사용
- Exchange Server 로 직접 서버를 구축(세팅)하지 않더라도 이메일을 제공해주는 서버들이 있다
ex) Naver 메일 또는 Gmail 서버를 사용하면 메일 보내고 받기가 가능하다
- 문자로 보내는 것은 유료가 많음, 메일 보내는 것은 Naver, Google 메일 활용시 무료로 ID, 비번 찾기 구현 가능
메일 서버 프로토콜
- Maile 송신(보내기) : STMP (Simple Mail Transfer Protocol) , 기본포트는 25번
- Maile 수신(받기) : POP3(Post Office Protocol 3) , 기본포트는 110번
- 네이버 메일 또는 Gmail 에서 이메일 서버를 사용하려면 환경설정이 필요하다
- 환경설정 후 Spring 을 이용해서 메일 서버를 사용해서 메일 보내기 가능
- 주로 회원관리 프로그램의 아이디 찾기, 비밀번호 찾기 시 메일을 보낸다
Email 보내기
1. Naver Mail Server 활용 : 네이버 메일에서 STMP 활성화
- 우리는 메일을 보내는 작업을 할 것이므로 네이버에서 SMTP 를 활성화 시켜야한다

- SMTP 기본포트 25번 대신 새로운 포트 465 을 사용하고 있다, 즉 기본 포트는 잘 사용하지 않음, 대부분 포트번호를 바꿔서 사용함

- IMAP(POP3 와 비슷한 기능, 암호화 된 것) 도 사용함으로 설정해준다
2. mailTest 프로젝트 가져오기
- 클라우드에서 프로젝트 mailTest 를 다운받아 압축을 풀고 import 한다


- 메일을 보내기 위해 pom.xml 부터 먼저 설정
3. pom.xml 에 메일 보내기 위한 의존 라이브러리 추가
- 메일을 보내기 위한 의존 라이브러리를 추가해야한다
- 여러가지 라이브러리가 있다, 지금은 그 중 commons-email 라이브러리 사용
- pom.xml 부분
<!-- EMail -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.5</version>
</dependency>
- 이 부분이 추가되어 있어야 메일 보내기 가능
+ web.xml 설정
- web.xml 에 servlet-mapping 의 url-pattern 가 *.do 로 설정되어있다, 확장자 do 로 요청해야한다
4. index 파일 실행
- 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>
<a href="send.do">메일 전송</a><br><br>
<script>
// location.href="send.do";
</script>
<%
// response.sendRedirect("send.do");
%>
</body>
</html>
- "메일 전송" 클릭 시 "send.do" 로 요청한다
- Contorller 클래스를 보자
- servlet-context.xml 의 base-pacakge 가 controller 이므로 controller 폴더 아래에 Controller 클래스 MailTest.java 가 있다
package controller;
import java.util.Random;
import org.apache.commons.mail.HtmlEmail;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class MailTest {
@RequestMapping("/send.do")
public String send(Model model) {
Random random = new Random();
int a = random.nextInt(100);
// Mail Server 설정
String charSet = "utf-8";
String hostSMTP = "smtp.naver.com";
String hostSMTPid = "내아이디@naver.com";
String hostSMTPpwd = "내비번"; // 비밀번호 입력해야함
// 보내는 사람 EMail, 제목, 내용
String fromEmail = "내아이디@naver.com";
String fromName = "친절한 Lay씨";
String subject = "Overflow인증메일입니다.";
// 받는 사람 E-Mail 주소
String mail = "내아이디@naver.com";
try {
HtmlEmail email = new HtmlEmail();
email.setDebug(true);
email.setCharset(charSet);
email.setSSL(true);
email.setHostName(hostSMTP);
email.setSmtpPort(587);
email.setAuthentication(hostSMTPid, hostSMTPpwd);
email.setTLS(true);
email.addTo(mail, charSet);
email.setFrom(fromEmail, fromName, charSet);
email.setSubject(subject);
email.setHtmlMsg("<p align = 'center'>Overflow에 오신것을 환영합니다.</p><br>"
+ "<div align='center'> 인증번호 : " + a + "</div>");
email.send();
} catch (Exception e) {
System.out.println(e);
}
model.addAttribute("result", "good~!!\n 등록된 E-Mail 확인");
return "result";
}
}
- 0 ~ 99 까지의 난수값을 메일로 보낼 것
- 난수를 발생시켜서 변수 a 에 저장했다
1. 메일 서버 설정
- 네이버 메일의 smtp 서버를 쓰므로 서버의 네임 hostSTMP 는 "smtp.naver.com" 이다

- hostSTMPid 에는 내 이메일, hostSTMPpwd 에는 내 비밀번호
- 반드시 네이버 이메일이어야함
2. 보내는 사람 Email, 제목, 내용 설정
- 보내는 사람 Email 도 내 이메일로 설정하자
- 반드시 네이버 이메일이어야함
- 메일 서버의 이메일과 같아야함
3. 받는 사람 Email 주소 설정
- 메일을 받을 곳의 이메일 주소를 쓰면 된다
- 반드시 네이버 이메일이 아니어도 된다
HtmlEmail 객체 생성 및 세팅
- commons-email 라이브러리에서 제공하는 HtmlEmail 클래스를 통해 여러가지 설정을 한다
+ setSSL(true) 를 통해 보안 연결을 사용한다

HtmlEmail 클래스의 메소드
- addTo() 메소드는 "받는 사람"의 메일 주소 설정하는 메소드
- setFrom() 메소드는 "보내는 사람" 의 메일 주소, 이름, charSet 등을 설정
- setSubject() 메소드로 제목을 세팅하고, setHtmlMsg() 메소드로 내용을 세팅, 여기에 변수 a ㄱ밧을 전달
- send() 메소드로 이메일을 실제로 전송
- 메일을 전송한 후 전송 성공 메세지를 뿌리기 위해 Model 객체에 성공 메세지를 저장
- prefix 값이 /jsp/ 로 되어있으므로 webabb/jsp/ 안의 result.jsp 파일로 이동한다
- result.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>
결과 페이지: ${result}
</body>
</html>
- 메일을 보내보자


- 네이버 메일에 가서 확인
보낸 메일함 확인


받은 메일함 확인


- 현재는 보낸 메일과 받는 메일을 같게 설정했었다
회원 관리 프로그램
- 회원 가입시 이메일 보내기 기능을 활용해서 아이디 찾기, 비밀번호 찾기를 구현한 회원 관리 프로그램을 보자
- 클라우드의 springmember 프로젝트를 import


파일들 살펴보기 : pom.xml
- pom.xml 부분
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2</version>
</dependency>
- 프로필 사진 (첨부파일) 을 업로드하기 위해 fileupload 라이브러리가 추가되어 있다
- 이메일 관련 라이브러리는 javax.mail 과 commons-email 등이 있다, 현재는 commons-email 라이브러리만 사용
<!-- javax.mail -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
<!-- EMail -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.5</version>
</dependency>
- 아래가 commons-email 라이브러리
파일들 살펴보기 : web.xml
- 들어가는 3가지 내용 중 DispatcherServlet 매핑 부분이 변경되었다
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
- *.do 로 요청해야함
- 들어가는 3가지 내용 중 한글 인코딩 설정 코드가 들어가 있다
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 들어가는 3가지 내용 중 servlet-context.xml , root-context.xml 파일을 불러오고 있다
파일들 살펴보기 : servlet-context.xml
servlet-context.xml 부분 중 base-package
<context:component-scan base-package="myspring" />
- base-package 를 "myspring" 으로 설정해뒀다, src/main/java 하위에 myspring 디렉토리가 있음
- 이 base-package 안의 클래스들은 4가지 어노테이션 중 하나가 붙어있어야한다
* 4가지 어노테이션 : @Component, @Controller, @Service, @Repository

servlet-context.xml 부분 중 ViewResolver
<!-- ViewResolver -->
<beans:bean id="internalResourceViewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="jsp/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
- View 페이지 저장 위치는 webapp 폴더가 기준이다
- 그러므로 prefix 의 value 에 "jsp/" 라고 썼다면 webapp 하위에 jsp 파일을 생성하고 그 안에 View 파일들이 있어야함

servlet-context.xml 부분 중 인터셉터 설정
<!-- 인터셉터 설정 -->
<beans:bean id="sessionChk" class="myspring.controller.SessionCheckInter"/>
<interceptors>
<interceptor>
<mapping path="/member_edit.do"/>
<mapping path="/member_edit_ok.do"/>
<mapping path="/member_del.do"/>
<mapping path="/member_del_ok.do"/>
<mapping path="/member_logout.do"/>
<beans:ref bean="sessionChk"/>
</interceptor>
</interceptors>
- 인터셉터 매핑을 잡고 있다, 로그인 해야만 쓸 수 있는 메뉴(요청) 들이다
ex) 수정, 삭제, 로그아웃관련 요청
- 로그인 해야만 사용할 수 있는 기능을 사용할때 인터셉터를 사용해서 Controller 로 가기 전에 인터셉터 클래스의 preHandler() 부분으로 감
- 인터셉터 클래스인 SessionCheckInter.java 를 보자
- SessionCheckInter.java
package myspring.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
public class SessionCheckInter extends HandlerInterceptorAdapter {
// preHandle(request,response,handler)메소드
// 1.Controller에서 요청(*.do)을 받기 전에 preHandle()가 호출되어 가로채는 역할로 사용
// 2.로그인 하지않고(세션이 없으면) 요청하면 로그인 폼으로 이동 하도록 해주는 역할
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
String id = (String)session.getAttribute("id");
if (id == null || id.equals("")) {
response.sendRedirect("member_login.do"); // 세션이 없으면 로그인 폼으로 이동
return false;
}
return true;
}
}
- 여기서는 HandlerInterceptor 클래스를 상속받아서 인터셉터 클래스를 구현했다
- preHandle() 메소드를 오버라이딩 하고, 그 안에서
- 인터셉터 관련 설멍 : https://laker99.tistory.com/145
인터셉터 구현 방법
1. HandlerInterceptorAdapter 클래스 상속
2. HandlerInterceptor 인터페이스 상속
servlet-context.xml 부분 중 fileupload 설정
<!-- 파일 업로드 설정 -->
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<beans:property name="maxUploadSize" value="10000000"/>
</beans:bean>
- fileupload 설정 bean 객체를 생성하면서 Setter DI 를 수행함
- 이(1MB) 이상의 크기인 첨부파일 업로드시 오류 발생하며 멈춤, 즉 너무 큰 첨부파일을 첨부할 수 없게 함
- 이 설정을 하지 않아도 상관 없음, 이 설정만으로는 막을 수 없다
- Controller 클래스에서 특정 크기가 넘는 파일을 첨부하지 못하도록 처리 가능
+ Controller 클래스에서 "jpg", "gif", "png" 가 아닌 파일은 업로드 되지 않는 등의 설정도 가능
파일들 살펴보기 : root-context.xml
root-context.xml 부분 1
<!-- Data Source -->
<!--
<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<property name="driverClass" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />
<property name="username" value="spring" />
<property name="password" value="spring123" />
</bean>
-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />
<property name="username" value="spring" />
<property name="password" value="spring123" />
</bean>
- 여러가지 클래스들이 있는데 그 중 DriverManagerDataSource 클래스를 사용하고 있다
- 각 클래스마다 프로퍼티가 다르므로 주의
ex) DriverManagerDataSource 는 "driverClassName" 이 프로퍼티명, SimpleDriverDataSource 는 "driverClass" 가 프로퍼티명
root-context.xml 부분 2
<!-- 스프링으로 oracle 디비 연결 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:util/SqlMapConfig.xml" />
<property name="mapperLocations" value="classpath:sql/*.xml" />
</bean>
- 3줄은 순서대로 DB 연결하는 코드, MyBatis 환경설정 파일 Config 파일 불러오기, Mapper 파일 불러오기 코드이다
- 두 환경설정 파일은 resources 폴더 안에 있으므로 classpath: 를 붙여줌
- Setter DI 로 주입해서 sqlSEssionFactoryBean 객체를 생성하고 있다
root-context.xml 부분 3
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
- 이 sqlSessionTemplate 객체에 sqlSessionFactoryBean 객체인 sqlSessioFactory 가 Constructor DI 로 주입되었다, 주입되면서 sqlSessionTemplate 객체 sqlSession 이 생성됨
- 주입이 되었으므로 Config 파일, Mapper 파일을 DAO 에서 불러오는게 가능해짐
+ SqlMapConfig.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>
<typeAliases>
<typeAlias alias="member" type="myspring.model.MemberBean" />
</typeAliases>
</configuration>
- 현재 스프링에서 DB 연동하므로 DTO alis 값 설정하는 역할만 하고 있다
DB 에 테이블 생성
- join_member.sql
--회원관리
select * from tab;
select * from seq;
select * from join_member;
drop table join_member purge;
create table join_member(
join_code number(38) unique not null
/*unique 제약 조건은 중복값을 허용하지 않고 null은 허용*/
, join_id varchar2(20) primary key /*회원아이디*/
, join_pwd varchar2(20) not null /*회원비번*/
, join_name varchar2(50) not null /*회원이름*/
, join_zip1 varchar2(5) not null /*첫번째 우편번호 */
, join_zip2 varchar2(5) /*두번째 우편번호 */
, join_addr1 varchar2(100) not null /*주소*/
, join_addr2 varchar2(100) not null /*나머지 주소 */
, join_tel varchar2(20) not null /*전번*/
, join_phone varchar2(20) not null /*폰번호 */
, join_email varchar2(100) not null /*전자우편 주소*/
, join_profile varchar2(100) /*이진파일명*/
, join_regdate date /*가입 날짜*/
, join_state number(10) /*가입회원 1, 탈퇴회원 2 */
, join_delcont varchar2(4000) /*탈퇴 사유 */
, join_deldate date /*탈퇴 날짜 */
);
/***** join_member 테이블의 join_code 시퀀스 생성 *****/
create sequence join_member_joincode_seq
increment by 1 start with 1 nocache;
--drop sequence join_member_joincode_seq;
insert into join_member (join_code,join_id,join_pwd,join_name,join_zip1,
join_zip2,join_addr1,join_addr2,join_tel,join_phone,join_email,join_regdate,
join_state)
values(join_member_joincode_seq.nextval,'aaaaa',
'77777','홍길동','745','850','서울시 마포구 대흥동','중앙정보 처리학원',
'02-7777-7777','010-9999-9999','hong@naver.com',sysdate,1);
select * from join_member;
--delete from join_member where join_code=21;
--update join_member set join_tel='032-999-9999' where join_id='bbbbb';
join_member 테이블 생성 부분만 (join_member.sql 부분)
create table join_member(
join_code number(38) unique not null
/*unique 제약 조건은 중복값을 허용하지 않고 null은 허용*/
, join_id varchar2(20) primary key /*회원아이디*/
, join_pwd varchar2(20) not null /*회원비번*/
, join_name varchar2(50) not null /*회원이름*/
, join_zip1 varchar2(5) not null /*첫번째 우편번호 */
, join_zip2 varchar2(5) /*두번째 우편번호 */
, join_addr1 varchar2(100) not null /*주소*/
, join_addr2 varchar2(100) not null /*나머지 주소 */
, join_tel varchar2(20) not null /*전번*/
, join_phone varchar2(20) not null /*폰번호 */
, join_email varchar2(100) not null /*전자우편 주소*/
, join_profile varchar2(100) /*이진파일명*/
, join_regdate date /*가입 날짜*/
, join_state number(10) /*가입회원 1, 탈퇴회원 2 */
, join_delcont varchar2(4000) /*탈퇴 사유 */
, join_deldate date /*탈퇴 날짜 */
);
1. join_code : 회원 번호가 저장되는 컬럼, unique 이자 not null 이므로 primary key 나 마찬가지
- sequence 로 넣는다
2. join_id : 회원 id 값이 저장되는 컬럼, primary key
3. join_tel : 1개의 컬럼에 전화번호 앞자리, 중간자리, 끝자리를 결합해서 저장해야한다
4. join_profile : 프로필 이미지 첨부파일명을 저장할 컬럼
5. join_state : 가입한 회원은 1 을 저장, 탈퇴를 하면 delete 시키는 대신 join_state 를 2 로 저장
- 회원 등록시에 insert 문 안에 1 을 넣어서 삽입, 회원 탈퇴시에 "update 문" 으로 join_state 만 2 로 수정
- 실무에선 회원정보를 함부로 삭제 하지 않는다, 회원 상태값만 변경함
- 이 부분을 실행시켜서 테이블 join_member 를 생성
join_member 시퀀스 생성 부분만 (join_member.sql 부분)
/***** join_member 테이블의 join_code 시퀀스 생성 *****/
create sequence join_member_joincode_seq
increment by 1 start with 1 nocache;
- 이 부분을 실행시켜서 시퀀스 join_member_joincode_seq 를 생성
join_member 더미 데이터 삽입 부분만 (join_member.sql 부분)
insert into join_member (join_code,join_id,join_pwd,join_name,join_zip1,
join_zip2,join_addr1,join_addr2,join_tel,join_phone,join_email,join_regdate,
join_state)
values(join_member_joincode_seq.nextval,'aaaaa',
'77777','홍길동','745','850','서울시 마포구 대흥동','중앙정보 처리학원',
'02-7777-7777','010-9999-9999','hong@naver.com',sysdate,1);
- 강제로 더미 데이터들을 삽입
- 이 부분을 실행시켜서 더미 데이터 1개를 생성

기능 확인
- 이제 index 파일 실행시켜보자
- index.jsp 실행
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
response.sendRedirect("member_login.do");
%>

- 아이디 "aaaaa", 비번 77777 로 로그인 가능


- 정보 수정해서 프로필 사진을 등록해보자


- 회원 가입 또는 수정시 첨부파일 확장자가 이미지 확장자가 아니거나 100KB 가 넘는 경우 프로필 사진 등록 불가, 즉 회원가입 및 수정 불가
- 100KB 넘지 않는 작은 사진만 업로드 가능
- 100KB 가 넘는 큰 사진 업로드시 캡처


- 회원가입을 시켜보자


- 비밀번호 찾기를 할때 이메일 주소로 비밀번호를 알려줘야 하기 때문에 회원가입시 이메일 주소는 실제 이메일을 받을 수 있는 이메일 주소로 등록해야한다
- 콘솔창을 확인해서 path: 뒤에 적힌 경로를 확인

Path=C:\Users\admin\Documents\workspace-sts-3.9.11.RELEASE\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\springmember\upload
- 파일 탐색기를 통해 들어가면 실제 업로드가 되어있음을 확인 가능하고, "난수값 형태"로 파일명이 저장되어 있다

- 중복 파일명 문제를 직접 해결하는 방법으로서, 문자형태의 난수값을 발생시켜서 중복문제를 해결했다
- 가입한 계정으로 로그인을 하자


- 비번찾기 기능을 하기전에 Controller 클래스인 MemberAction.java 에서 Mail Server, 보내는 사람 Email 설정을 내 이메일로 바꿔줘야한다
- 받는 쪽 주소 설정은 회원가입시 등록했던 이메일 주소로 설정된다

- 설정하면 다시 실행해서 비번 찾기 기능을 해보자



- 회원가입시 등록했던 이메일 주소의 받은 메일함에서 확인


- SQL Developer 에서 spring 계정 연결 후 select 문으로 들어온 DB에 저장된 데이터(회원)를 확인해보자

코드 확인
- 기능을 봤으니 코드가 어떻게 되어있는지 확인
- 로그인 기능은 생략
- 회원가입 기능부터 보자
- 로그인 페이지에서 "회원가입" 클릭시 "member_join.do" 로 요청한다
<input type="button" value="회원가입" class="input_button"
onclick="location='member_join.do'" />
- 이후 Controller -> member_join.jsp 로 온다
/* 회원가입 폼 */
@RequestMapping(value = "/member_join.do")
public String member_join() {
return "member/member_join";
// member 폴더의 member_join.jsp 뷰 페이지 실행
}
- member_join.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>
<link rel="stylesheet" type="text/css" href="./css/admin.css" />
<link rel="stylesheet" type="text/css" href="<%=request.getContextPath()%>/css/member.css" />
<!-- <script src="/springmember/js/jquery.js"></script> -->
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="<%=request.getContextPath()%>/js/member.js"></script>
<script src="http://dmaps.daum.net/map_js_init/postcode.v2.js"></script>
<script>
//우편번호, 주소 Daum API
function openDaumPostcode() {
new daum.Postcode({
oncomplete : function(data) {
// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
// 우편번호와 주소 정보를 해당 필드에 넣고, 커서를 상세주소 필드로 이동한다.
document.getElementById('join_zip1').value = data.zonecode;
document.getElementById('join_addr1').value = data.address;
}
}).open();
}
</script>
</head>
<body>
<div id="join_wrap">
<h2 class="join_title">회원가입</h2>
<form name="f" method="post" action="member_join_ok.do"
onsubmit="return check()" enctype="multipart/form-data">
<!-- 이진파일을 업로드 할려면 enctype 속성을 지정 -->
<table id="join_t">
<tr>
<th>회원아이디</th>
<td>
<input name="join_id" id="join_id" size="14" class="input_box" />
<input type="button" value="아이디 중복체크" class="input_button"
onclick="id_check()" />
<div id="idcheck"></div>
</td>
</tr>
<tr>
<th>회원비번</th>
<td>
<input type="password" name="join_pwd" id="join_pwd1" size="14"
class="input_box" />
</td>
</tr>
<tr>
<th>회원비번확인</th>
<td>
<input type="password" name="join_pwd2" id="join_pwd2" size="14"
class="input_box" />
</td>
</tr>
<tr>
<th>회원이름</th>
<td>
<input name="join_name" id="join_name" size="14" class="input_box" />
</td>
</tr>
<tr>
<th>우편번호</th>
<td>
<input name="join_zip1" id="join_zip1" size="5" class="input_box"
readonly onclick="post_search()" />
<!-- -<input name="join_zip2" id="join_zip2" size="3" class="input_box" readonly
onclick="post_search()"/> -->
<input type="button" value="우편번호검색" class="input_button"
onclick="openDaumPostcode()" />
</td>
</tr>
<tr>
<th>주소</th>
<td>
<input name="join_addr1" id="join_addr1" size="50" class="input_box"
readonly onclick="post_search()" />
</td>
</tr>
<tr>
<th>나머지 주소</th>
<td>
<input name="join_addr2" id="join_addr2" size="37" class="input_box" />
</td>
</tr>
<tr>
<th>집전화번호</th>
<td>
<%@ include file="../../jsp/include/tel_number.jsp"%>
<select name="join_tel1" >
<c:forEach var="t" items="${tel}" begin="0" end="16">
<option value="${t}">${t}</option>
</c:forEach>
</select>-<input name="join_tel2" id="join_tel2" size="4"
maxlength="4" class="input_box" />-<input name="join_tel3"
id="join_tel3" size="4" maxlength="4" class="input_box" />
</td>
</tr>
<tr>
<th>휴대전화번호</th>
<td>
<%@ include file="../../jsp/include/phone_number.jsp" %>
<select name="join_phone1">
<c:forEach var="p" items="${phone}" begin="0" end="5">
<option value="${p}">${p}</option>
</c:forEach>
</select>-<input name="join_phone2" id="join_phone2" size="4"
maxlength="4" class="input_box" />-<input name="join_phone3"
id="join_phone3" size="4" maxlength="4" class="input_box" />
</td>
</tr>
<tr>
<th>전자우편</th>
<td>
<input name="join_mailid" id="join_mailid" size="10"
class="input_box" />@<input name="join_maildomain"
id="join_maildomain" size="20" class="input_box" readonly />
<!--readonly는 단지 쓰기,수정이 불가능하고 읽기만 가능하다 //-->
<select name="mail_list" onchange="domain_list()">
<option value="">=이메일선택=</option>
<option value="daum.net">daum.net</option>
<option value="nate.com">nate.com</option>
<option value="naver.com">naver.com</option>
<option value="hotmail.com">hotmail.com</option>
<option value="gmail.com">gmail.com</option>
<option value="0">직접입력</option>
</select>
</td>
</tr>
<tr>
<th>프로필사진</th>
<td>
<input type="file" name="join_profile1" />
</td>
</tr>
</table>
<div id="join_menu">
<input type="submit" value="회원가입" class="input_button" />
<input type="reset" value="가입취소" class="input_button"
onclick="$('#join_id').focus();" />
</div>
</form>
</div>
</body>
</html>
- 첨부파일을 전송해야하므로 form 태그에 enctype="multipart/form-data" 속성을 반드시 추가해야한다
전화번호 앞자리 출력
1. 별도의 파일 tel_number.jsp 파일 안의 String 배열 tel 에 전화번호 앞자리들을 저장한다
2. 그 배열 tel 을 request 객체로 공유설정
- import/tel_numeber.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
String[] tel={"02","031","032","033","041","042","043","044","051","052","053","054","055","061","062","063","064"};
//for(int i=0; i<tel.length;i++){
// out.println(tel[i]+"<hr/>");
//}
request.setAttribute("tel",tel);
%>
3. member_join.jsp 파일에서 forEach 태그의 items 에 그 배열 tel 의 요소를 출력
<c:forEach var="t" items="${tel}" begin="0" end="16">
<option value="${t}">${t}</option>
</c:forEach>
- 회원가입 폼에 입력 후 "회원가입" 을 클릭시 "member_join_ok.do" 로 요청한다
- Controller 클래스 MemberAction.java 에서 "member_join_ok.do" 요청 부분 일부
- "member_join_ok.do" 처리 부분이 기므로 하나씩 잘라서 설명

- 여기서 넘어간 첨부파일은 @RequestParam 어노테이션을 통해서 MultipartFile 객체 nf 로 받는다
- 나머지 정보들은 @ModelAttribute (생략) 어노테이션을 통해 DTO MemberBean 객체 member 로 받는다
- 그 객체 nf 로 파일의 원래 이름과 크기를 구한다
- 이후 request 객체로 폴더 upload 의 실제 경로를 구함

난수 발생
- UUID 를 통해 난수를 발생시킨다 * 아래 설명
- 난수화된 파일명은 newfilename 에 저장되게 된다

파일 크기 제한 & 파일 확장자 제한
- 파일 크기가 100KB 초과시 result 에 1 을 저장해서 실패 처리를 uploadResult.jsp 에서 할 수 있게끔 함
- 파싱으로 원래 있던 파일명을 . 으로 파싱해서 파일명은 file[0]에, 확장자는 file[1] 에 저장한다
- 그 확장자가 "jpg", "gif", "png" 가 아닌 경우 업로드 불가, 이때 2 를 저장해서 실패 처리를 uploadResult.jsp 에서 할 수 있게끔 함
업로드 시키기 (MemberAction.java 에서 "member_join_ok.do" 요청 부분 일부)
if (size > 0) { // 첨부파일이 전송된 경우
mf.transferTo(new File(path + "/" + newfilename));
}
- 실제 업로드 폴더에 업로드를 시켜주는 코드이다
- File 객체를 생성하고, 실제 업로드 폴더 경로인 path 에 newfilename 을 붙여서 업로드 시킴

전화번호, 이메일 등 결합
- 컬럼이 하나이므로 결합해서 저장
DB에 회원 등록(삽입)
memberService.insertMember(member);
Mapper 파일에서 회원 등록 SQL문
<!-- 회원저장 -->
<insert id="member_join" parameterType="member">
insert into join_member (join_code,join_id,join_pwd,join_name,
join_zip1,join_addr1,join_addr2,join_tel,join_phone,join_email,join_profile,
join_regdate,join_state) values(join_member_joincode_seq.nextval,
#{join_id},#{join_pwd},#{join_name},
#{join_zip1},#{join_addr1},#{join_addr2},#{join_tel},
#{join_phone},#{join_email},#{join_profile, jdbcType=VARCHAR},sysdate,1)
</insert>
- MyBatis 는 null 값을 허용하지 않으므로 첨부파일명을 저장하는 컬럼에는 null 값을 허용해주는 코드인 jdbcType=VARCHAR 를 추가해줘야한다
+ 첨부파일은 등록할 수도 있고 등록하지 않을수도 있기때문에
- 마지막 컬럼은 회원의 상태값을 저장하는 컬럼, 가입시이므로 1을 저장
return 문
- Controller 클래스에서 return 문에서 새로운 요청을 할때는 redirect: 를 붙여야한다
- 지금까지는 View 페이지로 바로 갔지만, 이번엔 회원가입 성공 후 회원가입 성공 메세지를 뿌리지 않고 바로 로그인 폼으로 가도록 요청, 이땐 redirect: 를 붙여서 새로운 요청을 해야한다
- 난수를 발생시키는 방법을 정리한 예제 RandomFile.java 를 보자
import java.util.UUID;
public class RandomFile {
public static void main(String[] args) {
// TODO Auto-generated method stub
String filename = "clock.jpg";
String extension = filename.substring(filename.lastIndexOf("."), filename.length());
System.out.println("extension:"+extension);
UUID uuid = UUID.randomUUID();
System.out.println("uuid:"+uuid);
String newfilename = uuid.toString() + extension;
System.out.println("newfilename:"+newfilename);
}
}
1. clock.jpg 라는 첨부파일을 저장한다고 하면, 확장자를 subsgring() 을 사용해서 분리한 후 extension 에 저장
2. UUID 클래스로 난수를 발생시킨다
3. 그 난수를 String 형으로 변환시킨 후 확장자를 붙이면 파일명을 문자형태 난수로 만들 수 있다
- 난수가 중복될 확률은 현저히 낮다