iT邦幫忙

2022 iThome 鐵人賽

DAY 27
0

關連式資料庫 (RDB) 也是一種常見的 backing serivce, 在 Java 的世界中常以 JDBC 作為關連式資料庫存取的方式, 或是也會在其上作一層 ORM。所以 Quarkus 有相關的 JDBC 的操作, 也有 HIBERNATE ORM 的整合。

SQL or ORM

ORM 很像是 Java 開發者說,我的世界只能有物件,什麼資料庫的表、主鍵、外鍵我不想管而弄出來的複雜框架,有一定的學習曲線。但就大多數的狀況 ORM 只能符合很特定的情境。太複雜的查詢還是用 SQL 比較直接,太簡單又覺得何必引入。回頭想想是不是利用了 ORM 反增加了開發或是除錯的複雜度

總而言之,這篇就是要講用 JDBC,關於是否需要 ORM 有不少文章在討論就先打住

https://ionutbalosin.com/2021/12/do-we-really-need-an-orm/

Quarkus Reactive to Coroutines

Quarkus 有自帶 reactive-sql-clients, 所以可以用這個配合 awaitSuspending(),轉成 coroutines 的操作。

範例說明

這裡會用前幾天的 Film 範例,把資料庫改接 PostgreSQL, 用 reactive-sql-clients

pom.xml dependency 修改

把 Mongo dependency 換成

    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-reactive-pg-client</artifactId>
    </dependency>

FilmRepository 修改

Row -> Film

先在 FilmRepository 修改 from function , 改成 資料庫的 (Row) -> Film ,有一個重點是欄位名要用小寫才會取得到。這點要注意一下。

import io.vertx.mutiny.sqlclient.Row
import tw.brandy.ironman.entity.Film
    private fun from(row: Row) = Either.catch {
        Film(
            row.getString("title"),
            row.getLong("episodeid").toInt(),
            row.getString("director"),
            row.getLocalDate("releasedate"),
            row.getString("updater")
        )
    }.mapLeft { AppError.DatabaseProblem(it) }

find All

    suspend fun findAll(): Either<AppError, List<Film>> = Either.catch {
        client.preparedQuery("SELECT episodeId,title,director,releaseDate,updater FROM films")
            .execute().awaitSuspending() //(1)
    }.mapLeft { e -> AppError.DatabaseProblem(e) }
        .flatMap { list -> list.traverse(::from)   //(2) }
  • (1) 的動作就是在 execute 後,接 .awaitSuspending()
  • (2) 用 traverse 遍歷 (Row) -> Film

Insert, Update

    private fun toFilmTuple(film: Film): Tuple = //(1)
        Tuple.of(film.episodeID, film.title, film.director, film.releaseDate, film.updater)

    suspend fun add(film: Film): Either<AppError, Film> = Either.catch {
        client.preparedQuery(
            """ INSERT INTO films (episodeId,title,director,releaseDate,updater) 
                 VALUES ($1,$2,$3,$4,$5)"""
        ).execute(toFilmTuple(film)).awaitSuspending()
    }.mapLeft { AppError.DatabaseProblem(it) }.map { film }

Tuple 為有限個元素所組成的序列,在 FP 中很常出現,這裡帶一下 Kotlin 自身只有 Pair, Triple, 再往上是建議用直接用 data class, 這個是用 Mutiny 的 tuple 是可以到6個,再往上要用 Tuple.wrap(listOf(....))

所以剩下的 delete 也可以如法炮製

Count

計算個數的 sql 要用 count ,這裡會給一個 cnt 的別名方便抓取


    suspend fun count(): Either<AppError, Long> = Either.catch {
        client.preparedQuery("SELECT count(*) cnt FROM films")
            .execute().awaitSuspending()
    }.mapLeft { AppError.DatabaseProblem(it) }.map { it.first().getLong("cnt") }

FilmService

在新增或修改後。以 id 取得資料才算成功

    suspend fun save(film: Film) = filmRepository.add(film)
        .flatMap { filmRepository.findByEpisodeId(film.episodeID) }
        
    suspend fun update(film: Film) = filmRepository.update(film)
        .flatMap { filmRepository.findByEpisodeId(film.episodeID) }

不同的資料庫來源差別

不同的 database reactive 要引入不同的資料庫實作,還有佔位符有些是 ? 就要照出現的順序使用

Database Extension name Pool class name Placeholders
IBM Db2 quarkus-reactive-db2-client io.vertx.mutiny.db2client.DB2Pool ?
MariaDB/MySQL quarkus-reactive-mysql-client io.vertx.mutiny.mysqlclient.MySQLPool ?
Microsoft SQL Server quarkus-reactive-mssql-client io.vertx.mutiny.mssqlclient.MSSQLPool @p1, @p2, etc.
Oracle quarkus-reactive-oracle-client io.vertx.mutiny.oracleclient.OraclePool ?
PostgreSQL quarkus-reactive-pg-client io.vertx.mutiny.pgclient.PgPool $1, $2, etc.

相關的程式碼已放置

https://github.com/hmchangm/getting-start-QK/tree/ironman2022-d26-jdb


上一篇
微單體 Quarkus 與 React Route 前後端整合
下一篇
Quarkus x Kotlin 呼叫 RESTful Service
系列文
Quarkus, Kotlin, Reactive 雲原生服務開發32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言