JNDI & JDBC & DBCP

Yoon·2023년 1월 25일
1

JNDI, JDBC, DBCP

JDBC, JNDI, DBCP 는 모두 JAVA에서 DB 커넥션을 할 때 사용하는 방법이다.


JDBC (Java DataBase Connectivity)

  • JDBC는 데이터베이스를 조작하는 표준 SQL 인터페이스 API이다. 즉, JDBC는 데이터 베이스 커넥션 인터페이스 이다.

  • 일반적인 JDBC는 데이터 베이스 풀 방식을 사용하지 않고 DB에서 정보를 가져올 때 마다 매번 디비연결을 열고 닫는다.

  • 따라서 상용 어플에는 JDBC 방식을 사용하는 경우가 거의 없다. (매우 비효율적이기 때문)

사용방법 (Oracle 기준)

  • 데이터베이스 연결 설정에서 JDBC 파일을 선택한다.(ojdbc6.jar, ojdbc7.jar, ojdbc8.jar...)

  • 드라이버 클래스 선택 후 아래의 접속 정보를 입력

  • 항목설명
    URL연결할 URL
    호스트서버 포트 넘버
    SIDOracle SID(System Identifier)
    사용자DB 접속 아이디
    비밀번호DB 접속 비밀번호
    Connect ASDB 연결 권한

DBCP (Database Connection Pool)

  • 기본적인 원리는 어플리케이션을 시작할 때 원하는 만큼 커넥션 객체를 만들어 놓고 pool에 넣어놓았다가 필요할 때 마다 갖다 쓰고 pool에 반납하는 방식으로 운영한다.

  • 데이터 베이스 커넥션 풀을 어플리케이션 소스단에 설정한다.

  • 다중 스레드를 스레드풀로 관리하는 것과 비슷한 방식이라고 한다.

  • JDBC 방식 보다는 일반적인 경우 이러한 Pool 방식을 사용한다.

사용예)

  1. DBCPInit.java
package jdbc;

import java.sql.DriverManager;
import java.util.Properties;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

import org.apache.commons.dbcp2.ConnectionFactory;
import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
import org.apache.commons.dbcp2.PoolableConnection;
import org.apache.commons.dbcp2.PoolableConnectionFactory;
import org.apache.commons.dbcp2.PoolingDriver;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

public class DBCPInit extends HttpServlet {

    @Override
    public void init() throws ServletException {
            loadJDBCDriver();
            initConnectionPool();
    }
    private void loadJDBCDriver() {
        try {
            //커넥션 풀에서 사용할 jdbc 드라이버를 로딩
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException("fail to load JDBC Driver", ex);
        }
    }
    private void initConnectionPool() {
        try {
            String jdbcUrl = "jdbc:mysql://localhost:3306/chap14?" + "useUnicode=true&characterEncoding=utf8";
            String username = "phs1116";
            String pw = "1234";
            
            //커넥션팩토리 생성. 커넥션 팩토리는 새로운 커넥션을 생성할때 사용한다.
            ConnectionFactory connFactory = new DriverManagerConnectionFactory(jdbcUrl, username, pw);
            
            //DBCP가 커넥션 풀에 커넥션을 보관할때 사용하는 PoolableConnectionFactory 생성
            //실제로 내부적으로 커넥션을 담고있고 커넥션을 관리하는데 기능을 제공한다. ex)커넥션을 close하면 종료하지 않고 커넥션 풀에 반환
            PoolableConnectionFactory poolableConnFactory = new PoolableConnectionFactory(connFactory, null);
            //커넥션이 유효한지 확인할때 사용하는 쿼리를 설정한다.
            poolableConnFactory.setValidationQuery("select 1");
            
            
            //커넥션 풀의 설정 정보를 생성한다.
            GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
            //유휴 커넥션 검사 주기
            poolConfig.setTimeBetweenEvictionRunsMillis(1000L * 60L * 1L);
            //풀에 있는 커넥션이 유효한지 검사 유무 설정
            poolConfig.setTestWhileIdle(true);
            //커넥션 최소갯수 설정
            poolConfig.setMinIdle(4);
            //커넥션 최대 갯수 설정
            poolConfig.setMaxTotal(50);

            //커넥션 풀 생성. 인자로는 위에서 생성한  PoolabeConnectionFactory와 GenericObjectPoolConfig를 사용한다.
            GenericObjectPool<PoolableConnection> connectionPool = new GenericObjectPool<>(poolableConnFactory,
                    poolConfig);
            
            //PoolabeConnectionFactory에도 커넥션 풀을 연결
            poolableConnFactory.setPool(connectionPool);
            
            //커넥션 풀을 제공하는 jdbc 드라이버를 등록.
            Class.forName("org.apache.commons.dbcp2.PoolingDriver");
            
 
            PoolingDriver driver = (PoolingDriver) DriverManager.getDriver("jdbc:apache:commons:dbcp:");
            //위에서 커넥션 풀 드라이버에 생성한 커넥션 풀을 등룍한다. 이름은 cp이다.
            driver.registerPool("cp", connectionPool);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
  1. web.xml에 서블릿 실행 설정
  <servlet>
        <servlet-name>DBCPInit</servlet-name>
        <servlet-class>jdbc.DBCPInit</servlet-class>
        <load-on-startup>1</load-on-startup>
  </servlet>
  1. 커넥션 사용
String jdbcDriver = "jdbc:apache:commons:dbcp:cp";
...
conn = DriverManager.getConnection(jdbcDriver);

커넥션을 사용할 댄 기존 DB주소가 아닌 커넥션 풀의 주소를 사용한다.


JNDI (Java Naming and Directory Interface)

  1. Java Naming and Directory Interface의 약자
  2. 명명 및 디렉토리 서비스에 접근하기 위한 API
  3. 많은 기능이 있지만 우리는 등록(bind, rebind)하고, 찾아쓰는(lookup) 방법만 알면 된다.
  4. 서버에 JNDI Driver(service provider라고 부름)만 있으면 명명/디렉토리 서비스를 이용할 수 있다.
  5. jndi 드라이버는 우리가 호출한 이름을 Naming 서비스에서 이해할 수 있는 것으로 바꿔주는 역할을 한다.

디렉터리 서비스에서 제공하는 데이터 및 객체를 발견(discover)하고 참고(lookup)하기 위한 자바 API이다.


JNDI 사용 이유

  1. 여러 프로젝트에서 하나의 datasource를 공유해서 사용할 때
  2. 실제 프로젝트는 WAS에 올라가 운영되는데 개발/소스를 잘 모르는 운영자가 이해하기 쉽게 하기 위함
  3. WAS의 교체에도 신속한 대응이 가능

사용방법 (설정 및 사용법, Tomcat8.5 기준)

  • 1) Tomcat8.5/conf/server.xml의 <GlobalNamingResources> 태그 사이에 셋팅 해준다.
<GlobalNamingResources>
	<!-- Editable user database that can also be used by UserDatabaseRealm 
			to authenticate users -->
           
    <!-- My DBCP Setting ========================= -->
	<Resource auth="Container"
		driverClassName="oracle.jdbc.driver.OracleDriver"
        maxIdle="10"
		maxTotal="20" 
        maxWaitMillis="-1" 
        name="jdbc/myshop" 
        password="tiger"
		type="javax.sql.DataSource"
        url="jdbc:oracle:thin:@127.0.0.1:1521:XE"
		username="myshop" />
	<!-- ========================================= -->
</GlobalNamingResources>
  • 오라클을 사용하기 때문에
    driverClassName="oracle.jdbc.driver.OracleDriver" 사용
    maxldle="10"은 평상시에 커넥션 풀 객체를 10개 사용한다는 뜻이고
    maxTotal="20"은 최대 20개 까지 객체를 만들겠다는 소리다.
    오라클에서 사용하는 아이디와 비밀번호는
    username과 password에 작성하면 된다.

  • type이 가장 중요하다. DataSource를 통해 객체에 이름을 등록해서 사용할 수 있다.
    name="jdbc/myshop"은 커넥션풀의 이름을 정해주는 것으로 유일한 이름이어야 한다.

  • 2) Tomcat8.5/conf/context.xml의 <Context> 태그 안에
<!-- MyDBCP Setting =================================== -->
	<ResourceLink 
        global="jdbc/myshop"
        name="jdbc/myshop"
        type="org.apache.tomcat.dbcp.dbcp2.BasicDataSource"/>
<!-- ================================================== -->
  • global="jdbc/myshop", name은 server.xml에서 설정했던 name과 동일한 이름으로 지정한다.

  • 3) XXproject/WEB-INF/web.xml의 <web-app> 태그사이에 아래 부분 세팅

<web-app>
	<!-- My DBCP Setting ================================== -->
  	<resource-ref>
 		<description>Oracle Datasource example</description>
 		<res-ref-name>jdbc/myshop</res-ref-name>
 		<res-type>javax.sql.DataSource</res-type>
 		<res-auth>Container</res-auth>
  	</resource-ref>
 	<!-- ================================================== -->
</web-app>

  • 셋팅한 커넥션풀을 자바소스로 불러오는 방법
// Initialcontext 생성 -> 이름으로 객체 찾을 수 있도록 도와준다.
Context initContext = new InitialContext(); 

// 톰캣(서버)을 먼저 찾는다.
// 톰캣을 찾을 때는 java:comp/env 로 찾는다.
Context envContext  = (Context)initContext.lookup("java:/comp/env"); 

// server.xml에 등록했던 이름 myshop을 찾는다.(타입은 DataSource 타입)
DataSource ds = (DataSource)envContext.lookup("jdbc/myshop"); 

Connection conn = ds.getConnection();

System.out.println("<h1>데이터 소스 룩업 성공: "+ds +"</h1>");
System.out.println("<h2> con="+ conn + "</h2>");

// 커넥션 연결을 끊는 것이 아니라 커넥션풀에 자동으로 반납을 한다.
if(conn!=null) conn.close();
  1. InitalContext 객체를 생성해서 이름으로 찾을 수 있도록 도와준다.
  2. lookUp 메소드를 통해 WAS서버를 찾자. (우리는 톰캣) --> java:comp/env 톰캣 찾을 때 사용하는 프로토콜
  3. 우리가 등록한 DataSource를 찾자. - resource name으로 룩업한다.
    ==> JNDI(Java Naming Directory Interface)
    객체들을 이름으로 등록해서 이름으로 찾는 방식
  4. ds.getConnection() 메소드를 사용하여 커넥션풀에 연결한다.
  • dbcpTest.jsp 파일을 만들어서 연결이 잘 됐는지 확인
    javax.naming., java.sql., javax.sql.* 을 임포트
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="javax.naming.*, java.sql.*, javax.sql.*"%>

<%
// Initialcontext 생성 -> 이름으로 객체 찾을 수 있도록 도와줌
Context initContext = new InitialContext(); 

// 톰캣(서버)을 먼저 찾는다.
// 톰캣을 찾을 때는 java:comp/env 로 찾는다.
Context envContext  = (Context)initContext.lookup("java:/comp/env"); 

// server.xml에 등록했던 이름 myshop을 찾는다.(타입은 DataSource 타입)
DataSource ds = (DataSource)envContext.lookup("jdbc/myshop"); 

//etc.
Connection conn = ds.getConnection();

out.println("<h1>데이터 소스 룩업 성공: "+ds +"</h1>");
out.println("<h2> con="+ conn + "</h2>");

// 커넥션 여결을 끊는 것이 아니라 커넥션풀에 자동으로 반납을 한다.
if(conn!=null) conn.close();
%>
profile
나의 공부 일기

2개의 댓글

comment-user-thumbnail
2023년 1월 27일

좋은글이네요 !

1개의 답글