kagamihogeの日記

kagamihogeの日記です。

Play Frameworkさわる

やったこと

新規アプリケーションの作成

playを解凍したディレクトリに移動して、下記のように play new [新規アプリケーション名]と打つ。

play new foobar

What is the application name?はとりあえずそのままで良いので空のままEnter.

次は、ScalaかJavaか選べ、と出てくるのでひとまず2のJavaを選ぶ。

これで、playのディレクトリに新規アプリケーションのfoobarのディレクトリが作成される。

Eclipseプロジェクトへの変換

サンプル動かす程度なら別にIDEの形式にする必要もないのだけど、気分的な問題なのでやる。

コマンドプロンプトでさっき作ったfoobarのディレクトリに移動して、playを実行*1してplay consoleを起動する。

で、eclipseコマンドを実行する。

eclipse

Eclipseのimportから、さっきのfoobarディレクトリをコピー&インポートする。

Webアプリケーションの起動

Eclipseにインポートしたプロジェクトでplay consoleを起動する。んで、runコマンドでサーバを起動する。

run

http://localhost:9000/ にアクセスする。

ブラウザにこんな画面が表示される。

Welcome to Play 2.1

さっきの画面にファーストステップ的なカンタンなガイドが書いてあるので、それを読んでいく。

まず、http://localhost:9000/ にアクセスしたとき、最初に呼ばれるcontrollerはどう定義されているかについて。conf/routesファイルに定義してある。

# Home page
GET     /                           controllers.Application.index()

という定義があり、"/"にアクセスすると、controllers.Applicationクラスのindexメソッドが呼ばれる、と書いてある。んで、そのindexメソッドはこうなっている。

public static Result index() {
    return ok(index.render("Your new application is ready."));
}

ドキュメントによると「このメソッドはHTTPリクエストを受け取ってHTTPレスポンスを返す。ここでは200 OKを返し、テンプレートにテキストを埋める」と書いてある。ヒジョーにアレな言い方をすればStrutsのAction的なもんなんでしょう。

で、ビュー側のテンプレートはapp/views/index.scala.htmlファイル。このファイルはJavaのフツーのクラスファイルとしてコンパイルされる、って書いてある。

@(message: String)

@main("Welcome to Play 2.1") {
    
    @play20.welcome(message, style = "Java")
    
}

↑の一行目は、テンプレートに対する関数の引数、とある。messageて名前のString型のパラメータが一つありますよ、と。で、mainていう別のテンプレートの関数を呼んでいる。そのファイルはapp/views/main.scala.htmlでHTMLのテンプレートが書いてありますよ、と。

なお、scalaっていきなり出てくるけどJavaと互換性あるしJavaとそんなに違うもんでもないしそーいうもんなので混乱するなや、とNoteに書いてある。HTMLとscalaのミックスがplayのviewの特徴、ってことなんでしょう。

Eclipseデバッグ

Scala+Play 2.0でWebアプリ開発入門(3):便利なPlayコンソールとEclipseでのデバッグ方法 (3/3) - @ITに書いてあることのコピペだけども。

まず、play consoleをデバッグモードで起動してサーバーを起動する。コマンドは、play debug run もちろんplay consoleをplay debugで起動したあと、runでサーバを起動しても良い。下記スクショはplay debug runしたところ。

eclipseに移って、Run -> Debug Configurations -> Remote Java Applicationでnewして下記スクショのように入力する。ポートを9999にするくらいかな。デバッグモードのスクショにもあるとおり、デフォルトではポート9999でソケットを待ち受けている。

あとはいつものデバッグモード。Scalaのコードもstep inとかでちゃんと止まってくれるっぽいんですかね?

依存性の解決

とりあえず最低限必要なことは、SBTDependenciesに書いてあります。

↑のドキュメントによると、外部のライブラリを使用する方法は二種類ある。一つは、/libディレクトリにjarを置くだけ。このエントリでは、下の「JDBC接続」のところでやってます。もう一つは、sbtを使う方法。ごくカンタンに言えば、Mavenのdependency追加するヤツと同様のもの。

まず、project/Build.scalaに依存モジュールを追加する。下記ではサンプルとしてpoiとfastutilを入れている。Maven Repository: Search/Browse/Explore で検索して、SBTのタブに入ってるヤツを使う。

  val appDependencies = Seq(
    // Add your project dependencies here,
    javaCore,
    javaJdbc,
    javaEbean,
    "org.apache.poi" % "poi" % "3.9",
    "fastutil" % "fastutil" % "5.0.9"
  )

んで、play consoleで、設定ファイルをリロードするためにreload、依存性解決のためにupdate、Eclipseプロジェクトの設定ファイル(.classpath)書き換えのためにeclipse、を実行する。

なお、eclipseコマンドでEclipseプロジェクトの設定ファイルが書き換えられるので、安定を求める人はEclipseを一旦落としておいたほうが良い。

reload
update
eclipse

下記スクショは上記コマンドの実行例。fastutilしかダウンロードが行われていないけど、poiは既にダウンロード済みなため出ていないです。

依存性を消したい場合は、project/Build.scalaから該当のモジュールを消してreload,update,eclipseする。

JDBC接続

とりあえず、ナマのJDBCでアクセスしてみる。使うDBはいつものようにOracle 11g XE。playの流儀では開発中はH2使ってねって感じみたいだけども、ひとまずソレは置いておく。なお、modelを見ながらやりました。

まず、プロジェクトのディレクトリに/libを作ってそこにJDBCドライバのojdbc6.jarをコピーする。

conf/application.confにDB接続情報を書く。

db.default.driver=oracle.jdbc.OracleDriver
db.default.url="jdbc:oracle:thin:@192.168.0.12:1521:XE"
db.default.user=kagamihoge
db.default.password=xxxx

play.db.DB.getConnection()からjava.sql.Connectionのインスタンスが取得できる。下記は、modelとか考えるめんどくさかったんで一先ずサンプルのcontrollers.ApplicationにJDBCアクセスのコードを追加したところ。

package controllers;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import play.db.DB;
import play.mvc.Controller;
import play.mvc.Result;
import views.html.index;

public class Application extends Controller {
  
    public static Result index() {
        try (Connection connection = DB.getConnection();
                PreparedStatement sql = connection.prepareStatement("SELECT * FROM SRC WHERE ROWNUM < 100")) {
            ResultSet resultSet = sql.executeQuery();
            while (resultSet.next()) {
                System.out.println(resultSet.getString(1));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return ok(index.render("Your new application is ready."));
    }
  
}

とりあえず、そんだけ。

JPA(Hibernate)

JavaJPAを見ながらやりました。

conf/application.confを編集する。JNDIの設定と、JPAのpersistence-unitとの関連付けを書く。

db.default.driver=oracle.jdbc.OracleDriver
db.default.url="jdbc:oracle:thin:@192.168.0.12:1521:XE"
db.default.user=kagamihoge
db.default.password=xxxx
db.default.jndiName=DefaultDS

jpa.default=defaultPersistenceUnit

project/Build.scalaを編集する。javaJpaと、JPAの実体であるHibernateの依存性を追加する。

  val appDependencies = Seq(
    // Add your project dependencies here,
    javaCore,
    javaJdbc,
    javaJpa,
    "org.hibernate" % "hibernate-entitymanager" % "4.2.1.Final"
  )

conf/META-INF/persistence.xmlを追加する。

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
    version="2.0">

    <persistence-unit name="defaultPersistenceUnit"
        transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <non-jta-data-source>DefaultDS</non-jta-data-source>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect" />
            <property name="hibernate.show_sql" value="true" />
        </properties>
    </persistence-unit>
</persistence>

Oracleにテキトーなテーブルを作る。

create table dest 
(
  column1 varchar2(16)
, column2 varchar2(16)
);

Modelとなるクラスを作る。play.db.jpa.JPA#emでEntityManagerインスタンス取得してからは、いつものJPA

package models;

import javax.persistence.Column;

import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.Id;

import play.db.jpa.JPA;

@Entity
public class Dest {
    @Id
    public String column1;

    @Column
    public String column2;

    public static Dest findById(String id) {
        EntityManager em = JPA.em();
        return em.find(Dest.class, id);
    }
}

Contollerのアクションメソッドに@play.db.jpa.Transactionalを付けて上記のModelを使う。詳細はドキュメントに譲るし俺自身よくわかってないが、とにかくTransactionalをつけないといけない。

public class Application extends Controller {
    @Transactional
    public static Result index() {
        Dest dest = Dest.findById("0000000000000001");
        System.out.println(dest.column1 + ":" + dest.column2);;
        return ok(index.render("Your new application is ready."));
    }
}

これで http://localhost:9000/ にアクセスすると、play consoleにこんな感じで表示される。

[info] play - datasource [jdbc:oracle:thin:@192.168.0.12:1521:XE] bound to JNDIas DefaultDS
[info] play - database [default] connected at jdbc:oracle:thin:@192.168.0.12:1521:XE
[info] play - Application started (Dev)
Hibernate: select dest0_.column1 as column1_0_0_, dest0_.column2 as column2_0_0_ from Dest dest0_ where dest0_.column1=?
0000000000000001:VYjJXvPElDXMpCnr

以下、JPA使うときのハマりどころなど。

・play.db.jpa.JPA can not be resolved.
Build.scala のappDependenciesにjavaJpaを追加する。


・No EntityManager bound to this thread. Try to annotate your action method with @play.db.jpa.Transactional
Controllerのアクションメソッド(index()とか)に@play.db.jpa.Transactionalをつける。


・RuntimeException: No JPA EntityManagerFactory configured for name [default]
application.confにjpa.default=defaultPersistenceUnitを追加する。persistence-unitの名前は適宜読み替え。


・ORA-00911 が発生する。
Hibernate: select dest0_.column1 as column1_0_0_, dest0_._ebean_intercept as col
umn2_0_0_, dest0_.column2 as column3_0_0_ from Dest dest0_ where dest0_.column1=
?
[error] o.h.e.j.s.SqlExceptionHelper - ORA-00911:

Build.scala のappDependenciesからjavaEbeanを削除する。


・RuntimeException: java.lang.NoClassDefFoundError: com/avaje/ebean/bean/EntityBean
javaのソース適当にいじって何回かRELOADしてたら出なくなった。まぁebeanの設定消した関係で何かが残存してただけと思うけど。

*1:playを解凍したディレクトリのplay.batを起動する。スクショは絶対パスをイチイチ実行している