메인 페이지
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
: 메인 페이지에서 회원가입 메뉴를 선택하면 넘어오는 뷰페이지
<%@ 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}, ...