[DB] DB 연동 및 데이터 작업, DBCP

been·2021년 2월 5일
0

TIL2

목록 보기
12/14

##[DB] JDBC

: JDBC는 자바에서 데이터베이스에 접속할 수 있도록 하는 자바 API이다. JDBC는 데이터베이스에서 자료를 쿼리하거나 업데이트하는 방법을 제공한다

##JDBC 드라이버 로딩과 Connection 생성

1.JDBC 드라이버 로딩

  • MySQL의 JDBC Driver Class를 로딩합니다.
  • Class.forName(“driver”)을 이용해서 Driver Class를 로딩하면 객체가 생성되고, DriverManager에 등록됩니다.
  • ex) Class.forName(“com.mysql.jdbc.Driver”)
  • Driver 클래스를 찾지 못할 경우, ClassNotFoundException 예외가 발생 합니다.

2. Connection 생성

  • Connection - 데이터베이스와 연결하는 객체입니다.
  • DriverManager.getConnection(연결문자열, DB_ID, DB_PW) 으로 Connection 객체를 생성합니다.
  • 연결문자열(Connection String) - “jdbc:Driver 종류://IP:포트번호/DB명”
  • ex) jdbc:mysql://localhost:3306/test_db
  • DB_ID : MySQL 아이디
  • DB_PW : MySQL 비밀번호

2.1 DriverManager 클래스

  • DriverManager 클래스는 JDBC 드라이버를 통하여 Connection을 만드는 역할을 합니다.
  • DriverManager는 Class.forName( ) 메소드를 통해서 생성됩니다.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class Main {
	/*
    * [JDBC - Java Database Connectivity] 
    * - Java에서 데이터베이스에 접속할 수 있도록 하는 Java API 
    */ 
    public static void main(String[] args) { 
    //데이터베이스와 연결하는 객체
        Connection con = null;
        // 1. JDBC Driver Class
        String driver = "com.mysql.jdbc.Driver";

        // 2. 데이터베이스에 연결하기 위한 정보
        String url = "jdbc:mysql://localhost:3306/test_db"; // 연결문자열, localhost - 127.0.0.1 
        String user = "root"; // 데이터베이스 ID 
        String pw = "1234"; // 데이터베이스 PW

        try {
            //1. JDBC 드라이버 로딩
            Class.forName(driver);

            // 2. Connection 생성
            con = DriverManager.getConnection(url, user, pw);	//데이터베이스 연결 

            System.out.println("[Database 연결 성공]");
        } catch (SQLException e) {
            System.out.println("[SQL Error : " + e.getMessage() +"]");
        } catch (ClassNotFoundException e1) {
            System.out.println("[JDBC Connector Driver Error : " + e1.getMessage() + "]");
        } finally {
            //Connection 사용 후 Close
            if(con != null) { 
                try { 
                    con.close();
                } catch (Exception e) {

                } 
            } 
        }
    }
}

##JNDI(Java Naming and Directory Interface)

  • java에서 Naming and Directory Service를 사용할 수 있게 하는 라이브러리.
  • 각 데이터베이스에 대한 서비스를 디렉토리 형태로 등록할 수 있다.

#JNDI를 사용한 DB 연결

  • jndi name 으로 database 정보를 가져온다.

context configuration

<Resource name="jdbc/myoracle" auth="Container"
              type="javax.sql.DataSource" driverClassName="oracle.jdbc.OracleDriver"
              url="jdbc:oracle:thin:@127.0.0.1:1521:mysid"
              username="scott" password="tiger" maxTotal="20" maxIdle="5"
              maxWaitMillis="-1"/>
  • maxIdle: 미리 연결해두는 최대 연결수
  • 메모리 번지수 대신 이름으로 관리.
  • 이름 등록은 아래의 web.xml에서 설정.

web.xml configuration

<resource-ref>
  <description>Oracle Datasource example</description>
  <res-ref-name>jdbc/myoracle</res-ref-name>
  <res-type>javax.sql.DataSource</res-type>
  <res-auth>Container</res-auth>
</resource-ref>
  • res-ref-name: Resource에 대한 이름 등록

이처럼 jndi 이름을 통해 dataSource를 찾아온다.

config.xml

<jee:jndi-lookup id="dataSource" jndi-name="jdbc/myoracle" resource-ref="true" />

혹은 xml 파일 내부에서 jndi-lookup 태그를 통해 dataSource를 찾아올 수 있다.

##Statement

: Database Query 는 Statement 객체를 이용하는데, 이 객체는 DBConnection 을 통해서 얻을 수 있다.

Statement stmt = con.createStatement();

Statement 에는 다음의 2개의 대표함수가 있다.

int executeUpdate( String sql ); // db 조작에 관한 어떤 명령을 수행한다. 

ResultSet executeQuery( String sql ) // db query 에 사용된다.

##PrepareStatement

Statement 문제점 : Statement를 사용하여 쿼리를 하면 대소문자만 바뀌어도 다른 문장으로 인식 -> 서버에 문장정보를 저장하는 작업(파싱)을 다시 수행 -> 성능문제

해결책 : 그래서 나타난 것이 PreparedStatement.

  • 조건값 부분만 바뀌어 오는 경우에 이 문장을 다른 문장으로 인식하지 않아 파싱작업을 생략하여 성능향상을 가져올 수 있다.
  • PreparedStatement 는 생성당시에 파싱을 수행하기 때문에 생성부문에서는 "약간" 일반 Statement 에 비해 성능이 떨어지지만, 조건값만 바꾸어 지속적으로 사용하는 경우 "엄청난" 성능 향상을 가져올 수 있다.

1.Statement 객체를 사용시

String EmpName;
EmpName = "ABC"
 
String sql = "SELECT * FROM Emp WHERE EmpName = " + EmpName;
 
stmt = conn.prepareStatement(sql);
stmt.executeUpdate();

잘못된 방식 : Statement 객체를 이용해 문자열에 변수를 + 하는 형태로 매개변수를 전달하는 방식을 사용

SELECT * FROM Emp WHERE EmpName = 'ABC'
SELECT * FROM Emp WHERE EmpName = 'ABC123'

1.[SELECT * FROM Emp WHERE EmpName = 'ABC'] 라는 문장을 실행할 최적의 실행계획을 생성

2.해당 SQL문에 대한 Id를 부여하고 플랜 캐시에 저장

3.값을 추출

4.[SELECT * FROM Emp WHERE EmpName = 'ABC123'] 이라는 문장을 실행할 실행계획을 다시 생성

5.2번 3번의 반복

문제점 : 같은 실행계획을 사용하지만 실행계획을 다시 생성

2.PreparedStatement 객체를 사용시

sql = "SELECT * FROM Emp WHERE EmpName = ?";

psmt = conn.prepareStatement(sql);
psmt.setString(1, "ABC");
psmt.executeUpdate();

[SELECT * FROM Emp WHERE EmpName = ?] 라는 SQL 실행계획을 공유할 수 있어 실행계획이 다시 생성되지 않음 -> 속도도 훨씬 빠르며, 적은 부하로 데이터를 추출가능

##ResultSet

  • Statement.executeUpdate(DML) >> int 반환

  • Statement.executeQuery(DQL) >> ResultSet 반환 >> 명령을 전달하여 실행시키는 메소드

  • ResultSet : 검색행을 저장하기 위한 인스턴스 정보

  • SELECT 명령은 Statement.executeQuery() 메소드로 전달하여 실행
    => 검색결과를 ResultSet 인스턴스에 저장하여 반환

1.ResultSet은 스크롤 기능을 설정, 사용 가능(커서유형)

  • ResultSet.TYPE_FORWARD_ONLY : 단방향 이동

  • ResultSet.TYPE_SCROLL_INSENSITIVE : 양방향 이동은 가능하지만 데이터의 변경이 바로 적용되지 않는다.

  • ResultSet.TYPE_SCROLL_SENSITIVE : 양방향 이동도 가능하고, 데이터 변경이 바로 적용된다.

2.병행성 : ResultSet 내의 결과값 집합에 대한 Update와 같은 조작이 가능한가?

  • CONCUR_READ_ONLY : 기본값. 읽기만 가능.

  • CONCUR_UPDATABLE : 값 수정 가능.

3.유지성 : ResultSet 객체를 계속 유지할 것인가?

  • HOLD_CURSORS_OVER_COMMIT : 커밋후에도 ResultSet 객체를 유지한다.

  • CLOSE_CURSORS_AT_COMMIT : 커밋후에는 ResultSet 객체를 닫는다. 성능향상.

Connection.createStatement() 메소드를 통해 지정한다.

4.DBMS가 지원하는 ResultSet 형태 정보 알아내기

  • DatabaseMetaData dbmd = con.getMetaData();
  • String dbmd.getDatabaseProductName() : 데이타베이스 제품 이름
  • boolean dbmd.supportsResultSetType() : ResultSet Type 지원 여부
  • boolean dbmd.supportsResultSetConcurrency() : 병행성 지원 여부
  • int dbmd.getResultSetHoldability() : JDBC 3.0. 유지성 지원 여부

5.Cursor

  • ResultSet 데이타의 각 행을 가리킨다.
  • Cursor가 가리키는 위치를 이동하면서 각 행의 값을 조회하고 조작할 수 있다.
  • 최초 커서의 위치는 Before the first row이다.

6.메소드 리턴값

  • boolean next()
    • 가장 많이 쓰임. 아래 방향으로 한 행씩 이동.
    • 더이상 행이 존재하지 않을 때(After the last row)는 false.
    • TYPE_FORWARD_ONLY 일 경우에는 next() 메소드만 정상적으로 수행된다.
  • boolean previous() :
    • 위 방향으로 이동.
    • 더이상 위에 행이 없을 때(Before the first row)는 false.
  • boolean last() :
    • 마지막 행.
    • false이면 현재 ResultSet에 아무 값도 없다는 뜻.
  • void first() :
    • 첫번째 행.
    • false이면 현재 ResultSet에 아무 값도 없다는 뜻.
  • void beforeFirst() :
    • Before the first row로 이동
  • void afterLast() :
    • After the last row로 이동
  • boolean relative(int i) :
    • 현재 위치에서 i 만큼 이동. i가 음수이면 위로, 양수이면 아래로.
  • boolean absolute(int i) :
    • 양수일 경우에는 Before the first row를 기준으로, 음수일 경우에는 After the first row를 기준으로 하여 지정된 행으로 이동. 각 행은 1 혹은 -1 부터 시작한다.

##DBCP(Database Connection Pool)

1.DBCP란?

  • 커넥션 풀 : 웹 컨테이너(WAS)가 실행되면서 DB와 미리 connection(연결)을 해놓은 객체들을 pool에 저장해두었다가. 클라이언트 요청이 오면 connection을 빌려주고, 처리가 끝나면 다시 connection을 반납받아 pool에 저장하는 방식을 말합니다.
  • DBCP : DB와 커넥션을 맺고 있는 객체를 관리하는 역할을 합니다.

2. 커넥션 풀(DBCP)을 사용하는 이유

Connection conn = null;
PreparedStatement  pstmt = null;
ResultSet rs = null;

try {
    sql = "SELECT * FROM T_BOARD"

    // 1. 드라이버 연결 DB 커넥션 객체를 얻음
    connection = DriverManager.getConnection(DBURL, DBUSER, DBPASSWORD);

    // 2. 쿼리 수행을 위한 PreparedStatement 객체 생성
    pstmt = conn.createStatement();

    // 3. executeQuery: 쿼리 실행 후
    // ResultSet: DB 레코드 ResultSet에 객체에 담김
    rs = pstmt.executeQuery(sql);
    } catch (Exception e) {
    } finally {
        conn.close();
        pstmt.close();
        rs.close();
    }
}
  • 위와 같이 자바에서 DB에 직접 연결해서 처리하는 경우(JDBC) 드라이버(Driver)를 로드하고 커넥션(connection) 객체를 받아와야 한다.
  • 그러면 매번 사용자가 요청을 할 때마다 드라이버를 로드하고 커넥션 객체를 생성하여 연결하고 종료하기 때문에 매우 비효율적이다.

이런 문제를 해결하기 위해서 커넥션풀(DBCP)를 사용한다.

3. 커넥션 풀(DBCP) 특징

  • 웹 컨테이너(WAS)가 실행되면서 connection 객체를 미리 pool에 생성해 둡니다.
  • HTTP 요청에 따라 pool에서 connection객체를 가져다 쓰고 반환한다.
  • 이와 같은 방식으로 물리적인 데이터베이스 connection(연결) 부하를 줄이고 연결 관리 한다.
  • pool에 미리 connection이 생성되어 있기 때문에 connection을 생성하는 데 드는 요정 마다 연결 시간이 소비되지 않는다.
  • 커넥션을 계속해서 재사용하기 때문에 생성되는 커넥션 수를 제한적으로 설정함

4. Apache의 Commons DBCP

Commons DBCP 스프링 설정

<bean id="dataSource-mysql"
        class="org.apache.commons.dbcp.BasicDataSource" 
        destroy-method="close">
        <property name="driverClassName" value="${Globals.DriverClassName}"/>
        <property name="url" value="${Globals.Url}" />
        <property name="username" value="${Globals.UserName}"/>
        <property name="password" value="${Globals.Password}"/>
        <property name="maxActive" value="${Globals.maxActive}"/>
        <property name="maxIdle" value="${Globals.maxIdle}"/>
        <property name="maxWait" value="${Globals.maxWait}"/>
    </bean>

pom.xml

<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
</dependency>

Commons DBCP 구조

속성 이름설명
initialSizeBasicDataSource 클래스 생성 후 최초로 getConnection() 메서드를 호출할 때 커넥션 풀에 채워 넣을 커넥션 개수
maxActive동시에 사용할 수 있는 최대 커넥션 개수(기본값: 8)
maxIdle커넥션 풀에 반납할 때 최대로 유지될 수 있는 커넥션 개수(기본값: 8)
minIdle최소한으로 유지할 커넥션 개수(기본값: 0)
  • maxActive >= initialSize
  • maxIdle >= minIdle
  • maxActive = maxIdle

0개의 댓글