본문 바로가기
교재 실습/자바 웹 개발 워크북

73. DataSource와 JNDI (4)

by Jint 2022. 7. 19.

- ContextLoaderListener 클래스 변경

ContextLoaderListener 클래스를 수정한다.

package spms.listeners;

import java.sql.SQLException;

import javax.naming.InitialContext;
//import java.sql.Connection;
//import java.sql.DriverManager;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
//import javax.servlet.annotation.WebListener;

import javax.servlet.annotation.WebListener;
import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;

import spms.dao.MemberDao;
import spms.util.DBConnectionPool;

//리스너의 배치 : @WebListener 어노테이션 사용
@WebListener
public class ContextLoaderListener implements ServletContextListener {
	//Connection conn; //인스턴스 변수
	//DBConnectionPool connPool;
	//BasicDataSource ds;
  
	@Override
	public void contextInitialized(ServletContextEvent event) {
		try {
			//DB 커넥션 객체 준비
			ServletContext sc = event.getServletContext();
			/*Class.forName(sc.getInitParameter("driver"));
			conn = DriverManager.getConnection(
					sc.getInitParameter("url"),
					sc.getInitParameter("username"),
					sc.getInitParameter("password"));*/
			//DBConnectionPool 객체 생성
			/*connPool = new DBConnectionPool(sc.getInitParameter("driver")
                                             ,sc.getInitParameter("url")
                                             ,sc.getInitParameter("username")
                                             ,sc.getInitParameter("password"));*/ //DB커넥션 생성에 필요한 값 준비
			//BasicDataSource 객체 생성
			/*ds = new BasicDataSource();
			ds.setDriverClassName(sc.getInitParameter("driver"));
			ds.setUrl(sc.getInitParameter("url"));
			ds.setUsername(sc.getInitParameter("username"));
			ds.setPassword(sc.getInitParameter("password"));*/
			//InitialContext 객체 생성 - 톰캣 서버에서 자원 찾기 위함
			InitialContext initialContext = new InitialContext();
			//lookup() 메서드로 JNDI 이름으로 등록된 서버 자원 찾을 수 있다.
			//lookup() 메서드 매개변수는 서버 자원의 JNDI 이름
			DataSource ds = (DataSource)initialContext.lookup("java:comp/env/jdbc/studydb");
			
			//MemberDao 객체 준비하여 ServletContext에 보관
			MemberDao memberDao = new MemberDao();
			//memberDao.setConnection(conn);
			//memberDao.setDbConnectionPool(connPool); //DAO에 DBConnectionPool 객체 주입
			memberDao.setDataSource(ds); //DAO에 DataSource 객체 주입
			sc.setAttribute("memberDao", memberDao);
		} catch(Throwable e) {
			e.printStackTrace();
		}
	}

	@Override
	public void contextDestroyed(ServletContextEvent event) {
		/*try {
			conn.close(); //데이터베이스와 연결 끊기
		} catch (Exception e) {}*/
		//connPool.closeAll(); //웹 애플리케이션 종료 전 데이터베이스와 연결된 것 끊기
		//try {if(ds != null) ds.close();} catch (SQLException e) {}
		//톰캣 서버에 자동으로 해제하라고 설정되어 있기 때문에 메서드가 비어있다.
		//context.xml 파일의 <Resource> 태그의 closeMethod 속성값이 close라고 되어있기 때문에 contextDestroyed() 메서드는 비어 있게 된다.
	}
}

BasicDataSource 클래스를 직접 사용하지 않기 때문에 참조 변수와 객체를 생성하는 코드를 제거하였다. 대신 톰캣 서버에서 자원을 찾기 위해 InitialContext 객체를 생성하였다. InitialContext의 lookup() 메서드를 이용하면, JNDI 이름으로 등록되어 있는 서버 자원을 찾을 수 있다.

//InitialContext 객체 생성 - 톰캣 서버에서 자원 찾기 위함
InitialContext initialContext = new InitialContext();
//lookup() 메서드로 JNDI 이름으로 등록된 서버 자원 찾을 수 있다.
//lookup() 메서드 매개변수는 서버 자원의 JNDI 이름
DataSource ds = (DataSource)initialContext.lookup("java:comp/env/jdbc/studydb");

lookup() 메서드의 매개변수 값은 서버 자원의 JNDI 이름이다. 찾으려는 자원이 JDBC 데이터 소스이기 때문에 JNDI 이름 앞에 "java:comp/env"가 붙는다. lookup() 메서드가 리턴하는 자원이 DataSource 이기 때문에 형변환을 하였다.

웹 애플리케이션이 종료될 때 DataSource가 만든 커넥션 객체들도 모두 닫아야 하는데, contextDestroyed() 메서드를 보면 자원을 해제하는 어떤 코드도 발견할 수 없다. 기존에 있던 코드마저 없어졌다.

@Override
public void contextDestroyed(ServletContextEvent event) {
	/*try {
		conn.close(); //데이터베이스와 연결 끊기
	} catch (Exception e) {}*/
	//connPool.closeAll(); //웹 애플리케이션 종료 전 데이터베이스와 연결된 것 끊기
	//try {if(ds != null) ds.close();} catch (SQLException e) {}
	//톰캣 서버에 자동으로 해제하라고 설정되어 있기 때문에 메서드가 비어있다.
	//context.xml 파일의 <Resource> 태그의 closeMethod 속성값이 close라고 되어있기 때문에 contextDestroyed() 메서드는 비어 있게 된다.
}

톰캣 서버에 자동으로 해제하라고 설정되어 있기 때문에 contextDestroyed() 메서드 안에 아무것도 작성하지 않은 것이다.

톰캣 서버의 설정 내용을 다시 한 번 살펴본다. context.xml 파일의 내용을 보면 <Resource> 태그의 closeMethod 속성값이 close라고 되어 있다.

<Resource name="jdbc/studydb" auth="Container" type="javax.sql.DataSource"
maxActive="10" maxWait="10000" username="study" password="study" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost/studydb" classMethod="close"/>

즉 톰캣 서버가 종료되면 이 자원에 대해 close() 메서드를 호출하라는 의미이다.

 

기술은 항상 이전의 기술을 참고하여 발전하기 때문에, 현재의 기술을 이해하려면 과거의 기술을 알아야 하고, 미래의 기술을 알고 싶다면 현재의 기술을 명확히 이해해야 한다.

 

★ MVC 아키텍처 총정리

웹 애플리케이션을 개발할 때 실무에서 많이 사용하는 MVC 구조에 대해 알아보았다. MVC의 핵심은 클라이언트의 요청을 3가지 역할자가 나누어 처리하는 것이다. 사용자와의 상호작용은 뷰(View) 컴포넌트가 담당하고, 데이터의 처리는 모델(Model) 컴포넌트, 컨트롤러(Controller)는 뷰와 모델을 제어하여 업무 처리를 수행한다. 이렇게 역할을 나누어 작업을 처리하는 방식은 객체지향 프로그래밍의 특징이다. 가능한 역할을 작은 단위로 나누면 다른 프로젝트에서 재사용할 가능성이 높아진다.

 

특히 이번 장에서는 MVC 구조에서 뷰 컴포넌트를 정의하는 방법을 배웠다. 기존에 서블릿이 하던 작업 중에서 화면 출력과 관련된 기능을 분리하여 JSP 페이지로 정의하였다. 보다 쉽게 HTML 화면을 만들고 클라이언트로 출력하기 위해 등장한 기술이 JSP 이다. 이전에 만들었던 예제를 가지고 JSP 기술을 적용하고, 이를 통해 JSP 주요 특징을 살펴보았다.

 

JSP 페이지는 출력문을 표현하는 템플릿 데이터와 지시자 <%@ ... %>, 선언문 <%! ... %>, 스크립트릿 <% ... %>, 표현식 <%= ... %> 등으로 구성된다. JSP 페이지는 그 자체로 실행되지 않고 서블릿을 생성하기 위한 소스로 사용된다. JSP 엔진은 JSP 파일을 참고하여 서블릿을 생성한다. 서블릿 파일을 만들 때 템플릿 데이터는 자바 출력문으로 바뀌고, 기타 JSP 전용 태그는 특정 자바 코드로 바뀐다.

 

JSP 전용 태그 중에서 액션(Action)은 자바 객체를 생성하거나 보관소에서 값을 꺼낼 때 유용하다. 서블릿 및 JSP에서 제공하는 보관소는 4가지가 있다. 보관소의 생명 주기에 따라 page, request, session, application 으로 표현한다. page는 JSP 페이지에서만 유효하고, request는 요청에서 응답까지 유효하다. session은 최초 요청에서 브라우저를 닫을 때까지 유효하다. 보통 로그인해서 로그아웃할 때까지를 session의 유효범위로 간주한다. application은 웹 애플리케이션의 시작에서 종료할 때까지 유효하다. 객체를 공유할 경우 그 유효 범위를 따져서 적절한 보관소를 선택해야 한다.

 

EL 표기를 사용하면 액션보다 더 쉬운 방법으로 보관소에 저장된 객체에 접근하여 값을 꺼낼 수 있다. 여기에 JSTL 확장 태그를 사용하면 JSP 페이지에서 자바 코드를 작성하지 않고 대부분의 출력 작업을 처리할 수 있다. 이렇게 JSTL과 EL 표기를 사용하여 자바 코드의 작성을 최소화 한다면, 웹 디자이너나 웹 퍼블리셔와 협업하기가 훨씬 쉬워질 것이다.

 

이번 장에서는 MVC의 마지막 조각인 모델 컴포넌트에 대해서도 알아보았다. 모델 컴포넌트를 만들기 위해 서블릿으로부터 데이터 처리 로직을 분리하여 DAO를 정의하였다. DAO를 도입하여 얻는 이점은 데이터 처리 부분을 공유함으로써 중복 코드를 줄일 수 있고, 데이터 구조에 변경이 발생하면 그 변경 사항을 적용하기 쉽다는 것이다. 또한, 다른 프로젝트에서 재사용하기가 쉽다.

 

웹 애플리케이션이 시작될 때 서블릿이 사용할 객체를 미리 준비하기 위하여 ServletContextListener를 활용하는 방법을 배웠다. 서블릿 컨테이너는 웹 애플리케이션의 시작과 종료를 알리기 위해 이벤트를 발생시킨다. 만약 이런 이벤트에 반응하여 뭔가 작업을 수행하고 싶다면, ServletContextListener 인터페이스의 구현체를 만들고 web.xml 파일에 등록하면 된다. (구현체 클래스에 어노테이션 @WebListener으로도 등록 가능)

 

서블릿들 사이에서 값을 공유하기 위해 ServletContext(application) 객체를 활용하는 방법을 배웠다. 이 객체는 웹 애플리케이션이 시작할 때 생성되고 종료할 때까지 유지된다. 따라서 웹 애플리케이션이 실행되는 동안 공유할 값이 있다면 이 객체에 보관하면 된다. 이 장에서는 DAO 객체를 공유하기 위해 ServletContext에 보관하였다.

 

데이터베이스에 질의하기 위해 매번 연결을 한다면 사용자 인증과 연결을 준비하는데 많은 시간을 낭비하게 된다. 이를 개선하기 위해 연결 객체를 생성하고 사용한 후, 버리지 않고 보관해 두었다가 다시 사용하는 방식을 적용하였다. 자주 사용하는 객체들을 보관해 두고 필요할 때마다 재사용하는 방식을 '풀링(pooling)'이라고 한다. 이 장에서도 DB 커넥션풀(ConnectionPool)을 정의하여 DB 커넥션을 재활용하도록 하였다.

 

DAO가 데이터를 처리하기 위해서는 DB 커넥션을 만들어주는 DB 커넥션풀이 필요하다. 이렇게 어떤 작업을 처리하기 위해 사용하는 객체를 '의존 객체'라 한다. 의존 객체는 필요할 때마다 직접 생성할 수 있지만, 실무에서는 주로 단위 테스트나 유지보수를 쉽게 하기 위해 외부에서 주입하는 방식을 사용하는데, 이것을 '의존성 주입(dependency injection)'이라 부른다. 이 장에서는 DAO가 사용하는 DB 커넥션풀에 대해 의존성 주입을 적용하였다.

 

마지막으로 서버측 애플리케이션을 개발할 때 데이터베이스 접속을 원활히 하기 위해 만든 javax.sql 확장 패키지에 대해 알아보았다. 특히 이 패키지에서 제공하는 DataSource는 DB 커넥션풀 기능을 제공하기 때문에 매우 편리하다. DataSource는 서버에서 관리하기 때문에 데이터베이스가 바뀌거나 JDBC 드라이버가 변경되더라도 애플리케이션 쪽에는 영향을 미치지 않는다. 톰캣 서버에 DataSource를 등록해 놓으면, JNDI 이름으로 이 서버 자원을 찾을 수 있어 개발 편이성이 향상된다.

 

이번 장이 MVC 구조를 이해하는 시간이었다면, 다음 장은 미니 MVC 프레임워크를 만들어 봄으로써 프레임워크의 개념을 이해하는 시간을 갖는다.

 

참고도서 : https://freelec.co.kr/book/1674/

 

[열혈강의] 자바 웹 개발 워크북

[열혈강의] 자바 웹 개발 워크북

freelec.co.kr

댓글