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위치에 추가.

image19

라이브러리를 추가했다면 META-INF폴더안에 context.xml파일을 추가해주자.

image20

원래 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/envJNDI네임 스페이스에 적혀있는 웹어플의 구성된 엔트리와 리소스들이 배치되어있는 부분. 그래서 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

웹브라우저에 쿠키정보를 저장한다면
웹서버에 정보를 저장하는 것이 세션이다.

쿠키와 같이 상태관리를 위한 웹 기술이다.

세션은 웹 컨테이너에 저장이 되는데 웹 컨테이너는 기본적으로 한 웹 브라우저마다 하나의 세션을 생성한다.

image21

세션 가져오기

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.xmlsession-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>

image25

<%@ include file="sessionAuth.jsp" %>을 통해 모듈역할을 하는 sessionAuth.jsp를 가져온다.
sessionAuth.jsp안의 코드에 따라 아직 로그인하지 않았기 때문에 auth에는 null이 설정되어 있다.

null이 아닐 때 출력되는 환영문구와 일정관리, 자료실 링크 또한 보이지 않는다.

ID와 PW를 입력하고 submit하면 <form action="login.jsp"> form태그에 설정된 대로 login.do로 get방식으로 요청한다.

일단 로그인/로그아웃 과정은 서블릿 객체로 처리하도록 하자!
먼저 web.xmlurl-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>

image26

만약 로그인이 성공한다면 session.setAttribute("auth", id)을 통해 auth를 설정하고 리다이렉트 하기 때문에 다음 사진처럼 환영문구가 출력된다.

image26

로그아웃 과정을 수행하는 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가 있는지 없는지 체크하는 형식으로 진행하면 된다.

카테고리:

업데이트: