Servlet/JSP - login기능
C:\web-workspace2 폴더 생성
* 추가 셋팅 작업 (자동 주석 지우기)
JAVA-CodeStyle-CodeTemplates-Code탭-catchblock body&method body -edit
서버 구동환경 작업
Server Runtime Environments - add - 8.5 - 체크
+ 포트번호 8001로 변경하기
+ SQL Developer로 새로운 실습용 계정 만들기 & 슬랙 #수업자료에서 sql 스크립트 실행
1. WebContent - WEP-INF-classes-web.xml
2. 웰컴파일 index.jsp 빼고 다지우기
3. WebContent에 index.jsp 파일 생성
4. lib폴더에 ojdbc 넣기
<WebContent/ index.jsp>
* CRUD : 대부분의 컴퓨터 소프트웨어가 가지는 기본적인 데이터 처리기능을 묶어서 이르는 말
사용자 인터페이스가 갖춰야 하는 기능을 가리키는 용도로도 사용됨
- C : Create (생성) => INSERT
- R : Read (읽기 / 인출) => SELECT
- U : Update (갱신) => UPDATE
- D : Delete (삭제/갱신) => DELETE
< 만들어볼 서비스 >
* 회원서비스
로그인(R), 회원가입(C)-(아이디중복체크(R)), 내정보변경(U), 회원탈퇴(D/U), 마이페이지(R)
* 공지사항 서비스
공지사항리스트조회(R), 공지사항상세조회(R), 공지사항작성(C), 공지사항수정(U), 공지사항삭제(D/U)
* 일반게시판서비스
게시글리스트조회(R)-페이징처리, 게시글 상세조회(R), 게시글작성(C)-첨부파일(1개 업로드), 게시글수정(U), 게시글삭제(D/U)
(댓글리스트조회(R), 댓글작성(C)=> AJax로 할 예정)
* 사진게시판서비스
사진 게시판 리스트 조회(R)-썸네일, 사진게시글 상세조회(R), 사진게시판작성(C)-다중파일업로드
subwaystore워크스페이스 폴더에서 driver.properties 복사해서 driver에 넣고 userId, userPwd를 SERVER로 변경
(properties 파일에는 공백이 있어선 절대 안됨 !! 주의 !!)
subway-mapper.xml도 sql-member에 복사 후 이름변경 & entry 지우기
C:\web-workspace1\3_SubwayStore\src\com\kh\common의 JDBCTemplate.java파일을
scr > common폴더에 붙여넣기
<WebContent/ index.jsp>
- 실행하기 전 잘 돌아가는지 테스트
: import하는 이유는 경로가 어디인지 알려주기 위해서이기 때문에 아래처럼 써도 됨
class패키지 경로 제시
<% com.kh.common.JDBCTemplate.getConnection(); %>
=> 아무것도 안뜨면 잘 실행되고 있다는 뜻
- include 활용하기
: 상단에는 메뉴바를 보이게 할 것임 => 하나 만들고 여러 곳에 가져다 쓰기
<%@ include file="views/common/menubar.jsp" %>
- menu.jsp 파일 생성하기
<WebContent/views/common/menubar.jsp>
form태그 안에 있는 제출버튼(submit) 클릭 시 form태그가 가지고 있는 속성 중
action에 작성된 url로 요청됨(제출)
즉, Controller(Servlet)을 호출한다고 생각하면 됨
Servlet요청 같은 경우 반드시 그 요청값이 현재 웹 어플리케이션의
context Path == /context Root/뒤에 붙는 경로
형식으로 작성해야 함
=> http://localhost:8001/jsp/test1.do (서블릿 매핑값)
* context Root루트 바꾸는 법 : server 더블클릭 > 바꿀 수 있음
* 경로 지정 방식
- 절대경로방식 (/) : /Context Root/요청할 url
/로 시작하는 경우
localhost:8001 뒤에 action에 작성한 값이 붙여지면서 요청
- 상대경로방식(test1.do) : 요청할 url문구로 시작하는 경우
현재 이 페이지가 보여질 때의 url경로 중에서
마지막 / 로부터 뒤에 action에 작성한 값이 붙어지면서 요청
- 아이디 비밀번호 영역
<div class="login-area">
<form action="/jsp/login.me" method="post">
<table>
<tr>
<th>아이디</th>
<td><input type="text" required name="userId"></td>
</tr>
<th>비밀번호</th>
<td><input type="password" required name="userPwd"></td>
<tr>
<th colspan="2">
<button type="submit">로그인</button>
<button type="button">회원가입</button>
</th>
</tr>
</table>
</form>
</div>
- navigator만들기
<div class="nav-area" align="center"> <!--이번엔 list방식이 아닌 div로 네비바 만들기 -->
<div class="menu"><a href="#">HOME</a></div>
<div class="menu"><a href="#">공지사항</a></div>
<div class="menu"><a href="#">일반게시판</a></div>
<div class="menu"><a href="#">사진게시판</a></div>
</div>
+ 스타일 부여
.nav-area{
background-color: olive;
}
.menu{
display: table-cell; /*가로로 바꾸는 법*/
height: 50px;
width: 150px;
}
.menu a{
text-decoration: none;
color: white;
font-size: 22px;
font-weight: bold;
display: block; /*크기 지정하기 위해서 블럭요소로 바꿈*/
width: 100%;
height: 100%;
line-height: 50px;
}
.menu a:hover{
background-color: olivedrab;
}
member만들기
1. vo
Member: <com.kh.member.model.vo>
package com.kh.member.model.vo;
import java.sql.Date;
public class Member {
private int userNO;
private String userId;
private String userPwd;
private String userName;
private String phone;
private String email;
private String address;
private String interest;
private Date enrollDate; //반드시 sql의 date import하기**
private Date modifuDate;
private String status;
//기본생성자, 모든필드에 대한 매개변수를 가지고 있는 생성자, 각각 getter/setter, toString
public Member() {
super();
}
public Member(int userNO, String userId, String userPwd, String userName, String phone, String email,
String address, String interest, Date enrollDate, Date modifuDate, String status) {
super();
this.userNO = userNO;
this.userId = userId;
this.userPwd = userPwd;
this.userName = userName;
this.phone = phone;
this.email = email;
this.address = address;
this.interest = interest;
this.enrollDate = enrollDate;
this.modifuDate = modifuDate;
this.status = status;
}
public int getUserNO() {
return userNO;
}
public void setUserNO(int userNO) {
this.userNO = userNO;
}
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 getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getInterest() {
return interest;
}
public void setInterest(String interest) {
this.interest = interest;
}
public Date getEnrollDate() {
return enrollDate;
}
public void setEnrollDate(Date enrollDate) {
this.enrollDate = enrollDate;
}
public Date getModifuDate() {
return modifuDate;
}
public void setModifuDate(Date modifuDate) {
this.modifuDate = modifuDate;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
@Override
public String toString() {
return "Member [userNO=" + userNO + ", userId=" + userId + ", userPwd=" + userPwd + ", userName=" + userName
+ ", phone=" + phone + ", email=" + email + ", address=" + address + ", interest=" + interest
+ ", enrollDate=" + enrollDate + ", modifuDate=" + modifuDate + ", status=" + status + "]";
}
}
2. Controller
LoginController < com.kh.member.controller >
매핑값 login.me로 한 Servlet 생성
<HttpServletRequest, HttpServletResponse 객체>
- request : 서버로 요청할 때 정보들이 담겨있음(요청 시 전달값, 요청전달 방식 등)
- response : 요청에 대해 응답하고자 할 때 사용하는 객체
1) POST방식의 경우 인코딩 설정
request.setCharacterEncoding("UTF-8");
2) 요청 시 전달값을 꺼내서 변수에 기록 => request의 getParameter영역
request.getParameter("키값"); : String
request.getParameterValues("키값") : String[] => checkbox일 경우 사용
String userId = request.getParameter("userId");
String userPwd = request.getParameter("userPwd");
3) 해당 요청을 처리해주는 서비스 클래스의 메소드를 호출
new MemberService().loginMember(userId, userPwd);
<응답>
3_2) Member객체 형태로 돌아왔으므로 Member로 받아주기
Member loginUser = new MemberService().loginMember(userId, userPwd);
일치하는 회원이 있다면 loginUser객체에 필드값이 회원정보로 꽉 차있을 것
일치하는 회원이 없다면 null;(초기값그대로=>if문 안에 못들어와서)
4) 처리된 결과를 가지고 사용자가 보게될 응답화면을 지정
* 로그인의 경우 특별한 경우이기 때문에 기본 request방식이 맞지 않음
기본 STEP
STEP1. request객체에 응답화면에 보여질 값 담기 -> request의 attribute영역에
STEP2. RequestDispatcher객체 생성 => 응답할 뷰 화면을 지정
STEP3. RequestDispatcher객체로부터 forward메소드 호출
로그인 기능의 경우 적합하지 않음(다음페이지로 넘어가도 로그인이 유지되어야하기 때문에)
STEP1. 어딘가에 응답화면에 보여질 값 담기(request, session, application, page)
* 응답페이지에 전달할 값 담을 객체 4종류 * (ServletScope 내장객체)
: 모두 다 Attribute 영역이 존재함
큰(위로갈 수록 큰)
(1) application : 웹 어플리케이션 전역에서 언제나 꺼내쓸 수 있음 (자바클래스에서 쓸 수 있음)
(2) session : 모든 jsp와 servlet에서 꺼내쓸 수 있음
단, 내가 직접적으로 session객체에 담은 값을 지우기 전까지
세션이 끊기는 경우 : 브라우저가 종료될 때, 서버가 멈춘 경우
=> 로그인
(3) request : 해당 request를 forwarding한 응답 jsp페이지에서만 쓸 수 있음
요청페이지부터 응답페이지까지에서만 쓸 수 있음
(4) page : 담은 값을 해당 jsp페이지에서만 쓸 수 있음(화면이 넘어가면 담은 값은 소멸)
작은(아래로갈수록 작은)
=> session, request가 가장 많이 쓰임
=> 공통적으로 데이터를 담고자 한다면 : XXX.setAttribute(key, value)
데이터를 뽑고자 한다면 : XXX.getAttribute(key);
데이터를 지우고자 한다면 : XXX.removeAttribute(key);
예시)
로그인 시 : session.setAttribute("userInfo", loginUser)
로그아웃 시 : session.removeAttribute("userInfo"); 또는 무효화 시키는 메소드 사용
Q. 그럼 매개변수에 있는 HttpServletRequest request는?
STEP2. case1. RequestDispatcher 객체 생성(응답할 뷰화면 지정) => forward();
- 로그인 실패했을 경우와 성공했을 때를 나누어서 응답 뷰 지정하기
- 로그인 실패
if(loginUser==null) { //로그인 실패
//에러메시지넘기기
//1) request의 attribute영역 메시지 담기
request.setAttribute("errorMsg", "로그인에 실패하였습니다.");
//2) RequestDispatcher객체 생성
RequestDispatcher view = request.getRequestDispatcher("views/common/errorPage.jsp");
//3) forward
view.forward(request, response);
}
- views/common/errorPage.jsp 파일 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
String errorMsg = (String)request.getAttribute("errorMsg");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1 align="center" style="color:red; margin-top:100px;"><%=errorMsg %></h1>
</body>
</html>
만약 로그인 실패 시,
이런 화면이 나타나게 됨
- 로그인 성공
=> index.jsp페이지 응답
사용자의 정보 넘기기 : 로그인 한 회원의 정보를 로그아웃하기 전까지 계속 가져다 쓸거기 때문에 session에 담기
1) session에 attribute영역에 사용자 정보 담기
=> session 객체의 Type : HttpSession
=> session 객체 생성 방법 : request.getSession();
HttpSession session = request.getSession();
session.setAttribute("loginUser", loginUser);
방식 1. 포워딩 방식
: 요청한 곳에서 다시 응답 줌
2) RequestDispatcher 객체 생성
RequestDispatcher view = request.getRequestDispatcher("index.jsp");
3) forward()
view.forward(request, response);
//http://localhost:8001/jsp/login.me
//내가 요청한 mapping값이 url에 잘 나옴
방식 2. url재요청방식(sendRedirect방식) : 사용자에게 url을 재요청 하게 함으로써 응답페이지가 보여지게끔 해줌
response객체를 이용 => response.sendRedirect(재요청할 경로);
response.sendRedirect("/jsp");
= 내가 요청한 경로가 url에 보임 : http://localhost:8001/jsp
* 로그인 실패화면과 성공화면 나누기 (성공했을 때는 XXX님 환영합니다. / 실패했을 때는 로그인창) : if문 이용
* 로그인 성공 시 alert창 띄우기 : 로그인 성공
if(loginUser==null) { //로그인 실패
request.setAttribute("errorMsg", "로그인에 실패하였습니다.");
RequestDispatcher view = request.getRequestDispatcher("views/common/errorPage.jsp");
view.forward(request, response);
} else { //로그인 성공 => index.jsp페이지 응답
HttpSession session = request.getSession();
session.setAttribute("loginUser", loginUser);
session.setAttribute("alertMsg", "로그인 성공"); //alert창(성공)
response.sendRedirect("/jsp");
}
menubar.jsp에서 로그인/로그인 전 화면을 나누는 이유는 메뉴는 어느페이지나 존재하기 때문에 매번 나눌 필요가 없음
<WebContent/views/common/menubar.jsp>
+ 스타일 추가부여
.login-area, #user-info{
float : right;
}
#user-info a{
text-decoration : none;
color : ligthgray;
font-size : 12px;
}
loginController로 부터 가져온 session객체의 attribute에 접근해 값 가져오기 & 변수에 담기
<% Member loginUser = (Member)session.getAttribute("loginUser"); //Object=>Member
String alertMsg = (String)session.getAttribute("alertMsg"); %>
>로그인 전 : menubar.jsp가 로딩될 때 null
>로그인 후 : menubar.jsp가 로딩될 때 로그인한 회원의 정보가 담겨있음
> 서비스 요청 전 : alertMsg = null
> 서비스 요청 후 성공 시 : alertMsg = 메시지문구
- body영역 젤 초반에 script태그 만들기 => script태그 안에서도 스크립틀릿 사용가능
<script>
var msg = '<%=alertMsg%>';
case1 : '메시지 문구' / case2: null(만약에 null이면 null에도 ''가 붙게됨)
alert창은 한 번 실행 후 더이상 실행되지 않아야 함
session에 들어있는 alertMsg키값에 대한 벨류를 지우기 => 만약 지우지 않는다면 menubar.jsp가 로딩될 때마다 alert이 계속 뜰 것임
=> XX.removeAttribute("키값");
if(msg != 'null'){
alert(msg);
<%session.removeAttribute("alertMsg");%>
- 화면 지정하기
<% if(loginUser==null) { %>
<!-- 사용자가 로그인 전 보게 될 화면 -->
<form action="/jsp/login.me" method="post">
<table>
<tr>
<th>아이디</th>
<td><input type="text" required name="userId"></td>
</tr>
<th>비밀번호</th>
<td><input type="password" required name="userPwd"></td>
<tr>
<th colspan="2">
<button type="submit">로그인</button>
<button type="button">회원가입</button>
</th>
</tr>
</table>
</form>
<% } else { %>
<!-- 로그인 성공 시 보게될 화면 -->
<div id="user-info">
<b><%=loginUser.getUserName() %></b>님 환영합니다. <br><br>
<div align="center">
<a href="#">마이페이지</a>
<a href="#">로그아웃</a>
</div>
<% } %>
3. Service
MemberService<m.kh.member.model.service>
Service => Connection객체 생성
1) Connection객체 생성
2) controller에서 넘어온 전달값과 Connection객체를 가지고 DAO메소드 호출
public void loginMember(String userId, String userPwd) {
Connection conn = JDBCTemplate.getConnection();
new MemberDao().loginMember(conn, userId, userPwd);
}
< 응답 >
3) Connection 객체 반납 & 4) Controller로 결과넘기기
Member loginUser = new MemberDao().loginMember(conn, userId, userPwd);
JDBCTemplate.close(conn);
return loginUser;
4. Dao
MemberDao<m.kh.member.model.dao>
DAO(Data Access Object) : 데이터가 보관되어있는 공간에 직접 접근해서 데이터를 입출력하는 메소드들을 모아둔 클래스(dao)
0) properties 객체 생성 (바깥에서 쓰지 않을 거니까 private)
private Properties prop = new Properties();
1) 기본생성자 만들기(예외처리해주기) > 자주쓰니까 호출할 때마다 생성하기 위해서
스트림 연결 & member-mapper에서 xml파일 가져오기
public MemberDao() {
String file = MemberDao.class.getResource("/sql/member/member-mapper.xml").getPath();
try {
prop.loadFromXML(new FileInputStream(file));
} catch (IOException e) {
e.printStackTrace();
}
}
2) loginMember(Connection conn, String userId, String userPwd)
로그인 요청 => SELECT문 => ResultSet객체 (unique key제약조건에 의해 한 행만 조회됨) => Member
Member loginUser = null;
ResultSet rset = null;
PreparedStatement pstmt= null;
String sql = prop.getProperty("loginMember");
try {
//pstmt객체 생성
pstmt = conn.prepareStatement(sql);
//위치홀더(?)채우기
pstmt.setString(1, userId);
pstmt.setString(2, userPwd);
//쿼리문 실행 후 결과 받기
//쿼리문 실행메소드
//pstmt.executeQuery(); => SELECT : ResultSet
//pstmt.executeUpdate(); => INSERT, UPDATE, DELETE : int / 0
rset = pstmt.executeQuery();
//rset으로부터 각각의 컬럼값을 뽑아서 Member객체에 담는다.
//조회결과가 한 행 일 때 -> if()
//조회결과가 여러 행일 때 -> while()
if(rset.next()) {
// 각 컬럼의 값 뽑기
// rset.getInt/getString/getDate(뽑아올 컬럼명 또는 컬럼의 순번)
loginUser = new Member(rset.getInt("USER_NO"),
rset.getString("USER_ID"),
rset.getString("USER_PWD"),
rset.getString("USER_NAME"),
rset.getString("PHONE"),
rset.getString("EMAIL"),
rset.getString("ADDRESS"),
rset.getString("INTEREST"),
rset.getDate("ENROLL_DATE"),
rset.getDate("MODIFY_DATE"),
rset.getString("STATUS")
);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//자원반납
JDBCTemplate.close(rset);
JDBCTemplate.close(pstmt);
}
//Service에 결과(Member)넘기기
return loginUser;
}