복습

파일명 중복문제 해결

- fileupload 라이브러리 사용시 중복문제를 자동 해결해주지 않음
- UUID (java.util) 클래스는 통해 문자형태의 난수값을 만들어 준다

- 이 난수값을 String 형으로 변환하고 확장자를 결합해서 중복문제 해결

- 이 방법 외에도 다른 방법들도 있다

 

- 난수를 발생시키는 방법을 정리한 예제 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 형으로 변환시킨 후 확장자를 붙이면 파일명을 문자형태 난수로 만들 수 있다

- 난수가 중복될 확률은 현저히 낮다

 

첨부파일명 컬럼 크기 주의

- join_profile 컬럼에 난수화된 파일명이 들어가 있다

- 문자형 난수값이 들어가기때문에 첨부파일명 저장되는 컬럼 크기를 50Byte 이상으로 해두기

 


첨부파일 입력양식 name 값 주의

- member_join.jsp 부분

    <tr>
     <th>프로필사진</th>
     <td>
      <input type="file" name="join_profile1" />
     </td>
    </tr>

- DTO 에서 첨부파일명이 저장되는 컬럼과 폼에서 첨부파일 입력양식의 name 을 같은 이름으로 써선 안된다!

- DTO 에 저장하는건 첨부파일"명"이고 첨부파일 입력양식에서 입력하는건 파일이다

- 그 join_profile1 은 여기 @RequestParam("") 에 들어가는 값이다

- 그리고 의도한대로 DB 에 저장할 파일명 프로퍼티명과 폼의 name 이 일치하지 않아으므로 DTO 객체에 자동으로 매핑되어 저장되지 않게 됨

- 이후 따로 중복문제를 처리한 파일명을 구해서 Setter 메소드로 세팅해야한다

- newfile 은 중복문제 해결 후의 파일명

 

INSERT 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>
출처: https://laker99.tistory.com/149?category=1087168 [레이커 갓생일기:티스토리]

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

- MyBatis 는 null 값을 허용하지 않기때문에 null 값이 들어가면 오류가 생김

- jdbcType=VARCHAR 속성을 넣어서 null 값을 허용시켜야 한다

 

Controller 클래스에서 바로 다시 요청하기

		return "redirect:member_login.do";

- Controller 클래스에서 위 문장을 작성하면, 다시 "member_login.do" 로 요청하며 로그인 폼으로 가게 됨

- 이전까지는 Controller 클래스에서 View 페이지로만 이동했다

- 만약 return 하며 다시 요청을 할때는 "redirect:" 를 붙여야한다

 

 

회원 관리 프로그램 (이어서)

id 중복검사

- member.jsp 부분

	//아이디 중복확인
    $.ajax({
        type:"POST",
        url:"member_idcheck.do",
        data: {"memid":memid},        
        success: function (data) { 
        	alert("return success="+data);
      	  if(data==1){	//중복 ID
      		var newtext='<font color="red">중복 아이디입니다.</font>';
      			$("#idcheck").text('');
        		$("#idcheck").show();
        		$("#idcheck").append(newtext);
          		$("#join_id").val('').focus();
          		return false;
	     
      	  }else{	//사용 가능한 ID
      		var newtext='<font color="blue">사용가능한 아이디입니다.</font>';
      		$("#idcheck").text('');
      		$("#idcheck").show();
      		$("#idcheck").append(newtext);
      		$("#join_pwd1").focus();
      	  }  	    	  
        }
        ,
    	  error:function(e){
    		  alert("data error"+e);
    	  }
      });//$.ajax

- ajax() 메소드를 사용했고 url 로 "member_idcheck.do" 로 요청한다, 전달할 데이터는 json 형태로 전달

- 여기서 전달한 값은 받는 쪽에서 request.getParameter() 또는 @RequestParam("") 으로 받음

- 콜백함수로 1 또는 -1 을 돌려받도록 했다, 그것에 맞춰서 처리를 함

 

- Controller 클래스 MemberAction.java 에서 "member_idcheck.do" 요청 처리 부분만

	// ID중복검사 ajax함수로 처리부분
	@RequestMapping(value = "/member_idcheck.do", method = RequestMethod.POST)
	public String member_idcheck(@RequestParam("memid") String id, Model model) throws Exception {
		System.out.println("id:"+id);
		
		int result = memberService.checkMemberId(id);
		model.addAttribute("result", result);

		return "member/idcheckResult";
	}

- 요청을 받아서 @RequestParam("memid") 로 전달된 값 memid 를 받아온다

- 이 memid 는 사용자가 입력했던 아이디이다

- checkMemberId() 메소드를 호출하며 중복검사를 한다 * 아래에 설명

- 그 결과 1 또는 -1 을 result 변수로 받고 그걸 Model 객체에 저장 후 jsp/idcheckResult.jsp 로 이동

 

- Service 클래스 MemberServiceImpl.java 에서 checkMemberId() 메소드 부분만

	public int checkMemberId(String id) throws Exception{
		return memberDao.checkMemberId(id);
	}

- DAO 클래스 MemberDaoImpl.java 에서 checkMemberId() 메소드 부분만

	/***** 아이디 중복 체크 *****/
//	@Transactional
	public int checkMemberId(String id) throws Exception {
		int re = -1;	// 사용 가능한 ID
		MemberBean mb = sqlSession.selectOne("login_check", id);
		if (mb != null)
			re = 1; 	// 중복id
		return re;
	}

- 검색된 결과가 있으면 중복 id 이므로 -1 이 리턴되고, 검색된 결과가 없으면 중복이 아니므로 1 을 리턴한다

 

- Mapper 파일 member.xml 에서 id 가 "login_check" 인 SQL문 부분만

    <!-- 로그인 인증 -->
    <select id="login_check" parameterType="String" resultType="member">
     select * from join_member where join_id=#{id} and join_state=1
    </select>

- 이제는 검색할때 where 조건문에 join_state = 1 이라는 조건을 추가해서 현재 가입된 회원중에서만 검색해야한다

- 해당 id 의 회원 상세정보를 DTO 객체에 저장해서 돌려준다

- 해당 SQL문은 id 중복검사시에도 사용하고 로그인 시에도 사용함

 

- 다시 돌아가며 DAO -> Service -> Controller -> View 페이지로 넘어왔을때 

- idCheckResult.jsp

${result}

- 이 값이 출력되므로 member.js 의 콜백함수로 반환되는 값임, 성공시 1 이 반환, 실패시 -1 이 반환됨


로그인 기능

- 가입이 끝나고 로그인 부분을 보자

		return "redirect:member_login.do";

- 회원가입이 끝나면 Conroller 클래스에서 return redirect: 에 의해 "member_login.do" 로 요청한다

 

- Controller 클래스 MemberAction.java 에서 "member_login.do" 요청 부분만

	/* 로그인 폼 뷰 */
	@RequestMapping(value = "/member_login.do")
	public String member_login() {
		return "member/member_login";
		// member 폴더의 member_login.jsp 뷰 페이지 실행
	}

- 로그인 폼 member_login.jsp 로 가게 됨

 

- member_login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인</title>
<link rel="stylesheet" type="text/css" href="<%=request.getContextPath()%>/css/admin.css" />
<link rel="stylesheet" type="text/css" href="<%=request.getContextPath()%>/css/member.css" />
<!-- <script src="./js/jquery.js"></script> -->
<script src="http://code.jquery.com/jquery-latest.js"></script>

<script>
 function check(){
	 if($.trim($("#id").val())==""){
		 alert("로그인 아이디를 입력하세요!");
		 $("#id").val("").focus();
		 return false;
	 }
	 if($.trim($("#pwd").val())==""){
		 alert("비밀번호를 입력하세요!");
		 $("#pwd").val("").focus();
		 return false;
	 }
 }
 
 /*비번찾기 공지창*/
 function pwd_find(){
	 window.open("pwd_find.do","비번찾기","width=450,height=500");
	 //자바 스크립트에서 window객체의 open("공지창경로와 파일명","공지창이름","공지창속성")
	 //메서드로 새로운 공지창을 만듬.폭이 400,높이가 400인 새로운 공지창을 만듬.단위는 픽셀
 }
</script>
</head>
<body>
 <div id="login_wrap">
  <h2 class="login_title">로그인</h2>
  <form method="post" action="member_login_ok.do" onsubmit="return check()">
   <table id="login_t">
    <tr>
     <th>아이디</th>
     <td>
      <input name="id" id="id" size="20" class="input_box" />
     </td>
    </tr>
    
    <tr>
     <th>비밀번호</th>
     <td>
     <input type="password" name="pwd" id="pwd" size="20" class="input_box"/>
     </td>
    </tr>
   </table>
    <div id="login_menu">
    <input type="submit" value="로그인" class="input_button" />
    <input type="reset" value="취소" class="input_button"
    		onclick="$('#id').focus();" />
    <input type="button" value="회원가입" class="input_button"
    		onclick="location='member_join.do'" />
    <input type="button" value="비번찾기" class="input_button"
    		onclick="pwd_find()" />
    </div>
  </form>
 </div>
</body>
</html>

+ submit 버튼은 하나여야 한다

- 유효성 검사, 비밀번호 찾기 팝업창을 띄워주는 함수 정의, 입력양식 으로 구성되어 있음

- 아이디, 비밀번호를 입력 후 "로그인" 버튼 클릭시 "member_login_ok.do" 로 요청, 그 요청에서 아이디, 비번이 일치하는지 DB에서 확인한다

 

- Controller 클래스 MeberAction.java 에서 "member_login_ok.do" 요청 부분만

	/* 로그인 인증 */
	@RequestMapping(value = "/member_login_ok.do", method = RequestMethod.POST)
	public String member_login_ok(@RequestParam("id") String id, 
			                      @RequestParam("pwd") String pwd,
			                      HttpSession session, 
			                      Model model) throws Exception {
		int result=0;		
		MemberBean m = memberService.userCheck(id);

		if (m == null) {	// 등록되지 않은 회원일때
			
			result = 1;
			model.addAttribute("result", result);
			
			return "member/loginResult";
			
		} else {			// 등록된 회원일때
			if (m.getJoin_pwd().equals(pwd)) {// 비번이 같을때
				session.setAttribute("id", id);

				String join_name = m.getJoin_name();
				String join_profile = m.getJoin_profile();

				model.addAttribute("join_name", join_name);
				model.addAttribute("join_profile", join_profile);

				return "member/main";
				
			} else {// 비번이 다를때
				result = 2;
				model.addAttribute("result", result);
				
				return "member/loginResult";				
			}
		}

	}

- 사용자가 입력한 아이디 , 비밀번호를 @RequestParam 으로 받음

- 회원인증 성공시 SESSION 으로 공유설정해야하므로 session 객체를 매개변수에 선언해서 받는다, 자동으로 생성됨

- Service 클래스의 userCheck() 메소드를 호출해서 회원 인증을 한다, 이때 id 를 전달

- id 만으로 DB에서 해당 id를 가진 데이터가 있는지 확인 후 그 데이터를 가져와서 객체 m 에 저장

- 데이터가 있는 경우에는 그 데이터에서 DB 비밀번호를 m.getJoin_pwd() 로 가져와서 사용자가 입력한 비밀번호가 맞는지 확인하고 있다

- 아이디에 해당하는 데이터가 존재하고, 비밀번호도 일치시 session 객체로 id 를 공유설정함

- 로그인이 되어있는 한 계속 SESSION 값 id 가 공유됨, 사용 가능

- id 가 틀렸을땐 변수 result 에 1을 저장, 비번 틀렸을떈 변수 result 에 2 를 저장

- 이후로그인 성공시 main.jsp 로 이동, 로그인 실패시 loginResult.jsp 로 이동해서 처리

- main.jsp 로 이동할때 사용자의 이름, 프로필 첨부파일명을 Model 객체에 저장해서 전달

 

- Service 클래스 MemberServiceImpl.java 에서 userCheck() 메소드 부분만

	public MemberBean userCheck(String id) throws Exception{
		return memberDao.userCheck(id);		
	}

- DAO 클래스 MemberDaoImpl.java 에서 userCheck() 메소드 부분만

	/* 로그인 인증 체크 */
//	@Transactional
	public MemberBean userCheck(String id) throws Exception {
		return sqlSession.selectOne("login_check", id);
	}

- Mapper 파일 member.xml 에서 id 가 "login_check" 인 SQL문 부분만

    <!-- 로그인 인증 -->
    <select id="login_check" parameterType="String" resultType="member">
     select * from join_member where join_id=#{id} and join_state=1
    </select>

- id 중복체크 시에도 해당 SQL문을 사용했었음

 

- 로그인 실패시엔 loginResult.jsp 로 이동

- loginResult.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="${result == 1}">
	<script>
		alert("등록되지 않는 회원 입니다.");
		history.go(-1);
	</script>
</c:if>   

<c:if test="${result == 2}">
	<script>
		alert("회원정보가 틀렸습니다.");
		history.go(-1);
	</script>
</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" %>    

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>사용자 메인화면</title>
<link rel="stylesheet" type="text/css" href="./css/main.css" />
<link rel="stylesheet" type="text/css" href="./css/admin.css" />
</head>
<body>

<c:if test="${sessionScope.id == null }"> 
  <script>
   alert("다시 로그인 해주세요!");
   location.href="<%=request.getContextPath()%>/member_login.do";
  </script>
</c:if>

<c:if test="${sessionScope.id != null }">  
 <div id="main_wrap">
   <h2 class="main_title">사용자 메인화면</h2>  
   <form method="post" action="member_logout.do"> 
   <table id="main_t">
    <tr>
     <th colspan="2">
     <input type="button" value="정보수정" class="input_button"
     		onclick="location='member_edit.do'" />
     <input type="button" value="회원탈퇴" class="input_button"
     		onclick="location='member_del.do'" />
     <input type="submit" value="로그아웃" class="input_button" />     
     </th>
    </tr>
    
    <tr>
     <th>회원이름</th>
     <td>${join_name}님 로그인을 환영합니다</td>
    </tr>
    
    <tr>
     <th>프로필사진</th>
     <td>
       <c:if test="${empty join_profile}">
       &nbsp;
       </c:if>
       <c:if test="${!empty join_profile}">
       <img src="<%=request.getContextPath() %>/upload/${join_profile}" height="100" width="100" />
       </c:if>
     </td>
    </tr>
   </table>   
   </form>
 </div>
</c:if>

</body>
</html>

- 전체를 if 태그로 감싸서 세션이 있는 경우에만 해당 내용이 출력됨

- 로그인 성공 후, 또는 수정 성공 후 여기로 이동하므로, "main.do" 는 인터셉터 매핑을 잡아두지 않았고, 여기서 세션값이 없을땐 로그인 폼으로 보내주는 처리를 한다

* 프로필 이미지 출력은 나중에 정보 수정 후 main.jsp 로 돌아왔을때 수정


+ 인터셉터 설정

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

- 로그인해야만 쓸 수 있는 메뉴 요청에 대해 매핑을 잡아둠

- 인터셉터 사용 이유 : 비정상적인 접근(세션이 없는 경우) 을 막기 위해서



로그아웃 기능

 

버튼 처리 : "로그아웃" 버튼 클릭시

   <form method="post" action="member_logout.do">
     <input type="button" value="정보수정" class="input_button"
     		onclick="location='member_edit.do'" />

- main.jsp 의 "로그아웃" 버튼은 submit 버튼이므로 action 에 있는 "member_logout.do" 로 요청한다

- 해당 요청은 인터셉터에 등록되었으므로 세션이 있을때만 Controler 의 member_logout.do 로 감

- 세션이 없으면 로그인 폼으로 이동

 

- Controller 클래스 MemberAction.java "member_logout.do" 요청 부분만

	// 로그아웃
	@RequestMapping("member_logout.do")
	public String logout(HttpSession session) {
		session.invalidate();

		return "member/member_logout";
	}

- 인터셉터를 통과해야 즉, 세션값이 있을때만 여기에 도착함

- 로그아웃 시 해야할 일 2가지 : 세션 삭제 , 로그아웃되었다는 메세지 뿌리고 로그인 폼으로 이동

 

- member_logout.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
  
<script>
  alert("로그아웃 되었습니다!");
  location="member_login.do";
</script>

정보 수정폼 기능

 

버튼 처리 : "정보수정" 버튼 클릭시

     <input type="button" value="정보수정" class="input_button"
     		onclick="location='member_edit.do'" />

- main.jsp 에서 "정보수정" 클릭시 "member_edit.do" 로 요청

- 해당 요청은 인터셉터에 등록되었으므로 인터셉터를 거쳐 세션이 있을때만 Controler 의 member_edit.do 로 감

- 세션이 없으면 로그인 폼으로 이동

- "member_edit.do" 로 가면서 아무 값을 가져가지 않음, 세션값을 활용한다

 

- Controller 클래스 MemberAction.java "member_edit.do" 요청 부분만

	/* 회원정보 수정 폼 */
	@RequestMapping(value = "/member_edit.do")
	public String member_edit(HttpSession session, Model m) throws Exception {

		String id = (String) session.getAttribute("id");

		MemberBean editm = memberService.userCheck(id);

		String join_tel = editm.getJoin_tel();
		StringTokenizer st01 = new StringTokenizer(join_tel, "-");
		// java.util 패키지의 StringTokenizer 클래스는 첫번째 전달인자를
		// 두번째 -를 기준으로 문자열을 파싱해준다.
		String join_tel1 = st01.nextToken();// 첫번째 전화번호 저장
		String join_tel2 = st01.nextToken(); // 두번째 전번 저장
		String join_tel3 = st01.nextToken();// 세번째 전번 저장

		String join_phone = editm.getJoin_phone();
		StringTokenizer st02 = new StringTokenizer(join_phone, "-");
		// java.util 패키지의 StringTokenizer 클래스는 첫번째 전달인자를
		// 두번째 -를 기준으로 문자열을 파싱해준다.
		String join_phone1 = st02.nextToken();// 첫번째 전화번호 저장
		String join_phone2 = st02.nextToken(); // 두번째 전번 저장
		String join_phone3 = st02.nextToken();// 세번째 전번 저장

		String join_email = editm.getJoin_email();
		StringTokenizer st03 = new StringTokenizer(join_email, "@");
		// java.util 패키지의 StringTokenizer 클래스는 첫번째 전달인자를
		// 두번째 @를 기준으로 문자열을 파싱해준다.
		String join_mailid = st03.nextToken();// 첫번째 전화번호 저장
		String join_maildomain = st03.nextToken(); // 두번째 전번 저장

		m.addAttribute("editm", editm);
		m.addAttribute("join_tel1", join_tel1);
		m.addAttribute("join_tel2", join_tel2);
		m.addAttribute("join_tel3", join_tel3);
		m.addAttribute("join_phone1", join_phone1);
		m.addAttribute("join_phone2", join_phone2);
		m.addAttribute("join_phone3", join_phone3);
		m.addAttribute("join_mailid", join_mailid);
		m.addAttribute("join_maildomain", join_maildomain);

		return "member/member_edit";
	}

- 수정폼에 상세정보를 뿌려줘야 한다

- 세션에 공유된 id 를 session.getAttribuate("id") 로 받아서 그 id 를 매개변수로 userCheck() 호출하여 상세 정보를 구해옴

- 구해온 상세정보를 DTO 객체 editm 으로 받는다, 그 editm 을 Model 객체에 저장해서 member_edit.jsp 로 전달

- 그 DB에 저장된 정보인 editm 에서 결합된 전화번호, 휴대폰번호, 이메일을 잘라서 따로 저장하고 Model 객체에 저장해서 member_edit.jsp 로 전달

 

- userCheck() 메소드는 이전에도 설명 했으므로 생략, 바로 View 를 보자

 

- member_edit.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="./css/member.css" />
<script src="./js/jquery.js"></script>
<script src="./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_edit_ok.do"
  		onsubmit="return edit_check()" enctype="multipart/form-data">
   <!-- 이진파일을 업로드 할려면 enctype 속성을 지정 -->
   <table id="join_t">
    <tr>
     <th>회원아이디</th>
     <td>
      ${id}
     </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"
      value="${editm.join_name}" />
     </td>
    </tr>
    
    <tr>
     <th>우편번호</th>
     <td>
      <input name="join_zip1" id="join_zip1" size="5" class="input_box"
      		readonly onclick="post_search()" value="${editm.join_zip1}"/>
      <%-- -<input name="join_zip2"  id="join_zip2" size="3" class="input_box" readonly 
      		onclick="post_search()" value="${editm.join_zip2}"/> --%>
      <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 value="${editm.join_addr1}" onclick="post_search()" />
     </td>
    </tr>
    
    <tr>
     <th>나머지 주소</th>
     <td>
      <input name="join_addr2" id="join_addr2" size="37" 
      value="${editm.join_addr2}" 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}" <c:if test="${join_tel1 == t}">${'selected'}
          </c:if>>${t}
        </option>
      </c:forEach>        
      </select>-<input name="join_tel2" id="join_tel2" size="4"
      maxlength="4" class="input_box" value="${join_tel2}"/>-<input  name="join_tel3"
      id="join_tel3" size="4" maxlength="4" class="input_box" 
      value="${join_tel3}"/>
     </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}" <c:if test="${join_phone1 == p}">${'selected'}
          </c:if>>${p}
        </option>
      </c:forEach>
     </select>-<input name="join_phone2" id="join_phone2" size="4"
     maxlength="4" class="input_box" value="${join_phone2}"/>-<input name="join_phone3"
     id="join_phone3" size="4" maxlength="4" class="input_box" 
     value="${join_phone3}"/>
     </td>
    </tr>
    
    <tr>
     <th>전자우편</th>
     <td>
      <input name="join_mailid" id="join_mailid" size="10" 
      class="input_box" value="${join_mailid}"/>@<input name="join_maildomain" 
      id="join_maildomain" size="20" class="input_box" readonly
      value="${join_maildomain}" />
      
      <!--readonly는 단지 쓰기,수정이 불가능하고 읽기만 가능하다 //-->
      <select name="mail_list" onchange="domain_list()">
      <option value="">=이메일선택=</option>
      <option value="daum.net" 
      		<c:if test="${join_maildomain == 'daum.net'}">${'selected'}
            </c:if>>daum.net</option>
      <option value="nate.com" 
      		<c:if test="${join_maildomain == 'nate.com'}">${'selected'}
            </c:if>>nate.com</option>
      <option value="naver.com" 
      		<c:if test="${join_maildomain == 'naver.com'}">${'selected'}
            </c:if>>naver.com</option>
      <option value="hotmail.com" 
            <c:if test="${join_maildomain == 'hotmail.com'}">${'selected'}
            </c:if>>hotmail.com</option>
      <option value="gmail.com" 
            <c:if test="${join_maildomain == 'gmail.com'}">${'selected'}
            </c:if>>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_pwd1').focus();" />
   </div>
  </form>
 </div>
</body>
</html>

- value 속성에 상세정보를 뿌려준다

+ 주솟값이 저장되는 DB 테이블의 컬럼이 두개이다, 주소와 상세정보

- 현재 수정폼에서는 DB와 연동해서 비밀번호가 맞는지 확인하고 있지 않다

- 수정폼의 비밀번호와 비밀번호확인이 같다면 문제없이 "member_edit_ok.do" 로 요청

- 요청하면서 id 값을 가져가지 않는다, 여기선 세션으로 해결하지만 hidden 으로 전달하는 편이 나음

 

회원 아이디 출력 (member_edit.jsp)

     <th>회원아이디</th>
     <td>
      ${id}
     </td>

- 넘어온 객체 editm에도 id 값이 들어있고 Session 값에도 id 가 들어있다

- 현재는 Session 에 공유설정된 id 를 가져오고 있다, 앞에 SessionScope 가 생략된것

- ${editm.id} 로 출력해도 된다

 

집전화번호 앞자리에 값뿌리기

    <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}" <c:if test="${join_tel1 == t}">${'selected'}
          </c:if>>${t}
        </option>
      </c:forEach>        
      </select>-<input name="join_tel2" id="join_tel2" size="4"
      maxlength="4" class="input_box" value="${join_tel2}"/>-<input  name="join_tel3"
      id="join_tel3" size="4" maxlength="4" class="input_box" 
      value="${join_tel3}"/>
     </td>
    </tr>

+ tel_number.jsp 에서 배열 tel 에 지역번호들을 저장하고, REQUEST 공유설정했다

- 여기 member_edit 에서 ${tel} 을 forEach 의 items 에 넣고 Model 에서 넘어온 전화번호 앞자리와 일치하는지 확인해서 일치시 selected 를 추가함

- 휴대폰 번호 앞자리도 마찬가지로 처리

- 이외에도 jQuery 로 간략하게 처리할 수도 있다

 

이메일 도메인 select-option 처리

    <tr>
     <th>전자우편</th>
     <td>
      <input name="join_mailid" id="join_mailid" size="10" 
      class="input_box" value="${join_mailid}"/>@<input name="join_maildomain" 
      id="join_maildomain" size="20" class="input_box" readonly
      value="${join_maildomain}" />
      
      <!--readonly는 단지 쓰기,수정이 불가능하고 읽기만 가능하다 //-->
      <select name="mail_list" onchange="domain_list()">
      <option value="">=이메일선택=</option>
      <option value="daum.net" 
      		<c:if test="${join_maildomain == 'daum.net'}">${'selected'}
            </c:if>>daum.net</option>
      <option value="nate.com" 
      		<c:if test="${join_maildomain == 'nate.com'}">${'selected'}
            </c:if>>nate.com</option>
      <option value="naver.com" 
      		<c:if test="${join_maildomain == 'naver.com'}">${'selected'}
            </c:if>>naver.com</option>
      <option value="hotmail.com" 
            <c:if test="${join_maildomain == 'hotmail.com'}">${'selected'}
            </c:if>>hotmail.com</option>
      <option value="gmail.com" 
            <c:if test="${join_maildomain == 'gmail.com'}">${'selected'}
            </c:if>>gmail.com</option>
      <option value="0">직접입력</option>
     </select> 
     </td>
    </tr>

- if 태그를 option 태그에 일일히 사용함

 

+ readonly vs disabled

- readonly 는 form의 action 으로 값이 넘어가고 disabled 는 값이 넘어가지 않음!

 


정보 수정

- 수정폼 member_edit.jsp 에서 값을 입력 후 "회원수정" 클릭시 "member_edit_ok.do" 로 요청함

- id 값은 넘어오지 않았다

 

- Controller 클래스 MemberAction.java 에서 "member_edit_ok.do" 요청 부분만

	/* 회원정보 수정(fileupload) */
	@RequestMapping(value = "/member_edit_ok.do", method = RequestMethod.POST)
	public String member_edit_ok(@RequestParam("join_profile1") MultipartFile mf, 
								 MemberBean member,
								 HttpServletRequest request, 
								 HttpSession session, 
								 Model model) throws Exception {

		String filename = mf.getOriginalFilename();
		int size = (int) mf.getSize();		
		
		String path = request.getRealPath("upload");
		System.out.println("path:"+path);
		
		int result=0;		
		String file[] = new String[2];
//		file = filename.split(".");
//		System.out.println(file.length);
//		System.out.println("file0="+file[0]);
//		System.out.println("file1="+file[1]);
		
		String newfilename = "";
		
	if(filename != ""){	 // 첨부파일이 전송된 경우		
		
		// 파일 중복문제 해결
		String extension = filename.substring(filename.lastIndexOf("."), filename.length());
		System.out.println("extension:"+extension);
				
		UUID uuid = UUID.randomUUID();
				
		newfilename = uuid.toString() + extension;
		System.out.println("newfilename:"+newfilename);			
		
		StringTokenizer st = new StringTokenizer(filename, ".");
		file[0] = st.nextToken();		// 파일명
		file[1] = st.nextToken();		// 확장자	
		
		if(size > 100000){
			result=1;
			model.addAttribute("result", result);
			
			return "member/uploadResult";
			
		}else if(!file[1].equals("jpg") &&
				 !file[1].equals("gif") &&
				 !file[1].equals("png") ){
			
			result=2;
			model.addAttribute("result", result);
			
			return "member/uploadResult";
		}	
		
	}
		
		if (size > 0) { // 첨부파일이 전송된 경우

			mf.transferTo(new File(path + "/" + newfilename));

		}		
		

		String id = (String) session.getAttribute("id");

		String join_tel1 = request.getParameter("join_tel1").trim();
		String join_tel2 = request.getParameter("join_tel2").trim();
		String join_tel3 = request.getParameter("join_tel3").trim();
		String join_tel = join_tel1 + "-" + join_tel2 + "-" + join_tel3;
		String join_phone1 = request.getParameter("join_phone1").trim();
		String join_phone2 = request.getParameter("join_phone2").trim();
		String join_phone3 = request.getParameter("join_phone3").trim();
		String join_phone = join_phone1 + "-" + join_phone2 + "-" + join_phone3;
		String join_mailid = request.getParameter("join_mailid").trim();
		String join_maildomain = request.getParameter("join_maildomain").trim();
		String join_email = join_mailid + "@" + join_maildomain;

		MemberBean editm = this.memberService.userCheck(id);		
		
		if (size > 0 ) { 			// 첨부 파일이 수정되면
			member.setJoin_profile(newfilename);			
		} else { 					// 첨부파일이 수정되지 않으면
			member.setJoin_profile(editm.getJoin_profile());
		}

		member.setJoin_id(id);
		member.setJoin_tel(join_tel);
		member.setJoin_phone(join_phone);
		member.setJoin_email(join_email);

		memberService.updateMember(member);// 수정 메서드 호출

		model.addAttribute("join_name", member.getJoin_name());
		model.addAttribute("join_profile", member.getJoin_profile());

		return "member/main";
	}

1. 수정폼에선 넘어온 첨부파일은 @RequestParam("join_profile1") 으로 받아서 MultipartFile 객체 mf 에 저장

2. 나머지 수정폼에서 넘어온 값들은 DTO 객체 member 에 저장해서 받음

3. mf.getOriginalFilename() 으로 파일명 filename 을 구한다

4. mf.getSize() 메소드는 return 타입이 LONG 형이지만 int 형으로 강제형변환시켜서 변수 size 에 저장

5. 확장자 처리를 위해 String 배열 file 을 만들어둠

6. 확장자를 분리시켜 변수 extension 에 저장, UUID.randomUUID() 로 난수 발생시켜서 extension 과 붙여서 새로운 파일명 newfilename 을 구함

6. 파일 사이즈 제한에 대한 설정은 root-context.xml 에서 했지만, 그 코드는 더 큰 파일 첨부시 실행을 멈추고 오류를 발생시킴, 그러므로 여기서 if 문을 통해 더 큰 파일 첨부시 return uploadResult 로 보내면서 업로드를 불가능하게 만듬

7. filename 에서 확장자를 빼서 file[1] 에 저장후 file[1] 이 특정 확장자가 아닌 경우 return uploadResult 로 보내면서 업로드를 불가능하게 만듬

8. 첨부파일 업로드를 mf.transferTo() 메소드로 먼저 한다, 그 후 첨부파일을 제외한 다른 수정폼에 입력한 값들을 따로 update 시킨다

9. 수정폼의 전화번호, 휴대폰 번호, 이메일은 분리되어 값이 넘어온 상태이므로 @ModelAttribute 에 의해 DTO 객체 member 에 저장되지 않은 상태이다

10. 수정폼의 분리되어 넘어온 전화번호, 휴대폰 번호, 이메일을 결합해서 Setter 메소드로 객체 member 에 세팅해준다

11. 그리고 그 객체 member 를 매개변수로 updateMember() 를 호출해서 실제 수정을 한다 * 아래에서 설명

+ 현재는 비번 비교를 하지 않음

12. 수정 후 돌아와서 마이페이지 main.jsp 로 이동할 것이므로 회원 이름과 프로필 파일명을 Model 객체에 저장해서 전달해야만한다

- main.jsp 에서는 이 회원 이름과 프로필 파일을 출력하고 있으므로 main.jsp 로 가려면 반드시 여기서 회원 이름과 프로필 파일명을 전달해야함

 

첨부파일을 수정할때와 수정하지 않았을때 ( MemberAction.java "member_edit_ok.do" 요청 부분 일부)

		if (size > 0 ) { 			// 첨부 파일이 수정되면
			member.setJoin_profile(newfilename);			
		} else { 					// 첨부파일이 수정되지 않으면
			member.setJoin_profile(editm.getJoin_profile());
		}

- 사용자가 첨부파일을 수정할때와 수정하지 않을떄의 경우를 나눠서 처리해야한다

- 수정시에는 수정된 파일명이 컬럼에 저장되어야하고 수정되지 않았을땐 기존 파일명이 컬럼에 저장되어야한다

- size 는 사용자가 첨부한 파일을 받은 객체 mf 에서 mf.getSize() 로 구해온 값을 저장하고 있는 변수이다, 즉 사용자가 첨부를 했으면 size 는 0 보다 크고, 첨부를 하지 않았으면 0 이다

- 첨부파일을 수정하면 새 파일의 난수화된 파일명을 객체 member 에 Setter 메소드로 세팅

- 첨부파일을 수정하지 않으면 기존에 DB에 저장된 파일명을 그대로 다시 객체 member 에 Setter 메소드로 세팅

 

- Service 클래스 MemberServiceImpl.java 에서 updateMember() 메소드 부분만

	public void updateMember(MemberBean member) throws Exception{
		memberDao.updateMember(member);
	}

- DAO클래스 MemberDaoImpl.java 에서 updateMember() 메소드 부분만

	/* 회원수정 */
//	@Transactional
	public void updateMember(MemberBean member) throws Exception {
		sqlSession.update("member_edit", member);
	}

- Mapper 파일 member.xml 에서 id 가 "member_edit" 인 SQL문 부분만

    <!-- 회원수정 -->
    <update id="member_edit" parameterType="member">
     update join_member set join_pwd=#{join_pwd},join_name=#{join_name},
     join_zip1=#{join_zip1},join_addr1=#{join_addr1},
     join_addr2=#{join_addr2},join_tel=#{join_tel},join_phone=#{join_phone},
     join_email=#{join_email},join_profile=#{join_profile,jdbcType=VARCHAR} 
     where join_id=#{join_id}
    </update>

- update 문을 사용해서 해당 회원의 정보룰 수정

- 이때도 join_profile 컬럼에 null 이 저장될 수 있다, 기존에도 파일이 없었는데 수정시에도 파일을 업로드 하지 않으면 join_profile 에는 null 이 들어간다

- 그러므로 null 을 허용하기 위해 jdbcType=VARCHAR 를 추가해야한다

 

- 정보 수정 후 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" %>    

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>사용자 메인화면</title>
<link rel="stylesheet" type="text/css" href="./css/main.css" />
<link rel="stylesheet" type="text/css" href="./css/admin.css" />
</head>
<body>

<c:if test="${sessionScope.id == null }"> 
  <script>
   alert("다시 로그인 해주세요!");
   location.href="<%=request.getContextPath()%>/member_login.do";
  </script>
</c:if>

<c:if test="${sessionScope.id != null }">  
 <div id="main_wrap">
   <h2 class="main_title">사용자 메인화면</h2>  
   <form method="post" action="member_logout.do"> 
   <table id="main_t">
    <tr>
     <th colspan="2">
     <input type="button" value="정보수정" class="input_button"
     		onclick="location='member_edit.do'" />
     <input type="button" value="회원탈퇴" class="input_button"
     		onclick="location='member_del.do'" />
     <input type="submit" value="로그아웃" class="input_button" />     
     </th>
    </tr>
    
    <tr>
     <th>회원이름</th>
     <td>${join_name}님 로그인을 환영합니다</td>
    </tr>
    
    <tr>
     <th>프로필사진</th>
     <td>
       <c:if test="${empty join_profile}">
       &nbsp;
       </c:if>
       <c:if test="${!empty join_profile}">
       <img src="<%=request.getContextPath() %>/upload/${join_profile}" height="100" width="100" />
       </c:if>
     </td>
    </tr>
   </table>   
   </form>
 </div>
</c:if>

</body>
</html>

-  ${join_profile} 을 사용해 경로를 잡아서 img 태그로 이미지를 불러옴

 


회원 탈퇴폼 기능

버튼 처리 : "회원탈퇴" 버튼 클릭시

     <input type="button" value="회원탈퇴" class="input_button"
     		onclick="location='member_del.do'" />

- main.jsp 에서 "회원탈퇴" 클릭시 "member_del.do" 로 요청

- 해당 요청은 인터셉터에 등록되었으므로 인터셉터를 거쳐 세션이 있을때만 Controler 의 member_del.do 로 감

- 세션이 없으면 로그인 폼으로 이동

- "member_del.do" 로 가면서 아무 값을 가져가지 않음, 세션값을 활용한다

- 사용자가 회원 탈퇴를 해도 고객 정보를 실제로 DB에서 삭제하지 않고, 상태값을 변경시켜서 탈퇴 회원 처리를 함

- 비밀번호가 맞아야 탈퇴 가능

- 테이블 join_member 에 탈퇴 사유와 탈퇴 날짜를 저장할 수 있는 컬럼 또한 있다

 

join_member 테이블에서 탈퇴 관련 컬럼 3가지

1. 회원 상태 (가입 또는 탈퇴)

2. 탈퇴 사유

3. 탈퇴 날짜

 

- 먼저 삭제폼으로 이동

- Controller 클래스 MemberAction.java "member_del.do" 요청 부분만

	/* 회원정보 삭제 폼 */
	@RequestMapping(value = "/member_del.do")
	public String member_del(HttpSession session, Model dm) throws Exception {

		String id = (String) session.getAttribute("id");
		MemberBean deleteM = memberService.userCheck(id);
		dm.addAttribute("d_id", id);
		dm.addAttribute("d_name", deleteM.getJoin_name());

		return "member/member_del";
	}

- 비밀번호 비교를 하기 위해, 세션에서 id 값을 가져와서 그 id 를 매개변수로 userCheck() 를 호출하여 해당 id 회원의 상세정보를 구해옴

- 탈퇴폼에서 회원 아이디, 이름을 뿌릴 것이므로 회원 아이디와 이름을 Model 객체에 저장하고 탈퇴폼 member_del.jsp 로 이동

 

- userCheck() 메소드는 이전에도 설명했으므로 설명 생략

 

- member_del.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<!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="./css/member.css" />
<script src="./js/jquery.js"></script>
<script>
 function check(){
	 if($.trim($("#pwd").val())==""){
		 alert("비밀번호를 입력하세요!");
		 $("#pwd").val("").focus();
		 return false;
	 }
	 if($.trim($("#del_cont").val())==""){
		 alert("탈퇴사유를 입력하세요!");
		 $("#del_cont").val("").focus();
		 return false;
	 }
 }
</script>
</head>
<body>
 <div id="del_wrap">
  <h2 class="del_title">회원탈퇴</h2>
  <form method="post" action="member_del_ok.do" onsubmit="return check()">
    <table id="del_t">
     <tr>
      <th>회원아이디</th>
      <td>
      ${d_id}
      </td>
     </tr>
     
     <tr>
      <th>회원이름</th>
      <td>${d_name}</td>
     </tr>
     
     <tr>
      <th>비밀번호</th>
      <td>
      <input type="password" name="pwd" id="pwd" size="14" 
      			class="input_box" />
      </td>
     </tr>
     
     <tr>
      <th>탈퇴사유</th>
      <td>
      <textarea name="del_cont" id="del_cont" rows="7" 
      			cols="30" class="input_box"></textarea>
      </td>
     </tr>
    </table>
    
    <div id="del_menu">
     <input type="submit" value="탈퇴" class="input_button" />
     <input type="reset" value="취소" class="input_button"
     	onclick="$('#pwd').focus();" />
    </div>
  </form>
 </div>
</body>
</html>

- 회원 아이디, 회원 이름을 출력함

- 비밀번호와 탈퇴 사유를 입력하고 "탈퇴" 버튼 클릭시 "member_del_ok.do" 로 요청한다

- 이때 아이디와 회원 이름은 넘어가지 않음


회원 탈퇴

- 회원 탈퇴폼 member_del.jsp 에서 비밀번호와 탈퇴 사유 입력 후 "탈퇴" 클릭시 "member_del_ok.do" 로 요청함

 

- Controller 클래스 MemberAction.java 에서 "mebmer_del_ok.do" 요청 부분만

	/* 회원정보 삭제 완료 */
	@RequestMapping(value = "/member_del_ok.do", method = RequestMethod.POST)
	public String member_del_ok(@RequestParam("pwd") String pass, 
							    @RequestParam("del_cont") String del_cont,
							    HttpSession session) throws Exception {

		String id = (String) session.getAttribute("id");
		MemberBean member = this.memberService.userCheck(id);

		if (!member.getJoin_pwd().equals(pass)) {

			return "member/deleteResult";
			
		} else {				// 비번이 같은 경우
			
			String up = session.getServletContext().getRealPath("upload");
			String fname = member.getJoin_profile();
			System.out.println("up:"+up);
			
			// 디비에 저장된 기존 이진파일명을 가져옴
			if (fname != null) {// 기존 이진파일이 존재하면
				File delFile = new File(up +"/"+fname);
				delFile.delete();// 기존 이진파일을 삭제
			}
			MemberBean delm = new MemberBean();
			delm.setJoin_id(id);
			delm.setJoin_delcont(del_cont);

			memberService.deleteMember(delm);// 삭제 메서드 호출

			session.invalidate();	// 세션만료

			return "redirect:member_login.do";
		}
	}

- 넘어온 비밀번호와 탈퇴사유를 @RequestParam 으로 각각 변수 pass, del_cont 에 받는다

- 회원 아이디는 세션에서 구해온다, 그 id 를 매개변수로, 비밀번호가 맞는지 확인하기 위해 userCheck() 메소드 호출

- DB의 비번이 사용자가 탈퇴폼에서 입력한 비밀번호와 일치하지 않으면 deleteResult.jsp 로 이동, 거기서 탈퇴 실패 처리

- 비번이 일치하면, 첨부파일이 있는 경우 첨부파일을 삭제해야한다

 

첨부파일 삭제 (Controller 클래스 MemberAction.java 에서 "mebmer_del_ok.do" 요청 부분 일부)

			String up = session.getServletContext().getRealPath("upload");
			String fname = member.getJoin_profile();
			System.out.println("up:"+up);
			
			// 디비에 저장된 기존 이진파일명을 가져옴
			if (fname != null) {// 기존 이진파일이 존재하면
				File delFile = new File(up +"/"+fname);
				delFile.delete();// 기존 이진파일을 삭제
			}

- 업로드 폴더 경로를 구해온다, 현재는 session 객체로 절대 경로를 구해서 변수 up 에 저장

+ request 객체로 getReaulPath() 해서 경로를 구할 수도 있다

- DB에 저장된 프로필 파일명을 fname 파일명에 저장

- up 과 filename 을 사용해서 File 객체를 생성 하고 그 파일 객체 delFile 을 통해 delte() 로 해당 파일 삭제

 

탈퇴 SQL문에 전달하기 위해 DTO객체 생성 후 두가지 값 세팅  (Controller 클래스 MemberAction.java 에서 "mebmer_del_ok.do" 요청 부분 일부)

			MemberBean delm = new MemberBean();
			delm.setJoin_id(id);
			delm.setJoin_delcont(del_cont);

			memberService.deleteMember(delm);// 삭제 메서드 호출

- DTO 객체 delm을 생성해서 id 값과 탈퇴 사유를 delm 저장함

- 탈퇴 처리를 위해 id 도 필요하고, 탈퇴 사유도 필요하다. id 는 where 조건절에 들어갈 것

- Mapper 파일에 값을 전달할때 1개의 값만 전달가능하므로 DTO 객체 안에 두가지 값을 저장해서 DTO 객체를 넘길 것

 

탈퇴 메소드 호출 (Controller 클래스 MemberAction.java 에서 "mebmer_del_ok.do" 요청 부분 일부)

			memberService.deleteMember(delm);// 삭제 메서드 호출

- Service 클래스의 메소드 deleteMember() 호출 * 아래에서 설명

 

세션 끊고  (Controller 클래스 MemberAction.java 에서 "mebmer_del_ok.do" 요청 부분 일부)

			session.invalidate();	// 세션만료

			return "redirect:member_login.do";

- DB에서 탈퇴 처리한 후 세션을 삭제해줌

- 이후 "member_login.do" 로 요청하면서 로그인 폼으로 이동


- Service 클래스 MemberServiceImpl.java 에서 deleteMember() 메소드 부분만

	public void deleteMember(MemberBean member) throws Exception{
		memberDao.deleteMember(member);
	}

- DAO클래스 MemberDaoImpl.java 에서 deleteMember() 메소드 부분만

	/* 회원삭제 */
//	@Transactional
	public void deleteMember(MemberBean delm) throws Exception {
		sqlSession.update("member_delete", delm);
	}

- 값을 하나만 전달가능하지만 탈퇴 SQL문에서 id 도 필요하고 탈퇴사유도 필요하므로 두개를 저장한 DTO 객체 delm 을 넘김

 

- Mapper 파일 member.xml 에서 id 가 "member_delete" 인 SQL문 부분만

    <!-- 회원삭제 -->
    <update id="member_delete" parameterType="member">
      update join_member set join_delcont=#{join_delcont}, join_state=2,
      join_deldate=sysdate where join_id=#{join_id}
    </update>

- where 조건절에 DTO 로 넘어온 id 값이 들어가서 해당 회원의 데이터를 변경킴

- delete 가 아닌 update 문으로 join_state 를 2로 변경시켜서 탈퇴를 처리함

- 이때 DTO 로 넘어온 탈퇴 사유를 세팅하고, 탈퇴 날짜를 sysdate 로 세팅


비번 찾기 기능 실행

- 실행해서 비번 찾기를 해보자

 

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

- 아래에서 비번 찾기 기능 코드 설명할 것

 

+ 임시비번 기능 활용

- 이메일로 임시비번을 저장하고 DB에도 그 임시비번으로 비밀번호를 업데이트 시켜둬야한다

- 그래야 그 임시비번으로 로그인 가능

 


비번 찾기 기능

- 로그인 폼 member_login.jsp 에서 버튼 "비번찾기" 클릭시 pwd_find() 메소드 호출함

- 비번 찾기도 비번 찾기 폼과 비번 찾기가 있음, 여기선 한번에 설명

- pwd_find() 메소드는 팝업창을 띄운다

 

- member_login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인</title>
<link rel="stylesheet" type="text/css" href="<%=request.getContextPath()%>/css/admin.css" />
<link rel="stylesheet" type="text/css" href="<%=request.getContextPath()%>/css/member.css" />
<!-- <script src="./js/jquery.js"></script> -->
<script src="http://code.jquery.com/jquery-latest.js"></script>

<script>
 function check(){
	 if($.trim($("#id").val())==""){
		 alert("로그인 아이디를 입력하세요!");
		 $("#id").val("").focus();
		 return false;
	 }
	 if($.trim($("#pwd").val())==""){
		 alert("비밀번호를 입력하세요!");
		 $("#pwd").val("").focus();
		 return false;
	 }
 }
 
 /*비번찾기 공지창*/
 function pwd_find(){
	 window.open("pwd_find.do","비번찾기","width=450,height=500");
	 //자바 스크립트에서 window객체의 open("공지창경로와 파일명","공지창이름","공지창속성")
	 //메서드로 새로운 공지창을 만듬.폭이 400,높이가 400인 새로운 공지창을 만듬.단위는 픽셀
 }
</script>
</head>
<body>
 <div id="login_wrap">
  <h2 class="login_title">로그인</h2>
  <form method="post" action="member_login_ok.do" onsubmit="return check()">
   <table id="login_t">
    <tr>
     <th>아이디</th>
     <td>
      <input name="id" id="id" size="20" class="input_box" />
     </td>
    </tr>
    
    <tr>
     <th>비밀번호</th>
     <td>
     <input type="password" name="pwd" id="pwd" size="20" class="input_box"/>
     </td>
    </tr>
   </table>
    <div id="login_menu">
    <input type="submit" value="로그인" class="input_button" />
    <input type="reset" value="취소" class="input_button"
    		onclick="$('#id').focus();" />
    <input type="button" value="회원가입" class="input_button"
    		onclick="location='member_join.do'" />
    <input type="button" value="비번찾기" class="input_button"
    		onclick="pwd_find()" />
    </div>
  </form>
 </div>
</body>
</html>

 

팝업창 띄우기 (member_login.jsp 부분)

 /*비번찾기 공지창*/
 function pwd_find(){
	 window.open("pwd_find.do","비번찾기","width=450,height=500");
	 //자바 스크립트에서 window객체의 open("공지창경로와 파일명","공지창이름","공지창속성")
	 //메서드로 새로운 공지창을 만듬.폭이 400,높이가 400인 새로운 공지창을 만듬.단위는 픽셀
 }

- 자바 스크립트에서 window객체의 open("공지창경로와 파일명","공지창이름","공지창속성") 을 써서 팝업창 띄움

- Model 1 에서는 jsp 파일을 첫번째 매개변수에 썼지만 지금은 Model 2 기반이므로 Controller 를 거쳐야한다, 그러므로 요청명을 쓴다

 

- 즉 사용자가 "비번찾기" 클릭시 pwd_find() 메소드가 호출되어 "pwd_find.do" 로 요청한다

- Controller 클래스 MemberAction.java 에서 "pwd_find.do" 요청 부분만

	/* 비번찾기 폼 */
	@RequestMapping(value = "/pwd_find.do")
	public String pwd_find() {
		return "member/pwd_find";
		// member 폴더의 pwd_find.jsp 뷰 페이지 실행
	}

- Controller 클래스를 거쳐 pwd_find.jsp 로 간다, 그 pwd_find.jsp 안의 내용이 바로 팝업창에 출력될 내용임

 

- pwd_find.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="./css/member.css" />
<script src="./js/jquery.js"></script>
<script>
 function check(){
	 if($.trim($("#id").val())==""){
		 alert("아이디를 입력하세요!");
		 $("#id").val("").focus();
		 return false;
	 }
	 if($.trim($("#name").val())==""){
		 alert("회원이름을 입력하세요!");
		 $("#name").val("").focus();
		 return false;
	 }
 }
</script>
</head>
<body>
 <div id="pwd_wrap">
 
 <c:if test="${empty pwdok}"> 
  <h2 class="pwd_title">비번찾기</h2>
  <form method="post" action="pwd_find_ok.do" onsubmit="return check()">  
   <table id="pwd_t">
    <tr>
     <th>아이디</th>
     <td><input name="join_id" id="id" size="14" class="input_box" /></td>
    </tr>
    
    <tr>
     <th>회원이름</th>
     <td><input name="join_name" id="name" size="14" class="input_box" /></td>
    </tr>
   </table>
   <div id="pwd_menu">
    <input type="submit" value="찾기" class="input_button" />
    <input type="reset" value="취소" class="input_button" 
    onclick="$('#id').focus();"/>
   </div>
   <div id="pwd_close">
    <input type="button" value="닫기" class="input_button"
    onclick="self.close();" />
    <!-- close()메서드로 공지창을 닫는다. self.close()는 자바스크립트이다. -->
   </div>
  </form>
  </c:if>
  
  
  <c:if test="${!empty pwdok}">
    <h2 class="pwd_title2">비번찾기 결과</h2>
    <table id="pwd_t2">
     <tr>
      <th>검색한 비번:</th>
      <td>${pwdok}</td>
     </tr>
    </table>
    <div id="pwd_close2">
    <input type="button" value="닫기" class="input_button"
    onclick="self.close();" />
    <!-- close()메서드로 공지창을 닫는다. self.close()는 자바스크립트이다. -->
    </div>
  </c:if> 
  
 </div>
</body>
</html>

- 이 파일 pwd_find.jsp 안의 내용이 팝업창에 나타난다

- 조건식을 써서 pwdok 가 empty 인 경우와 empty 가 아닌경우로 나누어서 다른 내용을 실행함

1. 처음 이 파일에 올때는 pwdok 이 empty 인 상태이다,

2. 사용자가 아이디, 비번을 쓰고 "찾기" 클릭시 "pwd_find_ok.do" 로 요청하며 폼에 작성된 아이디, 비번값을 전송한다

* 아래에서 설명

3. DB에서 아이디, 비번이 일치되면 비밀번호를 메일을 보낸 후 Controller 클래스에서 pwdok 에서 pwdok 에 메세지를 저장한 뒤 다시 return 으로 pwd_find.jsp 로 온다

4. 다시 돌아오면 pwdok 가 empty 가 아니게 되므로 아래의 if 태그를 실행, 즉 아래 화면이 출력됨

 

 

- Controller 클래스 MemberAction.java 에서 "pwd_find_ok.do" 요청 부분만

	/* 비번찾기 완료 */
	@RequestMapping(value = "/pwd_find_ok.do", method = RequestMethod.POST)
	public String pwd_find_ok(@ModelAttribute MemberBean mem, HttpServletResponse response, Model model)
			throws Exception {
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();

		MemberBean member = memberService.findpwd(mem);

		if (member == null) {// 값이 없는 경우

			return "member/pwdResult";

		} else {

			// Mail Server 설정
			String charSet = "utf-8";
			String hostSMTP = "smtp.naver.com";
			String hostSMTPid = "아이디@naver.com";
			String hostSMTPpwd = "비밀번호"; // 비밀번호 입력해야함

			// 보내는 사람 EMail, 제목, 내용
			String fromEmail = "아이디@naver.com";
			String fromName = "관리자";
			String subject = "비밀번호 찾기";

			// 받는 사람 E-Mail 주소
			String mail = member.getJoin_email();

			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'>비밀번호 찾기</p><br>" + "<div align='center'> 비밀번호 : "
						+ member.getJoin_pwd() + "</div>");
				email.send();
			} catch (Exception e) {
				System.out.println(e);
			}

			model.addAttribute("pwdok", "등록된 email을 확인 하세요~!!");
			return "member/pwd_find";

		}

	}

- 넘어온 아이디와 회원이름은 @ModelAttribute 로 DTO MemberBean 객체 mem 에 바로 받아서 저장한다

- 그 객체 mem 을 사용해서 Service 클래스의 findpwd() 메소드를 호출해서 회원 상세정보를 받아옴

+ SQL문에 값 1개만 전달 가능하므로 id 와 이름을 DTO 객체에 저장해서 통째로 넘김

+ findpwd() 를 따라가보자 * 아래에서 설명

- findpwd() 에서 돌아온 후, 돌아온 회원의 상세정보를 객체 member 에 저장

- member 값이 없다면 없는 회원이므로 pwdResult.jsp 로 이동, 값이 있다면 메일을 보내기위한 설정을 한다

- 메일을 보낸 후 Model 객체를 통해 "pwdok" 에 메세지를 저장하고 다시 pwd_find.jsp 로 이동

 

이메일 보내는 설정 및 보내는 코드 (Controller 클래스 MemberAction.java 에서 "pwd_find_ok.do" 요청 부분 일부)

			// Mail Server 설정
			String charSet = "utf-8";
			String hostSMTP = "smtp.naver.com";
			String hostSMTPid = "아이디@naver.com";
			String hostSMTPpwd = "비밀번호"; // 비밀번호 입력해야함

			// 보내는 사람 EMail, 제목, 내용
			String fromEmail = "아이디@naver.com";
			String fromName = "관리자";
			String subject = "비밀번호 찾기";

			// 받는 사람 E-Mail 주소
			String mail = member.getJoin_email();

			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'>비밀번호 찾기</p><br>" + "<div align='center'> 비밀번호 : "
						+ member.getJoin_pwd() + "</div>");
				email.send();
			} catch (Exception e) {
				System.out.println(e);
			}

- 받는 사람의 이메일 주소로는 객체 member 에서 member.getJoin_email() 로 회원의 이메일 주소를 가져와서 설정

- HtmlEmail 객체 email 를 생성, setSubject() 로 제목 설정, setHtmlMsg() 로 내용 설정하며 검색된 비밀번호를 보냄


- Service 클래스 MemberServiceImpl.java 에서 findpwd() 메소드 부분만

	public MemberBean findpwd(MemberBean m)throws Exception {
		return memberDao.findpwd(m);
	}

- DAO 클래스 MemberDaoImpl.java 에서 findpwd() 메소드 부분만

	/* 비번 검색 */
//	@Transactional
	public MemberBean findpwd(MemberBean pm) throws Exception {
		return sqlSession.selectOne("pwd_find", pm);
	}

- Mapper 파일 member.xml 에서 id 가 "pwd_find" 인 SQL문 부분만

    <!-- 비번 검색 -->
    <select id="pwd_find" resultType="member" parameterType="member">
     select *  from join_member where join_id=#{join_id} and join_name=#{join_name}
    </select>

- 사용자가 비밀번호 찾기 폼에 입력한 id 와 이름이 모두 일치하는 데이터를 검색한다



댓글 게시판 프로그램

실습 준비

- 클라우드의 springboard 프로젝트 다운, 압축 해제, import 하기

 

파일들 살펴보기 : pom.xml

- inject 라이브러리 : @inject 어노테이션을 쓰기 위한 라이브러리, @Autowired 와 같은 역할

- 메일 보내기 위한 라이브러리 등 라이브러리가 들어가있다

 

파일들 살펴보기 : web.xml

- 반드시 WEB-INF 폴더 하위에 있어야함

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

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


	<!-- The definition of the Root Spring Container shared by all Servlets 
		and Filters -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/spring/root-context.xml</param-value>
	</context-param>

	<!-- Creates the Spring Container shared by all Servlets and Filters -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- Processes application requests -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>

</web-app>

- url-patteron 을 *.do 로 설정했다

- servlet-context.xml 과 root-context.xml 을 불러옴

- filter 와 filter-mapping 을 통해 한글값 인코딩 처리를 함

 

파일들 살펴보기 : servlet-context.xml

- 이름과 위치가 지정되어있지 않다

- resources 폴더 하위에 있을 수도 있음

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<resources mapping="/resources/**" location="/resources/" />

	<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/jsp/" />
		<beans:property name="suffix" value=".jsp" />
	</beans:bean>
	
	<context:component-scan base-package="myspring" />
	
</beans:beans>

- base-package 를 지정했다 = 어노테이션 기반으로 쓰겠다는 의미

- base-package 를 "myspring" 으로 지정, 이게 JAVA 파일이 저장될 최상위 디렉토리이다

- webapp 가 기준이므로 prefix 로 설정된 /jsp/ 에서 jsp 폴더는 webapp 폴더 하위에 있어야한다

- 폴더 jsp 는 View 파일이 저장될 최상위 디렉토리이다.

 

파일들 살펴보기 : mybatis-config.xml

- MyBatis 환경설정 파일이다

- MyBatis 환경설정 파일 mybatis-config.xml (이름이 다를 수도 있음) 과 Mapper 파일은 주로 resources 폴더에 있음

- resources 폴더 하위에 있을때는 classpath: 를 붙여서 경로를 구함

<?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 type="myspring.model.BoardBean" alias="board"></typeAlias>
	</typeAliases>

</configuration>

- alias 로 DTO BoardBean 의 별칭을 "board" 로 설정

 

파일들 살펴보기 : board.xml

- Mapper 파일이다

- MyBatis 환경설정 파일 mybatis-config.xml (이름이 다를 수도 있음) 과 Mapper 파일은 주로 resources 폴더에 있음

- resources 폴더 하위에 있을때는 classpath: 를 붙여서 경로를 구함

<?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="Test">

	<!-- 게시판 저장 -->
	<insert id="board_insert" parameterType="board">
		insert into board53
		(board_num,board_name,board_pass,board_subject,
		board_content,board_re_ref,board_re_lev,board_re_seq,board_readcount,board_date)
		values
		(board53_num_seq.nextval,#{board_name},#{board_pass},#{board_subject},
		#{board_content},board53_num_seq.nextval,0,0,0,SYSDATE)
	</insert>

	<!-- 게시판 총게시물 수 -->
	<select id="board_count" resultType="int">
		select count(board_num) from board53
	</select>

	<!-- 게시판 목록 (page번호를 전달받아서 startRow와 endRow를 구함) -->
	<select id="board_list" parameterType="int" resultType="board">
	    <![CDATA[
		select * from 
		 (select rownum rnum,BOARD_NUM,BOARD_NAME,BOARD_SUBJECT,BOARD_CONTENT,
		 BOARD_RE_REF,BOARD_RE_LEV,BOARD_RE_SEQ,BOARD_READCOUNT,
	 	 BOARD_DATE from  
	  	 (select * from board53 order by BOARD_RE_REF desc,BOARD_RE_SEQ asc)) 
	   		 where rnum >= ((#{page}-1) * 10+1)  and rnum <= (#{page} * 10)
		]]>
	</select>

	<!-- 게시판 내용보기 -->
	<select id="board_cont" resultType="board"
		parameterType="int">
		select * from board53 where board_num=#{board_num}
	</select>

	<!-- 게시판 조회수 증가 -->
	<update id="board_hit" parameterType="int">
		update board53 set
		board_readcount=board_readcount+1
		where board_num=#{board_num}
	</update>

	<!-- 게시물 수정 -->
	<update id="board_edit" parameterType="board">
		update board53 set
		board_name=#{board_name},
		board_subject=#{board_subject},
		board_content=#{board_content}
		where board_num=#{board_num}
	</update>

	<!-- 게시물 삭제 -->
	<delete id="board_del" parameterType="int">
		delete from board53 where
		board_num=#{board_num}
	</delete>

	<!-- 답변글 레벨 증가 -->
	<update id="board_Level" parameterType="board">
		update board53 set
		board_re_seq=board_re_seq+1
		where board_re_ref=#{board_re_ref} and
		board_re_seq > #{board_re_seq}
	</update>

	<!-- 답변글 저장 -->
	<insert id="board_reply" parameterType="board">
		insert into board53
		(board_num,board_name,board_subject,board_content,
		board_pass,board_re_ref,board_re_lev,board_re_seq,board_readcount,board_date)
		values(board53_num_seq.nextval,#{board_name},#{board_subject},#{board_content},
		#{board_pass},#{board_re_ref},#{board_re_lev},#{board_re_seq},0,SYSDATE)
	</insert>

</mapper>

 

파일들 살펴보기 : root-context.xml

- 이름과 위치가 지정되어있지 않다, resources 폴더 하위에 있을 수도 있음

- root-context.xml 에서 mybatis-config.xml 과 Mapper 파일을 불러오기 때문에 root-context.xml 은 마지막에 설정해야한다

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">

	<!-- 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> -->
	
	
	<!-- 스프링으로 oracle 디비 연결 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="configLocation" value="classpath:util/mybatis-config.xml" />
		<property name="mapperLocations" value="classpath:sql/*.xml" />
	</bean>

	<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg index="0" ref="sqlSessionFactory" />
	</bean>

</beans>

- 이 파일을 세팅해야만 DAO 클래스에서 @Autowired 어노테이션으로 SqlSession 주입 가능

- 여기서 Constructor DI 로 생성된 SqlSession 객체 sqlSessionFactory 를 어노테이션 기반으로 DAO 클래스의 @Autowried 쪽으로 주입한다

+ SqlSession 클래스는 MyBatis 지원 인터페이스, SqlSessionTemplate 는 SqlSession 인터페이스의 구현 클래스

- root-context.xml 에 대한 자세한 설명은 이전에 했으므로 생략

- root-context.xml 코드 자세한 설명 (검색어 : 파일들 살펴보기 : root-context.xml ) : https://laker99.tistory.com/147

 

Data Source Bean 생성 중 url 부분

		<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />

- 이후 프로젝트할땐 localhost 대신 AWS 에 오라클을 설치하고, 받은 IP 를 여기 작성한다


댓글 게시판 테이블 생성

- root-context.xml 에서 지정했던 계정 (spring 계정)을 활성화 시키기

- Connection Profile 도 설정하기

- board53.sql

--board53.sql
select * from tab;
select * from board53; 

create table board53(
    board_num number(38) primary key
  , board_name varchar2(50) not null
  , board_pass varchar2(30) not null
  , board_subject varchar2(100) not null
  , board_content varchar2(4000) not null
  , board_re_ref number 
  , board_re_lev number 
  , board_re_seq number 
  , board_readcount number 
  , board_date date 
);

create sequence board53_num_seq
                increment by 1 start with 1 nocache;

- 실행해서 테이블 board53, 시퀀스 board53_num_seq 를 생성

 

테이블 board53 컬럼 설명

- board_num : 글 번호, board53_num_seq 값을 넣음

- board_pass : 글 비밀번호, 비밀번호를 알야아 수정 / 삭제 가능

- board_re_ref : 댓글 관련 컬럼 1, 원문은 board_num 과 값 같음, 댓글은 부모의 boadr_re_ref 와 값 같음

- board_re_lev : 댓글 관련 컬럼 2, 댓글의 깊이 저장, 원문은 0, 댓글은 1, 대댓글은 2

- board_re_seq : 댓글 관련 컬럼 3, 댓글의 출력 순서 저장, 원문은 0, 댓글들은 바뀜

- 값의 변화가 빈번하게 일어나는 것은 number 타입, 아닌 것은 varchar2 타입으로 설정

- 조회수는 자주 변화하므로 board_readcount 는 number 타입으로 설정


댓글 게시판 프로그램 흐름 보기

- 프로젝트 실행시 webapp 폴더 안의 index 파일을 실행해준다

- 프로젝트 또는 index 파일 실행

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

    
<script>
//	location.href="test.do";
	location.href="board_list.do";
</script>

- "글쓰기" 를 눌러 원문 글을 작성해보자

+ 원문과 댓글 작성 폼이 따로 있다

- 제목 클릭시 상세페이지로 이동하고 조회수 1 증가

- "답변" 을 누르면 댓글을 달 수 있다

- 댓글과 대댓글 달기

* 가장 최근에 달린 글이 위에 온다

- 위의 글 5개는 모두 같은 board_re_ref 값을 가지고 있음

- board_re_seq 의 오름차순 순으로 정렬되었다

- 원문의 board_re_seq 는 0 이고, 가장 최근에 달린 1단계 댓글의 board_re_seq 는 1

 

- SQL Developer 참고


댓글 게시판 프로그램 코드 보기

글 목록 (게시판) 기능 (간략, 자세한 설명은 나중에)

- index.jsp

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

    
<script>
//	location.href="test.do";
	location.href="board_list.do";
</script>

- "board_list.do" 로 요청, 목록을 가져오는 요청이다

 

- Controller 클래스 BoardController.java 에서 "board_list.do" 요청 부분만

	/* 게시판 목록 */
	@RequestMapping(value = "/board_list.do")
	public String list(HttpServletRequest request, Model model) throws Exception {

		List<BoardBean> boardlist = new ArrayList<BoardBean>();

		int page = 1;
		int limit = 10; // 한 화면에 출력할 레코드수

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

		// 총 리스트 수를 받아옴.
		int listcount = boardService.getListCount();

		// 페이지 번호(page)를 DAO클래스에게 전달한다.
		boardlist = boardService.getBoardList(page); // 리스트를 받아옴.

		// 총 페이지 수.
		int maxpage = (int) ((double) listcount / limit + 0.95); // 0.95를 더해서 올림
																	// 처리.
		// 현재 페이지에 보여줄 시작 페이지 수(1, 11, 21 등...)
		int startpage = (((int) ((double) page / 10 + 0.9)) - 1) * 10 + 1;
		// 현재 페이지에 보여줄 마지막 페이지 수.(10, 20, 30 등...)
		int endpage = maxpage;

		if (endpage > startpage + 10 - 1)
			endpage = startpage + 10 - 1;

		model.addAttribute("page", page);
		model.addAttribute("startpage", startpage);
		model.addAttribute("endpage", endpage);
		model.addAttribute("maxpage", maxpage);
		model.addAttribute("listcount", listcount);
		model.addAttribute("boardlist", boardlist);
		
		return "board/board_list";
	}

- 자세한 설명은 원문 글 작성 기능 설명 후

- 기본 변수와 파생변수들을 만들고 Model 객체에 저장한 후 board_list.jsp 로 이동

 

- board_list.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%@ page import="java.util.*"%>
<%@ page import="myspring.model.*"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!doctype html>
<html lang="ko">
<head>
	<meta charset="UTF-8">
	<title>게시판 목록</title>
    <link rel="stylesheet" href="<%=request.getContextPath() %>/css/bbs.css" type="text/css">
</head>

<body>
	<!-- 게시판 리스트 -->
	<div id="bbslist_wrap">
		<h2 class="bbslist_title">게시판 목록</h2>
		<div id="bbslist_c">글 개수 : ${listcount}</div>

		<table id="bbslist_t">
			<tr align="center" valign="middle" bordercolor="#333333">
				<td style="font-family: Tahoma; font-size: 11pt;" width="8%"
					height="26">
					<div align="center">번호</div>
				</td>
				<td style="font-family: Tahoma; font-size: 11pt;" width="47%">
					<div align="center">제목</div>
				</td>
				<td style="font-family: Tahoma; font-size: 11pt;" width="14%">
					<div align="center">작성자</div>
				</td>
				<td style="font-family: Tahoma; font-size: 11pt;" width="17%">
					<div align="center">날짜</div>
				</td>
				<td style="font-family: Tahoma; font-size: 11pt;" width="14%">
					<div align="center">조회수</div>
				</td>
			</tr>

			<!-- 화면 출력 번호  변수 정의 -->		
			<c:set var="num" value="${listcount-(page-1)*10}"/> 	
	
			<!-- 반복문 시작 -->
			<c:forEach var="b" items="${boardlist}">
			
			<tr align="center" valign="middle" bordercolor="#333333"
				onmouseover="this.style.backgroundColor='F8F8F8'"
				onmouseout="this.style.backgroundColor=''">
				<td height="23" style="font-family: Tahoma; font-size: 10pt;">					
 					<!-- 번호 출력 부분 -->	
 					<c:out value="${num}"/>			
					<c:set var="num" value="${num-1}"/>	 
				</td>
				
				<td style="font-family: Tahoma; font-size: 10pt;">
					<div align="left">						
						
					<c:if test="${b.board_re_lev != 0}"> 
						<c:forEach var="k" begin="1" end="${b.board_re_lev}">
							&nbsp;&nbsp;			
						</c:forEach>
						<img src="./images/AnswerLine.gif">	
					</c:if>					
						
					<!-- 제목 출력 부분 -->	
					<a href="board_cont.do?board_num=${b.board_num}&page=${page}&state=cont">
							${b.board_subject}
					</a>
					</div>
				</td>

				<td style="font-family: Tahoma; font-size: 10pt;">
					<div align="center">${b.board_name}</div>
				</td>
				<td style="font-family: Tahoma; font-size: 10pt;">
					<div align="center">${b.board_date}</div>
				</td>
				<td style="font-family: Tahoma; font-size: 10pt;">
					<div align="center">${b.board_readcount}</div>
				</td>
			</tr>
			
			</c:forEach>
			<!-- 반복문 끝 -->			
		</table>
		

		<div id="bbslist_paging">			
			<c:if test="${page <=1 }">
				[이전]&nbsp;
			</c:if>
			
			<c:if test="${page > 1 }">
				<a href="board_list.do?page=${page-1}">[이전]</a>&nbsp;
			</c:if>			

			<c:forEach var="a" begin="${startpage}" end="${endpage}">
				<c:if test="${a == page }">
					[${a}]
				</c:if>
				<c:if test="${a != page }">
					<a href="board_list.do?page=${a}">[${a}]</a>&nbsp;
				</c:if>
			</c:forEach>			
			
			<c:if test="${page >= maxpage }">
				[다음] 
			</c:if>
			<c:if test="${page < maxpage }">
				<a href="board_list.do?page=${page+1}">[다음]</a>
			</c:if>			
			
		</div>
		<div id="bbslist_w">
			<input type="button" value="글쓰기" class="input_button"
				onclick="location='board_write.do?page=${page}'">
		</div>
		
	</div>
</body>
</html>

- 자세한 설명은 원문 글 작성 기능 설명 후

- 목록 페이지 board_list.jsp 에서 "글쓰기" 클릭시 "board_write.do" 요청

 

원문 글 작성 기능

- Controller 클래스 BoardController.java 에서 "board_write.do" 요청 부분만

	/* 게시판 글쓰기 폼 */
	@RequestMapping(value = "/board_write.do")
	public String board_write() {
		return "board/board_write";
	}

- 원문 글 작성 폼 board_write.jsp 로 이동

 

- board_write.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>    
    
<!doctype html>
<html lang="ko">
<head>
	<meta charset="UTF-8">
	<title>게시판 입력폼</title>
	<link rel="stylesheet" type="text/css" href="<%=request.getContextPath() %>/css/bbs.css" />
	<script src="http://code.jquery.com/jquery-latest.js"></script>
	<script src="<%=request.getContextPath() %>/js/board.js"></script>
</head>

<body>
 <div id="bbswrite_wrap">
  <h2 class="bbswrite_title">게시판 입력폼</h2>
  <form method="post" action="<%=request.getContextPath() %>/board_write_ok.do" onSubmit="return board_check()">
   <table id="bbswrite_t">
    <tr>
     <th>글쓴이</th>
     <td>
     <input name="board_name" id="board_name" size="14" class="input_box" />
     </td>
    </tr>
    
    <tr>
     <th>비밀번호</th>
     <td>
      <input type="password" name="board_pass" id="board_pass" size="14"
      class="input_box" />
     </td>
    </tr>
    
    <tr>
     <th>글제목</th>
     <td>
      <input name="board_subject" id="board_subject" size="40" 
      class="input_box" />
     </td>
    </tr>
    
    <tr>
     <th>글내용</th>
     <td>
      <textarea name="board_content"  id="board_content" rows="8" cols="50"
      class="input_box"></textarea>
     </td>
    </tr> 
    
   </table>
   
   <div id="bbswrite_menu">
    <input type="submit" value="등록" class="input_button" />
    <input type="reset" value="취소" class="input_button"
    onclick="$('#board_name').focus();" />
   </div>
  </form>
 </div>
</body>
</html>

+ Spring 에서는 어노테이션 기반이므로 Model 2 와 달리 action 으로 <%=request.getContext() 가 없어도 잘 찾아간다

- 원문 글 작성 폼의 입력양식 name 값 을 DTO 프로퍼티명과 일치시킨다

- 입력 후 "등록" 클릭시 "board_write_ok.do" 로 요청한다

 

- Controller 클래스 BoardController.java 에서 "board_write_ok.do" 요청 부분만

	/* 게시판 저장 */
	@RequestMapping(value = "/board_write_ok.do", method = RequestMethod.POST)
	public String board_write_ok(@ModelAttribute BoardBean board)
			throws Exception {
//	public String board_write_ok(@RequestParam HashMap board)
//			throws Exception {
	
		boardService.insert(board);// 저장 메서드 호출	

		return "redirect:/board_list.do";
	}

- 입력양식의 name 값을 DTO 프로퍼티명과 일치시켰으므로 @ModelAttribute 를 통해 입력되어 넘어온 값들을 모두 생성된 DTO 객체 board 에 저장

- 이 DTO 객체 board 에 board_re_ref, board_re_lev, board_re_seq, board_readcount 는 자동으로 0으로 초기화된다

+ DTO int 형 멤버변수(필드, 프로퍼티) 는 자동으로 0 으로 초기화 된다

+ 주석으로 설정된 부분은 Map 을 통해서 넘어온 값들을 받는 방법이다, 키 밸류 형태로 저장됨

- Service 클래스의 메소드 insert() 를 호출하고, 삽입할 글을 담은 객체 board 를 매개변수로 전달

<돌아온 후>

- 글 작성 이후에 목록 페이지로 돌아갈 것이므로 여기서 redirect: 를 써서 "board_list.do" 로 요청

 

- Service 클래스 BoardServiceImpl.java 에서 insert() 메소드 부분만

	/* 게시판 저장 */
	public void insert(BoardBean b) throws Exception {
		boardDao.insertBoard(b);
	}

- DAO 클래스 BoardServiceImpl.java 에서 insertBoard() 메소드 부분만

	/* 게시판 저장 */
	public void insertBoard(BoardBean b) throws Exception {
		sqlSession.insert("Test.board_insert", b);
	}

- Mapper 파일 중 namespace 가 Test 인 board.xml 에서 id 가 "board_insert" 인 SQL문 부분만

	<!-- 게시판 저장 -->
	<insert id="board_insert" parameterType="board">
		insert into board53
		(board_num,board_name,board_pass,board_subject,
		board_content,board_re_ref,board_re_lev,board_re_seq,board_readcount,board_date)
		values
		(board53_num_seq.nextval,#{board_name},#{board_pass},#{board_subject},
		#{board_content},board53_num_seq.nextval,0,0,0,SYSDATE)
	</insert>

 - 원문이므로 board_re_ref 는 board_num 과 같은 값이 들어간다

- 즉 board_num 과 board_re_ref 는 같은 시퀀스로 값이 입력됨 

- 원믄 글이므로 board_re_lev, board_re_seq 는 0 이 들어감, 글 작성 SQL문이므로 조회수도 0 으로 설정

- 작성 날짜는 SYSDATE 로 넣기

- 나머지는 전달받은 객체 board 로 부터 값을 꺼내서 세팅


글 목록 (게시판) 기능

- 게시판 글 목록 출력은 많이 했으므로 간략한 설명 또는 처음하는 부분만 설명

- Controller 클래스 BoardController.java 에서 "board_list.do" 요청 부분만

	/* 게시판 목록 */
	@RequestMapping(value = "/board_list.do")
	public String list(HttpServletRequest request, Model model) throws Exception {

		List<BoardBean> boardlist = new ArrayList<BoardBean>();

		int page = 1;
		int limit = 10; // 한 화면에 출력할 레코드수

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

		// 총 리스트 수를 받아옴.
		int listcount = boardService.getListCount();

		// 페이지 번호(page)를 DAO클래스에게 전달한다.
		boardlist = boardService.getBoardList(page); // 리스트를 받아옴.

		// 총 페이지 수.
		int maxpage = (int) ((double) listcount / limit + 0.95); // 0.95를 더해서 올림
																	// 처리.
		// 현재 페이지에 보여줄 시작 페이지 수(1, 11, 21 등...)
		int startpage = (((int) ((double) page / 10 + 0.9)) - 1) * 10 + 1;
		// 현재 페이지에 보여줄 마지막 페이지 수.(10, 20, 30 등...)
		int endpage = maxpage;

		if (endpage > startpage + 10 - 1)
			endpage = startpage + 10 - 1;

		model.addAttribute("page", page);
		model.addAttribute("startpage", startpage);
		model.addAttribute("endpage", endpage);
		model.addAttribute("maxpage", maxpage);
		model.addAttribute("listcount", listcount);
		model.addAttribute("boardlist", boardlist);
		
		return "board/board_list";
	}

- 자세한 설명은 원문 글 작성 기능 설명 후

- 기본 변수와 파생변수들을 만들고, 그 페이지에 해당하는 리스트를 받아온 후 변수와 리스트를 Model 객체에 저장한 후 board_list.jsp 로 이동

 

총 페이지 수 계산 부분 (BoardController.java 부분)

		// 총 페이지 수.
		int maxpage = (int) ((double) listcount / limit + 0.95); // 0.95를 더해서 올림 처리

- 이렇게 해도 계산 된다, 새로운 방법

 

startpage, endpage 변수 설정 (BoardController.java 부분)

		// 현재 페이지에 보여줄 시작 페이지 수(1, 11, 21 등...)
		int startpage = (((int) ((double) page / 10 + 0.9)) - 1) * 10 + 1;
		// 현재 페이지에 보여줄 마지막 페이지 수.(10, 20, 30 등...)
		int endpage = maxpage;

- 이렇게 해도 계산 된다, 새로운 방법

 

+ DAO 클래스 BoardDaoImpl.java 에서 getListCount() 부분만

	/* 게시판 총 갯수  */
	public int getListCount() throws Exception {
		int count = 0;	
		count = ((Integer) sqlSession.selectOne("Test.board_count")).intValue();

		return count;
	}

- selectOne() 메소드의 리턴자료형은 Object 이다, 원래는 (Integer) 과 .intValue() 로 다운캐스팅, 언박싱을 해야한다

- 하지만 생략해도 됨, MyBatis 기능이다

 

+ Mapper 파일 board.xml 에서 id 가 "board_list" 인 SQL문 부분만

	<!-- 게시판 목록 (page번호를 전달받아서 startRow와 endRow를 구함) -->
	<select id="board_list" parameterType="int" resultType="board">
	    <![CDATA[
		select * from 
		 (select rownum rnum,BOARD_NUM,BOARD_NAME,BOARD_SUBJECT,BOARD_CONTENT,
		 BOARD_RE_REF,BOARD_RE_LEV,BOARD_RE_SEQ,BOARD_READCOUNT,
	 	 BOARD_DATE from  
	  	 (select * from board53 order by BOARD_RE_REF desc,BOARD_RE_SEQ asc)) 
	   		 where rnum >= ((#{page}-1) * 10+1)  and rnum <= (#{page} * 10)
		]]>
	</select>

- 두번째 서브쿼리를 별칭을 쓰지 않고 있으므로, 첫번째 서브쿼리에서 별칭.* 으로 처리하는 대신 일일히 두번째 서브쿼리의 모든 컬럼을 쓰고 있다

- board_re_ref 로 내림차순 정렬하고 두번째 정렬로 board_re_seq 를 오름차순으로 정렬함

+ 같은 부모를 가진 댓글들의 board_re_ref 는 부모의 board_re_ref 값으로 모두 같다

- < , > 를 인식하지 못하므로 대신해서 &gt; %lt; 를 쓰거나 또는 전체를 CDATA 로 묶은 뒤 < , > 사용

- where 절에 있는 10 은 limit 를 의미함

 

 

- View 페이지로 이동

- board_list.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"%>
<%@ page import="java.util.*"%>
<%@ page import="myspring.model.*"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!doctype html>
<html lang="ko">
<head>
	<meta charset="UTF-8">
	<title>게시판 목록</title>
    <link rel="stylesheet" href="<%=request.getContextPath() %>/css/bbs.css" type="text/css">
</head>

<body>
	<!-- 게시판 리스트 -->
	<div id="bbslist_wrap">
		<h2 class="bbslist_title">게시판 목록</h2>
		<div id="bbslist_c">글 개수 : ${listcount}</div>

		<table id="bbslist_t">
			<tr align="center" valign="middle" bordercolor="#333333">
				<td style="font-family: Tahoma; font-size: 11pt;" width="8%"
					height="26">
					<div align="center">번호</div>
				</td>
				<td style="font-family: Tahoma; font-size: 11pt;" width="47%">
					<div align="center">제목</div>
				</td>
				<td style="font-family: Tahoma; font-size: 11pt;" width="14%">
					<div align="center">작성자</div>
				</td>
				<td style="font-family: Tahoma; font-size: 11pt;" width="17%">
					<div align="center">날짜</div>
				</td>
				<td style="font-family: Tahoma; font-size: 11pt;" width="14%">
					<div align="center">조회수</div>
				</td>
			</tr>

			<!-- 화면 출력 번호  변수 정의 -->		
			<c:set var="num" value="${listcount-(page-1)*10}"/> 	
	
			<!-- 반복문 시작 -->
			<c:forEach var="b" items="${boardlist}">
			
			<tr align="center" valign="middle" bordercolor="#333333"
				onmouseover="this.style.backgroundColor='F8F8F8'"
				onmouseout="this.style.backgroundColor=''">
				<td height="23" style="font-family: Tahoma; font-size: 10pt;">					
 					<!-- 번호 출력 부분 -->	
 					<c:out value="${num}"/>			
					<c:set var="num" value="${num-1}"/>	 
				</td>
				
				<td style="font-family: Tahoma; font-size: 10pt;">
					<div align="left">						
						
					<c:if test="${b.board_re_lev != 0}"> 
						<c:forEach var="k" begin="1" end="${b.board_re_lev}">
							&nbsp;&nbsp;			
						</c:forEach>
						<img src="./images/AnswerLine.gif">	
					</c:if>					
						
					<!-- 제목 출력 부분 -->	
					<a href="board_cont.do?board_num=${b.board_num}&page=${page}&state=cont">
							${b.board_subject}
					</a>
					</div>
				</td>

				<td style="font-family: Tahoma; font-size: 10pt;">
					<div align="center">${b.board_name}</div>
				</td>
				<td style="font-family: Tahoma; font-size: 10pt;">
					<div align="center">${b.board_date}</div>
				</td>
				<td style="font-family: Tahoma; font-size: 10pt;">
					<div align="center">${b.board_readcount}</div>
				</td>
			</tr>
			
			</c:forEach>
			<!-- 반복문 끝 -->			
		</table>
		

		<div id="bbslist_paging">			
			<c:if test="${page <=1 }">
				[이전]&nbsp;
			</c:if>
			
			<c:if test="${page > 1 }">
				<a href="board_list.do?page=${page-1}">[이전]</a>&nbsp;
			</c:if>			

			<c:forEach var="a" begin="${startpage}" end="${endpage}">
				<c:if test="${a == page }">
					[${a}]
				</c:if>
				<c:if test="${a != page }">
					<a href="board_list.do?page=${a}">[${a}]</a>&nbsp;
				</c:if>
			</c:forEach>			
			
			<c:if test="${page >= maxpage }">
				[다음] 
			</c:if>
			<c:if test="${page < maxpage }">
				<a href="board_list.do?page=${page+1}">[다음]</a>
			</c:if>			
			
		</div>
		<div id="bbslist_w">
			<input type="button" value="글쓰기" class="input_button"
				onclick="location='board_write.do?page=${page}'">
		</div>
		
	</div>
</body>
</html>

- 화면 출력 번호를 만들어서 forEach 루프가 돌아갈때마다 화면 출력번호를 출력 하고, 재정의해서 1씩 감소시킴

- 글 목록에서의 원문과 댓글의 제목 출력 형태를 다르게 함, 댓글의 깊이 board_re_lev만큼 루프를 돌려서 왼쪽 간격 띄움

+ 여기서는 [이전] 클릭시 이전 페이지 ${page-1} 로, [다음] 클릭시 다음 페이지 ${page+1} 로 이동

 

제목 클릭시 상세 페이지로 이동시 넘기는 값 (board_list.jsp 부분)

		<!-- 제목 출력 부분 -->	
		<a href="board_cont.do?board_num=${b.board_num}&page=${page}&state=cont">
				${b.board_subject}
		</a>

- 상세페이지로 이동하려고 한다, "board_cont.do" 로 요청함

- 요청하면서 전달하는 값이 글 번호와 페이지 번호 외에도 state 라는 변수에 cont 라는 값을 저장해서 전달함

- 상세 페이지, 수정, 삭제 등 여러 기능을 1개의 요청으로 처리하기 위해서 state 값을 다르게 설정함

 

- Controller 클래스에서 "board_cont.do" 요청 처리 부분을 보자

	/* 게시판 내용보기,삭제폼,수정폼,답변글폼 */
	@RequestMapping(value = "/board_cont.do")
	public String board_cont(@RequestParam("board_num") int board_num,
			@RequestParam("page") String page,
			@RequestParam("state") String state, 
			Model model) throws Exception {

		if (state.equals("cont")) { // 내용보기일때만
			boardService.hit(board_num); // 조회수 증가
		}

		BoardBean board = boardService.board_cont(board_num);

		model.addAttribute("bcont", board);
		model.addAttribute("page", page);

		if (state.equals("cont")) {// 내용보기일때
			return "board/board_cont";// 내용보기 페이지 설정
			// String board_cont = board.getBoard_content().replace("\n",
			// "<br/>");
			// 글내용중 엔터키 친부분을 웹상에 보이게 할때 다음줄로 개행
			// contM.addObject("board_cont", board_cont);
		} else if (state.equals("edit")) {// 수정폼
			return "board/board_edit";
		} else if (state.equals("del")) {// 삭제폼
			return "board/board_del";
		} else if (state.equals("reply")) {// 답변달기 폼
			return "board/board_reply";
		}
		return null;
	}

- 상세 페이지, 삭제폼, 수정폼, 답변글 폼 요청을 모두 이 @RequestMapping("/board_cont.do") 에서 처리한다

- state 값을 @RequestParam 으로 받아옴

- state 값이 "cont" 인 경우, 즉 상세페이지 요청인 경우에만 조회수 값을 증가시키고 있다

-  상세 페이지, 삭제폼, 수정폼, 답변글 폼 요청은 글 번호와 페이지 번호를 전달하는 등의 같은 형식으로 되어있고 상세 정보를 구해오는 등의 비슷한 기능을 수행

- 그러므로 같은 요청으로 처리하고 다른 부분은 if-else if 문으로 state 값에 따른 다른 처리를 함

 

 

게시판 직접 만들어보기 (이어서)

환경 설정 파일 작성(세팅) 순서

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 형으로 변환시킨 후 확장자를 붙이면 파일명을 문자형태 난수로 만들 수 있다

- 난수가 중복될 확률은 현저히 낮다


 

복습

Setter DI

- Setter 메소드가 만들어져 있어야함

ex) Contoller 에 Service 클래스를 주입할때 Service 클래스 안에 DI 가 있어야함

Constructor DI

- 생성자가 만들어져 있어야함

어노테이션 기반 DI

- 3가지 조건을 만족해야함

- 생성자나 Setter 메소드가 없어도 가능함

 

servlet-xml

ViewResolver

- View 파일들이 저장될 최상위 디렉토리 지정

- servlet-context.xml 에서 설정

 

 base-package

- 자바 파일들이 저장될 최상위 디렉토리 지정

- servlet-context.xml 에서 설정

 

root-context.xml

- 직접 bean 을 만들어서 Setter DI, Constructor DI 로 DB 연동 관련 내용 처리

- Controller, Service 와는 달리, DAO 에서 SqlSession 객체를 주입하기 위해서는 root-context.xml 에 bean 이 만들어져 있어야한다

 

+ 프로젝트 실행방법

- index 파일이 만들어져 있을때 프로젝트 Run As -> Run on Server 로 실행 가능

- index 파일 없을땐 다른 방법으로 실행해야한다


프로젝트 myBatis2 : 사원 등록 (이어서)

- 사원 등록폼 및 등록 기능은 모두 구현했고 이제 실제로 50번 부서에 사원을 등록해보자

- 이제 개발부는 참조하는 사원(자식) 이 있으므로 삭제되지 않는다


프로젝트 myBatis2 : 사원 상세 페이지

- 해당 부서의 직원 목록을 출력하는 부서 상세 페이지 empList.jsp 에서 사원의 이름을 클릭하면 사원 상세 페이지로 이동

 

<td><a href="empView.do?empno=${emp.empno}"
		class="btn btn-info">${emp.ename}</a></td>

- 사원명을 클릭하면 "empView.do" 로 요청한다, 요청하면서 사원 번호인 empno 를 GET 방식으로 전달함

 

Controller 클래스 EmpController.java 에서 "empView.do" 요청 부분만

	// 사원 상세페이지
	@RequestMapping("empView.do")
	public String empView(int empno, Model model) {
		Emp emp = es.select(empno); // 사원 상세 정보 구하기
		model.addAttribute("emp", emp);
		return "emp/empView";
	}

- 앞에서 넘어온 값인 사원번호를 @RequestParam (생략) 으로 바로 변수 empno 로 값을 받는다

- 자동으로 int 형으로 형변환되어 저장됨

- 1명에 대한 상세정보를 DB에서 구해와서 View 에 뿌려야 하므로 Model 객체를 매개변수에 선언해서 받음

<돌아온 후>

- 사원의 상세정보는 select() 가 리턴되면서 Emp DTO 객체에 저장되어 돌아온다, 그걸 Emp DTO 객체 emp 에 저장함

- 그 객체 emp 를 Model 객체에 저장하고 /WEB-INF/views/emp/empView.jsp 파일로 이동

- View 페이지로 이동할때 prefix, suffix 를 빼고 작성하고, 하위의 패키지는 적어줘야하므로 "emp/empView" 로 작성

 

Service 클래스 EmpServiceImpl.java 에서 select() 메소드 부분만

	public Emp select(int empno) {
		return ed.select(empno);
	}

- DB 연동을 해야하므로 Service 로 넘어왔다

 

DAO 클래스 EmpDaoImpl.java 에서 select() 메소드 부분만

	public Emp select(int empno) {
		return sst.selectOne("empns.select", empno);
	}

- root-context.xml 에서 SqlSession bean 객체를 생성했고, 그걸 DAO 클래스에 주입했으므로 selectOne() 사용 가능

- 전달된 사원번호 empno 를 selectOne() 을 호추랗며 그대로 전달함

- 사원 1명에 대한 상세정보를 구해야하므로 selectOne() 을 사용

 

Mapper 파일 Emp.xml 에서 id 가 "select" 인 태그 부분만

	<select id="select" parameterType="int" resultType="emp">
		select * from emp where empno=#{empno}
	</select>

- 전달받은 사원 번호로 해당 사원의 상세정보(모든 정보) 를 검색함

- 전달받은 값은 사원번호이므로 전달받은 자료형 parameterType 은 "int"

- 돌려주는 자료형 resultType 은 Emp DTO alias 인 "emp"

 

- 다시 DAO -> Service -> Controller 로 갔다가 View 로 오게 된다

 

View 페이지 empView.jsp

- emp/empView.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ include file="../header.jsp"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
	$(function() {
		$('#list').load('empList.do?deptno=${emp.deptno}');
	});
</script>
</head>
<body>
	<div class="container">
		<h2 class="text-primary">직원 상세정보</h2>
		<table class="table table-bordered">
			<tr>
				<td>사번</td>
				<td>${emp.empno }</td>
			</tr>
			<tr>
				<td>이름</td>
				<td>${emp.ename}</td>
			</tr>
			<tr>
				<td>업무</td>
				<td>${emp.job }</td>
			</tr>
			<tr>
				<td>관리자</td>
				<td>${emp.mgr }</td>
			</tr>
			<tr>
				<td>입사일</td>
				<td>${emp.hiredate }</td>
			</tr>
			<tr>
				<td>급여</td>
				<td>${emp.sal }</td>
			</tr>
			<tr>
				<td>보너스</td>
				<td>${emp.comm }</td>
			</tr>
			<tr>
				<td>부서코드</td>
				<td>${emp.deptno }</td>
			</tr>
		</table>
		<a href="empUpdateForm.do?empno=${emp.empno}" class="btn btn-info">수정</a>
		<a class="btn btn-danger" href="empDelete.do?empno=${emp.empno}">삭제</a>
		<a href="empList.do?deptno=${emp.deptno}" class="btn btn-default">목록</a>
		<div id="list"></div>
	</div>
</body>
</html>

- Emp DTO 객체 emp 가 Model 객체에 저장되어 넘어왔으므로 ${emp.필드명} 으로 값을 가져와서 출력한다

ex) 사원번호를 출력할때 ${emp.empno}

- header.jsp 안에 공통적인 내용들이 들어있다 (core 라이브러리, Bootstrap 등)

 

버튼 기능 (empView.jsp 부분)

		<a href="empUpdateForm.do?empno=${emp.empno}" class="btn btn-info">수정</a>
		<a class="btn btn-danger" href="empDelete.do?empno=${emp.empno}">삭제</a>
		<a href="empList.do?deptno=${emp.deptno}" class="btn btn-default">목록</a>

- 사원 상세 페이지에도 '수정', '삭제', '목록' 버튼이 있다

- '목록' 을 누르면 해당 부서 상세 페이지로 간다, 해당 부서의 부서번호인 ${emp.deptno} 전달

- '수정' 을 누르면 사원 정보 수정 페이지로 간다

- '삭제' 를 누르면 사원 삭제 페이지로 간다

 


프로젝트 myBatis2 : 사원 정보 수정폼

- 사원 상세 페이지 empView.jsp 에서 '수정' 버튼을 누르면 사원 정보 수정폼으로 간다

		<a href="empUpdateForm.do?empno=${emp.empno}" class="btn btn-info">수정</a>

- '수정' 을 누르면 "empUpdateForm.do" 료 요청한다, 요청하면서 사원 번호인 empno 를 전달

 

Controller 클래스 EmpController.java 에서 "empUpdateFomr.do" 요청 부분만

	// 사원 수정폼
	@RequestMapping("empUpdateForm.do")
	public String empUpdateForm(int empno, Model model) {
		Emp emp = es.select(empno);
		List<Dept> deptList = ds.list();
		model.addAttribute("emp", emp);
		model.addAttribute("deptList", deptList);
		return "emp/empUpdateForm";
	}

- 앞에서 넘어온 사원번호 empno 를 @RequestParam("empno") (생략) 을 통해 바로 매개변수의 empno 에 저장한다

사원 수정폼에서 할 DB 연동 작업 2가지

1. 사원 수정폼에 뿌려줄 사원 상세 정보 구하기

- Emp Service 객체 es 로 Emp Service 클래스의 select() 메소드를 호출하고, 사원 번호 empno 를 전달

2. 사원 수정폼에서 부서를 수정할때, 부서 목록을 가져와야한다 

- Dept Service 객체 ds 로 Dept Service 클래스의 list() 메소드를 호출함, 돌아올땐 부서 목록을 List 형태로 돌려줌

 

1. 사원 수정폼에 뿌려줄 사원 상세 정보 구하기

- 이전에 사원 상세 정보를 구할때 했던 내용과 비슷하므로 설명 생략 https://laker99.tistory.com/147

2. 사원 수정폼에서 부서를 수정할때, 부서 목록을 가져와야한다 

- 이전에 사원 등록폼에서 했던 내용과 비슷하므로 설명 생략 https://laker99.tistory.com/147

 

<돌아올때>

- 해당 사원의 상세 정보를 저장한 DTO 객체 emp 와 부서 전체 목록인 deptList 를 Model 에 저장해서 "empUpdateForm.jsp" 로 이동

 

View 페이지인 empUpdateForm.jsp

- emp/empUpdateForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ include file="../header.jsp"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<div class="container" align="center">
		<h2 class="text-primary">직원정보 수정</h2>
		<form action="empUpdate.do" method="post">
			<table class="table table-bordered">
				<tr>
					<td>사번</td>
					<td><input type="text" name="empno" readonly="readonly"
						value="${emp.empno}"></td>
				</tr>
				<tr>
					<td>이름</td>
					<td><input type="text" name="ename" required="required"
						value="${emp.ename }"></td>
				</tr>
				<tr>
					<td>업무</td>
					<td><input type="text" name="job" required="required"
						value="${emp.job }"></td>
				</tr>
				<tr>
					<td>급여</td>
					<td><input type="text" name="sal" required="required"
						value="${emp.sal}"></td>
				</tr>
				<tr>
					<td>보너스</td>
					<td><input type="text" name="comm" required="required"
						value="${emp.comm }"></td>
				</tr>
				<tr>
					<td>부서코드</td>
					<td><select name="deptno">
							<c:forEach var="dept" items="${deptList}">
								<c:if test="${emp.deptno==dept.deptno}">
									<option value="${dept.deptno}" selected="selected">
										${dept.dname}(${dept.deptno})</option>
								</c:if>
								<c:if test="${emp.deptno!=dept.deptno}">
									<option value="${dept.deptno}">${dept.dname}(${dept.deptno})</option>
								</c:if>
							</c:forEach>
					</select></td>
				</tr>
				<tr>
					<td colspan="2"><input type="submit" value="수정"></td>
				</tr>
			</table>
		</form>
	</div>
</body>
</html>

- emp.deptno (해당 사원의 부서) 와 dept.deptno (forEach에 의해 모든 부서의 부서번호가 차례대로 들어감) 가 같으면 selected 함으로서 해당 사원이 가입할때 등록했던 부서는 select-option 에서 선택되어 나타나도록 했다

+ jQuery 로 처리하면 간단하게 처리 가능

- 사원 번호을 readonly 속성으로 비활성화했다, readonly 이므로 값이 넘어간다!

- select-option 에 출력되는 내용은 부서명(부서번호) 형식이지만, 저장하는 값인 value 는 부서 번호가 된다

- 사원 수정폼에 수정할 정보를 입력하고 '수정' 을 누르면 action 인 "empUpdate.do" 로 요청한다


프로젝트 myBatis2 : 사원 정보 수정

Controller 클래스 EmpController.java 에서 "empUpdate.do" 요청 부분만

	// 사원 정보 수정
	@RequestMapping("empUpdate.do")
	public String empUpdate(Emp emp, Model model) {
		int result = es.update(emp);
		model.addAttribute("deptno", emp.getDeptno());
		model.addAttribute("result", result);
		return "emp/empUpdate";
	}

- 사원 수정폼에서 넘어온 값들을 @ModelAttribuate (생략) 을 사용해서 Setter 메소드들로 한번에 Emp DTO 객체인 emp 에 저장한다

- Emp Service 객체 es 를 사용해서 update() 메소드를 호출하고, 호출할때 수정할 정보를 저장한 객체 emp 를 전달

- 수정할 사원을 특정하는 사원 번호는 앞의 사원 수정폼에서 넘어왔으므로 emp 객체 안 emp.empno 에 저장되어있음

<돌아온 후>

- 수정 성공시 result 에는 1이 저장된다

- 수정이 끝난 후 부서 상세 페이지로 돌아가므로, 해당 사원이 소속된 부서의 부서번호가 필요하다, 그래서 emp.getDeptno() 를 "deptno" 네임으로 Model 객체에 저장

- View 페이지에서 수정 성공 / 실패 처리를 하기 위해 update() 에서 돌려받은 값 result 를 Model 객체에 저장

- /WEB-INF/views/emp/empUpdate.jsp 로 이동

 

Service 클래스 EmpServiceImpl.java 에서 update() 메소드 부분만

	public int update(Emp emp) {
		return ed.update(emp);
	}

 

DAO 클래스 EmpDaoImpl.java 에서 update() 메소드 부분만

	public int update(Emp emp) {
		return sst.update("empns.update", emp);
	}

- 수정할 정보를 담은 Emp DTO 객체 emp 를 받아서 그대로 전달

- 수정 성공시 자동으로 1을 리턴한다

 

Mapper 파일 Emp.xml 에서 id 가 "update" 태그 부분만

	<update id="update" parameterType="emp">
		update emp set ename=#{ename},job=#{job},sal=${sal},
			comm=#{comm},deptno=#{deptno} where empno=#{empno}
	</update>

- "emp" 는 MyBatis 환경설정 파일 Configuration.xml 에서 설정한 Emp DTO 의 alias 이다

- 객체 emp 가 넘어왔으므로 #{ename} 은 emp.getEname() 과 같은 의미

- 사원번호는 객체 emp 의 emp.empno 에 저장되어 있으므로 #{empno} 로 가져와서 where 절에 넣는다

 

View 페이지 empUpdate.jsp

- emp/empUpdate.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ include file="../header.jsp"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<c:if test="${result > 0 }">
		<script type="text/javascript">
			alert("수정 성공");
			location.href = "empList.do?deptno=${deptno}";
		</script>
	</c:if>
	<c:if test="${result <= 0 }">
		<script type="text/javascript">
			alert("수정 실패");
			history.go(-1);
		</script>
	</c:if>
</body>
</html>

- Model 객체에 저장된 "result" 를 통해 성공 / 실패 처리를한다

- 수정이 성공한 후에 "empList.do" 로 요청하고, Model 객체에 저장된 "deptno" 를 전달하면서 해당 부서 상세 페이지로 이동한다

 


프로젝트 myBatis2 : 사원 정보 삭제

- 사원 상세 페이지 empView.jsp 에서 '삭제' 버튼을 누르면 사원 삭제를 할 수 있다

- 삭제 폼은 없고 바로 삭제 된다

		<a class="btn btn-danger" href="empDelete.do?empno=${emp.empno}">삭제</a>

- '삭제' 를 누르면 "empDelete.do" 료 요청한다, 요청하면서 사원 번호인 empno 를 전달

 

Controller 클래스 EmpController.java 에서 "empDelete.do" 요청 부분만

	// 사원 삭제
	@RequestMapping("empDelete.do")
	public String empDelete(int empno, Model model) {
		Emp emp = es.select(empno); // 사원 상세 정보 구하기
		int result = es.delete(empno);
		model.addAttribute("result", result);
		model.addAttribute("deptno", emp.getDeptno());
		return "emp/empDelete";
	}

- 앞에서 넘어온 사원번호 empno 를 @RequestParam("empno") (생략) 을 통해 바로 매개변수의 empno 에 저장한다
사원 삭제할때 DB 연동 작업 2가지

1. 사원 상세 정보 구하기

- 사원을 삭제 한 후 그 사원이 속해있었던 부서의 부서 상세 페이지로 이동할 것이다, 그러려면 부서 번호가 필요함

- 부서 상세 페이지로 가기 위해, 삭제될 사원의 상세 정보를 구해서 거기서 부서 번호를 가져올 것
- 사원의 상세 정보를 구하기 위해 select() 메소드를 호출하며, 사원번호를 전달

2. 사원 삭제 하기

- 사원 삭제를 위해 delete() 메소드를 호출하며, 사원번호를 전달

<돌아온 후>

- 삭제된 사원 상세정보를 받은 emp 에서 emp.getDeptno() 로 사원이 소속되어있던 부서번호를 가져와서 Model 객체에 저장하고 empDelete.jsp 로 이동

 

1. 사원 상세 정보 구하기

- 이전에 여러번 했으므로 설명 생략

2. 사원 삭제하기

Service 클래스 EmpServiceImpl.java 에서 delete() 메소드 부분만

	public int delete(int empno) {
		return ed.delete(empno);
	}

 

DAO 클래스 EmpDaoImpl.java 에서 delete() 메소드 부분만

	public int delete(int empno) {
		return sst.delete("empns.delete", empno);
	}

 

 

Mapper 파일 Emp.xml 에서 id 가 "delete" 인 태그 부분만

	<delete id="delete" parameterType="int">
		delete from emp where empno=#{empno}
	</delete>

 

View 페이지 empDelete.jsp

- emp/empDelete.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ include file="../header.jsp"%>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<c:if test="${result > 0 }">
		<script type="text/javascript">
			alert("삭제 성공 ");
			location.href = "empList.do?deptno=${deptno}";
		</script>
	</c:if>
	<c:if test="${result <= 0 }">
		<script type="text/javascript">
			alert("삭제 실패");
			history.go(-1);
		</script>
	</c:if>
</body>
</html>

- 삭제 성공시 "empList.do" 로 요청하면서 삭제된 사원의 소속 부서인 deptno 를 넘겨주면서 해당 부서 상세 페이지로 이동

 

- 50번 부서에 등록되어있는 사원 LAY2 를 삭제해보자


STS 에 Data Source Management 추가

- STS 에는 이클립스와 다르게 Data Source Management 가 없으므로 DB 연결 불가능하다

- STS 에 plug-in 기능을 추가해야 Data Source Management 를 사용 가능

- 설치 이후 이제 필요한 SQL문을 STS 내에서 실행할 수 있다

 

STS 에 Data Source Management 추가하는 방법

- 이러면 STS 에 plug-in 을 설치한다, 원격 저장소에서 로컬 저장소로 다운받음

- 그 후 STS 를 Restart 시키면 plug-in 이 추가되어있다

- 이후 이제 필요한 SQL문을 STS 내에서 실행할 수 있다


게시판 직접 만들어보기

설계도

spring 실습 설계도

프로젝트 명
spring

계정생성
sqlplus system/oracle
create user spring identified by spring123;

권한 부여 (connect, resource 롤)
grant connect, resource to spring;

테이블명
myboard

create table myboard(
no number primary key,
writer varchar2(20),
passwd varchar2(20),
subject varchar2(50),
content varchar2(100),
readcount number, register date );

시퀀스명
myboard_seq

create sequence myboard_seq;

구조
- base-package 는 myspirng 으로 한다
- prefix 하위에 board 가 있다

환경 설정 파일 작성(세팅) 순서
1) pom.xml
2) web.xml
3) servlet-context.xml
4) configuration.xml
5) board.xml
6) root-context.xml
- 4), 5), 6) 은 DB 연동 관련 내용


오라클 계정 생성 및 권한 부여

- 기존 계정들은 복잡하므로 새 오라클 계정을 생성해서 프로젝트를 하자

 

Spring MVC 프로젝트 생성

- 도메인명 역순으로 지정해야한다

- com.myhome.spring 을 top-level 패키지로 지정

 

프토젝트 테스트

- 프로젝트 오른 마우스 -> Run As -> Run on Server

- 잘 실행되었음을 확인 가능

 

커넥션 생성, 테이블 생성하기

- webapp 폴더 하위에 sql 폴더 추가하고, 안에 SQL 파일 myboard.sql 생성

- 다음으로 커넥션을 연결해야함

- 커넥션이 생성되었다!

- Connection profile 도 설정한다

- 다음은 테이블 생성 위해 myboard.sql 작성

-- 게시판
select * from tab;
select * from seq;
select * from myboard;

-- 테이블명 : myboard
create table myboard(
	  no number primary key,
	  writer varchar2(20),
      passwd varchar2(20),
	  subject varchar2(50),
	  content varchar2(100),
	  readcount number,
	  register date );

-- 시퀀스명 : myboard_seq
create sequence myboard_seq;

- 테이블과 시퀀스 생성하기

- 테이블 및 시퀀스 생성 확인

 

환경설정 파일 세팅

+ 환경 설정 파일 작성(세팅) 순서
1) pom.xml
2) web.xml
3) servlet-context.xml
4) configuration.xml
5) board.xml
6) root-context.xml

 

pom.xml 파일 세팅

- Maven 의 환경설정 파일이다

- 잘 실행되는 내용을 가져와서 복붙하면 된다, 프로젝트 myBatis2 의 pom.xml 을 그대로 가져와서 복붙

- pom.xml 은 프로젝트 바로 하위에 있으므로 프로젝트를 선택하고 붙여넣기 (overwrite)

- 오라클, MyBatis, JSTL 라이브러리들이 모두 들어가있다

- 프로젝트 실행하기

- 환경설정 파일 만들면서 중간중간 실행해서 확인해야함

 

web.xml 파일 세팅

- url-pattern 을 / 에서 *.do 로 수정

	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>

- 한글값 인코딩 처리 코드는 web.xml 생성시에 들어가있지 않는다, 직접 넣어줘야한다

- 프로젝트 myBatis2 에서 web.xml 에서 filter 와 filter-mapping 부분 가져오기

	<!-- 한글 입력 -->
	<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>

- 이 부분 넣어주기

 

요청 -> Dispatcher Servlet -> Controller 테스트

- HomeController.java 부분

- url-pattern 을 *.do 로 바꾸었으므로 샘플로 만들어진 HomeController.java 클래스에서 @RequestMapping 에 value 를 "/" 에서 "/test.do" 로 수정

- 요청 -> Dispatcher Servlet -> Controller 까지의 흐름을 확인하기 위해서이다

- 요청은 index.jsp 에 넣어줄 것

 

index 파일 생성

- 자동으로 실행되도록 하기 위해서 반드시 WEB-INF 폴더 하위에 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>
<script>
	location.href = "test.do";
</script>
</body>
</html>

- "test.do" 로 요청해준다

- index.jsp 실행

- 이 화면이 나오면 테스트 완료

- 요청 -> Dispatcher Servlet -> Controller -> View (home.jsp) 로 갔기때문에 이 화면에 나타난다

 

servlet-context.xml 파일 세팅

- base-package 가 top-level 패키지로 되어있다, 이 패키지는 java 폴더 하위에 생성되어잇음

- 즉 base-pacakge 는 자바파일들이 저장될 최상위 패키지

- 우리는 이 top-level 패키지 사용하지 않을 것

 

- myspring 패키지를 java 폴더 하위에 생성하고 myspring 을 base-package 로 설정하기

	<context:component-scan base-package="myspring" />

 

- 최상위 디렉토리 myspring 을 생성했으므로 그 myspring 아래에 controller, service, dao, model 폴더 생성

 

- 다음으로 샘플로 만들어진 HomeContorller.java 를 myspring/controller 로 옮기기

- 그 후 com.myhome.spring 패키지는 삭제 (com 만 삭제하면 다 삭제된다)

- HomeContorller.java 의 패키지도 수정해주기

package myspring.controller;

 

- 환경파일 하나를 설정했으므로 다시 프로젝트 실행해서 실행확인

- servlet-context.xml 환경설정 파일 설정 완료

- 나머지 환경설정은 DB 연동 관련 내용이다, 나중에 할 것

 

Java 파일들 생성하기

- Controller, Service, DAO, DTO 파일들을 myspring 하위의 해당 폴더 안에 파일 생성

- 생성 된 Java 파일들에 어노테이션 추가 (@Controller, @Service, @Repository 등)

- 그리고 주입할 객체 위에 @Autowired 까지 추가

+ 어노테이션은 import 를 해야한다

* 위에서 프로퍼티명을 bd 대신 dao 로 수정했다

- 일단 @Autowired 는 현재 주석해두고 @Controller, @Service, @Repository 만 붙이기

- 여기까지 설정한 후 index 파일 실행해서 확인

 

- 이제 Controller 와 Service클래스에서의 @Autowired 어노테이션 주석을 하나씩 풀고 실행을 다시 해보면 실행이 잘 된다

- DAO 클래스는 아직 @Autowired 주입이 되지 않는다, root-context.xml 세팅을 먼저 해야 주입 가능

 

- BoardDao.java

- SqlSession 또는 SqlSession 구현 클래스로 객체를 주입해야한다

- 현재는 @Autowired 로 객체 주입이 불가능하다, @Autowired 를 쓰면 실행시 오류 발생

-  @Autowired 를 주석으로 막자

- root-context.xml 에서 SqlSessionTemplate bean 객체를 생성해야하고, Constructor DI 를 해서 bean 객체를 생성해야 여기서 @Autowired 사용해서 주입 가능

 

DTO 클래스 작성

- Board.java (DTO) 부분

- 테이블안의 컬럼과 같은 이름으로 프로퍼티들을 만들고 getter / setter 메소드를 생성한다

- 테이블의 컬럼명과 DTO 프로퍼티명이 일치가 되어야 매핑이 자동으로 된다

- number 타입은 int로, Date 타입은 Date(java.util)로, varchar2 타입은 String 으로 변환

- DTO 클래스 안에는 어노테이션을 붙이지 않음

 

View 파일 위치 설정

- servlet-context.xml 에서 /WEB-INF/views/ 가 prefix 로 설정되어있다, 이건 View 파일들이 저장될 최상위 디렉토리이다

	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</beans:bean>

- 이 /WEB-INF/views/ 하위에 Viwe 파일들을 저장할 폴더 board 를 생성하자

 

폼 가져오기

- 게시판 글 작성 폼 boardform.jsp 를 클라우드에서 폴더 board 로 가져오자

- 가져오는 파일은 이것 하나뿐임, 나머지는 직접 생성해야함

 

+ 현재 상태에서 프로젝트 시작해서 실행 확인해보기, 실행 잘 된다

+ boardform.jsp 는 View 페이지이므로 결과가 여기서 출력되는 것이다,

- boardform.jsp 를 그냥 실행하면 실행되지 않음, 오류발생

 

- boardform.jsp 를 실행해보자

 

- index 파일을 수정해서 boardform.do 로 요청시키자

- Controller 클래스 BoardController.java 에 boardform.do 요청을 받는 코드를 작성해주자

+ HomeController.java 는 삭제하기

 

- BoardController.java 에서 "boardform.do" 요청 받는 부분만

	// 글 작성 폼
	@RequestMapping("boardform.do")
	public String boardform() {
		return "board/boardform";
	}

- 접근제어자는 public, 리턴자료형은 주로 String 으로 메소드 작성

- DB 연동은 아직 처리하지 않으므로 Controller 에서 View 로 가기

- prefix 하위 폴더인 board 를 경로 앞에 붙여야한다

 

- 이제 index 파일 실행시 글 작성 폼이 나타남

- boardform.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="boardwrite.do">
<table border=1 width=400 align=center>
	<caption>글작성</caption>
	<tr><th>작성자명</th>
		<td><input type=text name="writer" required="required"></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"></td>
	</tr>
	<tr><th>내용</th>
		<td><textarea cols=40 rows=5 name="content" required="required"></textarea></td>
	</tr>
	<tr><td colspan=2 align=center>
			<input type=submit value="글작성">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

- 이 폼에서 넘어가는 내용은 writer, passwd, subject, content 이다

- 입력하고 "글작성" 클릭시 "boardwrite.do" 로 넘어간다, Controller -> Service -> DAO (Mapper 파일) 으로 가야한다

- 그러기 위해서 DB 연동을 먼저 처리해야함

 

configuration.xml 파일 생성 및 세팅

- 프로젝트 myBatis2 의 configuration.xml 파일 복사해서 현재 프로젝트인 spring 의 resources 폴더에 붙여넣기, 이후 수정

- configuration.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="board" type="myspring.model.Board" />
	</typeAliases>	
	
	<!-- 
	<mappers>
		<mapper resource="sql/Dept.xml" />
		<mapper resource="sql/Emp.xml" />
	</mappers>  -->	
	
</configuration>

- 가져온 파일을 현재 프로젝트에 맞게 수정, DTO의 alias 값을 설정해준다

- base-package 부터 시작해서 DTO 까지의 경로를 써준다, myspring.model.Board

- DTO 클래스에 대한 alias 내용 한가지만 들어간다

 

Board.xml (Mapper 파일) 생성 및 세팅

- 먼저 resources 폴더 하위에 sql 폴더를 생성

- myBatis2 의 Mapper 파일인 Dept.xml 이나 Emp.xml 을 현재 프로젝트의 resources/sql로 가져와서 이름을 Board.xml 으로 수정

- Board.xml

- 루트엘리먼트인 mapper 빼고 모두 지우기, namespace 는 "boardns" 로 지정

- 여기까지 하고 프로젝트 실행해서 실행 확인, 잘 된다

 

root-context.xml

- DB 연동 관련 내용이 들어간다

- DB 설정하는 3개의 파일(configuration.xml, board.xml(Mapper), root-context.xml) 중에서 가장 마지막에 root-context.xml 내용을 세팅해야한다

- 프로젝트 myBatis2의 root-context.xml 을 복사해서 같은 위치인 /WEB-INF/spring 폴더에 붙여넣기 (overwrite)

- root-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
	
	<!-- <context:property-placeholder location="classpath:jdbc.properties" /> -->
	
	<!-- dataSource -->
	<!-- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
		destroy-method="close">
		<property name="driverClass" value="${jdbc.driverClassName}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		<property name="maxPoolSize" value="${jdbc.maxPoolSize}" />
	</bean> -->
	
	<!-- 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>
	
	<!-- 스프링 jdbc 즉 스프링으로 oracle 디비 연결 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="configLocation" value="classpath:configuration.xml" />
		<property name="mapperLocations" value="classpath:sql/*.xml" />
	</bean>
	
	<bean id="session" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg index="0" ref="sqlSessionFactory" />
	</bean>	
	
</beans>

- 첫번째 bean 에서 username, password 를 테이블을 만든 계정의 username, password 인 "spring", "spring123" 로 수정

- 두번째 bean 에서 Configuration 파일 위치를 설정하는 configLocation 을 설정해야하는데 위치가 맞으므로 그대로 두기

- 두번째 bean 에서 Mapper 파일 위치를 설정하는 mapperLocations 를 설정해야하는데 위치가 맞으므로 그대로 두기

- 세번째 bean 에서 SqlSessionTemplate bean 객체를 생성하고 있다, 위의 sqlSessionFactory 를 Construct DI 로 주입

 

- 그리고 DAO 인 BoardDao.java 로 가서 @Autowired 주석 풀기, 이제는 주석 풀어도 실행 된다

- index 파일 실행시 잘 실행된다

 

- 환경설정이 모두 끝났다, 모든 환경설정 파일 기본세팅을 완료했음

 

- 이제 흐름에 따라 파일들의 내용을 추가하자

- boardform.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="boardwrite.do">
<table border=1 width=400 align=center>
	<caption>글작성</caption>
	<tr><th>작성자명</th>
		<td><input type=text name="writer" required="required"></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"></td>
	</tr>
	<tr><th>내용</th>
		<td><textarea cols=40 rows=5 name="content" required="required"></textarea></td>
	</tr>
	<tr><td colspan=2 align=center>
			<input type=submit value="글작성">
			<input type=reset value="취소">
		</td>
	</tr>
</table>
</form>

</body>
</html>

- DB 연동을 처리한 후 넘어왔다

- 이 폼에서 넘어가는 내용은 writer, passwd, subject, content 이다

- 입력하고 "글작성" 클릭시 "boardwrite.do" 로 넘어간다, Controller -> Service -> DAO (Mapper 파일) 으로 가야한다

 

- Controller 클래스 BoardController.java 에서 "boardwrite.do" 요청 부분을 처리하자

- 아래 내용을 작성

	// 글 작성
	@RequestMapping("boardwrite.do")
	public String boardwrite(Board board, Model model) {
		int result = bs.insert(board);
		model.addAttribute("result", result);
		return "board/insertresult";
	}

- 폼에서 넘어온 값을 DTO 객체로 한번에 받기 위해 @ModelAttribute 사용

- 메소드 명은 요청 이름값과 같게 맞췄다

- 글을 작성(insert) 한 후 성공 / 실패를 View 에서 처리하기위해 어떠한 값들을 가져갈 것이므로 Model 객체 선언해서 받기

 

- Service 객체 bs 를 사용해서 Service 클래스의 insert() 메소드를 호출, 이때 삽입할 데이터인 객체 board 를 가져감, 리턴자료형은 int 이다

 

- Service 클래스 BoardService.java 에서 insert() 메소드를 생성해야한다

	public int insert(Board board) {
		return dao.insert(board);
	}

- Controller 에서 이 메소드를 호출해야하므로 public, 리턴자료형은 int, 메소드 명은 insert(), 매개변수 자료형은 Board

- DAO 의 insert() 를 호출

 

- 이 코드를 복사 후 DAO 클래스 BoardDao.java 로 가서 복붙 하고 수정

- BoardDao.java 에서 insert() 메소드 생성

	public int insert(Board board) {
		return session.insert("insert", board);
	}

- 이 insert() 가 실제 SQL문을 수행하고 성공시 1 을 반환함

 

- Mapper 파일인 Board.xml 에 id 가 "insert" 인 insert (글 작성) 문을 생성해야함

	<!-- 글 작성 -->
	<insert id="insert" parameterType="board">
		insert into myboard values(myboard_seq.nextval,#{writer},
		#{passwd},#{subject},#{content},0,sysdate)
	</insert>

- 전달되는 값이 DTO Board 객체 board 이므로 DTO Board 의 alias 인 "board" 를 parameterType 에 설정

- no 컬럼은 sequence 인 myboard_seq 로 세팅

- 앞에서 객체 board 가 넘어왔으므로 #{writer}, #{passwd}, #{subject}, #{content} 로 세팅

- 조회수 readcount 컬럼은 0, 등록일 register 컬럼은 sysdate 로 세팅

 

- DAO -> Service -> Controller 로 다시 돌아왔다

- Controller "boardwrite.do" 요청 부분 다시 보기

	// 글 작성
	@RequestMapping("boardwrite.do")
	public String boardwrite(Board board, Model model) {
		
		int result = bs.insert(board);
		if(result == 1) System.out.println("글 작성 성공");
		
		model.addAttribute("result", result);
		return "board/insertresult";
	}

- result 값을 한번 찍어보기

- 그리고 /WEB-INF/views/board/insetresult.jsp 로 이동한다, 이 insertresult.jsp 파일을 board 폴더 하위에 생성해야함

- 이 파일에서 성공 / 실패 처리, 즉 출력을 할 것이다

 

- insertresult.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="${result == 1}">
	<script>
		alert("글 작성 성공");
		location.href = "boardlist.do";
	</script>
</c:if>

<c:if test="${result != 1}">
	<script>
		alert("글 작성 실패");
		history.go(-1);
	</script>
</c:if>

- JSTL core 라이브러리 사용하기 위해 core 라이브러리 불러오는 코드 한 줄 추가

- 글 작성 성공하면 목록 페이지로 갈 것이므로 "boardlist.do" 요청하기

- 글 작성 실패시 이전 페이지인 글 작성 폼으로 돌아감

 

- index 파일을 실행시켜서 글 작성폼에서 글을 작성해보자

+ 글 수정 / 삭제 시 비밀번호가 맞아야 삭제되도록 하기 위해 비밀번호를 입력받는 것임

- "boardlist.do" 요청 처리를 하지 않았으므로 오류가 뜬다

 

- Controller 클래스 BoardController.java 에서 "boardlist.do" 요청을 처리하는 코드를 작성하자 (미완성, 수정 전)

	// 글 목록
	@RequestMapping("boardlist.do")
	public String boardlist(HttpServletRequest request, Model model) {
		
		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;
		
		int listcount = bs.getCount();	// 총 데이터 갯수
		System.out.println("listcount : " + listcount);
		
		return "board/boardlist";
	}

 

- 기본변수, 파생변수를 설정해서 DB에서 목록을 구해오고, 페이징 처리를 하고, 그걸 View 에 뿌려야함

- request 객체를 매개변수로 부터 받는다, 이렇게 매개변수에 선언하면 자동으로 request 객체를 받아옴

- 결과를 View 페이지에 뿌려야하므로 Model 객체도 매개변수에 선언해서 받아온다

1. 기본변수 page : 현재 페이지를 저장할 변수

2. 기본변수 limit : 한 화면에 출력할 데이터 개수를 저장하는 변수

3. 파생변수 starRow, endRow : page, limit 을 이용해서 구해준다, page, limit 로 생성 가능하므로 굳이 생성하지 않아도 된다

4. 기본변수 listcount : 총 데이터 갯수를 저장, DB 와 연동해서 count(*) 로 가져올 것 

<총 데이터 개수 가져오기>

- Service 클래스의 getCount() 를 호출해서 리턴받는다 * 아래에서 Service 에서 getCount() 를 생성함

- 나머지 부분은 아래에서 다시 완성

 

- Service 클래스 BoardService.java 에서 getCount() 메소드를 생성해야함

	public int getCount() {
		return dao.getCount();
	}

 

- DAO 클래스 BoardDao.java 에서 getCount() 메소드 생성하기

	public int getCount() {
		return session.selectOne("count");
	}

- 그룹함수는 결과가 1개이므로 selectOne() 메소드 사용, id 가 "count" 인 SQL문 불러오기

 

- Mapper 파일 Board.xml 에서 id 가 "count" 인 SQL문 생성

	<!-- 총 데이터 갯수 -->
	<select id="count" resultType="int">
		select count(*) from myboard
	</select>

- 돌려주는 값이 총 데이터 갯수이므로 resultType 은 "int"

 

- 총 데이터 갯수를 가져왔는지 확인하기 위해 현재 상태에서 index 파일에서 "boardlist.do" 로 요청해보자

- index 파일 실행 하고 콘솔창 보기

- listcount 값이 찍혀 나왔다, 글을 하나 작성했었으므로 listcount : 1 이 맞다

 

- 이제 목록을 가져오는 작업을 Controller 부터 처리하자

- Controller 클래스 BoardController.java 에서 "boardlist.do" 요청을 처리하는 코드를 작성하자 (이어서, 완성, 추가되었다)

	// 글 목록
	@RequestMapping("boardlist.do")
	public String boardlist(HttpServletRequest request, Model model) {
		
		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;
		
		int listcount = bs.getCount();	// 총 데이터 갯수
		System.out.println("listcount : " + listcount);
		
		List<Board> boardlist = bs.getBoardList(page); // 게시판 목록
		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;
		
		model.addAttribute("page", page);
		model.addAttribute("listcount", listcount);
		model.addAttribute("boardlist", boardlist); // 리스트
		model.addAttribute("pageCount", pageCount);
		model.addAttribute("startPage", startPage);
		model.addAttribute("endPage", endPage);
		
		return "board/boardlist";
	}

<목록 가져오기>

- MyBatis 로 처리하면 DAO -> Mapper 파일로 갈때 단 하나의 값만 전달가능하다

- limit 은 고정되어있고, startRow, endRow 는 나중에 페이지 번호 page 로 계산 가능하므로 페이지번호 page 만 넘기기

+ 또는 map 객체에 키밸류 형태로 저장하는 방법도 있다

- getBoardList() 에서 글들이 저장된 리스트를 반환하도록 할 것 * 아래에서 설명

<돌아온 후>

- getBoardList() 에서 돌아온 후 콘솔창 출력해보면 boadlist 에 내가 썼던 글의 주소가 출력된다

5. 파생변수 pageCount : 총 페이지 수를 구함, listcount 와 limit 사용

6. 파생변수 startPage & endPage : 각(현재) 블럭의 시작 페이지, 끝 페이지, page 와 limit 사용

- 이후 기본변수, 파생변수, 가져온 목록(List) 들을 Model 객체에 저장하고 board/boardlist.jsp 로 이동

 

- Service 클래스 BoardService.java 에서 목록을 가져오는 메소드 getBoardList() 를 생성하자

	public List<Board> getBoardList(int page) {
		return dao.getBoardList(page);
	}

 

- DAO 클래스 BoardDao.java 에서 getBoardList() 를 작성하자

	public List<Board> getBoardList(int page) {
		return session.selectList("list", page);
	}

- 여러개의 글(DTO 객체) 을 가져올 것이므로 selectList() 메소드 사용

- 페이지 번호 page 를 전달함, 받는 값은 DTO 객체들이 저장된 리스트이다

 

- Mapper 파일 Baord.xml 에서 id 가 "list" 인 SQL문 작성

	<!-- 글 목록 -->
	<!-- &gt; : > , &lt; : < -->
	<select id="list" parameterType="int" resultType="board">
		<![CDATA[
		select * from (select rownum rnum, board.* from (
		select * from myboard order by no desc) board )
		where rnum >= ((#{page}-1) * 10 + 1) and rnum <= (#{page} * 10)
		]]>
	</select>

- 페이지 번호가 넘어오므로 parameterType 은 "int" , 돌려주는 값은 DTO 가 저장된 리스트 이므로 resultType 은 "board"(DTO alias) 

- limit 은 10으로 고정되어있으므로 10 으로 쓰고, startRow, endRow 는 페이지 번호 page 로 계산 가능해서 사용하기

+ <. > 인식을 못하므로 &gt; , $lt; 사용, 또는 <![CDATA[ ]]> 로 전체 SQL문으로 감싸주면 부등호 기호 <, > 를 인식함

 

- View 페이지를 보기 전에 페이징 처리를 확인하기 위해 myboard.sql 에서 데이터를 강제로 많이 삽입하자

insert into myboard values(myboard_seq.nextval,'Lay','1234','Practice1',
	'Content',0,systdate)

 

- View 페이지 board/boardlist.jsp 를 생성해서 Contorller 에서 Model 객체에 저장된 값들을 활용해서 목록을 출력하자

- boardlist.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" %>

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시판 목록</title>
</head>
<body>
	<a href="boardform.do">글작성</a> <br>
	글갯수 : ${listcount }
	
	<table border=1 align=center width=700>
		<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>
				<a href="boardcontent.do?no=${b.no}&page=${page}">${b.subject}</a>
			</td>
			<td>${b.writer}</td>
			<td>
				<fmt:formatDate value="${b.register}" 
				pattern="yyyy-MM-dd HH:mm:ss"/>
			</td>
			<td>${b.readcount}</td>
		</tr>
		</c:forEach>
		
	</table>
<br>
<!-- 페이지 처리 -->
<center>
<c:if test="${listcount > 0}">

<!-- 1페이지로 이동 -->
<a href="boardlist.do?page=1" style="text-decoration:none"> << </a>
<!-- 이전 블럭으로 이동 -->
<c:if test="${startpage > 10}">
	<a href="boardlist.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="boardlist.do?page=${i}">[${i}]</a>
	</c:if>
</c:forEach>
<!-- 다음 블럭으로 이동 -->
<c:if test="${endPage < pageCount}">
	<a href="boardlist.do?page=${startPage + 10}">[다음]</a>
</c:if>
<!-- 마지막 페이지로 이동 -->
<a href="boardlist.do?page=${pageCount}" style="text-decoration:none"> >> </a>

</c:if>
</center>

</body>
</html>

- JSTL core 라이브러리, 국제화 라이브러리를 불러오기

7. 파생변수 num : 화면 출력 번호, 코어 라이브러리 set 태그 사용해서 생성했음, listcount, page 를 사용해서 생성

- forEach 태그 items 안에 목록 리스트인 ${boardlist} 를 써서 하나씩 글을 가져오기

- forEach 루프가 돌아갈때마다 num 을 1씩 감소시키기, 이때 코어 라이브러리 set 태그 사용해서 재정의 하는 방식

			<td>${num}
				<c:set var="num" value="${num-1}"
			</td>

- 등록일은 국제화 라이브러리 formatDate 태그로 패턴지정해서 적용

- 제목을 클릭하면 상세페이지로 가기 위해 "boardcontent.do" 로 요청하고 글번호 no, 페이지 번호 page 전달

- table 태그 아래에는 페이지 처리를 하고 있음, 자주 했으므로 설명 생략

 

+ Recent posts