3. DataSource의 적용
MemberDao 클래스에 DBConnectionPool 대신 DataSource를 적용해본다.
- DataSource 구현체 준비
JDBC를 사용하려면 JDBC API에 따라 작성한 구현체, 즉 JDBC 드라이버가 필요하듯이, DataSource를 사용하려면 javax.sql 패키지의 구현체가 필요하다.
웹 브라우저로 https://commons.apache.org/ 사이트에 접속한다. 컴포넌트 목록에서 'DBCP' 링크를 클릭한다.
DBPC 컴포넌트 페이지에서 왼쪽 카테고리의 'Downloads' 링크를 클릭한다(그림 2).
다운로드 페이지에서 다음 그림과 같이 파일 목록에서 'commons-dbcp2-2.9.0-bin.zip' 링크를 클릭하여 파일을 내려받는다. 다만, JDK 1.5 이하 버전을 사용하고 있다면 DBCP 1.3 버전을 사용한다(그림 3).
'commons-dbcp2-2.9.0-bin.zip' 파일의 압축을 푼 다음에 'commons-dbcp2-2.9.0.jar' 파일을 web05/src/main/webapp/WEB-INF/lib 폴더에 복사한다(그림 4).
DBCP 컴포넌트는 내부적으로 Pool 컴포넌트를 사용하기 때문에 이 컴포넌트도 필요하다. 다시 https://commons.apache.org/ 사이트의 메인 페이지로 이동하여, 컴포넌트 목록에서 'Pool' 링크를 클릭한다(그림 5).
다음 그림에서 Releases의 'downloads' 링크를 클릭한다(그림 6).
Releases의 'Apache Commons Pool Downloads page' 링크를 클릭한다(그림 7).
파일 목록에서 'commons-pool2-2.11.1-bin.zip' 링크를 클릭하여 파일을 내려받는다(그림 8).
DBCP 1.4 버전에서는 Pool 2.0 버전을 사용할 수 없기 때문에 Pool 1.6 버전을 내려받으면 된다. 현재 DBCP 2.9.0 버전이므로 Pool 2.11.1 버전을 내려받는다. 'commons-pool2-2.11.1-bin.zip' 파일의 압축을 푼 다음에 'commons-pool2-2.11.1.jar' 파일을 web05/src/main/webapp/WEB-INF/lib 폴더에 복사한다(그림 9).
- ContextLoaderListener 클래스 변경
ContextLoaderListener 클래스에서 DBConnectionPool 객체를 준비하는 대신 DataSource 객체를 준비한다.
package spms.listeners;
import java.sql.SQLException;
//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 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"));*/
/*connPool = new DBConnectionPool(sc.getInitParameter("driver")
,sc.getInitParameter("url")
,sc.getInitParameter("username")
,sc.getInitParameter("password"));*/ //DB커넥션 생성에 필요한 값 준비
ds = new BasicDataSource();
ds.setDriverClassName(sc.getInitParameter("driver"));
ds.setUrl(sc.getInitParameter("url"));
ds.setUsername(sc.getInitParameter("username"));
ds.setPassword(sc.getInitParameter("password"));
//MemberDao 객체 준비하여 ServletContext에 보관
MemberDao memberDao = new MemberDao();
//memberDao.setConnection(conn);
//memberDao.setDbConnectionPool(connPool); //DAO에 DBConnectionPool 객체 주입
memberDao.setDataSource(ds);
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) {}
}
}
아파치 DBCP 라이브러리에서 DataSource 인터페이스를 구현한 클래스는 BasicDataSource 클래스이다. 따라서 기존의 DBConnecctionPool 인스턴스 변수를 제거하고, BasicDataSource 인스턴스 변수를 선언한다.
BasicDataSource ds;
contextInitialized() 메서드에서도 DBConnecctionPool 객체를 생성하는 코드를 제거하고 BasicDataSource 객체를 생성하는 코드를 넣는다.
ds = new BasicDataSource();
ds.setDriverClassName(sc.getInitParameter("driver"));
ds.setUrl(sc.getInitParameter("url"));
ds.setUsername(sc.getInitParameter("username"));
ds.setPassword(sc.getInitParameter("password"));
DataSource 객체를 준비했으면 MemberDao 클래스에 주입해야 한다. 기존의 DBConnecctionPool 객체를 주입하는 셋터 메서드를 제거하고, DataSource 객체를 주입하는 셋터를 호출한다.
memberDao.setDataSource(ds); //DAO에 DataSource 객체 주입
아직 MemberDao 클래스에 setDataSource() 메서드를 정의하지 않았으므로 편집기에 오류 아이콘이 보여도 나중에 추가할테니 그냥 둔다.
웹 애플리케이션이 종료될 때 서블릿들이 사용한 공용 자원들을 바로 해재해 주어야 다른 애플리케이션이 사용할 수 있으므로, 웹 애플리케이션을 종료할 때 호출되는 contextDestroyed() 메서드는 공용 자원을 해제하는 코드가 놓이는 최적의 장소라 할 수 있다. contextDestroyed() 메서드에서 기존의 자원 해제를 수행하는 DBConnectionPool 클래스의 closeAll() 메서드를 호출하는 코드를 제거하고 DataSource에 들어 있는 모든 커넥션 객체를 닫는 close() 메서드를 호출한다.
try {if(ds != null) ds.close();} catch (SQLException e) {}
- MemberDao 클래스에 DataSource 적용
MemberDao 클래스에 DataSource를 적용한다. DataSource를 위한 인스턴스 변수와 셋터 메서드를 추가한다.
public class MemberDao {
//DBConnectionPool connPool;
//DBConnectionPool 객체 주입
/*public void setDbConnectionPool(DBConnectionPool connPool) {
this.connPool = connPool;
}*/
DataSource ds; //DataSource 인터페이스
//DataSource 인터페이스 객체 주입 (다른 구현체로 손쉽게 대체하기 위함)
public void setDataSource(DataSource ds) {
this.ds = ds;
}
...
}
인스턴스 변수의 타입이 BasicDataSource가 아닌 DataSource이다. BasicDataSource 클래스는 DataSource 인터페이스의 구현체다. 구현체의 이름을 직접 사용하면, 나중에 다른 구현체로 교체할 수 없다. 대신 인터페이스 이름을 사용하면 나중에 다른 구현체로 손쉽게 대체할 수 있다. 이런 이유로 참조 변수의 타입은 가능한 구현체의 이름보다 인터페이스의 이름을 사용한다.
- 메서드에서 DataSource 사용
MemberDao 클래스의 selectList() 메서드, selectOne() 메서드, insert() 메서드, update() 메서드, delete() 메서드, exist() 메서드를 DataSource를 사용하는 것으로 변경한다.
public class MemberDao {
DataSource ds; //DataSource 인터페이스
//DataSource 인터페이스 객체 주입 (다른 구현체로 손쉽게 대체하기 위함)
public void setDataSource(DataSource ds) {
this.ds = ds;
}
//회원 목록 조회
public List<Member> selectList() throws Exception {
Connection connection = null;
Statement stmt = null;
ResultSet rs = null;
try {
//connection = connPool.getConnection(); //DB 커넥션 객체 가져오기
connection = ds.getConnection(); //커넥션 객체 가져오기
stmt = connection.createStatement();
rs = stmt.executeQuery(
"SELECT MNO,MNAME,EMAIL,CRE_DATE" +
" FROM MEMBERS" +
" ORDER BY MNO ASC");
ArrayList<Member> members = new ArrayList<Member>();
while(rs.next()) {
members.add(new Member()
.setNo(rs.getInt("MNO"))
.setName(rs.getString("MNAME"))
.setEmail(rs.getString("EMAIL"))
.setCreatedDate(rs.getDate("CRE_DATE")) );
}
return members;
} catch (Exception e) {
throw e;
} finally {
try {if(rs != null) rs.close();} catch(Exception e) {}
try {if(stmt != null) stmt.close();} catch(Exception e) {}
//if(connection != null) connPool.returnConnection(connection); //DB 커넥션 객체 반환(반납)
try {if(connection != null) connection.close();} catch(Exception e) {} //커넥션 대행 객체(PoolableConnection)의 close() 호출
}
}
...
}
DBConnectionPool 클래스에서 커넥션 객체를 꺼내는 대신, DataSource로부터 커넥션 객체를 꺼낸다. DBConnectionPool 클래스를 사용하는 코드는 제거한다.
//connection = connPool.getConnection(); //DB 커넥션 객체 가져오기
connection = ds.getConnection(); //커넥션 객체 가져오기
finally 블록에서 connPool에게 커넥션 객체를 반납하는 코드 부분을 제거한다. 대신 커넥션 객체를 닫는 close() 메서드를 호출한다.
//if(connection != null) connPool.returnConnection(connection); //DB 커넥션 객체 반환(반납)
try {if(connection != null) connection.close();} catch(Exception e) {} //커넥션 대행 객체(PoolableConnection)의 close() 호출
여기서 의아스러운 부분이 "커넥션 객체를 닫으면 다시 사용할 수 없지 않나?, 닫지 말고 이전처럼 반납해야 하는 것이 아닌가?"라고 생각할 수 있다.
- DataSource의 Connection
DataSource가 만들어 주는 Connection 객체는 DriverManager가 만들어주는 커넥션 객체를 한 번 더 포장한 것이다.
① MemberDao 클래스가 DataSource에게 커넥션을 달라고 요청한다.
② DataSource는 DriverManager가 생성한 커넥션을 리턴하는 것이 아니라, 커넥션 대행 객체(Proxy Object)를 리턴한다. 아파치 DBCP 컴포넌트의 BasicDataSource를 사용할 경우, PoolableConnection 객체를 반환한다. 바로 이 객체가 커넥션의 대행 객체이다. 이 대행 객체에는 진짜 커넥션을 가리키는 참조 변수(_conn)와 커넥션풀을 가리키는 참조 변수(_pool)가 들어 있다. 물론, 대행 객체도 Connection처럼 보이기 위해, javax.sql.Connection 인터페이스를 구현하였다. 구현하였다는 의미는 외부에서 요청이 들어왔을 때, 자신이 직접 실행한다는 것이 아니라 진짜 커넥션에게 위임한다는 것이다.
따라서 DataCource가 만들어준 커넥션 대행 객체에 대해 close() 메서드를 호출하면, 커넥션 대행 객체는 진짜 커넥션 객체를 커넥션 풀에 반납한다. 그러니 커넥션이 닫힐까 걱정할 필요가 없다.
MemberDao 클래스에 있는 나머지 메서드들도 selectList() 메서드와 같이 변경한다. 톰캣 서버를 다시 실행할 때 오류가 발생한다.
오류 : Caused by: java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory
[java] java.lang.ClassNotFoundException 가져 오기 : org.apache.commons.logging.LogFactory 예외 - 리뷰나라
스프링의 간단한 의존성 주입 프로그램을 실행 중이며이 예외가 발생합니다. 이미 common-logging1.1.1.jar 및 spring.jar 파일을 포함 시켰습니다. 도와 주실 래요? Exception in thread "main" java.lang.NoClassDefFoun
daplus.net
'commons-logging-1.2-bin.zip'(Apache Commons Logging 1.2) 파일을 다운로드 받고 압축을 해제한 뒤 'commons-logging-1.2.jar' 파일을 web05/src/main/webapp/WEB-INF/lib 폴더에 추가하여 오류를 해결하였다.
다시 톰캣 서버를 다시 시작하고 기능을 테스트해보면 이전과 같이 잘 실행된다.
지금은 새로운 기능을 추가하는 것이 아닌 유지 보수를 좋게하려고 애플리케이션의 구조를 변경하고 있다.
참고도서 : https://freelec.co.kr/book/1674/
[열혈강의] 자바 웹 개발 워크북
[열혈강의] 자바 웹 개발 워크북
freelec.co.kr
'교재 실습 > 자바 웹 개발 워크북' 카테고리의 다른 글
73. DataSource와 JNDI (4) (0) | 2022.07.19 |
---|---|
72. DataSource와 JNDI (3) (0) | 2022.07.18 |
70. DataSource와 JNDI (1) (0) | 2022.07.16 |
69. DB 커넥션풀 (3) (0) | 2022.07.14 |
68. DB 커넥션풀 (2) (1) | 2022.07.13 |
댓글