JSP/Servlet - DBCP, 세션!
DBCP (DataBase Connection Pool)
커넥션 풀에 대해 전에 배웠었다.
https://kouzie.github.io/jdbc/JDBC.-6일차/#
DB연결을 빠르게 효율적으로 관리하기 위한 객체이다.
지금까지 DB접속시 DBConn
이라는 연결용 클래스를 정의하고 그때그때 사용했는데
사용자가 늘어나 DB연결 횟수가 늘어나게 되면 DBConn
하나로는 부족할 것 이다.
혹은 간단한 DB커넥션 클래스를 정의해서 <load-on-startup>
태그와 같이 사용했었다.
이를 위해 수십개의 DBConn
객체가 모여있는 커넥션 풀 클래스를 사용해야 하는데
위에선 간단하게 사용가능한 커넥션 펙토리와 커넥션 풀 클래스를 정의했지만
사실 커넥션풀을 지원하는 여러 오픈소스 라이브러리가 있다…
Commons DBCP
, Tomcat-JDBC
, BoneCP
, HikariCP
등
이중 톰캣(WAS)서버가 제공하는 커넥션 풀 객체를 사용해보자.
\apache-tomcat-8.5.39\lib
폴더안의 tomcat-dbcp.jar
파일을 \WebContent\WEB-INF\lib
위치에 추가.
라이브러리를 추가했다면 META-INF
폴더안에 context.xml
파일을 추가해주자.
원래 context.xml
은 톰캣 설정 파일로 \apache-tomcat-8.5.39\conf\
에 위치하는 파일이다.
이클립스의 Web 프로젝트 안에서만 사용하기 위해 META-INF
폴더안에 context.xml
파일을 새로 추가하자.
그리고 안에 다음 xml코드를 작성.
<!-- /META-INF/context.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource
name="jdbc/myoracle"
auth="Container"
type="javax.sql.DataSource"
driverClassName="oracle.jdbc.OracleDriver"
url="jdbc:oracle:thin:@172.17.107.68:1521:xe"
username="scott"
password="tiger"
maxTotal="20"
maxIdle="10"
maxWaitMillis="-1" />
</Context>
maxTotal
: 풀이 관리하는 커넥션의 최대개수
maxIdle
: 풀이 보관할 수 있는 최대 커넥션의 최대 유휴개수
위의 두 속성의 기본값은 8
이며 음수로 지정시 제한을 없앤다.
maxWaitMillis
: 최대 대기시간을 설정한다. 음수일 경우 제한을 없앤다.
META-INF
의 <Resource>
태그를 사용해서 javax.sql.DataSource
객체의 각종 속성(접근 계정, url, 각종 설정)을 지정하고
web.xml
에 서버 시작과 동시에 javax.sql.DataSource
객체가 메모리에 올라가도록 설정
이제 web.xml
에서 <Resource>
태그내용을 가져오자, 가져올 땐 <resource-ref>
태그를 사용하면 된다.
<!-- web.xml -->
<resource-ref>
<description>Oracle Datasource example</description>
<res-ref-name>jdbc/myoracle</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
위의 설정관련 코드는 아래 링크에서 가져올 수 있다.
http://tomcat.apache.org/tomcat-8.5-doc/jndi-datasource-examples-howto.html#Oracle_8i,9i&_10g
이제 톰켓이 제공하는 DBCP
가 메모리상에 올라가 있기 때문에 필요할 때 아래 코딩을 사용해서 가져오기만 하면 된다.
Context initContext = new InitialContext();
Context envContext = (Context)initContext.lookup("java:/comp/env");
DataSource ds = (DataSource)envContext.lookup("jdbc/myoracle");
Connection conn = ds.getConnection();
System.out.println(conn);
출력값
1713917052, URL=jdbc:oracle:thin:@172.17.107.68:1521:xe, UserName=SCOTT, Oracle JDBC driver
java:comp/env
는JNDI
네임 스페이스에 적혀있는 웹어플의 구성된 엔트리와 리소스들이 배치되어있는 부분. 그래서JNDI
에 접근을 하여web.xml
의 리소스,<resource-env-ref>
에 설정한jdbc/mysql
과 매핑되는 리소스를 가져온다.
어쨋건 메모리에 올라간 커네션 풀에 연결된 DataSource
객체를 사용해 Connection
객체를 받았다.
DB연결작업은 앞으로 많이 일어날 예정이니
간결한 코드를 위해 Connection
객체를 반환하는 클래스를 정의하자.
public class ConnectionProvider {
public static Connection getConncection() throws NamingException, SQLException
{
Context initContext = new InitialContext();
Context envContext = (Context)initContext.lookup("java:/comp/env");
DataSource ds = (DataSource)envContext.lookup("jdbc/myoracle");
Connection conn = ds.getConnection();
return conn;
}
}
getConncection
메서드가 NamingException
, SQLException
예외를 throws
하기 때문에
jsp코드에서도 항상 예외처리를 위해 try, catch
로 감싸주자.
<%
Connection conn = null;
try {
conn = ConnectionProvider.getConncection();
}
catch(Exception e) {
}
finally {
if( conn != null ) {
try {
conn.close();
}
catch(SQLException e){}
}
}
%>
이제 ConnectionProvider.getConncection();
만 하면 DB와 연결된 Connection
객체를 얻을 수 있다.
기본객체 - session
웹브라우저에 쿠키정보를 저장한다면
웹서버에 정보를 저장하는 것이 세션이다.
쿠키와 같이 상태관리를 위한 웹 기술이다.
세션은 웹 컨테이너에 저장이 되는데 웹 컨테이너는 기본적으로 한 웹 브라우저마다 하나의 세션을 생성한다.
세션 가져오기
jsp에서 session
은 기본객체로 제공되기 때문에 바로 사용할 수 있다.
(자동으로 session
객체가 제공되는것이 싫다면 디렉티브에서 session
속성을 false
로 변경)
이 session
을 사용해
유지하고 싶은 정보를 session.setAttribute()
메서드로 저장하고,
유지하고 있는 정보를 session.getAttribute()
메서드로 가져올 수 있다.
setAttribute
, getAttribute
다른 기본객체에서도 존재하는 메서드인데 세션 역시 상태유지를 위한 웹 기술이기 때문에 속성을 저장, 읽을 수 있는 메서드가 있다.
참고: https://kouzie.github.io/jsp/JSP-JSTL/#jsp영역
<body>
<%
Date time = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
%>
세션ID: <%= session.getId() %><br>
<%
time.setTime(session.getCreationTime());
%>
세션생성시간: <%= sdf.format(time) %><br>
<%
time.setTime(session.getLastAccessedTime());
%>
최근접근시간: <%= sdf.format(time) %><br>
</body>
출력값
세션ID: 632A1D416CDACA3C72177EBC965C9444
세션생성시간: 2019-05-08 14:18:52
최근접근시간: 2019-05-08 14:18:52
새로고침을 할 떄 마다 최근접근시간이 증가한다.
jsp에선 session
객체가 기본 제공되지만 서블릿 클래스는 그렇지 않다.
따라서 서블릿에선 request.getSession()
메서드를 사용해 session
객체를 가져와야 한다.
getSession()
의 매개변수로 아무것도 들어가지 않거나 boolean
형 변수가 들어갈 수 있다.
request.getSession(true);
request.getSession(false);
request.getSession();
request.getSession(true)
과 request.getSession()
는 같은 기능을 한다.
아직 해당 웹 브라우저에 대한 session
이 웹 컨테이너에 생성되지 않았다면 세션을 생성해서 session객체를 반환한다.
request.getSession(false)
의 경우 해당 웹 브라우저에 대한 session
이 웹 컨테이너에 세션이 생성되지 않았다면 null
을 반환한다.
만약 아래와 같은 코딩을 하고싶다면 꼭 매개변수로 false
를 주도록 하자.
HttpSession session = request.getSession(false);
if(session != null)
{
...
...
}
JSESSIONID
톰캣환경에서 웹개발을 진행하고 있다면
크롬 디버깅 창에서 cookies를 확인하면
JSESSIONID
이름으로 다음과 같은 value
값을 가지고 있다.
632A1D416CDACA3C72177EBC965C9444
이 값은 session.getId()
메서드로 출력한 세션의 ID값과 일치한다.
즉 JSESSIONID
쿠키value
는 세션값을 가져오기 위한 일종의 열쇠같은 것 이다.
클라이언트가 서버에 요청하면 서버는 클라이언트가 보낸 쿠키값을 확인한다.
JSESSIONID
쿠키값이 없다면 톰켓 서버는 해당 클라이언트에 대한 세션을 생성하고 세션 ID를 반환한다.
클라이언트는 반환받은 세션ID로 쿠키를 생성하고 다음 요청부터는 이 쿠키를 보내기 때문에 서버 입장에서 해당 JSESSIONID
쿠키 값이 있다면 클라이언트의 세션이 이미 생성됬음을 알 수 있다.
쿠키는 도메인과 path가 일치하면 여러페이지 뿐 아니라 여러 탭에서 공유하기 때문에 받은 요청안의 JSESSIONID
쿠키값이 같다면 동일한 클라이언트(브라우저)임을 알 수 있다.
브라우저를 여러개 열어도 JSESSIONID
똑같기 때문에 하나의 연결(세션)으로 인식한다.
따라서 세션을 유지하기 위해선 항상 이 세션 ID를 쿠키가 유지하고 있어야 한다.
(서버에 저장된 세션을 유지하기 위한 쿠키….)
세션 삭제
더이상 세션을 유지할 필요가 없다면 세션을 삭제하는 session.invalidate()
메서드를 호출하면 된다.
보통 로그아웃 요청이 들어오면 invalidate()
메서드를 사용한다.
invalidate()
메서드는 사실 객체를 메모리에서 제거하는 것 이 아닌 무효화 시키는 것.
실제로 메모리에 세션은 남아 있으며
getId()
메서드를 제외한 나머지 메서드들은 호출 불가능해 진다.
세션 유효시간 설정
사용자가 일정시간동안 웹 서버에 접근을 하지 않는다면
웹서버에서도 세션을 유지시킬 필요 없다.
이 세션 유효시간을 설정하는 방법은 두가지다.
-
session.setMaxInactiveInterval()
메서드 사용 -
web.xml
에서<session-config>
태그 사용
setMaxInactiveInterval
의 매개변수로 들어가는 정수의 단위는 초
이다.
session.setMaxInactiveInterval(30*60);
<session-config>
<session-timeout>30</session-timeout>
</session-config>
web.xml
의 session-timeout
태그 시간단위는 분
이다.
만약 <session-timeout>
을 지정하지 않거나 값을 0이나 음수값으로 설정한다면 세션유휴시간을 갖지 않는다…
계속 서버에 세션이 존재한다면 메모리 부족을 야기하기 때문에 자동 제거를 하지 않는다면 session.invalidate()
메서드 호출로 정리해주어야 한다.
세션 메서드 요약
메소드 이름 | 리턴 타입 | 설명 |
---|---|---|
getAttribute(String name) |
java.lang.Object |
세션 속성명이 name인 속성의 값을 Object 타입으로 리턴한다. 해당 되는 속성명이 없을 경우에는 null 값을 리턴한다. |
getAttributeNames() |
java.util.Enumeration |
세션 속성의 이름들을 Enumeration 객체 타입으로 리턴한다. |
getCreationTime() |
long |
1970년 1월 1일 0시 0초를 기준으로 하여 현재 세션이 생성된 시간까지 경과한 시간을 계산하여 1/1000초 값으로 리턴한다. |
getId() |
java.lang.String |
세션에 할당된 고유 식별자를 String 타입으로 리턴한다. |
getMaxInactiveInterval() |
int |
현재 생성된 세션을 유지하기 위해 설정된 세션 유지시간을 int형으로 리턴한다. |
invalidate() |
void |
현재 생성된 세션을 무효화 시킨다. |
removeAttribute(String.name) |
void |
세션 속성명이 name인 속성을 제거한다. |
setAttribute(String name, Object value) |
void |
세션 속성명이 name인 속성에 속성값으로 value를 할당한다. |
setMaxInactiveInterval(int interval) |
void |
세션을 유지하기 위한 세션 유지시간을 초 단위로 설정한다 |
세션을 위한 객체
연관정보 여러개를 세션에 저장해야 한다면
데이터를 저장할 클래스를 만들어 세션에 저장하도록 하자.(DTO객체처럼!)
public class MemberInfo {
private String id;
private String name;
private String mail;
private boolean male;
private int age;
private String grade;
public MemberInfo(String id, String name, String mail, boolean male, int age, String grade) {
this.id = id;
this.name = name;
this.mail = mail;
this.male = male;
this.age = age;
this.grade = grade;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
...
...
그리고 setAttribute
메서드를 통해 만들어진 데이터 전송용 객체를 세션 스코프에 저장한다.
session.setAttribute("auth", new MemberInfo(...))
세션 로그인 처리
만약 로그인이 되었다면 서버에서 세션을 생성하고 세션 스코프에 auth
이름으로 ID
값을 저장하자.
세션에 auth
가 존재하면 로그인이 이미 된 클라이언트이고
세션에 auth
가 존재하지 않는다면 로그인 되지 않은 클라이언트이다.
모든 페이지에서 로그인 체크를 해야하기 때문에 auth
를 체크하는 모듈역할을 하는 sessionAuth.jsp
파일을 생성하자.
/* sessionAuth.jsp */
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
String auth = null;
auth = (String)session.getAttribute("auth");
%>
만약 로그인이 이미 되어있는 클라이언트라면 auth
에 ID값이 들어가 있을 것이고 아니라면 auth
는 여전히 null
일 것이다.
메인페이지인 default.jsp
를 먼저 생성하자
이미 로그인된 회원이라면 “환영합니다”를 출력
아니라면 로그인 form
을 출력한다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ include file="sessionAuth.jsp" %>
<%
String error = request.getParameter("error");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
div {
border: solid 1px gray;
width: 300px;
height: 100px;
padding: 20px;
}
</style>
</head>
<body>
<h3>default(main) page</h3>
<c:if test="${ auth eq null }">
<div id="login">
<form action="login.do">
아이디: <input type="text" name="id" value="admin" /><br>
비밀번호: <input type="password" name="pass" value="1234" /><br>
<input type="submit" value="로그인" />
<input type="button" value="회원가입" /><br>
<c:if test="${ param.error != null }">
<span style="color: red">로그인에 실패했습니다.</span>
</c:if>
</form>
</div>
</c:if>
<c:if test="${ auth ne null }">
<div id="logout">
[<%= auth %>]님 환영합니다.<br>
<a href="logout.do">로그아웃</a>
</div>
</c:if>
<a href="board.jsp">게시판</a><br>
<a href="notice.jsp">공지사항</a><br>
<c:if test="${ auth != null }">
<a href="#">일정관리</a><br>
<a href="#">자료실</a><br>
</c:if>
<a href="#">도움말</a><br>
<script>
$("#login span").fadeOut(5000);
</script>
</body>
</html>
<%@ include file="sessionAuth.jsp" %>
을 통해 모듈역할을 하는 sessionAuth.jsp
를 가져온다.
sessionAuth.jsp
안의 코드에 따라 아직 로그인하지 않았기 때문에 auth
에는 null
이 설정되어 있다.
null
이 아닐 때 출력되는 환영문구와 일정관리, 자료실 링크 또한 보이지 않는다.
ID와 PW를 입력하고 submit하면 <form action="login.jsp">
form
태그에 설정된 대로 login.do
로 get방식으로 요청한다.
일단 로그인/로그아웃 과정은 서블릿 객체로 처리하도록 하자!
먼저 web.xml
에 url-mapping
설정
<servlet>
<servlet-name>sessionLogin</servlet-name>
<servlet-class>days09.Login</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>sessionLogin</servlet-name>
<url-pattern>/login.do</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>sessionLogout</servlet-name>
<servlet-class>days09.Logout</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>sessionLogout</servlet-name>
<url-pattern>/logout.do</url-pattern>
</servlet-mapping>
public class Login extends HttpServlet{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String id = request.getParameter("id");
String pass = request.getParameter("pass");
HttpSession session = request.getSession();
if(id.equals("admin") && pass.equals("1234")) {
session.setAttribute("auth", id);
response.sendRedirect("default.jsp");
}
else {
response.sendRedirect("default.jsp?error");
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
서블릿에선 session
객체를 request.getSession()
을 통해 가져와 사용해야 한다.
DB연동을 제외하고 ID가 admin
, PW가 1234
들어오지 않으면 다 잘못된 로그인이라 보고 error
을 파라미터로 붙여 리다이렉트 시킨다.
error
가 반환되면 default.jsp
의 아래 문구가 실행된다.
<c:if test="${ param.error != null }">
<span style="color: red">로그인에 실패했습니다.</span>
</c:if>
만약 로그인이 성공한다면 session.setAttribute("auth", id)
을 통해 auth를 설정하고 리다이렉트 하기 때문에
다음 사진처럼 환영문구가 출력된다.
로그아웃 과정을 수행하는 Logout
서블릿 클래스 정의
/* Logout.java */
public class Logout extends HttpServlet{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//세션 삭제,
//경고창 출력후 default.jsp로 이동
response.setContentType("text/html; charset=UTF-8");
HttpSession session = request.getSession();
String auth = (String) session.getAttribute("auth");
session.invalidate();
PrintWriter out = response.getWriter();
out.print("<script>");
out.print("alert(\""+ auth +"님 로그아웃 되었습니다.\");");
out.print("location.href = \"default.jsp\";");
out.print("</script>");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
session.invalidate();
을 수행하여 세션을 무효화 하고 out
객체를 통해 alert
창 출력후 location.href
으로 default.jsp
로 이동시킨다.
간단한 로그인 처리는 로그인에 해당하는 auth
가 있는지 없는지 체크하는 형식으로 진행하면 된다.