Slick 2.0.0 documentation - 03 v2.0 移行ガイド
Permalink to Migration Guide from Slick 1.0 to 2.0 — Slick 2.0.0 documentation
Slick2.0はSlick1.0に互換性のない拡張が含まれている。アプリケーションを1.0から2.0へ移行する際には、以下のような変更が必要になるだろう。
以前は手で書いていたテーブルへのマッピングを、2.0ではデータベーススキーマを用いて自動的に生成出来るようになった。code-generaterは柔軟にカスタマイズすることも出来るため、より最適化されたものに変更する事も出来る。詳細については、code-generationを参考にして欲しい。
Slick1.0では、テーブルはval
やtable objectと呼ばれるobject
によって定義がなされ、射影*
では~
オペレータを用いてタプルを表していた。
// --------------------- Slick 1.0 code -- v2.0では動かない ---------------------
object Suppliers extends Table[(Int, String, String)]("SUPPLIERS") {
def id = column[Int]("SUP_ID", O.PrimaryKey)
def name = column[String]("SUP_NAME")
def street = column[String]("STREET")
def * = id ~ name ~ street
}
Slick2.0ではTag
を引数にテーブルクラスの定義を行い、実際のデータベーステーブルを表すTableQuery
のインスタンスを定義する。射影*
に対し、基本的なタプルを用いて定義を行うことも出来る。
class Suppliers(tag: Tag) extends Table[(Int, String, String)](tag, "SUPPLIERS") {
def id = column[Int]("SUP_ID", O.PrimaryKey)
def name = column[String]("SUP_NAME")
def street = column[String]("STREET")
def * = (id, name, street)
}
val suppliers = TableQuery[Suppliers]
以前に用いていた~
シンタックスをそのまま使いたい場合には、TupleMethod._をインポートすれば良い。TableQuery[T]
を用いると、内部的にはnew TableQuery(new T(_))
のような処理が行われ、適切なTableQueryインスタンスが作成される。Slick1.0では共通処理に関して、静的メソッドでテーブルオブジェクトに定義がなされていた。2.0においても以下のようにカスタムされたTableQuery
オブジェクトを用いて、同様の事が出来る。
object suppliers extends TableQuery(new Suppliers(_)) {
// put extra methods here
val findByID = this.findBy(_.id)
}
TableQuery
はデータベーステーブルのためのQuery
オブジェクトのことである。予期せぬ場所で適用されるQuery
への暗黙的な変換はもはや必要無い。Slick 1.0において生身の table object を扱っていた場所は、全て table query が代わりに用いられることになる。例として、以下に挙げられる挿入(inserting)や、外部キー関連などがある。
Slick 1.0ではBasicProfile
とExtendedProfile
の2つのプロファイルを提供していた。Slick 2.0ではこれら2つのプロファイルをJdbcProfile
として統合している。今ではRelationalProfile
に挙げられるようなより抽象的なプロファイルを提供している。RelationalProfile
はJdbcProfile
の全ての特徴を持っているわけではないが、新しく出来たHeapDriver
やDistributedDriber
といった機能を支えている。Slick 1.0からコードを移植する際、JdbcProfile
へとプロファイルを変更して欲しい。特にSlick 2.0におけるBasicProfile
は1.0におけるBasicProfil
と非常に異なったものになっているので注意して欲しい。
Slick1.0では挿入時にtable objectの一部を射影していた。
// --------------------- Slick 1.0 code -- does not compile in 2.0 ---------------------
(Suppliers.name ~ Suppliers.street) insert ("foo", "bar")
suppliers.map(s => (s.name, s.street)) += ("foo", "bar")
+=
オペレータはScalaコレクションとの互換性のために用いられており、insert
という古い名前の関数はエイリアスとして依然用いる事が出来る。
Slick 2.0ではデータを挿入する際自動的にデフォルトでAutoInc
のついたカラムを除外する。1.0では、そのようなカラムについて手動で除外した射影関数を別に用意しなくてはならなかった。
// --------------------- Slick 1.0 code -- does not compile in 2.0 ---------------------
case class Supplier(id: Int, name: String, street: String)
...
object Suppliers extends Table[Supplier]("SUPPLIERS") {
def id = column[Int]("SUP_ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("SUP_NAME")
def street = column[String]("STREET")
// Map a Supplier case class:
def * = id ~ name ~ street <> (Supplier.tupled, Supplier.unapply)
// Special mapping without the 'id' field:
def forInsert = name ~ street <> (
{ case (name, street) => Supplier(-1, name, street) },
{ sup => (sup.name, sup.street) }
)
}
...
Suppliers.forInsert.insert(mySupplier)
id
というカラムをSlickが除外してくれる。
逆にAutoInc
のついたカラムに対し値を挿入したいのならば、新しく出来たforceInsert
やforceInsertAll
といった関数を用いれば良い。
<>
関数はオーバーロードされ、今やケースクラスのapply
関数を直接渡す事が出来る。
// --------------------- Slick 1.0 code -- does not compile in 2.0 ---------------------
def * = id ~ name ~ street <> (Supplier _, Supplier.unapply)
上記のような記述はもはや2.0ではサポートされていない。その理由の1つとして、このようなオーバーロードはエラーメッセージを複雑にしすぎるためである。現在では適切なタプル型を用いて関数を定義する事が出来る。もしケースクラスをマッピングしたいのならば、コンパニオンオブジェクトの.tupled
を単純に用いれば良いのである。
def * = (id, name, street) <> (Supplier.tupled, Supplier.unapply)
Slickはselect文において用いられるのと同じ方法で、update文における事前コンパイルもサポートしている。これについては、Compliled-Queriesのセクションを見て欲しい。
Slick 1.0ではDatabase
のファクトリオブジェクトとして標準的なJDBCベースなDatabase
とSession
といった型がscala.slick.session
パッケージにある。Slick 2.0からはJDBCベースなデータベースに制限せず、このパッケージは(backendとしても知られる)DatabaseComponent
階層
によって置き換えられている。もしJdbcProfile
抽象レベルで動かしたいのならば、以前にscala.slick.session
にあったものをインポートし、常にJdbcBackend
を用いれば良い。ただし、simple._
といったインポートを行うと自動的にスコープ内にこれらの型が持ち込まれてしまうので注意して欲しい。
Slick 2.0では依然としてスレッドローカルな動的セッションと静的スコープセッションを提供している。しかしシンタックスが変わっており、静的スコープセッションを用いる際にはより簡潔な記述が推奨される。以前のthreadLocalSession
はdynamicSession
という名前に変わっており、関連するwithSession
やwithTransaction
といった関数もwithDynSession
とwithDynTransaction
という名前にそれぞれ変わっている。Slick 1.0で記述されていた以下のようなシンタックスは、
// --------------------- Slick 1.0 code -- does not compile in 2.0 ---------------------
import scala.slick.session.Database.threadLocalSession
...
myDB withSession {
// use the implicit threadLocalSession here
}
Slick 2.0で以下のようなシンタックスへ変わる。
import scala.slick.jdbc.JdbcBackend.Database.dynamicSession
...
myDB withDynSession {
// use the implicit dynamicSession here
}
一方で、Slick 1.0で必要になっていた静的スコープセッションにおける明示的な型宣言は
myDB withSession { implicit session: Session =>
// use the implicit session here
}
myDB withSession { implicit session =>
// use the implicit session here
}
また、動的セッションを使うことは確かな情報を取得できるか分からない事から推奨されていない。静的セッションを用いる方がより安全である。
Slick 1.0のMappedTypeMapper
はMappedColumnType
へと名前が変わった。`MappedColumnType.base`を用いるような基本的な操作はRelationalProfile
レベル(高度な利用法をするのならば依然としてJdbcProfile
が必要)において現在も利用できる。
// --------------------- Slick 1.0 code -- does not compile in 2.0 ---------------------
case class MyID(value: Int)
...
implicit val myIDTypeMapper =
MappedTypeMapper.base[MyID, Int](_.value, new MyID(_))
この記述は、次のように変わる。
case class MyID(value: Int)
...
implicit val myIDColumnType =
MappedColumnType.base[MyID, Int](_.value, new MyID(_))
もしこの例のように単純なラッパー型へマッピングするのなら、MappedTo
を用いてもっと簡単に書くことが出来る。
case class MyID(value: Int) extends MappedTo[Int]
// No extra implicit required any more