Spring BootとKotlinでRDBへアクセスする方法いろいろ


Javaと違いKotlinの場合は、JDBCを直接つかってもあまり冗長にならない

データ取得方法3パターン

EntityManager

@Component
class Clazz1(
  private val entityManager: EntityManager
){
  fun procSomething() {
    val rows = entityManager.createNativeQuery("""
      select id, field1 from table1
    """).let { q ->
      q.resultList
    }
  }
}

JpaRepository

@Entity
data class AnyData(
  val id: Long,
  val field1: String
)

interface AnyDataRepository : JpaRepository<AnyData, Long> {
}

@Component
class Clazz1(
  private val anyDataRepository: AnyDataRepository
){
  fun procSomething() {
    val rows = anyDataRepository.findAll()
  }
}

JDBC

inline fun <T> iterator(crossinline next: () -> Boolean, crossinline value: () -> T):
        AbstractIterator<out T> = object : AbstractIterator<T>() {
  override fun computeNext() {
    if (next()) {
      setNext(value())
    } else {
      done()
    }
  }
}

@Component
class Clazz1(
  private val dataSource: DataSource
){
  fun procSomething() {
    dataSource.connection.use { con ->
      val rows = con.prepareStatement("""
        select id, field1 from table1
      """).use { pst ->
        pst.executeQuery().use { rs->
          iterator(rs::next) {
              Pair(rs.getLong(1), rs.getString(2))
          }.asSequence().toList()          
        }
      }
      ...
    }
  }
}

トランザクション3パターン

宣言的なトランザクション


interface AnyDataRepository : JpaRepository<AnyData, Long> {
    fun findOneById(id: Long): AnyData?
}

@Component
class Clazz1(
  private val entityManager: EntityManager,
  private val anyDataRepository: AnyDataRepository
){
  // メソッド内部から例外を発生させると自動でロールバックされる
  // 宣言的トランザクションだとtimeoutが使えるのも便利
  @Transactional(timeout = 5)
  fun procSomething() {
    // 行ロック(select ... for update)
    val data1 = entityManager.find(AnyData::class.java, 1, LockModeType.PESSIMISTIC_WRITE)
      ?: throw NotFoundException()

    data1.name = "name1"

    anyDataRepository.saveAndFlush(data1)

    entityManager.createNativeQuery("""
            update table1 set field1 = ? where id = ?
        """).let { q ->
        q.setParameter(1, "value1")
        q.setParameter(2, 1)
        q.executeUpdate()
    }
    entityManager.flush()

    // 備考:メール送信などのロールバック不能な処理は一番最後に置くと良い
    if(!sendMail(from, to, subject, body)){
      throw InternalException()
    }
  }
}

明示的なトランザクション

import javax.persistence.EntityManager
import org.springframework.transaction.support.TransactionTemplate
import org.springframework.transaction.PlatformTransactionManager

@Component
class Clazz1(
  private val entityManager: EntityManager,
  private val anyDataRepository: AnyDataRepository,
  private val transactionManager: PlatformTransactionManager
){
  fun procSomething() {
    TransactionTemplate(transactionManager).execute {
      val data1 = AnyData()
      data1.name ="name"
      anyDataRepository.saveAndFlush(data1)

      entityManager.createNativeQuery("""
            update table1 set field1 = ? where id = ?
        """).let { q ->
        q.setParameter(1, "value1")
        q.setParameter(2, 1)
        q.executeUpdate()
      }
      entityManager.flush()
    }
  }
}

JDBCトランザクション

@Component
class Clazz1(
  private val dataSource: DataSource
){
  fun procSomething() {
    dataSource.connection.use { con ->
      con.autoCommit = false
      try {
        _procSomething(con)
        con.commit()
      } catch (ex: Exception) {
        con.rollback()
      }
    }    
  }
  fun _procSomething(con: Connection) {
    con.prepareStatement("""
      update table1 set field1 = ? where id = ?
    """).use { pst ->
      pst.setString(1, "value1")
      pst.setLong(2, 1)
      q.executeUpdate()
    }
  }
}

行ロック(select for update)の方法2パターン

val data1 = entityManager.find(AnyData::class.java, 1, LockModeType.PESSIMISTIC_WRITE)
  ?: throw NotFoundException()
val data1 = anyDataRepository.findOneById(1) ?: throw NotFoundException()

entityManager.lock(data1, LockModeType.PESSIMISTIC_WRITE)