ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TIL #32 - Servlet, 톰캣을 이용한 회원가입 _ 회원가입
    프로그래밍/TIL(국비과정) 2020. 5. 28. 18:05

     

    톰캣에서 Servlet을 이용해 회원가입 페이지를 만든다. 

    회원가입 창에서 입력한 데이터는 DB 안에 넣는 것까지 수행한다. 

     

     

     

    프로그램의 구조는 아래와 같다 

    프로젝트 명은 memberServlet이다. 

    .java 파일은 전부 Java Resources 안의 src에 작성해준다. 

    src 안에는 DB와 직접적인 작업을 수행할 MemberDAO

    member 한 사람 분의 데이터를 가지고 있을 MemberDTO, 서블릿을 수행할 WriteServlet 이 있다. 

     

     

    javascript와 html은 WebContent 안에 따로 javascript 용 js 폴더와 html 용 member 폴더를 만들어 각각 넣어주었다. 

    앞서 servlet의 위치등을 알려주었던 web.xml은 사실 서블릿을 수행하는 WriteServlet.java 에 자동으로 생성되는 코드로 해결되므로 따로 건들일 필요가 없다. 

    (해당 코드에 대한 것은 아래에서 보도록 한다) 

     

    먼저 오라클로 테이블을 생성해주었다. 

    create table member(
    name varchar2(30) not null,
    id varchar2(30) primary key, --기본키, unique, not null, 무결성 제약 조건
    pwd varchar2(30) not null,
    gender varchar2(3),
    email1 varchar2(20),
    email2 varchar2(20),
    tel1 varchar2(10),
    tel2 varchar2(10),
    tel3 varchar2(10),
    zipcode varchar2(10),
    addr1 varchar2(100),
    addr2 varchar2(100),
    logtime date);
    
    commit;
    

     

     

    WebContent/member/writeForm.html

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>회원가입</title>
    <style>
    	
    </style>
    </head>
    <body>
    	<h3>회원가입</h3>
    	<form name="writeForm" method="post" action="/memberServlet/WriteServlet">
    	<table border="1" cellspacing="0" cellpadding="6">
    		<tr width="70">
    			<td>이름</td>
    			<td><input type="text" name="name" id="name" placeholder="이름입력" size=10></td>
    		</tr>
    
    		<tr width="70">
    			<td>아이디</td>
    			<td><input type="text" name="id" placeholder="아이디입력" size=15></td>
    		</tr>
    		<tr width="70">
    			<td>비밀번호</td>
    			<td><input type="password" name="pwd"></td>
    		</tr>
    		<tr width="70">
    			<td>비밀번호 재확인</td>
    			<td><input type="password" name="repwd"></td>
    		</tr>
    		<tr width="70">
    			<td>성별</td>
    			<td><input type="radio" name="gender" value="0"
    				checked="checked">남자
    				<input type="radio" name="gender"
    				value="1">여자</td>
    		</tr>
    		<tr width="70">
    			<td>이메일</td>
    			<td><input type="text" name="email1">
    			@
    			<input type="text" name="email2" size="12" list="defaultEmails" placeholder="직접입력">
    			<datalist id="defaultEmails">
    			<option value="gmail">
    			<option value="naver">
    			</datalist>
    	
    			</td>
    			</select>
    		</tr>
    		<tr>
    			<td>핸드폰</td>
    			<td>
    			<select name="tel1">
    				<option value="010">010</option>
    				<option value="011">011</option>
    				<option value="019">019</option>
    			</select>
    			-
    			<input type="text" name="tel2" size=5>
    			-
    			<input type="text" name="tel3" size=5>
    			</td>
    		</tr>
    		<tr>
    			<td>주소</td>
    			
    			<td>
    			<input type="text" name="zipcode" id="zipcode" size=7 readonly>
    			<input type="button" value="우편주소검색" onclick="checkPost()"><br>
    			
    			<input type="text" name="addr1" id="addr1" size=50 readonly><br>
    			<input type="text" name="addr2" id="addr2" size=50>
    			</td>
    		</tr>
    		
    		<tr>
    			<td colspan="2" align="center">
    			<input type="button" value="회원가입" onclick="javascript:checkWriteForm()">&emsp;
    			<input type="reset" value="다시작성">		
    			</td>
    
    		</tr>
    	</table>
    	</form>
    </body>

    여기까지는 간단한 회원가입 html 폼이다. 

    단, 회원가입 버튼을 눌렀을 경우 실행할 javascript 함수를 onclick으로 연결해준다. 

    또 우편주소를 검색할 시 우편주소를 찾아주는 checkPost도 연결해준다. 

    이렇게 각각 onclick으로 javascript 함수를 연결해주었다면 그 함수가 어디에 있는지 위치를 알려주어야한다. 

    우편주소를 검색하는 checkPost 함수는 다음에서 제공하는 코드이기 때문에 다음에서 알려주는 script 태그 코드를 적어준다. 

    <script src="https://t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
    <script type="text/javascript" src="../js/member.js">
    	
    </script>
    </html>

     

    다음의 우편주소검색 api는 아래에서 확인할 수 있다. 

    http://postcode.map.daum.net/guide

     

    Daum 우편번호 서비스

    우편번호 검색과 도로명 주소 입력 기능을 너무 간단하게 적용할 수 있는 방법. Daum 우편번호 서비스를 이용해보세요. 어느 사이트에서나 무료로 제약없이 사용 가능하답니다.

    postcode.map.daum.net

     

    이렇게 html 페이지는 끝이다. 

    다음은 바로 javascript 함수들이 있는 member.js 이다. 

     WebContent/js/member.js

    회원가입시 필수로 기입해야하는 값은 이름, 아이디, 비밀번호이다.

    비밀번호의 경우에는 최초의 비밀번호 입력과 비밀번호 재확인 두 번이 일치한지 까지 확인해야한다. 

    이와 같은 과정을 유효성검사 라고 한다. 

    function checkWriteForm() {
    	if (document.writeForm.name.value == "") {
    		alert("이름을 입력하세요");
    		document.writeForm.name.focus();
    	} else if (document.writeForm.id.value == "") {
    		alert("아이디를 입력하세요");
    	} else if (document.writeForm.pwd.value == "") {
    		alert("비밀번호를 입력하세요.");
    	} else if (document.writeForm.pwd.value != document.writeForm.repwd.value) {
    		alert("비밀번호가 일치하지 않습니다");
    	} else {
    		document.writeForm.submit();
    	}
    }

     

    우편번호의 경우는 다음에서 제공해준 그대로에 상세주소를 없애고 내가 쓰는 변수를 일부 변경해준 정도이다. 

    function checkPost() {
    	new daum.Postcode(
    			{
    				oncomplete : function(data) {
    					// 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
    
    					// 각 주소의 노출 규칙에 따라 주소를 조합한다.
    					// 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
    					var addr = ''; // 주소 변수
    					var extraAddr = ''; // 참고항목 변수
    
    					// 사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
    					if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을
    															// 경우
    						addr = data.roadAddress;
    					} else { // 사용자가 지번 주소를 선택했을 경우(J)
    						addr = data.jibunAddress;
    					}
    
    					// 사용자가 선택한 주소가 도로명 타입일때 참고항목을 조합한다.
    					if (data.userSelectedType === 'R') {
    						// 법정동명이 있을 경우 추가한다. (법정리는 제외)
    						// 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
    						if (data.bname !== '' && /[동|로|가]$/g.test(data.bname)) {
    							extraAddr += data.bname;
    						}
    						// 건물명이 있고, 공동주택일 경우 추가한다.
    						if (data.buildingName !== '' && data.apartment === 'Y') {
    							extraAddr += (extraAddr !== '' ? ', '
    									+ data.buildingName : data.buildingName);
    						}
    						// 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
    						//if (extraAddr !== '') {
    						//	extraAddr = ' (' + extraAddr + ')';
    						//}
    						// 조합된 참고항목을 해당 필드에 넣는다.
    						//document.getElementById("sample6_extraAddress").value = extraAddr;
    
    					}
    
    					// 우편번호와 주소 정보를 해당 필드에 넣는다.
    					document.getElementById('zipcode').value = data.zonecode;
    					document.getElementById("addr1").value = addr;
    					// 커서를 상세주소 필드로 이동한다.
    					document.getElementById("addr2").focus();
    				}
    			}).open();
    
    }

     

    다음은 서블릿인 WriteServlet 이다. 

    그전에 데이터를 담아 놓는 MemberDTO를 만들어준다.

    회원가입에서 기입할 데이터는 이름, 아이디, 비밀번호, 성별, 이메일 앞자리, 뒷자리, 전화번호 1, 전화번호 2, 전화번호 3, 우편번호, 주소1, 주소2 이다. 

    그에 따라 MemberDTO를 작성한다. 

    src/member/bean/MemberDTO

    package member.bean;
    
    public class MemberDTO {
    	private String name;
    	private String id;
    	private String pwd;
    	private String gender;
    	private String email1;
    	private String email2;
    	private String tel1;
    	private String tel2;
    	private String tel3;
    	private String zipcode;
    	private String addr1;
    	private String addr2;
    	
    	
    	// getter setter 
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public String getId() {
    		return id;
    	}
    	public void setId(String id) {
    		this.id = id;
    	}
    	public String getPwd() {
    		return pwd;
    	}
    	public void setPwd(String pwd) {
    		this.pwd = pwd;
    	}
    	public String getGender() {
    		return gender;
    	}
    	public void setGender(String gender) {
    		this.gender = gender;
    	}
    	public String getEmail1() {
    		return email1;
    	}
    	public void setEmail1(String email1) {
    		this.email1 = email1;
    	}
    	public String getEmail2() {
    		return email2;
    	}
    	public void setEmail2(String email2) {
    		this.email2 = email2;
    	}
    	public String getTel1() {
    		return tel1;
    	}
    	public void setTel1(String tel1) {
    		this.tel1 = tel1;
    	}
    	public String getTel2() {
    		return tel2;
    	}
    	public void setTel2(String tel2) {
    		this.tel2 = tel2;
    	}
    	public String getTel3() {
    		return tel3;
    	}
    	public void setTel3(String tel3) {
    		this.tel3 = tel3;
    	}
    	public String getZipcode() {
    		return zipcode;
    	}
    	public void setZipcode(String zipcode) {
    		this.zipcode = zipcode;
    	}
    	public String getAddr1() {
    		return addr1;
    	}
    	public void setAddr1(String addr1) {
    		this.addr1 = addr1;
    	}
    	public String getAddr2() {
    		return addr2;
    	}
    	public void setAddr2(String addr2) {
    		this.addr2 = addr2;
    	}
    	
    	
    }
    

      비밀번호의 경우 최초입력 한 번만 저장하면 된다. 

     

    다음은 서블릿인 WriteServlet 이다. 

    이클립스에서는 서블릿 형식의 파일을 제공하므로 파일 생성시 Servlet을 찾아 사용하도록 한다..

    서블릿 형식의 파일을 제공받으면 앞서 말했던 web.xml 파일의 기능을 하는 코드가 생긴다. 

    @WebServlet("/WriteServlet") 

    만약 서블릿과 web.xml 둘다 서블릿의 위치를 지정한다면 오류가 발생하므로 둘 중 하나만 쓰도록 한다. 

     

    본 서블릿에서는 세가지 동작을 수행한다. 

    1. 데이터 얻어오기 

    2. 얻어온 데이터가 사라지기 전에 DB 안에 넣기 

    3. 회원가입 완료에 맞는 응답 하기

     

    src/member/service/WriteServlet

    입력받은 데이터를 받아 MemberDTO에 set 한다. 

    package member.service;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import member.bean.MemberDTO;
    import member.dao.MemberDAO;
    
    @WebServlet("/WriteServlet") 
    public class WriteServlet extends HttpServlet {
    	private static final long serialVersionUID = 1L;
    
    	protected void doPost(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    		
    		// POST 방식의 한글 처리 
    		request.setCharacterEncoding("UTF-8");
    	
    		// 데이터 얻어오기 
    		String name = request.getParameter("name");
    		String id = request.getParameter("id");
    		String pwd = request.getParameter("pwd");
    		String gender = request.getParameter("gender");
    		String email1 = request.getParameter("email1");
    		String email2 = request.getParameter("email2");
    		String tel1 = request.getParameter("tel1");
    		String tel2 = request.getParameter("tel2");
    		String tel3 = request.getParameter("tel3");
    		String zipcode = request.getParameter("zipcode");
    		String addr1 = request.getParameter("addr1");
    		String addr2 = request.getParameter("addr2");
    		
    		
    		MemberDTO memberDTO = new MemberDTO();
    		memberDTO.setName(name);
    		memberDTO.setId(id);
    		memberDTO.setPwd(pwd);
    		memberDTO.setGender(gender);
    		memberDTO.setEmail1(email1);
    		memberDTO.setEmail2(email2);
    		memberDTO.setTel1(tel1);
    		memberDTO.setTel2(tel2);
    		memberDTO.setTel3(tel3);
    		memberDTO.setZipcode(zipcode);
    		memberDTO.setAddr1(addr1);
    		memberDTO.setAddr2(addr2);

     

    이제 얻어온 데이터가 사라지기 전에 DB에 보관한다. 

    DTO로 모은 데이터를 DAO에 Insert 해주면 되는데, 그렇게 되면 데이터가 들어올 때마다 매번 new MemberDAO() 를 해야하는 것인가 하는 의문이 들 것 이다. 

    만약 그렇게 매번 new를 한다면 부담이 있다. 

    그래서 싱글톤을 이용해 메모리에 단 하나의 new를 만들어 놓고 계속해서 가져다 쓰도록 한다. 

    싱글톤 생성은 MemberDAO에서 수행하고 여기서는 처음이자 마지막인 MemberDAO를 생성한다. 

    MemberDAO memberDAO = MemberDAO.getInstance();
    int su = memberDAO.writeMember(memberDTO); 

    싱글톤을 이용해 MemberDAO를 생성했다면 DTO에 담긴 데이터들을 가지고 DB에 Insert를 수행하는 writeMember() 메서드를 호출한다. 

    몇 행이 삽입이 되었는지 표시해주기 위해 su 라는 변수를 만들어 주었다. 

     

    아래는 회원가입에 대한 응답 코드이다. 

    회원가입이 일어나면 회원가입이 성공 혹은 실패할 수 있는데, (실패는 잘 없는 일이긴 하다)

    그에 따른 html 페이지를 만들어주는 것이다. 

    	response.setContentType("text/html;charset=UTF-8");
    		
    		PrintWriter out = response.getWriter();
    		out.println("<html>");
    		out.println("<head>");
    		out.println("<title>회원가입</title>");
    		out.println("</html>");
    		out.println("<body>");
    		
    		if(su == 1) {
    			out.println("회원가입 성공!");
    			out.println("<button>");
    		}else {
    			out.println("회원가입 실패"); 
    		}
    		
    		out.println("</body>");
    		out.println("</html>");
    		
    	}
    
    }
    

      위에서 charset=UTF-8 한글 처리해주는 것은 응답에 대한 한글 처리이다. 

    그러니까 HTML 페이지가 등장할 때 함께 뜨는 한글이 깨지지 않기 위한 처리인 것이다. 

    따라서 여기까지만 하고 DB를 확인해 보면 한글이 죄다 깨져서 나온다. 

    여기서 필요한 한글 처리는 name, id 와 같이 입력받는 데이터에 대한 한글처리이므로 아래의 코드를 데이터를 입력받기 전에 적어준다. 

    	// POST 방식의 한글 처리 
    	request.setCharacterEncoding("UTF-8");
    	

     

    마지막으로 DB와 직접적인 작업을 수행하는 MemberDAO 이다. 

    src/member/dao/MemberDAO

    DB 접속과 드라이버 로딩까지는 앞서 했던 코드와 똑같으므로 그대로 해준다. 

    package member.dao;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    import member.bean.MemberDTO;
    
    public class MemberDAO {
    	private static MemberDAO instance;
        
    	private String driver = "oracle.jdbc.driver.OracleDriver";
    	private String url = "jdbc:oracle:thin:@localhost:XXXX:xe";
    	private String username = "USERNAME";
    	private String password = "PASSWORD";
    
    	private Connection con;
    	private PreparedStatement pstmt;
    	private ResultSet rs;
    
    
    
    	public MemberDAO() {
    		try {
    			Class.forName(driver);
    			System.out.println("OracleDriver.class 생성, 드라이버 로딩 성공");
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    		}
    	}
    
    	public void getConnection() {
    		try {
    			con = DriverManager.getConnection(url, username, password);
    
    			System.out.println("접속 성공");
    		} catch (SQLException e) {
    			e.printStackTrace();
    		}
    	}

     

     단 다른 점은 앞의 서블릿에서 DAO를 싱글톤으로 생성해주었다는 것이다. 

    싱글톤은 DAO에서 생성해준다. 

    필드로 MemberDAO 타입의 싱글톤 instance 를 지정해준 후 getInstance 메서드를 만들어 준다. 

    	public static MemberDAO getInstance() {
    		if (instance == null) {
    			// 스레드 동기화 (여기 안에 들어오는 스레드는 한 번에 한 번 만 통과 시킨다 )
    			synchronized (MemberDAO.class) {
    				
    				instance = new MemberDAO(); // MemberDAO는 이제 메모리에 한 번 만 생성된다
    
    			}
    		}
    		return instance;
    	}

    getInstance 메서드에서는 instance가 null 인 경우 스레드 동기화를 해주는 작업을 한다. 

    스레드 동기화란 이 메서드에 들어오는 스레드를 한 번에 단 한 번 만 통과시는 것이다. 

    이 때 통과하는 클래스는 memberDAO 라는 이름의 클래스만 통과하기 때문에 MemberDAO.class 라고 지정해준다.  

    그리고 MemberDAO를 new 로 생성해준다. 

    이제 MemberDAO는 메모리에 한 번 만 생성된다. 

     

    DB에 데이터 삽입은 앞서 했던 것과 똑같다. 

    	// 데이터 insert
    	public int writeMember(MemberDTO memberDTO) {
    		int su = 0;
    
    		String sql = "insert into member values(?,?,?,?,?,?,?,?,?,?,?,?, sysdate)";
    
    		getConnection(); // 접속
    
    		try {
    			pstmt = con.prepareStatement(sql);
    
    			pstmt.setString(1, memberDTO.getName());
    			pstmt.setString(2, memberDTO.getId());
    			pstmt.setString(3, memberDTO.getPwd());
    			pstmt.setString(4, memberDTO.getGender());
    			pstmt.setString(5, memberDTO.getEmail1());
    			pstmt.setString(6, memberDTO.getEmail2());
    			pstmt.setString(7, memberDTO.getTel1());
    			pstmt.setString(8, memberDTO.getTel2());
    			pstmt.setString(9, memberDTO.getTel3());
    			pstmt.setString(10, memberDTO.getZipcode());
    			pstmt.setString(11, memberDTO.getAddr1());
    			pstmt.setString(12, memberDTO.getAddr2());
    			// logTime은 시스템 날짜 얻어오는 것이라 직접 입력받을 필요 없음
    
    			su = pstmt.executeUpdate();
    
    		} catch (SQLException e) {
    			e.printStackTrace();
    		} finally {
    			try {
    				if (pstmt != null)
    					pstmt.close();
    				if (con != null)
    					pstmt.close();
    
    			} catch (SQLException e) {
    				e.printStackTrace();
    			}
    		}
    		return su;
    	}
    }

     

    '프로그래밍 > TIL(국비과정)' 카테고리의 다른 글

    TIL #33-1 MVC 패턴 예시 추가  (0) 2020.06.17
    TIL #33 - MVC 패턴  (0) 2020.06.16
    TIL #31 - Servlet, 톰캣 (2)  (0) 2020.05.26
    TIL #30 - Servlet, 톰캣  (0) 2020.05.25
    TIL #29 - 데이터베이스 Update  (0) 2020.05.13

    댓글

Designed by Tistory.