본문 바로가기

Backend/Spring

[Spring] 홈페이지_회원 가입

 

 

 

 

메인 페이지

 

 

 

 

 

 

 

web.xml

<!-- include -->
<jsp-config>
	<jsp-property-group>
		<url-pattern>*.jsp</url-pattern>
		<include-prelude>/inc/top.jspf</include-prelude>
		<include-coda>/inc/bottom.jspf</include-coda>
	</jsp-property-group>
</jsp-config>

top을 맨 앞에 include 하고 bottom을 맨 뒤에 include 한다는 뜻

 

 


 

HomeController

 

package com.campus.myapp;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;


@Controller
public class HomeController {
	
	private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
	
	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home() {

		return "home";
	}
	
}

 

HomeController.java는 스프링 프로젝트를 생성할 때 자동으로 생성되는 컨트롤러이다.

 


@RequestMapping의 value가 "/"이다. 이는 루트/ 경로로 접속을 시도했을 때

HomeController와 매핑되도록 한다는 뜻이다. 즉, 웹을 실행시키면 HomeController가 작동해

home.jsp 페이지가 뷰페이지로 사용되는 것이다. (컨트롤러는 뷰페이지를 반환함)

 

 

private static final Logger logger = LoggerFactory.getLogger(HomeController.class);

 

위 코드를 작성하면 오류가 발생했을 때 어느 곳에서 어떤 오류가 발생했는지 알 수 있어 대응을 빨리 할 수 있다.

 

 


 

home.jsp

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<div class="container">
	<h1>
		Hello world!  
	</h1>
	<P>  The time on the server is ${serverTime}. </P>
</div>

 

 

 

※ jspf로 파일 생성한 이유 : 데이터 호환성 때문

 

inc/top.jspf

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<c:set var="url" value="<%=request.getContextPath() %>"/>


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="${url}/css/style.css" type="text/css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
<div class='top'>
	<c:if test="${logStatus!='Y'}">
		<a href="${url}/member/loginForm">로그인</a>
		<a href="${url}/member/memberForm">회원가입</a>
	</c:if>
	
	<c:if test="${logStatus=='Y'}">
		${logId}님 <a href="${url}/member/logout">로그아웃</a>
		<a href="">회원정보수정</a>
	</c:if>
</div>
<div class='logo'>멀티캠퍼스</div>
<div class='mainMenu'>
	<ul>
		<li><a href="/myapp/">HOME</a></li>
		
		<!-- 로그인 | 로그아웃 조건문 -->
		<c:choose>
			<c:when test="${logStatus != 'Y'}">
				<li><a href="${url}/member/loginForm">로그인</a></li>
				<li><a href="${url}/member/memberForm">회원가입</a></li>
			</c:when>
			
			<c:when test="${logStatus == 'Y'}">
				<li><a href="${url}/member/logout">로그아웃</a></li>
				<li><a href="${url}/member/memberEdit">회원정보수정</a></li>
			</c:when>
		</c:choose>
		
		<li><a href="">게시판</a></li>
		<li><a href="">자료실</a></li>
	</ul>
</div>

 

getContextPath() : 프로젝트의 Context path명 반환
사용하는 이유 : 컨텍스트가 바뀌어도 따로 소스 경로를 수정할 필요 없음

 

 

inc/bottom.jspf

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

<div class="bottom">corp. Multicapus</div>
</body>
</html>

 

 


 

회원가입

 

 

memberForm.jsp

 

: 메인 페이지에서 회원가입 메뉴를 선택하면 넘어오는 뷰페이지

 

memberForm.jsp

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<style>
	#mFrm li{
		float:left;
		height:40px;
		line-height:40px;
		width:20%;
		border-bottom:1px solid #ddd;
	}
	#mFrm li:nth-child(2n){
		width:80%;
	}
	#mFrm li:last-of-type{
		width:100%;
	}
</style>
<script src="/myapp/js/member.js"></script>
<script>
	$(function(){
		//아이디 중복 검사
		$("#userid").keyup(function(){
			var userid = $("#userid").val();
			if(userid!='' && userid.length>=3){
				var url = "/myapp/member/memberIdCheck";
				$.ajax({
					url : url,
					data : "userid="+userid,
					type : "POST",
					success : function(result){
						if(result>0){	//사용불가
							$("#chk").html("사용 불가능합니다.");
							$("#idchk").val("N");
							$("#chk").css("color","red");
						}else{	//사용가능
							$("#chk").html("사용 가능합니다.");
							$("#idchk").val("Y");
							$("#chk").css("color","blue");
						}
					},
					error : function(e){
						console.log(e.responseText);
					}
				});
			}else{	//사용불가
				$("#chk").html("사용 불가능합니다.");
				$("#idchk").val("N");
				$("#chk").css("color","red");
			}
			
		});
	});
</script>
<div class="container">
	<h1>회원가입 폼</h1>
	<form method="post" action="/myapp/member/memberOk" id="mFrm" onsubmit="return memberCheck()">
		<ul>
			<li>아이디</li>
			<li>
				<input type="text" name="userid" id="userid" placeholder="아이디 입력"/>
				<input type="button" value="중복확인"/>
				<span id='chk'></span>
				<input type="hidden" id="idchk" value="N"/>
			</li>
			<li>비밀번호</li>
			<li><input type="password" name="userpwd" id="userpwd" placeholder="비밀번호 입력"/></li>
			<li>비밀번호 확인</li>
			<li><input type="password" name="userpwd2" id="userpwd2" placeholder="비밀번호 입력"/></li>
			<li>이름</li>
			<li><input type="text" name="username" id="username"/></li>
			<li>연락처</li>
			<li>
				<select name="tel1">
					<option value="010">010</option>
					<option value="02">02</option>
					<option value="031">031</option>
					<option value="041">041</option>
				</select> -
				<input type="text" name="tel2" id="tel2" maxlength="4"/>-
				<input type="text" name="tel3" id="tel3" maxlength="4"/>
			</li>
			<li>이메일</li>
			<li><input type="text" name="email" id="email"/></li>
			<li><input type="submit" value="가입하기"/></li>
		</ul>
	</form>
</div>

 

 

member.js

(회원가입 유효성 검사)

//가입하기 버튼 클릭 이벤트
function memberCheck(){
	
	//아이디 확인
	let userid = document.getElementById("userid");
	if(userid.value==''){
		alert("아이디를 입력하세요.");
		userid.focus();
		return false;
	}
	if(document.getElementById("idchk").value=='N'){
		alert("아이디 중복");
		return false;
	}
	//비밀번호 확인
	let userpwd = document.getElementById("userpwd");
	let userpwd2 = document.getElementById("userpwd2");
	if(userpwd.value=='' || userpwd2.value==''){
		alert("비밀번호를 입력하세요.");
		userpwd.focus();
		return false;
	}
	if(userpwd.value!=userpwd2.value){
		alert("비밀번호를 다시 입력하세요.");
		userpwd2.focus();
		return false;
	}
	
	//이름확인
	let username = document.getElementById("username");
	if(username.value==''){
		alert("이름을 입력하세요.");
		username.focus();
		return false;
	}
	
	//전화번호 확인
	let tel2 = document.getElementById("tel2");
	let tel3 = document.getElementById("tel3");
	let regExp1 = /^[0-9]{3,4}$/;
	let regExp2 = /^[0-9]{4}$/;
	if(!regExp1.test(tel2.value) || !regExp2.test(tel3.value)){
		alert("전화번호를 잘못 입력하였습니다.");
		tel2.focus();
		return false;
	}
	return true;
}

 

 

MemberController.java

회원가입, 로그인 관련 컨트롤러 생성

@Controller
@RequestMapping("/member/")
public class MemberController {
	
    @Inject
	MemberService service;
    
    //(중략)
}

경로가 루트/member/인 경우 이 컨트롤러를 실행하도록 매핑함

 

 


@Inject
: 스프링의 빈 객체를 Jersey객체에 주입(자동으로 객체 생성)

의존성 주입(Dependency Injection, DI)

  • 클래스 간 직접적인 연결관계를 맺지 않도록 해 클래스 간 변경이 자유로워지도록 한다.
  • 필요한 객체를 직접 생성하지 않고 외부에서 밀어 넣는다.
  • beanFactory에 담아두고 사용되는 곳에 해당하는 bean을 찾아 주입시켜 객체를 사용한다.

 

뒤에서 설명하겠지만 MemberService는 로그인, 회원가입 기능을 제공할 인터페이스이다.

이를 구현할 클래스는 MemberServiceImple이다.

 

 


1. 회원가입 클릭 - 회원가입 뷰페이지 이동

 

<a href="${url}/member/memberForm">회원가입</a>

top.jspf에서 회원가입을 클릭하면 /member/memberForm 경로로 접속한다.

 

 

@GetMapping("memberForm")
public String memberForm() {
	return "member/memberForm";
}

경로가 /member/이므로 memberController로 이동하고,

컨트롤러 안에서 memberForm으로 매핑된 메소드를 실행한다. (이게 맞는 설명인지는 모르겠음)

 

 

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

참고로 servlet-contex.xml에서 설정한 ViewResolver의 prefix, suffix 값을 통해

/WEB-INF/views/리턴 값. jsp를 뷰페이지로 사용하게 된다.

즉 /WEB-INF/views/member/memberForm.jsp를 리턴하는 것!!!

수업 듣다가 왜 jsp파일인데 memberForm만 작성하지? 했는데 이런 이유가 있었다.

 

 

 


 

2. 회원등록

 

 

MemberVO

package com.campus.myapp.vo;

public class MemberVO {
	private String userid;
	private String userpwd;
	private String username;
	
	private String tel;
	private String tel1;
	private String tel2;
	private String tel3;
	
	private String email;
	private String writedate;
	
	
	public String getUserid() {
		return userid;
	}
	public void setUserid(String userid) {
		this.userid = userid;
	}
	public String getUserpwd() {
		return userpwd;
	}
	public void setUserpwd(String userpwd) {
		this.userpwd = userpwd;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getTel() {
		//연락처 합치기(DB로 보낼 때)
		tel = tel1+"-"+tel2+"-"+tel3;
		return tel;
	}
	public void setTel(String tel) {
		//연락처 쪼개기(DB에서 가져올 때)
		String telSp[] = tel.split("-");
		tel1 = telSp[0];
		tel2 = telSp[1];
		tel3 = telSp[2];
		this.tel = tel;
	}
	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 getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getWritedate() {
		return writedate;
	}
	public void setWritedate(String writedate) {
		this.writedate = writedate;
	}
	
}

 

 

 


 

 

인터페이스는 구현부 없이 선언부만 작성하므로 인터페이스를 구현할 클래스가 있어야 한다.

MemberService 인터페이스를 구현할 MemberServiceImple을 생성하고,

인터페이스에 선언한 추상 메소드를 모두 오버라이딩을 해준다.

 

 

MemberService

public interface MemberService {

	//회원등록
	public int memberInsert(MemberVO vo);
}

 

 

MemberServiceImpl

@Service
public class MemberServiceImpl implements MemberService {

	@Inject // DAO 객체 생성 (@QutoWired)
	MemberDAO dao;
	
    //회원등록
	@Override
	public int memberInsert(MemberVO vo) {
		return dao.memberInsert(vo);
	}
}

 

 

@Service : 비즈니스 영역의 로직 처리를 담당하는 객체임을 표현하기 위한 어노테이션

 

 

MemberMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  
 <mapper namespace="com.campus.myapp.dao.MemberDAO">
 		(중략)
 </mapper>

 

 

mapper태그 안에 회원등록 sql문 추가

<insert id="memberInsert">
	insert into member(username, userid, userpwd, tel, email)
	values(#{username}, #{userid}, #{userpwd}, #{tel}, #{email})
</insert>

 

매개변수가 vo인 경우

${변수명} : "" 생략

#{"변수명"} : "" 포함

 


 

 

MemberDAO

public interface MemberDAO {
	//회원등록
	public int memberInsert(MemberVO vo);
}

 

DAO : Data Access Object의 약자로 데이터에 접근하기 위해 사용되는 객체이다.
DAO는 SQL을 실행할 때 사용될 객체를 하나로 통합해 DB에 접근하기 때문에 효율적이다.

 

MemberService와 동일하게 메소드 작성

 


 

MemberController

//회원등록
@PostMapping("memberOk")
public String memberFormOk(MemberVO vo, Model model) {
	//회원등록 수(쿼리문 실행한 결과 cnt에 저장)
	int cnt = service.memberInsert(vo);
		
	//클라이언트 페이지로 Insert 결과 cnt 전송
	model.addAttribute("cnt",cnt);
		
	//memberResult.jsp 반환
	//servlet-context.xml prefix : /WEB-INF/views/ suffix : .jsp
	return "member/memberResult";
}

 

Model : Spring에서 서버의 데이터를 전달하는 객체 (request 대신 사용)

 

[동작 과정]

컨트롤러 - memberService - memberServiceImpl - DAO - mapper - 컨트롤러

 

1. 가입하기 버튼 클릭 후 유효성 검사를 마치면 /myapp/member/memberOk로 접속한다.

2. 위 경로로 접속하면 MemberController의 memberOk 메소드를 실행한다.

3. memberService에 선언된 memberInsert 메소드를 호출하면 memberServiceImple에서 dao 메소드 호출이 구현된다.

4. dao의 메소드가 호출되면 mapper에서 쿼리문을 실행한다.

5. memberInsert의 반환값은 추가된 회원수이다. 성공하면 1 이상, 실패하면 0으로 Int형 값이 리턴된다.

6. 리턴된 값 cnt를 Model 객체를 통해 View로 전송해준다.

7. memberResult.jsp 뷰페이지 리턴

 

 

 

memberResult.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!-- 등록 성공 -->
<c:if test="${cnt>0}">
	<script>
		alert("회원 등록 성공. 로그인 페이지로 이동합니다.");
		//매핑주소
		location.href="/myapp/member/loginForm";
	</script>
</c:if>

<!-- 등록 실패 -->
<c:if test="${cnt==0 || cnt==null}">
	<script>
		alert("회원 등록 실패");
		history.go(-1); //기록 남지 않게 이전 페이지로 이동
		
	</script>
</c:if>

등록 성공한 경우 알림창을 띄워주고 로그인 페이지로 이동한다.

등록 실패한 경우 알림창을 띄워주고 이전 페이지로 이동한다.

location을 사용하면 회원가입 폼에 작성한 정보가 남아있기 때문에 history를 사용한다.

 

 


아이디 중복 확인

 

 

memberForm.jsp

<input type="button" value="중복확인"/>
<span id='chk'></span>
<input type="hidden" id="idchk" value="N"/>

아이디 중복확인 뒤에 사용 가능 여부를 출력할 span 추가

아이디 중복 여부(Y, N) 담을 hidden 추가

 

 

	$(function(){
		//아이디 중복 검사
		$("#userid").keyup(function(){
			var userid = $("#userid").val();
			if(userid!='' && userid.length>=5){
				var url = "/myapp/member/memberIdCheck";
				$.ajax({
					url : url,
					data : "userid="+userid,
					type : "POST",
					success : function(result){
						if(result>0){	//사용불가
							$("#chk").html("사용 불가능합니다.");
							$("#idchk").val("N");
							$("#chk").css("color","red");
						}else{	//사용가능
							$("#chk").html("사용 가능합니다.");
							$("#idchk").val("Y");
							$("#chk").css("color","blue");
						}
					},
					error : function(e){
						console.log(e.responseText);
					}
				});
			}else{	//사용불가
				$("#chk").html("사용 불가능합니다.");
				$("#idchk").val("N");
				$("#chk").css("color","red");
			}
			
		});

아이디 입력칸에 입력할 때마다 실행할 keyup이벤트를 작성한다.

ajax에 대한 내용은 ajax 포스팅 참고!

 

 

 

 

MemberController

//아이디 중복검사
@PostMapping("memberIdCheck")
@ResponseBody
public int idCheck(String userid) {
	int cnt = service.idCheck(userid);
	return cnt;
}

 

 

member.jsp

	if(document.getElementById("idchk").value=='N'){
		alert("아이디 중복");
		return false;
	}

idchk 값이 N인 경우 아이디가 중복되기 때문에 false를 리턴해준다.

 

 

MemberDAO, MemberService에 추상메소드 작성

public int idCheck(String userid);

 

MemberServiceImpl

@Override
public int idCheck(String userid) {
	return dao.idCheck(userid);
}

 

 

MemberMapper

<select id="idCheck" resultType="int">
    select count(userid) cnt from member
    where userid=#{param1}
</select>

아이디 중복 개수를 count해서 cnt에 담아준다.

 

매개변수가 vo 아닌 경우
#{param1}, #{param2}, ...