scala jdbc/scalikejdbc

Scalikejdbc 를 이용해 DB 데이터 읽기

wefree 2021. 11. 14. 21:35

문제

MySQL test.person 테이블 스키마와 들어 있는 데이터는 아래와 같다.

CREATE table person
(
 name varchar(128),
 age INT
)
name age
a 10
b 20

 

test.person 에 저장된 데이터를 scalikejdbc 를 이용해 읽어보자

 

코드

build.sbt 에 아래 라이브러리를 추가한다.

libraryDependencies ++= Seq(
  "org.scalikejdbc" %% "scalikejdbc"          % "4.0.0",
  "org.scalikejdbc" %% "scalikejdbc-config"   % "4.0.0",
  "org.postgresql"  % "postgresql"            % "42.5.0",
  "mysql"           % "mysql-connector-java"  % "8.0.27"
)

 

코드 작성

connectionTimeoutMillis 는 pool.borrow() timeout 값으로 필요에 따라 충분히 크게 설정한다.

poolConnectionTimeoutMillis defines the amount of time a query will wait to acquire a connection before throwing an exception. This used to be called connectionTimeoutMillis
import scalikejdbc._

case class Person(name: String, age: Int)
object Person extends SQLSyntaxSupport[Person] {
  override def tableName: String = "person"
  def apply(rs: WrappedResultSet): Person = Person(
    rs.string("name"),
    rs.int("age")
  )
}

object ScalikeTest {
  def main(args: Array[String]): Unit = {
    Class.forName("org.postgresql.Driver")
    // Class.forName("com.mysql.jdbc.Driver")
    
    val settings = ConnectionPoolSettings(
      initialSize = 4,
      maxSize = 20,
      connectionTimeoutMillis = 120000L,
      validationQuery = "select 1"
    )
    ConnectionPool.singleton("jdbc:postgresql://localhost:5432/postgres", "user_id", "user_password", settings)
    // ConnectionPool.singleton("jdbc:mysql://db.host:3306/test", "user_id", "user_password", settings)
    
    implicit val session       = AutoSession
    
    // DB.autoCommit(), DB.localTx()
    val entities: List[Person] = DB.readOnly(implicit session => sql"select * from person".map(rs => Person(rs)).list().apply())
    // val entities: List[Person] = sql"select * from person".map(rs => Person(rs)).list().apply()

    for {
      e <- entities
    } println(s"${e.name} ${e.age}")
  }
}

 

출력 결과

a 10
b 20

 

설명

val name = "Alice"

val personAge: Option[Int] = DB.readOnly { implicit session =>
  sql"select age from person where name = ${name}" // don't worry, prevents SQL injection
    .map(rs => rs.int("age")) // extracts values from rich java.sql.ResultSet
    .single                   // single, list, traversable
    .apply()                  // Side effect!!! runs the SQL using Connection
}

 

Internals

기본적으로 아래와 같은 구조이다.

Global object 인 ConnectionPool 에 각각의 connection pool 을 이름(예: MyPool) 과 함께 등록하고, NamedDB 로 사용한다.

import scalikejdbc._

Class.forName("...")
val settings: ConnectionPoolSettings = ConnectionPoolSettings(...)
ConnectionPool.add("MyPool", ..., settings)

implicit val session: DBSession = NamedAutoSession("MyPool")
NamedDB("MyPool").???

보통 connection pool 을 하나만 등록하기 때문에 connection pool 이름을 default 로 지정할 수 있다.

라이브러리 차원에서 connection pool 이 하나일 때 아래처럼 편히 쓰도록 지원한다. (내부적으로 pool 이름을 default 로 처리)

 

import scalikejdbc._

Class.forName("...")
val settings: ConnectionPoolSettings = ConnectionPoolSettings(...)
ConnectionPool.singleton(..., settings)
// ConnectionPool.add("default", ..., settings)

implicit val session: DBSession = AutoSession
// implicit val session: DBSession = NamedAutoSession("default")

DB.readOnly.???
// NamedDB("MyPool").readOnly.???