• Home
  • About
    • Young's Github Pages photo

      一日不作一日不食

    • About
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

Java EE 정리 07

06 Mar 2019

Reading time ~5 minutes

Java EE 정리 07 - DBCP


DBCP(DataBase Connection Pool)

  • JDBC의 불편함을 개선한 DB연동 방식
    • JDBC
      • DB연동 시 초기 지연시간이 발생
        • 초기 지연시간 - 사용자가 서비스를 받기 전에 들어가는 시간(1~3)
        • 2번 “Connection 얻기”가 가장 오래 걸린다.
      • 항상 사용하고 연결을 끊기 때문에 Connection의 재사용성이 낮다.
// JDBC 연동순서
// 1. Driver 로딩(.jar)
Class.forName("oracle.jdbc.OracleDriver");

// 2. Connection 얻기
// url - "jdbc:oracle:thin:@000.000.000.000:1521:sid"
// url에 존재하는 아이피는 DB Server IP, Network으로 찾아가서 인증 후 결과를 받아
// 오기 때문에 2번이 가장 많은 시간이 소요된다.
Connection = DriverManager.getConnection(url, id, pw);

// 3. 쿼리문 실행 객체 얻기
Statement stmt = con.createStatement();

// 4. 쿼리문을 실행한 후 결과 얻기(사용자가 오직 필요로 하는 기능)
int cnt = stmt.executeUpdate(sql);

// 5. 연결 끊기
if (stmt != null) { stmt.close(); }
if (con != null) { con.close(); }

01

  • DBCP는 초기 지연시간(1~3)을 줄여주기 위한 방법
    • 일정 개수의 Connection을 미리 연결해두고 필요할 때 꺼내서 사용하는 방식
      • 초기지연시간이 줄어든다.
    • 연결이 끊어진 Connection은 Pool로 들어가기 때문에 Connection의 재사용성이 보장된다.
  • DBCP 의 단점은 DB연동을 사용하지 않아도 일정 개수의 Connection을 연결하고 있기 때문에 메모리를 항상 사용한다. (덕분에 빠르다)
    • JDBC는 DB작업이 필요할 때만 연결 유지, 자원을 사용(메모리 절약가능, 대신 느리다)
  • .jar로 배포되는 경우와 Container에서 제공하는 경우 두가지 존재
    • Tomcat(Container)에서 제공하는 경우만 다룰 예정
    • Tomcat기준 server.xml에 간단한 설정을 추가하여 사용할 수 있다.
  • DBCP 설정방법
    • DBCP가 필요한 Project의 Context노드에 설정한다.
    • Tomcat Doc보고 하면 된다.

02

DBCP 설정

  • 오라클 설치경로에서 JDBC 드라이버 “ojdbc6.jar” 복사, catalina_home/lib에 붙여넣기

03

<!-- server.xml Context노드 안에 Resource노드로 DBCP 설정 -->
<Resource name="jdbc/myoracle" -- Java에서 JNDI를 사용하여 찾을 이름
          auth="Container"
          type="javax.sql.DataSource" -- DBCP에서 DB와 연결할 객체
          driverClassName="oracle.jdbc.OracleDriver" -- 연동에 사용할 Driver클래스
          url="jdbc:oracle:thin:@127.0.0.1:1521:mysid" -- 연동할 DB의 url
          username="scott" password="tiger" -- DBO의 id/password
          maxTotal="20" 
          -- maxIdle이 모두 사용중일 때 늘릴 MAX Connection개수(보통 maxIdle*2)
          maxIdle="10" -- Connection을 연결할 개수
          maxWaitMillis="-1" -- Connection을 요청했을 때 대기할 시간 설정(즉시)
          />
<!-- server.xml -->
<!-- Context 노드는 docBase경로를 웹브라우저 url경로로 매핑해주는 것이었음 
<Context path="/브라우저" docBase="HDD실제경로"> -->
<Context docBase="servlet_prj" path="/servlet_prj" reloadable="true"  source="org.eclipse.jst.jee.server:servlet_prj">
      <Resource name="jdbc/dbcp" auth="Container"
        type="javax.sql.DataSource"  driverClassName="oracle.jdbc.OracleDriver"
        url="jdbc:oracle:thin:@localhost:1521:orcl"
        username="scott" password="tiger" maxTotal="20" maxIdle="10"
        maxWaitMillis="-1"/>
</Context>

04

  • Servlet/JSP에 요청 처리를 위해 JVM instance에서 객체가 생성됨
    • 객체 내에서 DB작업이 필요하면 DBCP에서 Connection을 얻어와 사용
      • DBCP를 이름으로 찾을 수 있는 JNDI(Java Naming and Directory Interface) 객체 생성
      • 이름으로 DataSource 객체를 찾음
      • 찾은 DataSource 객체로부터 Connection 얻음
  • DBCP 사용 예 1
// UseDBCP.java
@SuppressWarnings("serial")
public class UseDBCP extends HttpServlet {
     
     protected void doGet(HttpServletRequest request,  HttpServletResponse response) throws ServletException, IOException {
          
          response.setContentType("text/html;charset=UTF-8");
          PrintWriter out = response.getWriter();
          
          ...
          // DBCP 사용
          try {
               // 1. JDNI 사용 객체 생성
               Context ctx = new InitialContext();

               // 2. 이름으로 객체 찾기
               DataSource ds =  (DataSource)ctx.lookup("java:comp/env/jdbc/dbcp");
               
               // 3. Connection 얻기
               Connection con = ds.getConnection();
               
               out.println("DB연동 성공 : "+con);
                                   
               con.close(); // 끊어진 Connection은 다시 Pool로 들어가서 다음번에 재사용됨
          } catch (NamingException ne) {
               out.println("데둉합니다.<br/>");
               out.println("<img src='common/images/img.png'/>");
               ne.printStackTrace();
          } catch (SQLException se) {
               out.println("데둉합니다.<br/>");
               out.println("<img src='common/images/img2.jpg'/>");
               se.printStackTrace();
          }
          ...
     }
}

05

  • 만약 close()를 수행하지 않고 새로고침을 20번 해서 모든 Connection을 얻게되면 DBCP 최대치를 넘어버려 서버가 죽어버린다.
    • Connection을 얻으면 꼭 close()를 수행해야 한다

06

  • 단위테스트를 위해 DBCP 작업을 메소드로 빼서 main메소드로 서블릿 객체 생성 후 DBCP 작업 메소드를 실행하면 에러가 발생한다.
    • JVM이 먼저 실행되고 Tomcat이 실행되며 DBCP가 생성됨
    • JVM만 단독으로 실행하면 Tomcat이 실행되지 않기 때문에 DBCP가 생성되지 않아 사용할 수 없다.
    • 때문에 단위테스트를 하려면 Tomcat을 실행 후 테스트를 해야 함
      • 이런 테스트를 In Container Test라 한다.
      ...
      public static void main(String[] args) { // 단위테스트
          UseDBCP ud = new UseDBCP();
          
          ud.test();
      }
      
      public void test() {
          // DBCP 사용
          try {
               Context ctx = new InitialContext();
               DataSource ds =  (DataSource)ctx.lookup("java:comp/env/jdbc/dbcp");
               Connection con = ds.getConnection();
               
               System.out.println("DB연동 성공 : "+con);
               
               con.close();
               
          } catch (NamingException ne) {
               System.out.println("데둉합니다.<br/>");
               System.out.println("<img  src='common/images/img.png'/>");
               ne.printStackTrace();
          } catch (SQLException se) {
               System.out.println("데둉합니다.<br/>");
               System.out.println("<img  src='common/images/img2.jpg'/>");
               se.printStackTrace();
          }
     }
}

07

  • DBCP 사용 예2
    • EMP 테이블에서 정보를 조회해서 보여주기
// EmpVO.java
public class EmpVO { // VO - Value Object
      
     private int empno, mgr,sal;
     private String ename, job, hiredate;
     ... //  getter, 인자있는 생성자
}
// EmpDAO.java
public class EmpDAO { // DAO - Data Access Object
     public List<EmpVO> selectAllEmp() throws SQLException {
          List<EmpVO> list = new ArrayList<>();
          
          Connection con = null;
          PreparedStatement pstmt = null;
          ResultSet rs = null;
          
          try {
               // 1. JNDI 사용객체 생성
               Context ctx = new InitialContext();
               
               // 2. DataSource를 DBCP에서 꺼내온다.
               DataSource ds =  (DataSource)ctx.lookup("java:comp/env/jdbc/dbcp");
               
               // 3. Connection 얻기
               con = ds.getConnection();
               
               // 4. 쿼리문 수행 객체 얻기 (4부터는 JDBC와 동일)
               StringBuilder selectAllEmp = new StringBuilder();
               selectAllEmp
               .append(" select empno, ename, job, mgr,  to_char(hiredate,'yyyy-MM-dd') hiredate, sal ")
               .append(" from emp ")
               .append(" order by sal ");
               
               pstmt = con.prepareStatement(selectAllEmp.toString());
               
               // 5. 쿼리문 수행 후 결과 얻기.
               rs = pstmt.executeQuery();
               
               EmpVO evo = null;
               while(rs.next()) {
                    evo = new EmpVO(rs.getInt("empno"),  rs.getInt("mgr"), rs.getInt("sal"),
                                   rs.getString("ename"),  rs.getString("job"), rs.getString("hiredate"));
                    list.add(evo);          
               }
          } catch (NamingException e) {
               e.printStackTrace();
          } finally {
               // 6. 연결 끊기
               if (rs != null) { rs.close(); }
               if (pstmt != null) { pstmt.close(); }
               if (con != null) { con.close(); }
          }
          return list;
     }
}
// SelectEmp.java - 단위테스트(In Container Test)
@SuppressWarnings("serial")
public class SelectEmp extends HttpServlet {
     protected void doGet(HttpServletRequest request,  HttpServletResponse response) throws ServletException, IOException {
          
          try { 
               System.out.println(new EmpDAO().selectAllEmp()); 
          } catch (SQLException e) {
               e.printStackTrace();
          }
     }
}

08

// SelectEmp.java
@SuppressWarnings("serial")
public class SelectEmp extends HttpServlet {
     protected void doGet(HttpServletRequest request,  HttpServletResponse response) throws ServletException, IOException {
          
          response.setContentType("text/html;charset=UTF-8");
          PrintWriter out = response.getWriter();
          ...
          out.write("<style type=\"text/css\">\r\n");
          ...
          out.println("table { border-spacing:0px; }");
          out.println("th { background-color: #0C4F7F; }");
          out.println("span { color:#FFFFFF; font-size:14px; }");
          out.println("td { border:1px solid #141650;  text-align:center; }");
          out.write("</style>\r\n");
          ...
          out.println("<table border='1'>");
          out.println("<tr>");
          out.println("\t<th width='80'><span>번호</span></th>");
          out.println("\t<th width='120'><span>사원번호</span></th>");
          out.println("\t<th width='150'><span>사원명</span></th>");
          out.println("\t<th  width='80'><span>매니저번호</span></th>");
          out.println("\t<th width='100'><span>직무</span></th>");
          out.println("\t<th width='100'><span>연봉</span></th>");
          out.println("\t<th width='150'><span>입사일</span></th>");
          out.println("</tr>");
          
          List<EmpVO> list = searchAllEmp();

          if(list == null) { // DB에 문제 발생한 경우
               out.println("<tr><td  colspan='7'>데둉합니다.</td></tr>");
          } else {
               EmpVO evo = null;
               for(int i=0; i<list.size(); i++) {
                    evo = list.get(i);
                    out.println("<tr>");
                    out.print("<td>");
                    out.print(i+1);
                    out.println("</td>");
                    out.print("<td>");
                    out.print(evo.getEmpno());
                    out.println("</td>");
                    out.print("<td>");
                    out.print(evo.getEname());
                    out.println("</td>");
                    out.print("<td>");
                    out.print(evo.getMgr());
                    out.println("</td>");
                    out.print("<td>");
                    out.print(evo.getJob());
                    out.println("</td>");
                    out.print("<td>");
                    out.print(evo.getSal());
                    out.println("</td>");
                    out.print("<td>");
                    out.print(evo.getHiredate());
                    out.println("</td>");
                    out.println("</tr>");
               }
               if (list.isEmpty()) {
                    out.print("<tr><td colspan='7'>사원정보가  존재하지 않습니다.<br/>");
                    out.print("<img src='common/images/img2.jpg'  title='#일안하냐? #사원관리'/>");
                    out.println("</td></tr>");
               }
          }
          out.println("</table>");
          ...
     }
     
     public List<EmpVO> searchAllEmp() {
          List<EmpVO> list = null;
          
          EmpDAO ed = new EmpDAO();
          
          try {
               list = ed.selectAllEmp();
          } catch (SQLException e) {
               e.printStackTrace();
          }
          
          return list;
     }
}

09



Java EEServlet