자바(JAVA)/JSP 웹 프로그래밍 공부 (성낙현의 JSP 자바 웹 프로그래밍 참고)

JAVA/JSP 16. 데이터베이스 - 커넥션 풀로 성능 개선, 간단한 쿼리 작성 및 실행

개발학생 2023. 11. 12. 17:47
반응형

6. 커넥션 풀로 성능 개선

  • 웹은 클라이언트의 요청에 서버가 응답하는 구조→ Connection 객체 생성 때마다 네트워크 통신이 이뤄지며, 시간이 걸리는 작업들이 수반됨 == 시스템 성능에 큰 영향을 미침
    ⇒ ’커넥션 풀’로 성능 개선 필요

 

1) 커넥션 풀과 JNDI

(1) 커넥션 풀

  • JSP 프로그래밍에서, 커넥션 풀은 WAS가 제공하는 것을 이용하는 게 좋음
    → WAS 하나에 여러 개의 웹 애플리케이션을 구동시키는 경우가 많아,
        각 자원을 한번에 관리하는 게 절약도 되고 관리하기가 쉬움
  • WAS가 시작될 때, 미리 생성한 커넥션 객체를 사용하므로 웹 애플리케이션 실행 속도 빨라짐
    + 클라이언트의 동시 요청이 많아지더라도 좀 더 응답이 수월함
    → 성능 향상 효과가 커서, 웹 뿐만 아니라 게임에서도 많이 사용

(2) JNDI

  • Java Naming and Directory Interface-자바에서 객체나 데이터의 이름만으로 찾아 쓸 수 있는 디렉토리 서비스
  • 객체 이름과 실제 객체를 연결해주는 역할
  • 대부분의 WAS는 커넥션 풀을 비롯한 여러 자원을 JNDI로 제공
    → 이름과 실제 객체와의 연결은 외부 설정 파일에서 관리
        [다른 객체로 연결하거나 세부 설정을 바꿀 때 소스코드 수정/컴파일 불필요]

 

*비슷한 예-DNS

  • Domain Name System - 웹에서 도메인을 입력하면 이것을 통해 웹 서버의 IP 주소를 얻어와 해당 주소로 접속 가능

 

(3) WAS의 JNDI를 통해 커넥션 풀을 사용하는 방법

  1. WAS(톰캣)가 시작할때 server.xml과 context.xml에 설정한 대로 커넥션 풀 생성
  2. JSP 코드에서 JNDI 서버(WAS가 제공)로부터 데이터소스 객체를 얻어 옴
  3. 데이터소스로부터 커넥션 객체를 가져옴
  4. 데이터베이스(DB) 작업을 수행
  5. 모든 작업이 끝나면, 커넥션 객체를 풀로 반환

 

2) 커넥션 풀 설정

(1) server.xml 파일에 코드 추가

//{톰캣 홈 디렉터리}/conf/server.xml

<GlobalNamingResources>
	...기존 내용 유지...
	<Resource auth="Container"
              driverClassName="oracle.jdbc.OracleDriver"
              type="javax.sql.DataSource" 
              initialSize="0"
              minIdle="5"
              maxTotal="20"
              maxIdle="20"
              maxWaitMillis="5000"
              url="jdbc:oracle:thin:@localhost:1521:xe"
              name="dbcp_myoracle"
              username="hello"
              password="1234" />
</GlobalNamingResources>
  • <GlobalNamingResources>는 전역 자원을 등록하는 영역
    → 이 엘리먼트 안에 등록한 자원은, 이 서버에서 구동되는 모든 웹 애플리케이션에서 사용 가능
  • Type 속성으로 지정한 javax.sql.DataSource: 물리적인 데이터소스와의 연결을 생성해주는 자바 표준 인터페이스
    → driverClassName 속성으로 지정한 oracle.jdbc.OracleDriver 클래스가 이 인터페이스를 구현하고 있음
    → 오라클의 OracleDriver 클래스가 커넥션 풀 기능을 구현하고,
        우리는 자바 표준 인터페이스인 DataSource 형태로 받아 이용하는 것
  • <Resource>엘리먼트의 다양한 속성
driverClassName JDBC 드라이버의 클래스명
type 데이터소스로 사용할 클래스명
initialSize 풀의 최초 초기화 과정에서 미리 만들어놓을 연결의 개수 (기본값은 0)
minIdle 최소한으로 유지할 연결 개수 (기본값은 0)
maxTotal 동시에 사용할 수 있는 최대 연결 개수 (기본값은 8)
maxIdle 풀에 반납할 때 최대로 유지될 수 있는 연결 개수 (기본값은 8)
maxWaitMillis 새로운 요청이 들어왔을 때 얼만큼 대기할지를 밀리초 단위로 기술
url 오라클 연결을 위한 URL
name 생성할 자원(여기서는 풀)의 이름
username 계정 아이디
password 계정 패스워드

 

(2) context.xml 파일에 코드 추가

//{톰캣 홈 디렉터리}/conf/context.xml

<Context>
	...기존 내용 유지...
	<ResourceLink global="dbcp_myoracle" name="dbcp_myoracle"
                  type="javax.sql.DataSource" />
</Context>
  • 풀의 이름과 데이터소스로 사용할 클래스명 기술
    - global 속성에 앞서 server.xml에서 등록한 커넥션 풀 전역 자원의 이름을 명시해야 함

 

(3) 이클립스에서 웹 서버 다시 생성

  • Servers 뷰에서 기존 서버 삭제

 

  • 새 서버 생성

 

  • 프로젝트 탐색기에서 Servers 폴더를 열고 server.xml과 context.xml을 열어보면, 편집한 내용이 그대로 적용되어 있음

 

3) 커넥션 풀 동작 검증

  • 사용하는 멤버 변수들과 close( ) 메소드는 JDBConnect 클래스와 같음
  • JDBConnect가 JDBC 드라이버를 로드하고 DB와의 연결을 직접 만들었다면, DBConnPool은 JNDI로부터 데이터소스를 찾은 후 데이터소스로부터 연결을 얻음
// Java Resources/src/common/DBConnPool.java

package common;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class DBConnPool {
    public Connection con;
    public Statement stmt;
    public PreparedStatement psmt;
    public ResultSet rs;

    // 기본 생성자
    public DBConnPool() {
        try {
            // 커넥션 풀(DataSource) 얻기
            Context initCtx = new InitialContext();    //1.
            Context ctx = (Context)initCtx.lookup("java:comp/env");    //2.
            DataSource source = (DataSource)ctx.lookup("dbcp_myoracle");    //3.

            // 커넥션 풀을 통해 연결 얻기
            con = source.getConnection();    //4.

            System.out.println("DB 커넥션 풀 연결 성공");
        }
        catch (Exception e) {
            System.out.println("DB 커넥션 풀 연결 실패");
            e.printStackTrace();
        }
    }

    // 연결 해제(자원 반납)
    public void close() {
        try {            
            if (rs != null) rs.close();
            if (stmt != null) stmt.close();
            if (psmt != null) psmt.close();
            if (con != null) con.close();  // 자동으로 커넥션 풀로 반납됨

            System.out.println("DB 커넥션 풀 자원 반납");
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  1. InitialContext라는 객체 생성: JNDI(자바 네이밍 서비스)에서 이름과 실제 객체를 연결해주는 개념이 Context 이고 InitialContext는 네이밍 서비스를 이용하기 위한 시작점
    → lookup( ) 메소드에 이름을 건네 원하는 객체를 찾아 올 수 있음
  2. “java:comp/env{현재 웹 애플리케이션의 루트 디렉토리}”라는 이름을 인수로 Context 객체를 얻음
    → 현재 웹 애플리케이션이 사용할 수 있는 모든 자원은 “java.comp/env”아래에 위치함
  3. “java:comp/env” 아래에 위치한 “dbcp_myoracle” 자원을 얻어옴: 이 자원이 바로 앞서 설정한 데이터소스(커넥션 풀)
    → “dbcp_myoracle”은 context.xml 파일에 추가한 <ResourceLink>에 있는 name 속성의 값
  4. 데이터소스를 통해 풀에 연결되어 있는 연결 객체를 얻어와 멤버 변수에 저장하면, 생성 과정이 마무리 됨
  5. 다 쓴 연결을 반납함 → Connection객체의 close( )만 호출해주면 됨 [풀을 사용하지 않을 때와 같음]

 

(2) ConnectionTest.jsp

// WebContent/ConnectionTest.jsp

<%@ page import="common.JDBConnect"%> 
<%@ page import="common.DBConnPool"%>    //1.새로 만든 DBConnPool 클래스 임포트
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

...생략...

    <h2>커넥션 풀 테스트</h2>
    <%
    DBConnPool pool = new DBConnPool();
    pool.close();
    %>
</body>
</html>

 

 

(3) ConnectTest.jsp 실행 결과

  • server.xml, context.xml 파일을 잘못건드린 건지… 서버가 계속 오류 나면서 실행이 안 됨
    (오류명: The Network Adapter could not establish the connection)
    • 해결이 불가능해서, 프로젝트+서버 다시 만듬 → JDBConnect의 DB정보만 옮겨놓기 → JDBConnect.jsp, Connection.jsp 수정하고 방화벽 인바운드규칙 추가했는데도 오류.. cmd로 sql도 켰음

 

 

 

  • 2) 커넥션 풀 설정 다시 반복하기
    → 성공! sever.xml, context.xml 파일에서 기존의 코드를 지우고 코드를 추가해버려서 오류가 났던 것 같다..

 

7. 간단한 쿼리 작성 및 실행

  • 데이터베이스 작업: 쿼리문(SQL)을 작성하고 실행하여 그 결과를 얻어오는 일을 말함

 

1) JDBC에서의 쿼리문 표현

  • Statement 계열의 인터페이스
Statement  인파라미터가 없는 정적 쿼리 처리 시 사용
PreparedStatement 인파라미터가 있는 동적 쿼리를 처리할 때 사용
CallableStatement 프로시져(procedure)나 함수(function)를 호출할 때 사용

 

  • Statement 계열의 객체로 쿼리문을 실행할 때 사용하는 메소드
    • Statement와 PreparedStatement에서의 메소드 사용 방법은 약간의 차이가 있음
executeUpdate() insert, update, delete 쿼리문 실행 시 사용 기존 코드를 변화시키거나 새로운 레코드를 입력함
→ 실행 후 영향을 받은 행의 개수를 int형으로 반환
executeQuery() select 쿼리문 실행 시 사용 기존 레코드 조회 시 사용 → 조회한 레코드들의 집합인 ResultSet 객체 반환

 

  • JDBC에서 쿼리문은 java.sql.Statement 인터페이스로 표현되는데,
    Statement 객체는 Connection 객체를 통해 얻어오도록 되어 있음
  • 인파라미터(IN Parameter): 미리 작성해둔 쿼리문에서 일부 값을 나중에 결정할 수 있게 해주는 매개변수.
                                                쿼리문에서 물음표(?)로 표현함

 

2) 동적 쿼리문으로 회원 추가 - sqlplus

  • 작업 순서: DB 연결 → 입력값 준비 → 쿼리문 생성 → 쿼리 수행 → 연결 닫기
  • PreparedStatement: 쿼리문의 틀을 준비해둔 후, 필요할 때 인파라미터를 설정해서 사용
                                      (데이터 타입에 맞는 set 메소드 사용하기!)
//데이터 타입에 맞는 메소드
void setInt(int index, int value);
void setDate(int index, Date value);
void setString(int index, String value);
  • ExeUpdate.jsp 파일
// WebContent/ExeUpdate.jsp
<%@ page import="java.sql.PreparedStatement"%>
<%@ page import="java.sql.Connection"%>
<%@ page import="common.JDBConnect"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
<head><title>JDBC</title></head>
<body>
    <h2>회원 추가 테스트(executeUpdate() 사용)</h2>
    <%
    // 1.DB에 연결
    JDBConnect jdbc = new JDBConnect();
    
    // 2.테스트용 입력값 준비 
    String id = "test1";
    String pass = "1111";
    String name = "테스트용";

    // 쿼리문 생성
    String sql = "INSERT INTO member VALUES (?, ?, ?, sysdate)";    //3.  
    PreparedStatement psmt = jdbc.con.prepareStatement(sql);    //4.  
		// 5.(아래 코드 3줄)
    psmt.setString(1, id);
    psmt.setString(2, pass);
    psmt.setString(3, name);

    // 쿼리 수행
    int inResult = psmt.executeUpdate();    //6.
    out.println(inResult + "행이 입력되었습니다.");    //7.

    // 연결 닫기
    jdbc.close(); 
    %>
</body>
</html>

1. 앞서 준비한 JDBConnect 객체 생성

2. 테이블에 입력할 값 준비
    → 원래 아래처럼 form 값을 전송받아야 하지만, DB 작업 테스트 중이니 간단하게 값을 하드코딩함

    String id = request.getParameter("id");
    String pass = request.getParameter("pass");
    String name = request.getParameter("name");

3. 쿼리문 생성 1 - SQL문 문자열 정의

INSERT INTO     member          VALUES (?, ?, ?, sysdate) 

//입력하라.   회원 테이블에      다음 3개의 값과 현재 시각을(?는 나중에 대입할 값)

4. 쿼리문 생성 2 - Connection 객체를 통해 PreparedStatement 객체 생성 (3.의 SQL문을 인수로 제공)

5. 쿼리문 생성 3 - 인파라미터들에 실제 값을 대입함 (setString()의 첫 번째 매개변수 = 인파라미터의 순서) →첫 번째 인파라미터가 1번이며 차례로 1씩 증가하므로, DB에서 인덱스가 1부터 시작됨
6. 쿼리문 실행 → 데이터베이스에 성공적으로 입력된 레코드의 수가 정수형으로 반환됨
                             (쿼리문이 update나 delete였다면, 수정/삭제된 레코드 수 반환)

7. DB 연결 해제

 

  • ExeUpdate.jsp을 [Run On Server]로 실행한 결과

 

  • [윈도우] + [R] → ‘cmd’ 입력 → cmd에서 “sqlplus” 입력 → user-name에 “hello”, password에 “1234” 입력 → “select * from member;” 입력

 

3) 정적 쿼리문으로 회원 조회 - JSP

  • 정적 쿼리문: 모든 내용이 처음부터 완벽하게 정의되어, 더는 변할 게 없는 쿼리문

 

(1) 회원 목록 조회 테스트

  • ExeQuery.jsp 파일 생성 - ExeUpdate.jsp 파일보다 쿼리문 생성 부분이 훨씬 간결함
// WebContent/ExeQuery.jsp

<%@ page import="java.sql.ResultSet"%>
<%@ page import="java.sql.Statement"%>
<%@ page import="java.sql.Connection"%>
<%@ page import="common.JDBConnect"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
       pageEncoding="UTF-8"%>  
<html>
<head><title>JDBC</title></head>
<body>
    <h2>회원 목록 조회 테스트(executeQuery() 사용)</h2>
    <%
    // DB에 연결
    JDBConnect jdbc = new JDBConnect();

    // 쿼리문 생성   
    String sql = "SELECT id, pass, name, regidate FROM member";    //1.
    Statement stmt = jdbc.con.createStatement();    //2.

    // 쿼리 수행
    ResultSet rs = stmt.executeQuery(sql);    //3.

    // 결과 확인(웹 페이지에 출력)
		//5.java.sql.Date~ 부분까지
    while (rs.next()) {     //4.
        String id = rs.getString(1);
        String pw = rs.getString(2);
        String name = rs.getString("name");
        java.sql.Date regidate = rs.getDate("regidate");
        
				//6.
        out.println(String.format("%s %s %s %s", id, pw, name, regidate) + "<br/>"); 
    }

    // 연결 닫기
    jdbc.close();
    %>
</body>
</html>
  1. SQL문에 인파라미터가 전혀 없음
  2. PreparedStatement가 아닌, Statement를 생성함. 메소드는 prepareSatatment()가 아닌, createStatement()
  3. 쿼리 수행에 executeUpdate()가 아닌 executeQuery() 메소드를 이용하고, 결과로 ResultSet(조회 결과를 담고 있는 집합-이 예제에서는 ‘회원 목록’) 객체를 받음
  4. next() 메소드: ResultSet 객체에서 다음 행(레코드) 반환
  5. 4.에서 반환한 행에서, ID/패스워드/이름/가입 날짜를 차례로 읽어옴
  6. 5.에서 읽어온 회원 목록을 웹 페이지에 출력
  • ExeQuery.jsp 파일을 [Run On Server]로 실행한 결과 (방화벽에서 8081 포트 열어줘야 함) → 2개의 레코드가 정상적으로 출력됨
    • JDBC 프로그래밍이 진행되는 순서
      • JDBC 드라이버 로드 → 데이터베이스 연결 → 쿼리문 작성 → 쿼리문(Statement 계열) 객체 생성 → 쿼리 실행 → 실행 결과 처리 → 연결 해제

 

4) ResultSet에서 결괏값 불러오기

  • Select문: 조건에 맞는 레코드를 모두 선택하라는 명령 → 실행 결과로 얻은 ResultSet에는 여러 개의 레코드가 담겨 있음
  • ResultSet 안의 쿼리 결과로 반환된 ResultSet에서는 커서가 첫 번째 행 부분에 위치하고, next() 메소드가 호출되면 다음 행으로 커서 이동
  • → 다음 행이 있다면 true 반환, 빈 ResultSet이거나 더 이상 읽어올 수 있는 행이 없다면 false 반환
  • ResultSet의 get 메소드들은 커서가 현재 가리키는 행의 걸럼값들을 읽어 옴 (컬럼의 인덱스를 사용하거나, 컬럼명을 그대로 사용해도 됨)
    → 다음 3개의 메소드로 대부분의 작업을 처리 가능
int getInt(int columnIndex) 혹은 int getInt(String columnLabel)  지정한 인덱스 혹은 이름의 컬럼에 해당하는 값을 정수형으로 추출
Date getDate(int columnIndex) 혹은
Date getDate(String columnLabel)
지정한 인덱스 혹은 이름의 컬럼에 해당하는 값을 날짜형으로 추출
String getString(int columnIndex)
혹은 String getString(String columnLabel)
지정한 인덱스 혹은 이름의 컬럼에 해당하는 값을 문자열로 추출
반응형