2020년 12월 24일
업데이트:
프로젝트 구성
sql
라이브러리 설정
- WebContent/WEB-INF/lib에 라이브러리 파일들을 추가한다.
메인 화면
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
.bg-image-full {
background: no-repeat center center scroll;
-webkit-background-size: cover;
-moz-background-size: cover;
background-size: cover;
-o-background-size: cover;
}
div.bg-image-full{
height: 300px;
text-align: center;
}
@font-face { font-family: 'GmarketSansBold'; src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2001@1.1/GmarketSansBold.woff') format('woff'); font-weight: normal; font-style: normal; }
div.bg-image-full>h1{
color : white;
position: relative;
top : 75px;
font-size: 3em;
font-family: 'GmarketSansBold';
text-shadow: -2px 0 black, 0 2px black, 2px 0 black, 0 -2px black;
}
</style>
</head>
<body>
<!-- WEB-INF/views/common/header.jsp 여기에 삽입(포함) -->
<jsp:include page="WEB-INF/views/common/header.jsp"></jsp:include>
<!-- 상대 경로로 작성함 -->
<!-- 메인 화면 이미지 -->
<div class="py-5 bg-image-full" style="background-image: url('https://iei.or.kr/resources/images/intro/intro_bg.jpg');">
<h1>Servlet/JSP<br>Web Application</h1>
</div>
<!-- 내용 작성 부분 -->
<div class="py-5">
<div class="container">
<h1>Section Heading</h1>
<p class="lead">Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid, suscipit, rerum quos facilis repellat architecto commodi officia atque nemo facere eum non illo voluptatem quae delectus odit vel itaque amet.</p>
</div>
</div>
<div class="py-5">
<div class="container">
<h1>Section Heading</h1>
<p class="lead">Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid, suscipit, rerum quos facilis repellat architecto commodi officia atque nemo facere eum non illo voluptatem quae delectus odit vel itaque amet.</p>
</div>
</div>
<!-- WEB-INF/views/common/footer.jsp 여기에 삽입(포함) -->
<jsp:include page="WEB-INF/views/common/footer.jsp"></jsp:include>
</body>
</html>
header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>WebServer Project</title>
<!-- Bootstrap core CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
<!-- Bootstrap core JS -->
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx"
crossorigin="anonymous">
</script>
<!-- sweetalert : alert창을 꾸밀 수 있게 해주는 라이브러리 https://sweetalert.js.org/ -->
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
<style>
body {
padding-top: 56px;
}
</style>
</head>
<body>
<!-- Navigation으로 된 header -->
<div class="header navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<div class="container">
<a class="navbar-brand" href="${contextPath}">WebServer Project</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive"
aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item"><a class="nav-link" href="#">Notice</a></li>
<li class="nav-item"><a class="nav-link" href="#">Board</a></li>
<li class="nav-item active">
<a class="nav-link" data-toggle="modal" href="#modal-container-1">Login</a>
</li>
</ul>
</div>
</div>
</div>
<%-- Modal창에 해당하는 html 코드는 페이지 마지막에 작성하는게 좋다 --%>
<div class="modal fade" id="modal-container-1" role="dialog"
aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="myModalLabel">로그인 모달창</h5>
<button type="button" class="close" data-dismiss="modal">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form class="form-signin" method="POST" action="#">
<input type="text" class="form-control" id="memberId"
name="memberId" placeholder="아이디" value="">
<br>
<input type="password" class="form-control" id="memberPwd"
name="memberPwd" placeholder="비밀번호">
<br>
<div class="checkbox mb-3">
<label>
<input type="checkbox" name="save" id="save"> 아이디 저장
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">
로그인
</button>
<a class="btn btn-lg btn-secondary btn-block" href="#">
회원가입
</a>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary"
data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</body>
</html>
footer.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<!-- Footer -->
<div class="py-5 bg-dark footer">
<div class="container">
<p class="m-0 text-center text-white">Copyright © KH Information Educational Institute A-Class</p>
</div>
</div>
</body>
</html>
include
index.jsp
body 부분에 작성
<!-- body 내 상단에 작성-->
<jsp:include page="WEB-INF/views/common/header.jsp"></jsp:include>
<!-- 생략 -->
<!-- body 내 하단에 작성 -->
<jsp:include page="WEB-INF/views/common/header.jsp"></jsp:include>
로고 클릭 시 메인 페이지로 이동
header.jsp
body 부분에 작성
<!--
프로젝트의 시작주소(Context root)를 얻어와 간단하게
사용할 수 있도록 contextPath라는 변수 생성
-->
<c:set var="contextPath" scope="application"
value="${pageContext.servletContext.contextPath}"/>
<!-- 로고 <a> 태그 href 부분 수정-->
<a class="navbar-brand" href="${contextPath}">WebServer Project</a>
로그인
-
로그인 전 헤더
-
로그인 모달창
-
로그인 후 헤더
-
로그인 실패 경고창
web.xml
<resource-ref>
<description>Oracle Datasource</description>
<res-ref-name>jdbc/oracle</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
context.xml (DBCP)
-
context.xml 경로
-
context.xml 하단 부분에 작성
<!-- 생략 -->
<!-- DataBase Connection Pool -->
<Resource
name="jdbc/oracle"
auth="Container"
type="javax.sql.DataSource"
driverClassName="oracle.jdbc.OracleDriver"
url="jdbc:oracle:thin:@127.0.0.1:1521:xe"
username="server"
password="server"
maxTotal="20"
maxIdle="10"
maxWaitMillis="-1"
/>
<!--
name : JNDI 이름 Context의 lookup()을 사용하여 자원을 찾을 때 사용한다.
java:comp/env 디렉터리에서 찾을 수 있음.
auth : 자원 관리 주체 지정(Application 또는 Container)
type : Resourtece의 타입을 지정한다.
driverClassName : JDBC 드라이버 클래스 이름
maxTotal : DataSource에서 유지할 수 있는 최대 커넥션의 수
maxIdle : 사용되지 않고 풀에 저장될 수 있는 최대 커넥션의 개수.
음수일 경우 제한이 없다.
maxWaitMillis : 커넥션 반납을 기다리는 시간
(-1은 반환될 때까지 기다린다.)
-->
JDBCTemlate.java
public class JDBCTemplate {
private static Connection conn = null;
private JDBCTemplate(){}
/*
WAS(Tomcat)가 미리 DB에 접속할 수 있는 객체(Connection)를 일정 개수를
만들어 두고 요청이 올 때마다 만들어 둔 객체를 전달하고 사용 완료 후에는
반환 받는 Connection Pool 사용
*/
public static Connection getConnection()
throws NamingException, SQLException{
// JNDI(Java Naming and Directory Interface API)
/*
디렉터리 서비스에 접근하는데 사용하는 API
애플리케이션은 JNDI를 사용하여 서버의 resource를 찾음.
특히 JDBC resouce를 data source라고 부름.
Resource를 서버에 등록할 때 고유한 JNDI 이름을 붙이는데,
JNDI 이름은 디렉터리 경로 형태를 갖는다.
ex) jdbc/mydb
서버에서 'jdbc/oracle'이라는 DataSource를 찾으려면
'java:comp/env/jdbc/oracle'이라는 JNDI 이름으로 찾아야함.
즉 lookup() 메소드에 'java:comp/env/jdbc/oracle'를 인자값으로 넘긴다.
*/
// Servers에 존재하는 context.xml 파일을 찾는 작업
Context initContext = new InitialContext();
Context envContext = (Context)initContext.lookup("java:/comp.env");
// context.xml 파일에서 name이 "jdbc/oracle"인 DataSource를 얻어옴
// DataSource : DriverManager를 대체하는 객체로 Connection 생성, Connection pool을 구현하는 객체
DataSource ds = (DataSource)envContext.lookup("jdbc/oracle");
conn = ds.getConnection(); // DataSource에 의해 미리 만들어진 Connection 중 하나
conn.setAutoCommit(false);
return conn;
}
public static void commit(Connection conn) {
try {
if(conn != null && !conn.isClosed()) {
conn.commit();
}
}catch (Exception e) {
e.printStackTrace();
}
}
public static void rollback(Connection conn) {
try {
if(conn != null && !conn.isClosed()) {
conn.rollback();
}
}catch (Exception e) {
e.printStackTrace();
}
}
// DB 연결 자원 반환 구문도 static으로 작성
public static void close(Connection conn) {
try {
if(conn != null && !conn.isClosed()) {
conn.close();
}
}catch (Exception e) {
e.printStackTrace();
}
}
public static void close(ResultSet rset) {
try {
if(rset != null && !rset.isClosed()) {
rset.close();
}
}catch (Exception e) {
e.printStackTrace();
}
}
public static void close(Statement stmt) {
try {
if(stmt != null && !stmt.isClosed()) {
stmt.close();
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
Member.java
public class Member {
private int memberNo; // 회원번호
private String memberId; // 아이디
private String memberPwd; // 비밀번호
private String memberName; // 이름
private String memberPhone; // 전화번호
private String memberEmail; //이메일
private String memberAddress; //주소
private String memberInterest; // 관심분야
private Date memberEnrollDate; // 가입일
private String memberStatus; // 상태
private String memberGrade; //등급
public Member() { }
public Member(int memberNo, String memberId, String memberName, String memberPhone, String memberEmail,
String memberAddress, String memberInterest, String memberGrade) {
super();
this.memberNo = memberNo;
this.memberId = memberId;
this.memberName = memberName;
this.memberPhone = memberPhone;
this.memberEmail = memberEmail;
this.memberAddress = memberAddress;
this.memberInterest = memberInterest;
this.memberGrade = memberGrade;
}
public Member(int memberNo, String memberId, String memberPwd, String memberName, String memberPhone,
String memberEmail, String memberAddress, String memberInterest, Date memberEnrollDate, String memberStatus,
String memberGrade) {
super();
this.memberNo = memberNo;
this.memberId = memberId;
this.memberPwd = memberPwd;
this.memberName = memberName;
this.memberPhone = memberPhone;
this.memberEmail = memberEmail;
this.memberAddress = memberAddress;
this.memberInterest = memberInterest;
this.memberEnrollDate = memberEnrollDate;
this.memberStatus = memberStatus;
this.memberGrade = memberGrade;
}
public int getMemberNo() {
return memberNo;
}
public void setMemberNo(int memberNo) {
this.memberNo = memberNo;
}
public String getMemberId() {
return memberId;
}
public void setMemberId(String memberId) {
this.memberId = memberId;
}
public String getMemberPwd() {
return memberPwd;
}
public void setMemberPwd(String memberPwd) {
this.memberPwd = memberPwd;
}
public String getMemberName() {
return memberName;
}
public void setMemberName(String memberName) {
this.memberName = memberName;
}
public String getMemberPhone() {
return memberPhone;
}
public void setMemberPhone(String memberPhone) {
this.memberPhone = memberPhone;
}
public String getMemberEmail() {
return memberEmail;
}
public void setMemberEmail(String memberEmail) {
this.memberEmail = memberEmail;
}
public String getMemberAddress() {
return memberAddress;
}
public void setMemberAddress(String memberAddress) {
this.memberAddress = memberAddress;
}
public String getMemberInterest() {
return memberInterest;
}
public void setMemberInterest(String memberInterest) {
this.memberInterest = memberInterest;
}
public Date getMemberEnrollDate() {
return memberEnrollDate;
}
public void setMemberEnrollDate(Date memberEnrollDate) {
this.memberEnrollDate = memberEnrollDate;
}
public String getMemberStatus() {
return memberStatus;
}
public void setMemberStatus(String memberStatus) {
this.memberStatus = memberStatus;
}
public String getMemberGrade() {
return memberGrade;
}
public void setMemberGrade(String memberGrade) {
this.memberGrade = memberGrade;
}
@Override
public String toString() {
return "Member [memberNo=" + memberNo + ", memberId=" + memberId + ", memberPwd=" + memberPwd + ", memberName="
+ memberName + ", memberPhone=" + memberPhone + ", memberEmail=" + memberEmail + ", memberAddress="
+ memberAddress + ", memberInterest=" + memberInterest + ", memberEnrollDate=" + memberEnrollDate
+ ", memberStatus=" + memberStatus + ", memberGrade=" + memberGrade + "]";
}
}
MemberService.java (service)
public class MemberService {
private MemberDAO dao = new MemberDAO();
/** 로그인 Service
* @param member
* @return loginMember
* @throws Exception
*/
public Member loginMember(Member member) throws Exception {
Connection conn = getConnection();
Member loginMember = dao.loginMember(conn, member);
close(conn);
return loginMember;
}
}
member-query.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<entry key="loginMember">
SELECT MEMBER_NO, MEMBER_ID, MEMBER_NM, MEMBER_PHONE,
MEMBER_EMAIL, MEMBER_ADDR, MEMBER_INTEREST,
MEMBER_GRADE
FROM MEMBER
WHERE MEMBER_ID = ?
AND MEMBER_PWD = ?
AND MEMBER_STATUS = 'Y'
</entry>
</properties>
MemberDAO.java
public class MemberDAO {
// DAO에서 자주 사용되는 JDBC 참조 변수 선언
private Statement stmt = null;
private PreparedStatement pstmt = null;
private ResultSet rset = null;
private Properties prop = null;
public MemberDAO() {
// 외부에 저장된 XML 파일로부터 SQL을 얻어옴 ---> 유지보수성 향상
try{
String filePath = MemberDAO.class
.getResource("/com/kh/wsp/sql/member/member-query.xml").getPath();
prop = new Properties();
prop.loadFromXML(new FileInputStream(filePath));
}catch(Exception e){
e.printStackTrace();
}
}
/** 로그인 DAO
* @param conn
* @param member
* @return loginMember
* @throws Exception
*/
public Member loginMember(Connection conn, Member member)
throws Exception {
// 결과 저장용 변수 선언
Member loginMember = null;
String query = prop.getProperty("loginMember");
try{
// 1) PreparedStatement 객체를 얻어와 query 세팅
pstmt = conn.prepareStatement(query);
// 2) 위치홀더(?)에 알맞은 값 세팅
pstmt.setString(1, member.getMemberId());
pstmt.setString(2, member.getMemberPwd());
// 3) SQL 수행 후 결과를 반환받아 저장
rset = pstmt.executeQuery();
// 4) 조회 결과가 있을 경우 Member 객체에 조회 내용을 저장
if(rset.next()) {
loginMember = new Member(rset.getInt("MEMBER_NO"),
rset.getString("MEMBER_ID"),
rset.getString("MEMBER_NM"),
rset.getString("MEMBER_PHONE"),
rset.getString("MEMBER_EMAIL"),
rset.getString("MEMBER_ADDR"),
rset.getString("MEMBER_INTEREST"),
rset.getString("MEMBER_GRADE"));
}
} finally {
close(rset);
close(pstmt);
}
return loginMember;
}
}
header.jsp
<!-- 모달 body 부분에 href 경로 지정-->
<form class="form-signin" method="POST" action="${ contextPath }/member/login.do">
LoginServlet.java
@WebServlet("/member/login.do")
public class LoginServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// 1. POST 방식으로 전달된 데이터의 문자 인코딩 변경
request.setCharacterEncoding("UTF-8");
// 2. 파라미터를 얻어와 변수에 저장
String memberId = request.getParameter("memberId");
String memberPwd = request.getParameter("memberPwd");
String save = request.getParameter("save"); // 체크 시 on, 아니면 null
// System.out.println(memberId + " / " + memberPwd + " / " + save);
// 정보를 잘 가져오는지 확인
// JDBC 수행
// 아이디와 비밀번호를 하나의 vo에 담아서 service로 전달
// 3. 아이디와 비밀번호를 Member 객체에 세팅
Member member = new Member();
member.setMemberId(memberId);
member.setMemberPwd(memberPwd);
try {
// 4. Member 객체를 service로 전달하여 결과 반환 받기
Member loginMember = new MemberService().loginMember(member);
// System.out.println(loginMember); 정보를 잘 가져왔는지 확인
// 로그인 정보는 로그아웃 또는 브라우저가 종료될 때까지 유지되어야 함
// --> session 활용
// 5. 응답 화면 문서 타입 지정
response.setContentType("text/html; charset=UTF-8");
// 6. Session 객체를 얻어와 로그인 정보 추가
HttpSession session = request.getSession();
// 6-1. 로그인이 성공했을 때만 Session에 로그인 정보 추가하기
if(loginMember != null) {
// 6-2. 30분 동안 동작이 없을 경우 Session 만료
session.setMaxInactiveInterval(60 * 1); // 테스트용 1분 후 만료
// 6-3. Session에 로그인 정보 추가
session.setAttribute("loginMember", loginMember);
} else{
// 로그인이 실패했을 경우 경고창 띄우기
// -> session에 문자열을 담아 리다이렉트
// sweet alert 사용하기
session.setAttribute("swalIcon", "error");
session.setAttribute("swalTitle", "로그인 실패");
session.setAttribute("swalText", "아이디 또는 비밀번호를 확인해주세요.");
}
// redirect 방식을 이용하여 로그인을 요청했던 페이지로 이동한다.
/*
forward 같은 경우에는 이동하는 페이지로 request, response
객체를 그대로 위임하고, 주소를 위임 전 주소로 유지하지만
redirect는 이전 request, response를 폐기하고 새롭게 만들어서
지정된 주소로 새로운 요청을 보낸다.
-> 새롭게 요청을 보내기 때문에 이전 요청 주소가 아닌 새로운 요청 주소가 타나남
*/
// request.getHeader("referer") : 요청 전 페이지 주소가 담겨있다.
response.sendRedirect(request.getHeader("referer"));
} catch {
e.printStackTrace();
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
header.jsp
- header.jsp 내 body 상단 부분에 작성
<!-- 로그인 실패 등 서버로부터 전달받은 메세지를 경고창으로 출력 -->
<!-- 서버로부터 전달받은 메세지가 있는지 검사하기 -->
<c:if test="${!empty sessionScope.swalTitle}">
<script>
swal({icon : "${swalIcon}", title : "${swalTitle}", text : "${swalText}"});
</script>
<!-- 2) 한 번 출력한 메세지를 Session에서 삭제 -->
<c:remove var="swalIcon" />
<c:remove var="swalTitle" />
<c:remove var="swalText" />
</c:if>
- header.jsp 내 로그인 모달창 전에 작성
<c:choose>
<!-- 로그인이 되어있지 않을 때 == session에 loginMember 값이 없을 때 -->
<c:when test="${empty sessionScope.loginMember}">
<!-- 헤더에 있는 login 버튼 클릭 시 #modal-container-1 이라는
아이디를 가진 요소를 보여지게 함. -->
<li class="nav-item active">
<a class="nav-link" data-toggle="modal" href="#modal-container-1">
Login
</a>
</li>
</c:when>
<!-- 로그인이 되어있을 때 -->
<c:otherwise>
<li class="nav-item active">
<!-- 로그인 회원의 이름을 가져와 출력 -->
<a class="nav-link" href="#">${loginMember.memberName}</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="${contextPath}/member/logout.do">Logout</a>
</li>
</c:otherwise>
</c:otherwise>
</c:choose>
오류 페이지
LoginServlet.java
- LoginServlet.java 내 catch 구문에 작성
request.setAttribute("errorMsg", "로그인 과정에서 오류가 발생했습니다.");
// 요청 위임 객체 생성
RequestDispatcher view
= requset.getRequestDispatcher("/WEB-INF/views/common/errorPage.jsp");
view.forward(request, response);
errorPage.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" isErrorPage="true"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>에러페이지</title>
</head>
<body>
<h1 align="center">${errorMsg}</h1>
<div align="center">
<button onclick="history.back();">이전 페이지로 이동</button>
<button onclick="location.href='${contextPath}'">메인 화면으로 돌아가기</button>
</div>
</body>
</html>
로그아웃
LogoutServlet.java
@WebServlet("/member/logout.do")
public class LogoutServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 로그인 상태 -> DB에서 조회한 회원 정보가 Session에 존재하는 것
// 로그아웃 상태 -> Session에 회원 정보가 없는 것
// 세션 만료(세션 무효화)
request.getSession().invalidate();
// 로그아웃 후 메인 or 로그아웃을 수행한 페이지
// response.sendRedirect(request.getContextPath()); // 메인
response.sendRedirect(request.getHeader("referer")); // 로그아웃을 수행한 페이지
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
댓글남기기