(▲ https://hackmd.io/@ddubson/rkn-sR4wU)

JDBC

  • Java Database Connectivity(JDBC)는 DBMS에 접근하기 위한 Java의 표준 API 스펙

  • JDBC 드라이버 로딩, DBMS 연결, SQL 전송 및 결과 반환 등의 처리

Example
fun main() {
	val driver = "com.mysql.cj.jdbc.Driver"
	val url = "jdbc:mytsql://localhost:3306/test"

	Class.forName(driver).newInstance()
	val con: Connection = DriverManager.getConnection(url, "root", "root")
	val st: Statement = con.createStatement()

	val sql = "SELECT * FROM member"
	val rs: ResultSet = st.executeQuery(sql)

	while(rs.next()) {
		const name = rs.getString(1)
		const date = rs.getString(2)

		println("$name - $date")
	}

	rs.close()
	st.close()
	con.close()
}

구성요소

jdbc components

Driver

import java.sql.Driver
  • Driver 인터페이스를 구현하여 DB와 연결할 JDBC Driver를 만들 수 있음

  • com.mysql:mysql-connector-j와 같은 라이브러리가 해당 DBMS에 맞는 드라이버를 구현함

Connection

import java.sql.Driver
import java.sql.Connection

val con: Connection = if (usingDriverManager) {
    // 방법 1 - DriverManager
    val driver = "com.mysql.cj.jdbc.Driver"
    Class.forName(driver).getDeclaredConstructor().newInstance()
    DriverManager.getConnection(url, user, password)
} else {
     // 방법 2 - MysqlDataSource
    MysqlDataSource().apply {
        setUrl(url)
        setUser(user)
        setPassword(password)
    }.connection
}
  • 특정 DB와의 연결정보를 가지는 인터페이스

  • DriverManager.getConnection() 를 통해 JDBC 연결 생성

  • 특정 database와 jdbc driver에 의존함

  • 연결이 되면 Stagement 를 통해 쿼리는 실행할 수 있음

Statement

import java.sql.Statement

val st: Statement = con.createStatement()
  • SQL Query를 DB에 전송하는 방법을 정의한 인터페이스

  • Connection 을 통해 가져옴

  • SQL 서버는 쿼리가 받으면 아래 단계를 거침

    QueryExecutionPhases

  • 위에서 1~3 단계를 캐싱하고 사용함

    PrepareStatement

  • 참고1, 참고2

PreparedStatement
import java.sql.PreparedStatement
  • Statement 의 하위 인터페이스

  • SQL을 미리 컴파일하여 실행 속도를 높힘

ResultSet

import java.sql.ResultSet

statement.executeQuery().let { rs: ResultSet ->
  Entity(
    id = resultSet.getLong(1),
    name = resultSet.getString(2),
  )
}
  • Statement 를 통한 쿼리 실행 결과에 사용되는 인터페이스

동작 분석

어떻게 데이터를 ResultSet 에 가져오는가?

  • mysql testcontainer 사용한 test case에서 디버깅해봤을 때

    • ResultSet 의 구현체는 com.mysql.cj.jdbc.result.ResultSetImpl 사용함

    • 쿼리 실행시 rowData: ResultsetRows (실제 인스턴스는 ResultsetRowsStatic)필드의 rowsRow 인스턴가 저장되어 있음 Row 인스턴스는 ByteArrayRow 이고, internalRowData 가 byte-array를 가지고 있음

      jdbc debug

    • DB와 무언가를 통한 통신을 통해 데이터를 byte array로 가져와서 저장하는 것으로 보임.

      • TODO: cursor는 그럼 어떻게 동작할까?

        // MySQL에서 이 설정이 있다면 스트리밍 방식으로 읽는 듯
        // https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-implementation-notes.html
        val stmt = con.createStatement(
            java.sql.ResultSet.TYPE_FORWARD_ONLY,
            java.sql.ResultSet.CONCUR_READ_ONLY,
        ).apply {
            fetchSize = Integer.MIN_VALUE
        }

ConnectionPool

  • 일정량의 Connection 인스턴스를 미리 만들어서 pool에 저장해두고 사용하기 위함

  • HikariCP

    • DBCP(Database Connection Pool) 라이브러리

      val config = HikariConfig().apply {
          jdbcUrl = url
          username = user
          this.password = password
      }
      val ds = HikariDataSource(config)
      val con: Connection = ds.connection

spring-jdbc

org.springframework.boot:spring-jdbc:5.2.9.RELEASE
> org.springframework:spring-beans:5.2.9.RELEASE
> org.springframework:spring-core:5.2.9.RELEASE
> org.springframework:spring-tx:5.2.9.RELEASE
  • spring-jdbc

  • JDBC에서 처리하는 데이터베이스 관련 작업들을 스프링 프레임워크로 위임하고, 별도 API를 통해 데이터베이스 연결 및 쿼리 실행을 함.

구성요소

@startuml
hide empty field
hide empty method

interface JdbcOperations {
  query(psc, rowMapper): List<T>
}
class JdbcTemplate

interface NamedParameterJdbcOperations
class NamedParameterJdbcTemplate {
  -classicJdbcTemplate: JdbcOperations
}

abstract class JdbcAccessor {
  -dataSource: DataSource
}

JdbcOperations <|-- JdbcTemplate
JdbcAccessor <|-- JdbcTemplate
NamedParameterJdbcOperations <|-- NamedParameterJdbcTemplate

NamedParameterJdbcTemplate --> JdbcOperations

@enduml

JdbcTemplate

  • DataSource 를 생성하고 JdbcTemplate 에 주입하여 사용.

  • JdbcTemplate 를 통해 JDBC를 편리하게 사용할 수 있음.

  • JdbcTemplate: 가장 저수준에서 동작하며, Spring 내부적으로 JdbcTemplate 을 사용함

    • thread-safe 하므로, DAO등에서 맴버 변수로 저장. DataSource 만 외부에서 유입받아 초기화해둘 수 있음.

NamedParameterJdbcTemplate
  • NamedParameterJdbcTemplate: JdbcTemplate 을 래핑해서 ? 가 아닌 이름이 붙은 파라미터 사용할 수 있게함.

RowMapper

  • RowMapper: ResultSet(쿼리 결과)에서 원하는 객체로 타입을 변환하는 역할

  • BeanPropertyRowMapper

    • DataClassRowMapper

spring-boot-starter-jdbc

org.springframework.boot:spring-boot-starter-jdbc:2.3.4.RELEASE
> com.zaxxer:HikariCP:3.4.5
> org.springframework.boot:spring-boot-starter:2.3.4.RELEASE
> org.springframework:spring-jdbc:5.2.9.RELEASE
  • spring-jdbc에 대하여 스프링부트 의존성 관리를 한번에 하고자 wrapping된 모듈.

ORM

  • ORMObject Relational Mapping

  • TODO

spring-data-jdbc

org.springframwork.data:spring-data-jdbc:2.0.4.RELEASE
> org.slf4j-api:1.7.30
> org.springframework.data:spring-data-commons:2.3.4.RELEASE
> org.springframework.data:spring-data-relational:2.0.4.RELEASE
> org.springframework:spring-beans:5.2.9.RELEASE
> org.springframework:spring-context:5.2.9.RELEASE
> org.springframework:spring-core:5.2.9.RELEASE
> org.springframework:spring-jdbc:5.2.9.RELEASE
> org.springframework:spring-tx:5.2.9.RELEASE
  • Spring Data의 미션은 데이터 액세스를 위해 친숙하고 일관된 Spring 기반의 프로그래밍 모델을 제공하는 동시에 기본 데이터 저장소의 특수한 특성을 유지하는 것.

  • spring data repository 추상화의 목표는 데이터 액세스 레이어를 구현하는 데 필요한 상용구 코드의 양을 줄이는 것

  • Spring Data JDBC는 Spring Data의 여러 모듈중 하나로, 말 그대로 JDBC를 지원하는 모듈.

    • 뭔가 문서가 JPA인것 같은데…​? 엔티티매니저 가지고 더티체킹하는게 큰 차이?

  • CrudRepository 를 활용하여 기본적인 CRUD 구현을 쉽게 함.

  • 중신에 Repository 인터페이스가 있고, CrudRepository, ListCurdRepository 인터페이스와 같이 엔티티 클래스에 대한 정교한 CURD기능을 제공

    • CurdRepository 인터페이스는 Iterable 반환

    • ListCurdRepository 인터페이스는 List 반환

구성요소

@startuml
hide empty field
hide empty method

interface Repository

interface CurdRepository {
  save(eneity: S)
  saveAll(entities: Iterable<S>)
  findById(id: ID)
  existsById(id: ID)
  findAll()
  findAllById(ids: Iterable<ID>)
  count()
  deleteById(id: ID)
  delete(entity: T)
  deleteAllById(ids: Iterable<? extends ID>)
  deletaAll(entities: Iterable<? extends T>)
  deleteAll()
}

interface PagingAndSortingRepository {
  findAll(sort: Sort): Iterable<T>
  findAll(pageable: Pageable): Page<T>
}

class SimpleJdbcRepository

interface JdbcAggregateOperations
class JdbcAggregateTemplate

Repository <|-- CurdRepository
CurdRepository <|-- SimpleJdbcRepository
PagingAndSortingRepository <|-- SimpleJdbcRepository

SimpleJdbcRepository ..> JdbcAggregateOperations

interface DataAccessStrategy
interface JdbcConverter

JdbcAggregateOperations <|-- JdbcAggregateTemplate
JdbcAggregateTemplate ..> DataAccessStrategy
JdbcAggregateTemplate ..> JdbcConverter

@enduml

Repository

CurdRepository

EntityRowMapper

  • spring-jdbc의 RowMapper의 구현체

JdbcConverter

JdbcValue

spring-boot-starter-data-jdbc

org.springframework.boot:spring-boot-starter-data-jdbc:2.3.4.RELEASE
> org.springframework.boot:spring-boot-starter-jdbc:2.3.4.RELEASE
> org.springframework.data:spring-data-jdbc:2.0.4.RELEASE

spring-jdbc-plus

  • 네이버에서 제공하는 Spring Data JDBC 확장 라이브러리.

References


findById

  1. CrudRepository 구현체인 SimpleJdbcRepository

    1. JdbcAggregateOperations 필드의 findById 메서드 실행됨

    2. JdbcAggregateOperations 구현체인 JdbcAggregateTemplate

      1. DataAccessStrategy 필드의 findById 메서드 호출

        1. DataAccessStrategy 구현체인 DefaultDataAccessStrategy

        2. SqlGeneratorSource 필드의 getSqlGenerator 메서드 통해서 쿼리 생성

        3. SqlParamgersFactory 필드의 forQueryById 메서드 통해서 쿼리 파라미터 생성

        4. getEntityRowMapper 메서드 통해 RowMapper 생성

        5. operations: NamedParameterJdbcOperations 필드의 queryForObject 메서드 통해서 쿼리 실행 (w/ rowMapper)

          1. converter: JdbcConverter 필드의 query 메서드 호출

          2. execute 메서드 호출

          3. DateSourceUtils.getConnection(DataSource) 메서드 통해서 Connection 생성

          4. doInPreparedStatement 실행

            1. PreparedStatement#executeQuery 메서드 통해서 ResultSet 생성

              1. update

              2. AbstractQueryProtocol#executeQuery

              3. ComQuery.sendSubCmd

          5. 쿼리 결과인 result 생성

      2. triggerAfterConvert 메서드 실행


  • 실제 DB로부터 어떻게 값이 오는지?

  • reading converter가 동작할 수 있는지?

  • spring batch에서 cursor는 어떻게?