Slick/Doobieによるデータベース連携
SlickやDoobieなどの代表的なScala ORM/ライブラリを使い、Playアプリケーションにデータベースを統合する方法を学びます。
「Slick/Doobieによるデータベース連携」はCoddyKit上の無料Scala for Backend Engineering & Functional Programmingレッスンです。 これはレッスン3/3です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはScala for Backend Engineering & Functional Programming学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Scala for Backend Engineering & Functional Programmingコースには全3レッスンが含まれています。
データベースを統合する理由
Webアプリケーションでは、データを永続的に保存したり取得したりする必要があることがよくあります。データには、ユーザープロフィール、商品情報、ブログ記事などがあります。
- 永続性:アプリケーションを再起動しても、データを利用できます。
- スケーラビリティ:大量のデータや多数の同時接続ユーザーに対応できます。
- 信頼性:データの整合性と復旧可能性を確保できます。
Play Frameworkで構築する動的なWebアプリケーションには、データベースの統合が不可欠です。
SlickとDoobie:Scalaのデータベースツール
Scalaにはデータベース操作のための強力なライブラリがあり、SlickとDoobieはその代表的な選択肢です。
- Slick:関数型リレーショナルマッピング(FRM)ライブラリです。SQLの詳細を抽象化し、型安全かつ関数型の方法でデータベースを操作できます。
- Doobie:純粋関数型のJDBCレイヤーです。Cats Effectなどの関数型プログラミングのパラダイムと統合しながら、SQLクエリを直接制御できます。
このレッスンでは、より高レベルの抽象化によってPlay Frameworkとの初期統合を簡単に行えることが多いため、Slickに焦点を当てます。
PlayでSlickをセットアップする
まず、Slickとデータベースドライバーをbuild.sbtに追加します。セットアップを簡単にするため、インメモリデータベースのH2を使用します。
次に、conf/application.confでデータベース接続を設定します。
import play.sbt.PlayImport._
libraryDependencies ++= Seq(
jdbc,
ws,
"com.typesafe.play" %% "play-slick" % "5.0.0",
"com.typesafe.play" %% "play-slick-evolutions" % "5.0.0",
"com.h2database" % "h2" % "2.2.224"
)データベースの設定
H2データベースとSlickプロファイルをセットアップするため、次の行をconf/application.confに追加します。slick.dbs.default.profileにはデータベースの種類を指定します。
play.db.prototype.slickは、データベース接続のテンプレートを定義します。
slick.dbs.default.profile="slick.jdbc.H2Profile$"
play.db {
prototype.slick = {
db.driver="org.h2.Driver"
db.url="jdbc:h2:mem:play;DB_CLOSE_DELAY=-1"
db.username="sa"
db.password=""
}
# Apply the prototype to our default DB
default = ${play.db.prototype.slick}
}モデルを定義する(SlickのTable)
Slickでは、slick.jdbc.H2Profile.api.Tableを継承するScalaクラスとしてデータベーステーブルを表現します。各カラムはScalaの型にマッピングされます。
テーブルごとにTableQueryを定義します。これはクエリを実行するためのエントリーポイントです。
import slick.jdbc.H2Profile.api._
// Define a case class for our data model
case class User(id: Option[Long], name: String, email: String)
// Define the table mapping
class Users(tag: Tag) extends Table[User](tag, "users") {
def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def email = column[String]("email")
// Every table needs a * projection with the default mapping
def * = (id, name, email) <> (User.tupled, User.unapply)
}
// TableQuery for accessing the Users table
val users = TableQuery[Users]コード:Tableとスキーマの作成
この実行可能な例では、Userテーブルを定義し、インメモリのH2データベースにそのスキーマを作成する方法を示します。db.runメソッドはデータベースアクションを非同期で実行します。
import slick.jdbc.H2Profile.api._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration._
case class User(id: Option[Long], name: String, email: String)
class Users(tag: Tag) extends Table[User](tag, "users") {
def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def email = column[String]("email")
def * = (id, name, email) <> (User.tupled, User.unapply)
}
object SlickDemo {
val users = TableQuery[Users]
def main(args: Array[String]): Unit = {
val db = Database.forURL("jdbc:h2:mem:test1;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver")
try {
val setup = DBIO.seq(
users.schema.create
)
val future = db.run(setup)
Await.result(future, 2.seconds)
println("Users table created successfully!")
} finally {
db.close()
}
}
}操作にDAOパターンを使用する
データアクセスオブジェクト(DAO)パターンは、データベース操作をカプセル化するのに役立ちます。アプリケーションのビジネスロジックと永続化ロジックを分離できます。
UserDAOには、Userオブジェクトの挿入、検索、更新、削除を行うメソッドを定義します。
import slick.jdbc.H2Profile.api._
import scala.concurrent.Future
class UserDAO(db: Database) {
import SlickDemo.users // Assuming users TableQuery is in SlickDemo object
def insert(user: User): Future[User] = {
db.run(users returning users.map(_.id) into ((user, id) => user.copy(id = id)) += user)
}
def findAll(): Future[Seq[User]] = {
db.run(users.result)
}
def findById(id: Long): Future[Option[User]] = {
db.run(users.filter(_.id === id).result.headOption)
}
}コード:データの挿入と検索
先ほど定義したDAOのメソッドを使って、新しいユーザーを挿入し、データベースからすべてのユーザーを検索する方法を見てみましょう。
import slick.jdbc.H2Profile.api._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration._
case class User(id: Option[Long], name: String, email: String)
class Users(tag: Tag) extends Table[User](tag, "users") {
def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def email = column[String]("email")
def * = (id, name, email) <> (User.tupled, User.unapply)
}
class UserDAO(db: Database) {
val users = TableQuery[Users]
def insert(user: User): Future[User] =
db.run(users returning users.map(_.id) into ((u, id) => u.copy(id = id)) += user)
def findAll(): Future[Seq[User]] =
db.run(users.result)
}
object SlickOperations {
def main(args: Array[String]): Unit = {
val db = Database.forURL("jdbc:h2:mem:test2;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver")
val userDAO = new UserDAO(db)
try {
Await.result(db.run(userDAO.users.schema.create), 2.seconds)
println("Table created.")
val future = for {
user1 <- userDAO.insert(User(None, "Alice", "alice@example.com"))
user2 <- userDAO.insert(User(None, "Bob", "bob@example.com"))
allUsers <- userDAO.findAll()
} yield allUsers
val result = Await.result(future, 5.seconds)
println(s"All users: $result")
} finally {
db.close()
}
}
}DAOをPlayに統合する
Playアプリケーションでは通常、依存性注入を使ってUserDAOをコントローラーに注入します。これにより、コントローラーのアクションからデータベースを操作できるようになります。
PlayのDI(例:Guice)を設定して、DatabaseとUserDAOのインスタンスを提供するようにします。
import play.api.mvc._
import play.api.libs.json._
import scala.concurrent.ExecutionContext
import javax.inject._
// Assuming User and UserDAO are defined as before
@Singleton
class UserController @Inject()(cc: ControllerComponents, userDAO: UserDAO)
(implicit ec: ExecutionContext) extends AbstractController(cc) {
def listUsers = Action.async {
userDAO.findAll().map { users =>
Ok(Json.toJson(users))
}
}
def createUser = Action.async(parse.json) {
request =>
request.body.validate[User].map { user =>
userDAO.insert(user).map(u => Created(Json.toJson(u)))
}.getOrElse(Future.successful(BadRequest("Invalid User Data")))
}
}クイックチェック:SlickのTable
次のScalaのケースクラスと、部分的に定義されたSlickのTableマッピングについて考えてみましょう。
case class Product(id: Option[Int], name: String, price: Double)id、name、priceの各カラムが定義されている場合、Productテーブルの*プロジェクションを正しく完成させる行はどれでしょうか?
復習:データベース統合
このレッスンでは、Slickを使ってPlay Frameworkアプリケーションにデータベースを統合する方法を学びました。
- Slickが関数型リレーショナルマッピング(FRM)ライブラリとして果たす役割を学びました。
- Slickの依存関係を追加し、
application.confでデータベースを設定する方法を確認しました。 - Scalaのケースクラスを定義し、Slickの
Tableクラスを使ってデータベーステーブルにマッピングする方法を学びました。 - データベース操作を整理するDAOパターンを紹介し、データの挿入と検索の例を確認しました。
- 最後に、Web APIエンドポイント用のPlayコントローラーにこれらのDAO操作を統合する方法にも触れました。
データベース統合を習得することは、堅牢でデータ駆動型のPlayアプリケーションを構築する鍵です。
よくある質問
「Slick/Doobieによるデータベース連携」レッスンは無料ですか?
はい。「Slick/Doobieによるデータベース連携」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Scala for Backend Engineering & Functional Programmingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Scala for Backend Engineering & Functional Programmingコースには全3レッスンが含まれています。
「Slick/Doobieによるデータベース連携」で何を学びますか?
SlickやDoobieなどの代表的なScala ORM/ライブラリを使い、Playアプリケーションにデータベースを統合する方法を学びます。 ブラウザで直接実行するハンズオンコードでScala for Backend Engineering & Functional Programmingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Scala for Backend Engineering & Functional Programmingを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのScala for Backend Engineering & Functional Programmingは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/3です。
「Slick/Doobieによるデータベース連携」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このScala for Backend Engineering & Functional Programmingレッスンでコードを書いて実行できますか?
はい。すべてのScala for Backend Engineering & Functional Programmingレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- Play Frameworkの基礎
- PlayでRESTful APIを構築する
- Slick/Doobieによるデータベース連携