Slick 2.0.0 documentation - 03 v2.0 移行ガイド

Permalink to Migration Guide from Slick 1.0 to 2.0 — Slick 2.0.0 documentation

Slick v2.0 Migration Guide 

Slick2.0はSlick1.0に互換性のない拡張が含まれている。アプリケーションを1.0から2.0へ移行する際には、以下のような変更が必要になるだろう。

Code generation 

以前は手で書いていたテーブルへのマッピングを、2.0ではデータベーススキーマを用いて自動的に生成出来るようになった。code-generaterは柔軟にカスタマイズすることも出来るため、より最適化されたものに変更する事も出来る。詳細については、code-generationを参考にして欲しい。

Table descriptions 

Slick1.0では、テーブルはvaltable 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)や、外部キー関連などがある。

Profile Hierarchy 

Slick 1.0ではBasicProfileExtendedProfileの2つのプロファイルを提供していた。Slick 2.0ではこれら2つのプロファイルをJdbcProfileとして統合している。今ではRelationalProfileに挙げられるようなより抽象的なプロファイルを提供している。RelationalProfileJdbcProfileの全ての特徴を持っているわけではないが、新しく出来たHeapDriverDistributedDriberといった機能を支えている。Slick 1.0からコードを移植する際、JdbcProfileへとプロファイルを変更して欲しい。特にSlick 2.0におけるBasicProfileは1.0におけるBasicProfilと非常に異なったものになっているので注意して欲しい。

Inserting 

Slick1.0では挿入時にtable objectの一部を射影していた。

// --------------------- Slick 1.0 code -- does not compile in 2.0 ---------------------
(Suppliers.name ~ Suppliers.street) insert ("foo", "bar")
  1. 0において生身のtable objectは存在していないため、table queryから射影しなくてはならない。
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)
  1. 0においてこのような冗長な記述は必要無くなる。デフォルトの射影関数を挿入時に用いる事で、自動インクリメントのついたidというカラムをSlickが除外してくれる。

逆にAutoIncのついたカラムに対し値を挿入したいのならば、新しく出来たforceInsertforceInsertAllといった関数を用いれば良い。

  1. 0において双方向マッピングを行っていた<>関数はオーバーロードされ、今やケースクラスの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)

Pre-compiled updates 

Slickはselect文において用いられるのと同じ方法で、update文における事前コンパイルもサポートしている。これについては、Compliled-Queriesのセクションを見て欲しい。

Database and Session Handling 

Slick 1.0ではDatabaseのファクトリオブジェクトとして標準的なJDBCベースなDatabaseSessionといった型がscala.slick.sessionパッケージにある。Slick 2.0からはJDBCベースなデータベースに制限せず、このパッケージは(backendとしても知られる)DatabaseComponent階層 によって置き換えられている。もしJdbcProfile抽象レベルで動かしたいのならば、以前にscala.slick.sessionにあったものをインポートし、常にJdbcBackendを用いれば良い。ただし、simple._といったインポートを行うと自動的にスコープ内にこれらの型が持ち込まれてしまうので注意して欲しい。

Dynamically and Statically Scoped Sessions 

Slick 2.0では依然としてスレッドローカルな動的セッションと静的スコープセッションを提供している。しかしシンタックスが変わっており、静的スコープセッションを用いる際にはより簡潔な記述が推奨される。以前のthreadLocalSessiondynamicSessionという名前に変わっており、関連するwithSessionwithTransactionといった関数もwithDynSessionwithDynTransactionという名前にそれぞれ変わっている。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
}
  1. 0においてもはや必要なくなる。
myDB withSession { implicit session =>
  // use the implicit session here
}

また、動的セッションを使うことは確かな情報を取得できるか分からない事から推奨されていない。静的セッションを用いる方がより安全である。

Mapped Column Types 

Slick 1.0のMappedTypeMapperMappedColumnTypeへと名前が変わった。`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
Fork me on GitHub