알게 모르게 한 번이라도 들어본 JDBC를 정리해보았습니다.

 

Java Database Connectivity?

  • Java와 여러 가지 데이터베이스 간의 연결을 위하여 제공되는 표준 인터페이스이다.
  • DB 벤더나, 써드파티 관련 라이브러리에서 JDBC를 구현한 드라이버를 제공한다.
  • DB 데이터 접근을 위해 계층 처리 모델을 제공한다. (기본 2계층 사용, 3계층 지원)

데이터베이스 벤더마다 각각의 SQL 문, 작성 방식을 사용함으로써 발생했던 문제점을 해결하였다.

  • 달랐던 메서드, 구조, 전역 변수 등의 모든 문법을 통일시켜 API로 명세한 것이다.

 

JDBC 구성 요소

Untitled (34)

 

JDBC DriverManager

  • DB의 드라이버 목록을 관리하는 클래스
  • Java Application의 연결 요청을 적절한 DB Driver와 매핑시킨다.
  • 이는 Driver가 가지고 있는 고유한 명칭 즉 ClassName을 이용하여 선택하게 된다.

 

DatabaseDriver

  • DB 서버와의 통신을 처리한다.

 

Connection

  • DB와 연결하기 위한 모든 메시지가 포함된 인터페이스
  • DB와의 통신은 연결(Connection) 객체를 통해서만 이루어진다.

 

Statement

  • 해당 인터페이스의 구현체가 SQL 문을 DB에 전달한다.
  • 일부 파생 인터페이스는 저장 프로시저를 실행하는 것 외에도 매개 변수를 허용한다.

 

PreparedStatement

  • Statement의 하위 인터페이스.
  • Statement의 실행 절차, 자원 사용 부분에 대하여 최적화를 진행하였다.

 

ResultSet

  • Statement 객체를 사용하여 SQL 쿼리를 실행한 뒤 데이터베이스에서 검색, 반환된 데이터를 저장한다. (쿼리에 대한 결과를 나타낸다.)
  • 데이터를 이동시키는 일종의 이터레이터 역할을 한다.

 

SQLException

  • 해당 클래스는 DB에서 발생하는 모든 오류를 나타낸다.
  • 각 벤더(Database)에서 제공하는 오류 코드를 Application에 그대로 전달한다.

 

JDBC의 처리 흐름

  • JDBC 클래스가 포함된 패키지를 로드하고 JDBC 드라이버를 등록하여야 한다.
  • DriverManager.getConnection() 혹은 datasource.getConnection() 를 이용하여 Connection 객체를 얻는다.
  • Statement 타입의 객체를 사용하여 쿼리를 실행한다.
  • 결과가 있는 경우 반환된 ResultSet.getXXX() 메서드를 통하여 데이터를 추출한다.
  • close() 를 통하여 사용한 객체들에 대한 자원을 풀에 반환한다.

Untitled (35)

Connection과 PreparedStatement 객체는 보통 Pool 방식으로 운영되며, 미리 정의된 제한된 수의 자원을 만들어두고 필요할 때 할당하고 반환하면 다시 풀에 넣는 방식으로 동작한다.

 

close()가 동작하지 않으면 풀에 저장된 자원은 계속해서 줄어들게 되고, 고갈됨으로써 문제를 일으키게 된다.

코드를 작성할 때에는 예외처리를 하여야 하며, 어떤 상황이 발생하더라도 자원이 반환되게 코드를 작성하여야 한다.

 

 

 

모든 상황에서 리소스를 반환하기 위한 예외처리 방법

 

try-catch-finally 방식

복잡한 흐름을 가짐으로써 가독성을 떨어트리게 된다

Connection con = null; 
PreparedStatement ps = null; 
try { 
	con = dataSource.getConnection(); 
    ps = con.prepareStatement("SQL"); 
    ps.executeUpdate(); 
} catch (SQLException exception) { 
	exception.printStackTrace(); 
} finally { 
	// 리소스 반납 코드 
   	try { 
    	if (ps != null) { 
        	ps.close(); 
        } 
        if (con != null) { 
        	con.close(); 
        } 
    } catch (SQLException exception) { 
    	exception.printStackTrace(); 
}

 

 

try-with-resources 방식

해당 방식은 내부적으로 close를 호출함으로써 자원을 반환한다.

try (Connection con = dataSource.getConnection(); PreparedStatement ps = con.prepareStatement("SELECT * FROM USER")){ 
	ResultSet resultSet = ps1.executeQuery(); 
} catch (SQLException exception) { 
	exception.printStackTrace(); 
}

 

 

JDBC Template

Spring은 JDBC의 반복되는 보일러 플레이트 코드를 템플릿, 콜백 패턴을 적용한 라이브러리인 Spring JDBC Template 을 제공한다. JDBC의 복잡했던 사용 방식을 저수준의 모듈, 메서드에 모아놓고 호출, 사용한다.

 

 

JDBC Template는

  • DB 연결, SQL 질의에 대한 사용한 자원을 내부적으로 정리하고 Pool에 반환한다.
  • JDBC SQLException (checked)을 RuntimeException으로 변환하여 제공함으로써, 유연한 대응을 할 수 있게끔 지원한다. (예외 처리 지원)
  • 다양한 SQL 질의 방식과 결과 추출을 제공한다.
    • ResultSetExtractor : 단일 객체에 대한 데이터 바인딩 제공 (Post, Member ....)
    • RowMapper : 객체 목록에 대한 데이터 바인딩 제공 (List, Map ....)
  • 멀티 스레드 환경에서 안전한 객체를 제공한다.
    • 객체의 행위에 대해 내부 상태변수 이용 없이 전달받은 것들을 사용하고 정리함을 의미한다.

 

템플릿 패턴?

객체의 행위(로직)에 대한 구조(템플릿)을 정의하고 일부 단계를 추상적으로 만들어 하위 클래스들에서 정의되게끔 한다.

 

이렇게 구현한 메서드는 사용자가 필요에 따라 재정의(선택)한 객체, 메서드를 호출하게끔하여 상호작용을 이용해 유연함을 제공한다.

  • 행위의 구조를 미리 구현한다.
  • 행위의 구조를 수정하지 않고도 하위 클래스를 통해 유연하게 변경하게끔 한다. (추상화)
  • 행위에 대한 유연한 구현 부를 제공함으로써 코드 재사용, 중복을 최소화한다.
  • 이를 통해 구현된 템플릿을 여러 지점에서 활용할 수 있다.

 

콜백 패턴?

호출된 객체(템플릿)가 호출한 객체(클라이언트)를 호출하는 흐름을 구현하는 패턴이다.

  • 객체가 다른 객체를 호출하면서 그 인자 값 중에 콜백 객체를 전달한다.
  • 해당 콜백 객체는 호출된 객체의 행위 중에 필요한 데이터가 있을 때 사용되어 호출한 객체에 정보를 요청하고 반환 데이터를 호출된 객체에 제공한다.
  • 구현된 방식에 따라서 콜백 객체가 최종 결과를 전달하게끔 할 수 있다.

 

JdbcTemplate의 구성 객체

 

JdbcTemplate

  • JDBC Template의 Core Class
  • JDBC 사용을 단순화하고 일반적인 예외를 방지하는 데 기능을 제공한다.
  • 설정된 Datasource 를 사용하여 JDBC Template 객체를 생성하게 된다.

 

PreparedStatementSetter

  • JDBC Template Class가 사용하는 Callback Interface
  • JDBC Template가 생성한 PreparedStatement의 매개 변수 값을 설정한다.

 

ResultSetExtractor

  • JDBC Template 질의 결과를 추출하는데 사용되는 Interface
  • ResultSet에서 결과를 추출하는 실제 작업을 수행한다.
  • 내부적으로 하나의 객체에 대해서 데이터를 Binding할 때 사용되는 Interface이다.

 

RowMapper

  • JDBC Template 질의 결과를 추출하는데 사용되는 Interface
  • 쿼리 질의 결과에 각각의 행들을 객체에 Mapping 하는데 사용된다.

 

그외에도

  • NamedParameterJdbcTemplate : ?를 통한 값 대입 대신 param 형식을 사용할 때 이용한다.
// 기존 방식 
String final query = "INSERT INTO PERSON(ID, NAME) VALUES(?, ?)"; 
Object[] args = new Object[] {1L, "Lob"}; 
template.update(query, args); 

// Param 방식 
String final query = "INSERT INTO PERSON(ID, NAME) VALUES(:id, :name)"; 
Map<String,Object> params = new HashMap<String,Object>(); 
params.put("id", 1L); 
params.put("name", "lob"); 
template.update(query, params);

 

  • SimpleJdbcTemplate : JdbcTemplate와 NamedParameterJdbcTemplate 에서 주로 사용되는 기능을 포함하는 인터페이스이다.
    • query, queryForXXX (Type, Collections), Update들이 포함되었다.

 

  • SimpleJdbcInsert, SimpleJdbcCall
    • 테이블 이름, 열 이름, 열값 이나 호출 프로시저, 함수명과 매개 변수만을 제공하게끔 구성되어있다.
  • INSERT 문과 프로시저 호출을 진행할 때 필요한 코드들을 단순화하기 위해 만들어진 클래스이다. 메타 데이터 처리를 방식을 제공한다.

 

  • MappingSqlQuery하위 클래스는 mapRow 메서드를 구현하여야 한다.
  • ResultSet의 각 행을 객체로 변환하기 위해 사용되는 추상 클래스이다.

 

  • SqlUpdateSetter를 통해 객체를 구성한 후에는 멀티스레드 환경에서 안전한 사용을 할 수 있다.
  • SQL 업데이트임을 나타내는 재사용 가능한 객체이며, 여러 형식의 Update 메서드를 제공한다.

 

  • StoredProcedure해당 타입의 서브클래스는 execute 메서드를 통해 저장 프로시저를 호출한다.
  • 관계형 데이터베이스의 저장 프로시저를 추상화한 클래스이다.

등이 존재한다.

 

Statement vs PreparedStatement

Statement의 Flow?

  1. 쿼리 문장의 분석
  2. 쿼리 컴파일
  3. 쿼리 실행

Statement는 매 작업시 위의 작성된 flow를 따르게 된다.

 

PreparedStatement는 이미 반환한 트랜잭션에 대해서는 (주로 조회성) 캐싱을 함으로써 이미 실행한 쿼리에 대해서는 불 필요한 Flow를 진행하지 않고 바로 값을 반환하게 된다.

 

 

Statement의 String SQL?

  • Statement 객체는 SQL 쿼리를 문자열 변수를 통하여 받게된다. Param들에 대해서도 문자열 연산을 통하여 제공받아야 되는데 이는
    • 코드의 가독성을 떨어트리고
    • SQL 인젝션에 취약한 환경을 만들며
    • 쿼리의 최적화가 되지 않는다.
String final query = "INSERT INTO PERSON(ID, NAME) VAULES("+postPerson.getId()+" ","+postPerson.getName()+")";

 

 

PreparedStatement의 String SQL?

  • PreparedStatement는 인자로 넘기는 문자열에 대하여서 parameters 기능을 제공한다.코드가 더 길어보이지만, 가독성을 생각하면 좀 더 나은 모습을 보인다.
String final query = "INSERT INTO PERSON(ID, NAME) VALUES(?, ?)"; 
PreparedStatement pr = con.preparedStatement(query); 

for (PostPerson person : PostPersons){ 
	pr.setInt(1, person.getId()); 
    pr.setString(2, person.getName()); pr.addBatch(); 
} 
preparedStatement.executeBatch();

 

 

PreparedStatement의 사전 컴파일, 일괄 실행?

 

사전 컴파일?

  • DB가 SQL을 받았을 경우 우선적으로 캐시를 확인하게끔 동작한다.
  • 캐시가 존재한다면 해당 정보를 제공하고, 캐시되지 않은 경우 쿼리를 실행하고 데이터를 캐싱한다.
  • 해당 기능은 non-SQL binary protocol 를 내부적으로 사용함으로써 DB와 JVM간의 통신 속도를 높인다. (패킷에 데이터가 적기 때문에 서버 간의 통신이 빨라진다. )

 

일괄 실행?

  • 대용량 쿼리를 한번에 전송 가능한 기능이다.
  • addBatch를 통하여 쿼리를 메모리에 적재하고, executeBatch를 통하여 한번에 전송한다. (내부적으로 ArrayList 를 활용한다.)
    • 매번 통신을 연결하고 데이터 전달을 진행하는 리소스들을 줄인다.
    • 한번에 여러 쿼리를 질의함으로써 불필요한 절차를 제거하고 빠르게 결과를 받는다.
// StatementImpl protected Query query; 
// mysql sub class ClientPreparedStatement 

public void addBatch() throws SQLException { 
	try { 
    	synchronized(this.checkClosed().getConnectionMutex()) { 
        	QueryBindings<?> queryBindings = ((PreparedQuery)this.query).getQueryBindings(); 
            queryBindings.checkAllParametersSet(); 
            this.query.addBatch(queryBindings.clone()); 
        } 
    } catch (CJException var6) { 
    	throw SQLExceptionsMapping.translateException(var6, this.getExceptionInterceptor()); 
    }     
} 

public void addBatch(String sql) throws SQLException { 
	try { 
    	synchronized(this.checkClosed().getConnectionMutex()) { 
        	this.batchHasPlainStatements = true; 
            super.addBatch(sql); 
    	} 
    } catch (CJException var6) { 
    	throw SQLExceptionsMapping.translateException(var6, this.getExceptionInterceptor()); 
    } 
} 

// AbstractQuery 
public void addBatch(Object batch) { 
	if (this.batchedArgs == null) { 
    	this.batchedArgs = new ArrayList(); 
    } 
    this.batchedArgs.add(batch); 
}

 

결론

PreparedStatement는 Statement가 가지고 있던 불필요한 절차, 리소스 사용, Non-Caching 등의 요소들을 최적화한 하위 클래스이다. DB와의 쿼리 성능이 중요한 Server든 아니든간에 유의미한 성능차이를 보이므로, PreparedStatement를 사용하는 것이 좋다.

 

참고자료

+ Recent posts