今回はPlayコンソール上でREPLを使用し、Anormの動作を確認してみましょう。先ほど作成したデータベースの起動を確認した後、playコマンドでPlayコンソールを立ち上げましょう。そしてconsoleコマンドを実行し、REPLを起動します。
% play [gyro] $ console Welcome to Scala version 2.10.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_04). Type in expressions to have them evaluated. Type :help for more information. scala>
このままではアプリが起動していないので、REPL上で起動させます。play.core.StaticApplicationをインスタンス化することで、Playアプリが起動します。
※下記コードはREPL上で入力してください。
scala> import play.core.StaticApplication scala> new StaticApplication(new java.io.File(".")) ・ ・ [info] play - Application started (Prod) res1: play.core.StaticApplication = play.core.StaticApplication@193d69e
アプリが起動したので、DBアクセスが可能になりました。SQLを実行してみましょう。必要パッケージをimportしてから、withConnectionブロック内で、anorm.SQLオブジェクトを使ってSQLクエリを実行します。
scala> import play.api.db._ scala> import play.api.Play.current scala> import anorm._ scala> DB.withConnection { implicit c => val result: Boolean = SQL("Select 1").execute() println("result =" + result) } [debug] c.j.b.PreparedStatementHandle - Select 1 result =true
execute関数はSQL実行結果をBooleanで返します。結果が表示されて、SQLが成功したのが分かりますね。
では、次のようにしてUserテーブルにデータを登録してみましょう。insert文の場合、executeInsert関数を使用すると、自動生成されたキーをOption型で返します(主キーが単数かつ数値の場合)。
//Userテーブルにレコードを1件登録 scala> DB.withConnection { implicit c => val count = SQL( """ insert into User(name,email,password) values({name},{email},{password}) """ ).on('name -> "taro", 'email -> "taro@classmethod.jp", 'password -> "taropass").executeInsert() println("count = " + count) } [debug] c.j.b.PreparedStatementHandle - insert into User(name) values('taro') count = Some(1)
on関数ではSQLへバインドするnameパラメータを「{}」を用いて指定しています。また、「"""」(ダブルクオート3つ)で文字列を囲えば、複数行の文字列を記述できるので、複雑なSQLもこの形式で記述できますね。SQL実行結果として、登録されたレコードのID「1」が取得できました。
登録ができたので、次は更新を行ってみましょう。executeUpdate関数を使用すれば、update文を実行できます。この関数はアップデートされた行数を返します。
//id:1のレコードを更新 scala> DB.withConnection { implicit c => val count = SQL( """ update User set name={name} where id={id} """ ).on('name -> "hanako", 'id -> 6).executeUpdate() println("updateCount = " + count) } [debug] c.j.b.PreparedStatementHandle - update User set name='hanako' where id=1 updateCount = 1
削除を実行してみましょう。delete文の場合もexecuteUpdate関数を使用します。
//id:1のレコードを削除 scala> DB.withConnection { implicit c => val count: Int = SQL( """ delete from User where id = {id} """ ).on('id -> 1).executeUpdate() println("deleteCount = " + count) } [debug] c.j.b.PreparedStatementHandle - delete from User where id = 1 deleteCount = 1
更新系のSQLを使ったので、次は参照系のSQLを使ってみましょう。まずはMySQLのコンソールで、テスト用データを登録しておきます。
% mysql -u<ユーザー名> -p<パスワード> mysql> use gyro; mysql> insert into User values(1,'taro','taro@classmethod.jp','taropass',now()); insert into User values(2,'hanako','hanako@classmethod.jp','hanakopass',now()); insert into User values(3,'mike','mike@mike.com','mikepass',now()); insert into Post values(100,1,'title1','body1',now()); insert into Post values(101,1,'title2','body2',now()); insert into Post values(102,2,'title3','body3',now());
参照系SQLを実行してみましょう。Stream APIを使用すればselect結果を取得/変換可能です。SQLオブジェクトのapply()を呼び出すと、Stream[Row]を取得できます。
次の例ではStream APIを使用してユーザーの一覧を取得してリストとして返しています。
scala> val selectQuery = SQL("select * from User") val result = DB.withConnection { implicit c => //Stream[Row] からList[(Long,(String,String))]に変換 selectQuery().map(row => row[Long]("id") -> (row[String]("name") , row[String]("email"))).toList } [debug] c.j.b.PreparedStatementHandle - Select * from User result: List[(Long, (String, String))] = List( (1,(taro,taro@classmethod.jp)), (2,(hanako,hanako@classmethod.jp)), (3,(mike,mike@mike.com)))
Parser APIを使用しても、select結果をパースできます。パーサは再利用することもできるため、Stream APIよりこちらの方が利用頻度は高いかもしれません。
次はParser APIを使ってみましょう。初めに、anorm.SqlParser._をインポートします。SQLオブジェクトのas関数を使用し、引数でカラムをパースします。
scala> import anorm.SqlParser._ scala> val result:List[Int~String~String] = DB.withConnection { implicit c => SQL("select * from User").as( int("id") ~ str("name") ~ str("email") * ) } [debug] c.j.b.PreparedStatementHandle - select * from User result: List[anorm.~[anorm.~[Int,String],String]] = List( ~(~(1,taro),taro@classmethod.jp), ~(~(2,hanako),hanako@classmethod.jp), ~(~(3,mike),mike@mike.com))
先ほどと同じように、ユーザーの一覧がリスト形式で取得できました。しかし、取得できた値の型が「List[Int~String~String]」となっており、このままでは使いにくいですね。
flatten関数を適用し、タプル(Tuple)型のリストに変換してみましょう。
scala> val result = DB.withConnection { implicit c => SQL("select * from User").as( int("id") ~ str("name") ~ str("email") map(flatten) * ) } [debug] c.j.b.PreparedStatementHandle - select * from User result: List[(Int, String, String)] = List( (1,taro,taro@classmethod.jp), (2,hanako,hanako@classmethod.jp), (3,mike,mike@mike.com))
mapとflattenによってTupleのリストとして取得できました。
また、取得結果を任意のクラスのリストにすることも可能です。次の処理では、emailの値に応じてクラスの型を切り替えています。
scala> case class CmEmp(name:String) scala> case class Person(name:String) scala> val result = DB.withConnection { implicit c => SQL("select * from User").as( int("id") ~ str("name") ~ str("email") map { //メールアドレスに@classmethod.jpが含まれていたら、CmEmpオブジェクトにする case id~name~email if(email.contains("@classmethod.jp")) => CmEmp(name) case id~name~email => Person(name) } *) } [debug] c.j.b.PreparedStatementHandle - select * from User result: List[Product with Serializable] = List(CmEmp(taro), CmEmp(hanako), Person(mike))
このように、AnormのParser APIを使用すれば、再利用可能なパーサを定義してSQL結果を変換できます。Stream API、Parser APIについても「Documentation: ScalaAnorm ― Playframework」で詳しく解説しているので、併せてご確認ください。
さて、今回はPlay2でDBアクセスを行うための方法について解説しましたが、いかがでしたか。
AnormはいままでのDBアクセスライブラリの感覚で使用すると、ちょっと使いにくいかもしれませんが、少し慣れれば自然に扱えるようになると思います。
また、先ほども少し言及しましたが、「Slick」というDBアクセスライブラリもあります。これはDBデータをScalaコレクションのように扱うことができ、Scalaでクエリを記述できるという特徴を持っています。Play2を使用する際には、こちらの使用も検討してみてください。
次回はScala+Play2でテストを行う方法を紹介する予定です。
中村修太(なかむら しゅうた)
クラスメソッド勤務の新しもの好きプログラマです。昨年、東京から山口県に引っ越し、現在はノマドワーカーとして働いています。好きなJazzを聴きながらプログラミングするのが大好きです。
Copyright © ITmedia, Inc. All Rights Reserved.