この記事の目的
Playframeworkは静的型付けにより型安全になっているため、実行時エラーが起こることはあまりありませんが、いくつか発生したケースがあるためメモとして記録します。
動作環境
- scala 2.11.7
- play 2.4.3
- slick 3.1.1
モデルのサンプルコード
説明に使用するサンプルコードです。
Dog.scala
package models
import slick.driver.MySQLDriver.api._
case class Dog(name: String, color: String, breed: Option[String])
class DogTables(tag: Tag) extends Table[Dog](tag, "DOGS") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def color = column[String]("color")
def breed = column[Option[String]]("breed")
def * = (name, color, breed) <> (Dog.tupled, Dog.unapply _)
}
例外
SlickException: Read NULL value for ResultSet column
DBのデータをモデルにマッピングにした際、Option指定がない属性に対応するカラムにNullがセットされていた場合に例外が発生します。
DogTablesを例にすると、nameはStringで定義されていますが、テーブル定義ではNullが許可されいて、かつ実際にNullの値が設定されていた場合に例外が発生します。
Dog.scala
class DogTables(tag: Tag) extends Table[Dog](tag, "DOGS") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name") // Optionの指定がないためRead時にNullが入るとエラー
def color = column[String]("color")
def breed = column[Option[String]]("breed")
def * = (name, color, breed) <> (Dog.tupled, Dog.unapply _)
}
table_description
mysql> desc dogs;
+-----------+--------------+-----+-----+----------+---------------+
| Field | Type | Null| Key | Default | Extra |
+-----------+--------------+-----+-----+----------+---------------+
| ID | int(11) | NO | PRI | NULL | auto_increment|
| NAME | varchar(255) | YES | | NULL | |
| COLOR | varchar(255) | NO | | NULL | |
| BREED | varchar(255) | YES | | NULL | |
+-----------+--------------+-----+-----+----------+---------------+
- 対応方法: モデルとテーブル定義の型を合わせる。
- テーブル定義でNull YesをNull Noに変更する。
- または、DogTablesのnameをcolumn[Option[String]]と指定する。
--
[MySQLSyntaxErrorException: Unknown column 'CREATED_AT' in 'field list’]
- モデルに定義している属性がDBのテーブル定義にない場合に発生します。
Dog.scala
class DogTables(tag: Tag) extends Table[Dog](tag, "DOGS") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def color = column[String]("color")
def breed = column[Option[String]]("breed")
def createdAt = column[Timestamp]("CREATED_AT") // dogsテーブルにないカラムを指定
def updatedAt = column[Timestamp]("UPDATED_AT") // dogsテーブルにないカラムを指定
def * = (name, color, breed, createdAt, updatedAt) <> (Dog.tupled, Dog.unapply _)
}
table_description
mysql> desc dogs;
+-----------+--------------+-----+-----+----------+---------------+
| Field | Type | Null| Key | Default | Extra |
+-----------+--------------+-----+-----+----------+---------------+
| ID | int(11) | NO | PRI | NULL | auto_increment|
| NAME | varchar(255) | YES | | NULL | |
| COLOR | varchar(255) | NO | | NULL | |
| BREED | varchar(255) | YES | | NULL | |
+-----------+--------------+-----+-----+----------+---------------+
- 対応方法: DBのテーブル構造にCREATED_ATとUPDATED_ATを追加する
[NoSuchElementException: Invoker.first]
- Queryを実行してDBからデータを取得した結果、Noneが返されたものを利用して後続の処理を行おうとしている。
- 結果が取得できない可能性があるものはOption型として扱う
- primary_key以外のキーで検索したもの
val dogs = TableQuery[DogTables]
val dog = dogs.filter { _.name === "Pochi" }.result.head
val owener = owners.filter { _.dog_id === dog.id }
- 次のことを疑ってみる
- headOption指定でデータを取得するところがheadになっていないか
- left outer joinの右側のテーブルを絞込の条件にしていないか