1. 序章

Slick は、Scala用の機能リレーショナルマッピングライブラリであり、他のScalaコレクションと同様にデータベースにクエリを実行してアクセスできます。 SQLの代わりにScalaでデータベースクエリを記述できるため、typesafeクエリを提供できます。 このチュートリアルでは、Slickを使用してデータベースに接続してクエリを実行する方法を見てみましょう。

2. 利点

Slickは、Scalaエコシステムでデータベースにアクセスするための最も人気のあるライブラリです。 Slickは、クエリにコンパイル時の安全性と構成可能性を提供します。 デフォルトでは、非同期で非ブロッキングスタイルのプログラミングも推奨されます。 また、 ReactiveStreamベースのストリーミングAPIに従います。

プレーンSQLクエリを記述して、他のSlickクエリと同様に実行することもできます。 Slickは、PostgreSQL、MySQL、Oracle、MSSQLServerなどの一般的なデータベースのほとんどをサポートしています。

3. 依存関係

sbtを使用してプロジェクトにSlickを追加する方法を見てみましょう。

libraryDependencies += "com.typesafe.slick" %% "slick" % "3.3.1"

この記事では、簡単にするためにH2データベースを使用します。 したがって、H2ドライバーをbuild.sbtファイルに追加します。

libraryDependencies += "com.h2database" % "h2" % "1.4.200"

他のデータベースを使用する場合は、関連するドライバーをプロジェクトに追加する必要があります。 たとえば、PostgreSQLデータベースのドライバーを追加しましょう。

libraryDependencies += "org.postgresql" % "postgresql" % "42.2.14"

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

データベースへの接続方法を見てみましょう。

4.1. データベース構成の提供

application.conf ファイルで、1つ以上のデータベースの接続プロパティを提供できます。 H2データベースの構成を作成するとします。

h2mem { 
    url = "jdbc:h2:mem:testDB" 
    driver = org.h2.Driver 
    keepAliveConnection = true 
    connectionPool = disabled 
}

または、次のようにH2の代わりにPostgresに接続することもできます。

postgres {
    dataSourceClass = "org.postgresql.ds.PGSimpleDataSource"
    properties = {
        serverName = "localhost"
        portNumber = "5432"
        databaseName = "slick-tutorial"
        user = "postgres"
        password = "admin"
    }
}

4.2. 接続の確立

構成が設定されると、データベースへの接続を確立できます。 まず、接続しているデータベースのAPIをインポートする必要があります。 H2データベースの場合:

import slick.jdbc.H2Profile.api._

次に、以下を使用してデータベースへの接続を作成します。

val db = Database.forConfig("h2mem")

変数dbを使用して、データベースでクエリを実行します。

他のデータベースを使用する場合は、そのデータベースのプロファイルを使用し、データベース固有の構成を使用する必要があります。

5. スキーマの設定

クエリの作成を開始する前に、データベーススキーマを提供する必要があります。 Slick Tablesを使用して、データベーステーブルのエンティティへのマッピングを提供します。 使用するデータベース構造について説明しましょう。

テニスプレーヤー用のデータベースを作成します。 さらに、ケースクラスを使用してデータベーステーブルをモデル化します。

case class Player(id:Long, name:String, country:String, dob:Option[LocalDate])

次に、データベース列をケースクラスフィールドにマップするスキーマを作成しましょう。

class PlayerTable(tag: Tag) extends Table[Player](tag, None, "Player") { 
    override def * = (id, name, country, dob) <> (Player.tupled, Player.unapply) 
    val id : Rep[Long] = column[Long]("PlayerId", O.AutoInc, O.PrimaryKey) 
    val name: Rep[String] = column[String]("Name") 
    val country : Rep[String] = column[String]("Country") 
    val dob : Rep[Option[LocalDate]] = column[Option[LocalDate]]("Dob") 
}

スキーマPlayerTableは、関連するデータベースドライバーからTableクラスを拡張します。 データベーススキーマとテーブル名は、Tableクラスに渡されます。

また、タイプに基づいて列のフィールドを定義する必要があります。 * メソッドは、 caseclassフィールドとデータベース列の間のマッパーです。

6. クエリの実行

これで、いくつかのクエリを実行する準備が整いました。 select insert delete updateなどのさまざまな操作がどのように行われるかを見てみましょう。

6.1. データベースからのクエリ

PlayerTableを使用してデータベースからクエリを実行する方法を見てみましょう。 そのためには、PlayerTableの参照を作成する必要があります。

val playerTable = TableQuery[PlayerTable]

これで、playerTableを使用してSlickクエリを作成できます。

ドイツからすべてのプレーヤーを選択するとします。

val germanPlayersQuery = playerTable.filter(_.country === "Germany")

これはSQLクエリと同等です。

SELECT "PlayerId", "Name", "Country", "Dob" FROM "Player" WHERE "Country" = 'Germany'

変数germanPlayersQueryはSlickQueryです。 このクエリを実行するには、前に作成した dbインスタンスのrun()メソッドを使用する必要があります。 Slickはクエリを非同期で実行するため、結果はFutureの値になります。

val germanPlayers: Future[Seq[Player]] = db.run[Seq[Player]](germanPlayersQuery.result)

db.run()メソッドは DBIOAction タイプのパラメーターを取りますが、germanPlayersQueryQueryタイプです。 Queryのメソッドresultは、それをタイプDBIOActionに変換します。

filter と同様に、 Slickは、追加のフィルタリングとグループ化に使用できるdrop take、groupBy、などの他のメソッドも提供します。

6.2. データベースへの挿入

Playerテーブルに新しいプレーヤーを追加する方法を見てみましょう。

val insertPlayerQuery = playerTable += player
val insertResult:Future[Int] = db.run(insertPlayerQuery)

+ =メソッドは単一の行を挿入し、自動インクリメントを無視します。 自動インクリメント列を無視したくない場合は、代わりにforceInsertを使用できます。

val forceInsertAction = playerTable.forceInsert(player)

さらに、 ++=メソッドを使用して複数のレコードを挿入できます。

val insertMultipleAction = playerTable ++= Seq(player)

6.3. 行の更新

update メソッドを使用して、選択した行を変更できます。 idが500のプレーヤーの国を更新するとします。

val updateCountryAction = playerTable.filter(_.id === 500L).map(_.country).update("Germany")

フィルタ条件が多くの結果を返す場合、更新操作は選択されたすべての行を変更します。 スイスのすべてのプレーヤーのために、をドイツからドイツに更新しましょう。

val updateMultipleAction = playerTable.filter(_.country === "Swiss").map(_.country).update("Switzerland")

6.4. 行の削除

delete メソッドは、選択したレコードを削除するために使用されます。 ナダルのプレーヤーを削除するとします。

val deleteAction = playerTable.filter(_.name === "Nadal").delete

6.5. 複数のクエリを作成する

Slickを使用すると、複数のアクションを作成して実行できます。 たとえば、新しいプレーヤーを挿入して既存のプレーヤーを削除する必要がある場合は、両方の操作を1つのアクションに組み合わせることができます。

val insertAction          = playerTable += player1
val insertAnotherAction   = playerTable += player2
val updateAction          = playerTable
                           .filter("_.name" === "Federer")
                           .map(_.country)
                           .update("Swiss")
val combinedAction = DBIO.seq(insertAction, updateAction, insertAnotherAction)

アクションcombinedActionは、 db.run()メソッドを使用して実行できるようになりました。

6.6. トランザクションでのクエリの実行

複数のクエリが関係している場合、それらを単一トランザクションで実行することが重要です。 これは、原子性を維持するのに役立ちます。 Slickでは、メソッド transactionally を使用して、単一のトランザクションで複合アクションを実行できます。 たとえば、トランザクションの前のセクションのcombinedActionを実行できます。

val transactionStatus:Future[Unit] = db.run(combinedAction.transactionally)

transactionally が適用されていない場合でも、両方のクエリが実行されますが、挿入と削除は2つの別々のトランザクションで実行されます。

7. プレーンSQLクエリの使用

Slickを使用すると、プレーンクエリを記述して、他のSlickアクションと同じように使用できます。 sql sqlu、、およびtsql補間器を使用して文字列補間を使用できます。 たとえば、テーブルを作成するには、次のようにします。

val createQuery: DBIO[Int] =sqlu"""create table "Player"(
  "player_id" bigserial primary key,
  "name" varchar not null,
  "country" varchar not null,
  "dob" date
  ) """

createQuery は、 db.run()メソッドを使用して実行できます。

sql 補間器は、プレーンクエリから結果セットを返す必要がある場合に使用できます。 たとえば、 Selectクエリを実行する場合:

val selectCountryAction:DBIO[Seq[String]] =
  sql"""select "name" from "Player" where "country" = 'Spain' """.as[String]

8. 結論

この記事では、Slickといくつかの基本的な機能について説明しました。 いつものように、このチュートリアルで使用されるコードサンプルは、GitHubからで入手できます。