🔥 메인 페이지 만들기 🔥
💻 main.jsp 생성
👉🏻 WAS가 관리하는 영역이므로 보안상 직접 접근 불가함
💻 index.jsp에 main.jsp 포워드
<jsp:forward page="WEB-INF/views/main.jsp" />
💻 header.jsp, footer.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- jQuery 라이브러리 -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<!-- 부트스트랩에서 제공하고 있는 스타일 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<!-- 부트스트랩에서 제공하고 있는 스크립트 -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<style>
div {box-sizing:border-box;}
#header {
width:80%;
height:100px;
padding-top:20px;
margin:auto;
}
#header>div {width:100%; margin-bottom:10px;}
#header_1 {height:40%;}
#header_2 {height:60%;}
#header_1>div{
height:100%;
float:left;
}
#header_1_left {width:30%; position:relative;}
#header_1_center {width:40%;}
#header_1_right {width:30%;}
#header_1_left>img {height:80%; position:absolute; margin:auto; top:0px; bottom:0px; right:0px; left:0px;}
#header_1_right {text-align:center; line-height:35px; font-size:12px; text-indent:35px;}
#header_1_right>a {margin:5px;}
#header_1_right>a:hover {cursor:pointer;}
#header_2>ul {width:100%; height:100%; list-style-type:none; margin:auto; padding:0;}
#header_2>ul>li {float:left; width:25%; height:100%; line-height:55px; text-align:center;}
#header_2>ul>li a {text-decoration:none; color:black; font-size:18px; font-weight:900;}
#header_2 {border-top:1px solid lightgray;}
#header a {text-decoration:none; color:black;}
/* 세부페이지마다 공통적으로 유지할 style */
.content {
background-color:rgb(247, 245, 245);
width:80%;
margin:auto;
}
.innerOuter {
border:1px solid lightgray;
width:80%;
margin:auto;
padding:5% 10%;
background-color:white;
}
</style>
</head>
<body>
<div id="header">
<div id="header_1">
<div id="header_1_left">
<img src="https://www.iei.or.kr/resources/images/common/top_logo_s.jpg" alt="">
</div>
<div id="header_1_center"></div>
<div id="header_1_right">
<!-- 로그인 전 -->
<a href="">회원가입</a>
<a data-toggle="modal" data-target="#loginModal">로그인</a> <!-- 모달의 원리 : 이 버튼 클릭시 data-targer에 제시되어있는 해당 아이디의 div요소를 띄워줌 -->
<!-- 로그인 후 -->
<!--
<label>홍길동님 환영합니다</label>
<a href="">마이페이지</a>
<a href="">로그아웃</a>
-->
</div>
</div>
<div id="header_2">
<ul>
<li><a href="">HOME</a></li>
<li><a href="">공지사항</a></li>
<li><a href="">자유게시판</a></li>
<li><a href="">사진게시판</a></li>
</ul>
</div>
</div>
<!-- 로그인 클릭 시 뜨는 모달 (기존에는 안보이다가 위의 a 클릭 시 보임) -->
<div class="modal fade" id="loginModal">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title">Login</h4>
<button type="button" class="close" data-dismiss="modal">×</button>
</div>
<form action="로그인요청받아주는서버" method="post">
<!-- Modal body -->
<div class="modal-body">
<label for="userId" class="mr-sm-2">ID : </label>
<input type="text" class="form-control mb-2 mr-sm-2" placeholder="Enter ID" id="userId" name=""> <br>
<label for="userPwd" class="mr-sm-2">Password : </label>
<input type="password" class="form-control mb-2 mr-sm-2" placeholder="Enter Password" id="userPwd" name="">
</div>
<!-- Modal footer -->
<div class="modal-footer">
<button type="submit" class="btn btn-primary">로그인</button>
<button type="button" class="btn btn-danger" data-dismiss="modal">취소</button>
</div>
</form>
</div>
</div>
</div>
<br clear="both">
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
/* div{border:1px solid red;} */
#footer {
width:80%;
height:200px;
margin:auto;
margin-top:50px;
}
#footer-1 {
width:100%;
height:20%;
border-top:1px solid lightgray;
border-bottom:1px solid lightgray;
}
#footer-2 {width:100%; height:80%;}
#footer-1, #footer-2 {padding-left:50px;}
#footer-1>a {
text-decoration:none;
font-weight:600;
margin:10px;
line-height:40px;
color:black;
}
#footer-2>p {
margin:0;
padding:10px;
font-size:13px;
}
#p2 {text-align:center;}
</style>
</head>
<body>
<div id="footer">
<div id="footer-1">
<a href="#">이용약관</a> |
<a href="#">개인정보취급방침</a> |
<a href="#">인재채용</a> |
<a href="#">고객센터</a>
</div>
<div id="footer-2">
<p id="p1">
강남지원 1관 : 서울특별시 강남구 테헤란로14길 6 남도빌딩 2F, 3F, 4F, 5F, 6F <br>
강남지원 2관 : 서울특별시 강남구 테헤란로10길 9 그랑프리 빌딩 4F, 5F, 7F <br>
강남지원 3관 : 서울특별시 강남구 테헤란로 130 호산빌딩 5F, 6F <br>
종로지원 : 서울특별시 중구 남대문로 120 대일빌딩 2F, 3F <br>
당산지원 : 서울특별시 영등포구 선유동2로 57 이레빌딩 (구관) 19F, 20F
</p>
<p id="p2">Copyright © 1998-2022 KH Information Educational Institute All Right Reserved</p>
</div>
</div>
</body>
</html>
💻 main.jsp에 header.jsp, footer.jsp 페이지를 include
<%@ 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>
<jsp:include page="common/header.jsp" />
<div style="height:600px;">
</div>
<jsp:include page="common/footer.jsp" />
</body>
</html>
🔥 로그인 기능 🔥
💻 Member 클래스 구성
package com.kh.spring.member.model.vo;
import java.sql.Date;
public class Member {
private String userId; // USER_ID VARCHAR2(30 BYTE)
private String userPwd; // USER_PWD VARCHAR2(100 BYTE)
private String userName; // USER_NAME VARCHAR2(15 BYTE)
private String email; // EMAIL VARCHAR2(100 BYTE)
private String gender; // GENDER VARCHAR2(1 BYTE)
private int age; // AGE NUMBER
private String phone; // PHONE VARCHAR2(13 BYTE)
private String address; // ADDRESS VARCHAR2(100 BYTE)
private Date enrollDate; // ENROLL_DATE DATE
private Date modifyDate; // MODIFY_DATE DATE
private String status; // STATUS VARCHAR2(1 BYTE)
public Member() { }
public Member(String userId, String userPwd, String userName, String email, String gender, int age, String phone,
String address, Date enrollDate, Date modifyDate, String status) {
super();
this.userId = userId;
this.userPwd = userPwd;
this.userName = userName;
this.email = email;
this.gender = gender;
this.age = age;
this.phone = phone;
this.address = address;
this.enrollDate = enrollDate;
this.modifyDate = modifyDate;
this.status = status;
}
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 getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Date getEnrollDate() {
return enrollDate;
}
public void setEnrollDate(Date enrollDate) {
this.enrollDate = enrollDate;
}
public Date getModifyDate() {
return modifyDate;
}
public void setModifyDate(Date modifyDate) {
this.modifyDate = modifyDate;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
@Override
public String toString() {
return "Member [userId=" + userId + ", userPwd=" + userPwd + ", userName=" + userName + ", email=" + email
+ ", gender=" + gender + ", age=" + age + ", phone=" + phone + ", address=" + address + ", enrollDate="
+ enrollDate + ", modifyDate=" + modifyDate + ", status=" + status + "]";
}
}
💻 header.jsp
👉🏻 taglib 지시어 추가
👉🏻 로그인 전후 조건 걸기
👉🏻 로그인 폼에 action 속성 걸고 name 속성 추가
// 상단에 taglib 지시어 추가
<%@ 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>
// body 영역에 로그인 전후 조건 추가 및 폼 action, name 속성 수정
<body>
<div id="header">
<div id="header_1">
<div id="header_1_left">
<img src="https://www.iei.or.kr/resources/images/common/top_logo_s.jpg" alt="">
</div>
<div id="header_1_center"></div>
<div id="header_1_right">
<c:choose>
<c:when test="${ empty loginUser }">
<!-- 로그인 전 -->
<a href="">회원가입</a>
<a data-toggle="modal" data-target="#loginModal">로그인</a>
<!-- 모달의 원리 : 이 버튼 클릭시 data-targer에 제시되어있는 해당 아이디의 div요소를 띄워줌 -->
</c:when>
<c:otherwise>
<!-- 로그인 후 -->
<label>${ loginUser.userName }님 환영합니다</label>
<a href="">마이페이지</a>
<a href="">로그아웃</a>
</c:otherwise>
</c:choose>
</div>
</div>
<div id="header_2">
<ul>
<li><a href="">HOME</a></li>
<li><a href="">공지사항</a></li>
<li><a href="">자유게시판</a></li>
<li><a href="">사진게시판</a></li>
</ul>
</div>
</div>
<!-- 로그인 클릭 시 뜨는 모달 (기존에는 안보이다가 위의 a 클릭 시 보임) -->
<div class="modal fade" id="loginModal">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title">Login</h4>
<button type="button" class="close" data-dismiss="modal">×</button>
</div>
<form action="login.me" method="post">
<!-- Modal body -->
<div class="modal-body">
<label for="userId" class="mr-sm-2">ID : </label>
<input type="text" class="form-control mb-2 mr-sm-2" placeholder="Enter ID" id="userId" name="userId"> <br>
<label for="userPwd" class="mr-sm-2">Password : </label>
<input type="password" class="form-control mb-2 mr-sm-2" placeholder="Enter Password" id="userPwd" name="userPwd">
</div>
<!-- Modal footer -->
<div class="modal-footer">
<button type="submit" class="btn btn-primary">로그인</button>
<button type="button" class="btn btn-danger" data-dismiss="modal">취소</button>
</div>
</form>
</div>
</div>
</div>
<br clear="both">
</body>
💻 회원과 관련된 모든 요청을 처리하는 MemberController 클래스 생성
💡 Controller의 코드가 달라져서 놀라셨나요? 아래 링크를 참고해 볼 것!
📖 Spring에서 Controller는 어떻게 달라졌을까?
👉🏻 서블릿의 개념이 없는 건 아니지만 DispatcherServlet이 모두 처리해 주기 때문에 서블릿이 아닌 클래스로 생성함!
👉🏻 내가 만든 클래스이므로 어노테이션을 기재함으로써 Spring에 bean 등록할 것!
@RequestMapping("login.me")
public ModelAndView loginMember(Member m, ModelAndView mv, HttpSession session) {
Member loginUser = memberService.loginMember(m);
if(loginUser == null) { // 로그인 실패
// model.addAttribute("errorMsg", "로그인 실패");
mv.addObject("errorPage", "로그인 실패");
// return "common/errorPage";
mv.setViewName("common/errorPage"); // 포워딩
// ModelAndView 객체를 이용해서 응답페이지를 지정할 경우 또한 ViewResolver에 의해 접두어와 접미어를 생략한 형태로 지정해야만 함
} else { // 로그인 성공
session.setAttribute("loginUser", loginUser);
// return "redirect:/";
mv.setViewName("redirect:/"); // url 재요청방식
}
// 어느 경우에나 mv를 리턴하기 때문에 따로 2번 적을 필요 없이 조건문 밖에 기술해도 됨!
return mv;
}
💻 errorPage.jsp 생성
📖 404 오류 주의! 경로 중복 출력 시 참고하기 (/WEB-INF/views와 .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>
<jsp:include page="header.jsp" />
<br>
<div align="center">
<img src="https://cdn2.iconfinder.com/data/icons/oops-404-error/64/208_balloon-bubble-chat-conversation-sorry-speech-256.png">
<br><br>
<h1 style="font-weight:bold;">${ errorMsg }</h1>
</div>
<br>
<jsp:include page="footer.jsp" />
</body>
</html>
💻 회원과 관련된 모든 요청을 처리하는 MemberService 인터페이스에 메소드 정의
package com.kh.spring.member.model.service;
import com.kh.spring.member.model.vo.Member;
public interface MemberService {
// 로그인 서비스(select)
Member loginMember(Member m);
// 회원가입 서비스 (insert)
int insertMember(Member m);
// 회원 정보 수정 서비스 (update)
int updateMember(Member m);
// 회원 탈퇴 서비스 (update)
int deleteMember(String userId);
// 아이디 중복체크 서비스 (select)
int idCheck(String checkId);
}
💻 MemberServiceimpl 클래스에 메소드 Add 추가 후 로그인 처리
✔️ 클래스에 @Service 어노테이션 꼭 붙여 주기!
순수 MyBatis에서는 SqlSession 객체가 필요했으나 스프링에서 MyBatis를 쓰려면 SqlSessionTemplate 객체가 필요함
👉🏻root-context.xml에서 bean으로 등록했었음
✔️ 내가 직접 new 구문을 통해 객체를 생성할 필요가 없음(@전역변수에 @Autowired 어노테이션으로 선언)
객체 생성을 스프링이 했다는 것
그것은 내가 객체를반납(close)해 줄필요도없다는 것....✨
package com.kh.spring.member.model.service;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.kh.spring.member.model.dao.MemberDao;
import com.kh.spring.member.model.vo.Member;
// @Conponent // 도 가능하긴 함!
@Service
public class MemberServiceImpl implements MemberService {
// Spring에서 MyBatis를 사용하기 위한 SqlSessionTemplate 변수 선언
@Autowired
private SqlSessionTemplate sqlSession;
// MemberDao 객체 생성 선언문
@Autowired
private MemberDao memberDao;
@Override
public Member loginMember(Member m) {
/*
* 기존의 순수 MyBatis 코드
* SqlSession sqlSession = Template.getSqlSession();
* // 이 시점에서 객체 생성됨
*
* Member loginUser = new MemberDao().loginMember(sqlSession, m);
* // 이 시점에서 객체 생성됨
*
* sqlSession.close();
* return loginUser;
*/
Member loginUser = memberDao.loginMember(sqlSession, m);
// SqlSessionTemplate 객체를 bean으로 등록 후 @Autowired 해 줌으로써
// Spring이 해당 객체를 생성해 사용 후 자동으로 객체를 반납함
// 내가 직접 close 메소드로 자원 반납할 필요가 없어짐!
return loginUser;
}
@Override
public int insertMember(Member m) {
return 0;
}
@Override
public int updateMember(Member m) {
return 0;
}
@Override
public int deleteMember(String userId) {
return 0;
}
@Override
public int idCheck(String checkId) {
return 0;
}
}
💻 MemberDao
👉🏻 @Repository:Dao 역할을 하는 bean으로 등록하겠다는 뜻
Repository: 저장소라는 뜻으로 주로 DB(저장소)와 관련된 작업인 영속성 작업을 처리하겠다라는 것!
package com.kh.spring.member.model.dao;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.stereotype.Repository;
import com.kh.spring.member.model.vo.Member;
@Repository // Dao 역할을 하는 bean으로 등록하겠다!
// 주로 DB(저장소)와 관련된 작업 (== 영속성 작업)을 처리하겠다라는 뜻
public class MemberDao {
public Member loginMember(SqlSessionTemplate sqlSession, Member m) {
/*
* SQL문 종류에 따른 메소드를 호출
* => SqlSessio 객체와 SqlSessionTemplate 객체에서 제공하는 메소드 종류가 동일
*/
// 로그인 == 단일행 조회
// sqlSession.selectOne("mapper파일의namespace.sql구문의id값",(쿼리문이미완성이라면)넘길값);
return sqlSession.selectOne("memberMapper.loginMember", m);
}
}
💻 member-mapper.xml
✔ parameterType="com.kh.spring.member.model.vo.Member" 👉🏻 parameterType="member"
✔ 내가 넘겨 받을 값은 "해당 경로" 안에 있는 값이야~ 👉🏻 근데 내가 mybatis-config.xml에 typeAliase 설정해 놓은 거 알지?? 그 별칭으로 쓸게!
☑️ 테이블로부터 조회된 내용을 원하는 객체 타입으로 가공해 주는 ResultMap
☑️ <resultMap id="memberResultSet" type="member">
☑️ memberResultSet이라는 이름으로 member라는 객체를 가공할 거야~
☑️ <result column="DB의 어느 컬럼 값을" property="Java의 어느 필드에 넣을 건지" />
☑️ property 오타 시 setter를 찾을 수 없다는 오류, column 오타 시 부적합한 식별자 오류 뜸!
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd" >
<mapper namespace="memberMapper">
<!-- MEMBER 테이블로부터 조회된 내용을 Member 타입으로 가공해 주는 ResultMap -->
<resultMap id="memberResultSet" type="member">
<result column="USER_ID" property="userId" />
<result column="USER_PWD" property="userPwd" />
<result column="USER_NAME" property="userName" />
<result column="EMAIL" property="email" />
<result column="GENDER" property="gender" />
<result column="AGE" property="age" />
<result column="PHONE" property="phone" />
<result column="ADDRESS" property="address" />
<result column="ENROLL_DATE" property="enrollDate" />
<result column="MODIFY_DATE" property="modifyDate" />
<result column="STATUS" property="status" />
</resultMap>
<!-- 로그인용 쿼리문 -->
<select id="loginMember" parameterType="member" resultMap="memberResultSet">
SELECT *
FROM MEMBER
WHERE USER_ID = #{userId}
AND USER_PWD = #{userPwd}
AND STATUS = 'Y'
</select>
</mapper>
🔥 로그아웃 기능 🔥
💻 header.jsp 로그아웃 url 설정
<c:otherwise>
<!-- 로그인 후 -->
<label>${ loginUser.userName }님 환영합니다</label>
<a href="">마이페이지</a>
<a href="logout.me">로그아웃</a>
</c:otherwise>
💻 MemberController
@RequestMapping("logout.me")
public String logoutMember(HttpSession session) {
// 세션 무효화 메소드: session.invalidate();
session.invalidate();
// 메인 페이지로 url 요청
// mv.setViewName("redirect:/"); // 반환형 수정, ModelAndView 매개변수 요청 후 이렇게 해도 무방!
return "redirect:/";
}