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의 재사용성이 낮다.
- DB연동 시 초기 지연시간이 발생
-
JDBC
// 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(); }
-
DBCP는 초기 지연시간(1~3)을 줄여주기 위한 방법
-
일정 개수의 Connection을 미리 연결해두고 필요할 때 꺼내서 사용하는 방식
- 초기지연시간이 줄어든다.
- 연결이 끊어진 Connection은 Pool로 들어가기 때문에 Connection의 재사용성이 보장된다.
-
일정 개수의 Connection을 미리 연결해두고 필요할 때 꺼내서 사용하는 방식
- DBCP 의 단점은 DB연동을 사용하지 않아도 일정 개수의 Connection을 연결하고 있기 때문에 메모리를 항상 사용한다. (덕분에 빠르다)
- JDBC는 DB작업이 필요할 때만 연결 유지, 자원을 사용(메모리 절약가능, 대신 느리다)
- .jar로 배포되는 경우와 Container에서 제공하는 경우 두가지 존재
- Tomcat(Container)에서 제공하는 경우만 다룰 예정
- Tomcat기준 server.xml에 간단한 설정을 추가하여 사용할 수 있다.
-
DBCP 설정방법
- DBCP가 필요한 Project의 Context노드에 설정한다.
- Tomcat Doc보고 하면 된다.
DBCP 설정
- 오라클 설치경로에서 JDBC 드라이버 “ojdbc6.jar” 복사, catalina_home/lib에 붙여넣기
<!-- 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>
- Servlet/JSP에 요청 처리를 위해 JVM instance에서 객체가 생성됨
- 객체 내에서 DB작업이 필요하면 DBCP에서 Connection을 얻어와 사용
- DBCP를 이름으로 찾을 수 있는 JNDI(Java Naming and Directory Interface) 객체 생성
- 이름으로 DataSource 객체를 찾음
- 찾은 DataSource 객체로부터 Connection 얻음
- 객체 내에서 DB작업이 필요하면 DBCP에서 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();
}
...
}
}
- 만약 close()를 수행하지 않고 새로고침을 20번 해서 모든 Connection을 얻게되면 DBCP 최대치를 넘어버려 서버가 죽어버린다.
- Connection을 얻으면 꼭 close()를 수행해야 한다
- 단위테스트를 위해 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();
}
}
}
-
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();
}
}
}
// 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;
}
}