1. 序章

データベースアクセスは、多くのソフトウェアシステムの重要な部分です。 JVMはJDBCを提供します。これを使用して、その対話を容易にすることができます。 この記事では、 doobie –Scala用の純粋に機能的なJDBCレイヤーについて見ていきます。

doobieはCatsおよびCatsEffect ライブラリを使用するため、doobieを使用する前にそれらにある程度精通していることが重要です。

2. 設定

プロジェクトにdoobieを追加するには、sbtを使用します。

libraryDependencies += "org.tpolecat" %% "doobie-core" % "1.0.0-RC1"

これは、doobieを使用する必要がある唯一の依存関係ですが、データベースとしてPostgreSQLを使用するため、PostgreSQL拡張機能も追加しましょう。

libraryDependencies += "org.tpolecat" %% "doobie-postgres" % "1.0.0-RC1"

この紹介では、 worldデータベースがロードされたPostgreSQLを使用します。具体的には、cityテーブルを使用します。

create table city (
    id           integer not null,
    name         text    not null,
    country_code char(3) not null,
    district     text    not null,
    population   integer not null
);

3. doobieを使用する

プロジェクトにdoobieが追加されたので、Transactionorを使用してデータベース接続を作成する方法を見てみましょう。

3.1. データベースへの接続

Transactionor を使用してデータベースに接続することを説明しましたが、これは完全に正確ではありません。 結果のタイプTransactorは、データベースに接続する方法を知っていますが、後でこの接続をクリーンアップする方法、そして最も重要なこととして、IOConnectionを取得する方法も知っています。そしてその結果を私たちが選んだ効果に変換します–この場合、IO。

これにより、定義を実行から明確に分離できます。

val transactor: Transactor[IO] = Transactor.fromDriverManager[IO](
  "org.postgresql.Driver",
  "jdbc:postgresql://localhost:5432/world-db",
  "world",
  "world123"
)

val operations: ConnectionIO[Unit] = ???

operations.transact(transactor) // IO[Unit]

操作内のすべてのアクションは、単一のトランザクションで実行されます。

Transactor を介してデータベース操作を直接実行することは確かに機能しますが、毎回新しい接続を作成するため、効率的ではありません。 したがって、接続プールを使用でき、使用する必要があります。

3.2. クエリ

データベースから都市の名前を選択するとします。 クエリは次のように記述できます。

sql"select name from city limit 5"
  .query[String]
  .to[List]

このコードを段階的に見ていきましょう。 まず、sql補間器を使用してSQLステートメントを定義します。

sql"select name from city" // Fragment

その結果、 Fragment タイプになります。これについては、後のセクションで説明します。 次に、このSQLを特定のタイプのクエリとして実行することをdoobieに伝えることができます。

.query[String] // Query0[String]

name は文字列であるため、Stringを使用しています。 タプルまたは一致するケースクラスを型として提供することもできます。重要なのは、パラメーターの数がSQL自体で指定したものと一致する必要があることです

最後に、コレクションタイプを指定して、クエリ定義を完成させることができます。

.to[List] // ConnectionIO[List[String]]

これにより、 ConnectionIO モナドが生成され、これを使用して複数の操作を連鎖させることができます。 クエリが単一の行を返すことが確実な場合は、 .unique メソッドを使用することもできます。これにより、 ConnectionIO [String] が生成され、次の場合にも例外がスローされます。複数の行が返されます。

データベースからデータを取得する方法がわかったので、いくつかのデータの挿入と更新について考えることができます。

3.3. データの挿入、更新、および削除

データベースに新しい都市を挿入しましょう。

sql"insert into city (id, name, country_code, district, population) values (5000, 'Baeldung', 'NLD', 'Baeldungland', 1337)"
  .update
  .run

それでは、 select 操作で行ったように、このコードを分析してみましょう。 まず、SQLステートメントを使用したsql補間器があります。

sql"insert into city (id, name, country_code, district, population) values (5000, 'Baeldung', 'NLD', 'Baeldungland', 1337)"

データをStringに直接挿入しましたが、補間することもできます。

val id = 5000
sql"insert into city (id, name, country_code, district, population) values ($id, 'Baeldung', 'NLD', 'Baeldungland', 1337)"

次に、このSQLが.updateメソッドを使用して変更することをdoobieに通知します。

.update // Update0

そして、あとは.runメソッドを使用してこの操作を完了するだけです。

.run // ConnectionIO[Int]

モナド内のIntに注意してください。 この場合、影響を受ける行の数を示しますが、代わりに特定のデータを要求することもできます。

insertedId <- sql"insert into city (id, name, country_code, district, population) values (${baeldungCity.id}, ${baeldungCity.name}, ${baeldungCity.countryCode}, ${baeldungCity.district}, ${baeldungCity.population})"
  .update
  .withUniqueGeneratedKeys[Int]("id")

withUniqueGeneratedKeys を使用して、挿入された id を取得しますが、他の列を取得するためにも使用できます。 ただし、この機能が機能するには、データベースでサポートされている必要があることに注意してください

更新と削除は、データベースの状態を変更するという意味で、挿入と非常によく似ています。 そのため、同じ方法を使用できます。

sql"update city set name = 'DungBael' where id = 5000".update.run

そしてdeleteの場合:

sql"delete from city where id = 5000".update.run

3.4. フラグメント

フラグメントはdoobieの非常に重要な概念です。 それらの有用性は、例で最もよく示されています。 オプションのパラメータoptionalCityNameParamがあるとしましょう。

val optionalCityNameParam: Option[String] = Some("%Pol%")

フラグメント補間器frを使用して、オプションのFragmentを作成するようにマップできます。

val optionalCityNameFragment: Option[Fragment] = optionalCityNameParam.map(name => fr"name like $name")

最後に、クエリ内に配置できます。

val operation = (fr"select name from city" ++ whereAndOpt(optionalCityNameFragment)).query[String].to[List]

whereAndOptメソッドの使用法に注意してください。 これは、 doobie.Fragments、にある多くのヘルパーメソッドの1つですが、この特定のメソッドはオプションのフラグメントを受け入れ、Someの場合にのみ適用します。 多くのフラグメントを作成し、さまざまな方法でそれらを接続できます。 たとえば、結果を5行に制限するSQLの一部は、フラグメントである可能性があります。

val limitFragment = fr"limit 5"

他のSQLにも追加できます。

fr"select id, name, country_code, district, population from city" ++ limitFragment

これは、でSQLスニペットを簡単に再利用でき、オプションで簡単に使用できる非常に便利な機能です。 ちなみに、frsql補間器の唯一の違いは、 fr がフラグメントの後にスペースを追加して連結を支援し、sqlがはそうではありません。 したがって、 2つのSQLスニペットを結合する場合は常に、fr補間器を使用する必要があります。

3.5. エラー処理

一般に、doobieは、 IO と同様に、は、明示的に処理されない限り、エラーの伝播とエスケープを許可します。 これを行うには、Catの.attemptまたは.raiseErrorを使用できますが、doobieには独自の省略形がいくつか用意されています。 これらは主にSQLExceptionの処理に重点を置いており、状況に応じて使用されるため、これらについて説明することはこの紹介の範囲外です。 完全なリストは、doobieのscaladocにあります。

4. 結論

この記事では、doobieで基本的な操作を実行する方法を見てきました。 接続を形成する方法と、選択、挿入、削除、および更新する方法を学び、doobieフラグメントについて学びました。 ロギング、データストリーミング、IO以外のエフェクトの使用方法など、未踏のトピックがまだいくつかありますが、これで十分に紹介できます。

いつものように、この記事のコードはGitHubから入手できます。