CLOVER🍀

That was when it all began.

ScalaでJava EE 6(JAX-RS+EJB+JPA)

*JAX-RSの部分を@emaggameさんからご指摘いただいたので、修正しました

JPAをちょこちょこ触ってきましたが、そろそろEE系のものも使ってみた方が面白いかなぁと思い、これまでずっと手を出してこなかったJava EEに踏み込んでみることにしました。

使うアプリケーションサーバは…やっぱりJBoss ASかなぁ。あと、WildFlyは開発中ですし、素直にJBoss AS 7にすることにします。

言語は、Scalaで!

Scalaを使うなら他のフレームワークあるじゃん?って意見もあろうかと思いますが、仕事上はこっちの方が近いので、Java EEにも少し触れていこうと思うのです。

なので、Scalaを使うのは完全に趣味ですが。

JBoss AS 7のインストール

オフィシャルサイトから、アーカイブをダウンロード。

JBoss Application Server
http://www.jboss.org/jbossas

自分は、「jboss-as-7.1.1.Final.zip」をダウンロードしました。展開すれば、インストールはお終いです。

$ unzip jboss-as-7.1.1.Final.zip

以降、これで展開してできるディレクトリを「$JBOSS_HOME」と呼びます。

$ ll $JBOSS_HOME
合計 340
drwxr-xr-x 10 xxxxx xxxxx   4096  3月 10  2012 ./
drwxrwxr-x  3 xxxxx xxxxx   4096 11月  4 18:20 ../
-rw-r--r--  1 xxxxx xxxxx  26530  3月 10  2012 LICENSE.txt
-rw-r--r--  1 xxxxx xxxxx   2421  3月 10  2012 README.txt
drwxr-xr-x  3 xxxxx xxxxx   4096  3月 10  2012 appclient/
drwxr-xr-x  4 xxxxx xxxxx   4096  3月 10  2012 bin/
drwxr-xr-x  4 xxxxx xxxxx   4096  3月 10  2012 bundles/
-rw-r--r--  1 xxxxx xxxxx   2451  3月 10  2012 copyright.txt
drwxr-xr-x  4 xxxxx xxxxx   4096  3月 10  2012 docs/
drwxr-xr-x  5 xxxxx xxxxx   4096  3月 10  2012 domain/
-rw-r--r--  1 xxxxx xxxxx 266549  3月 10  2012 jboss-modules.jar
drwxr-xr-x 13 xxxxx xxxxx   4096  3月 10  2012 modules/
drwxr-xr-x  8 xxxxx xxxxx   4096 11月  4 18:24 standalone/
drwxr-xr-x  2 xxxxx xxxxx   4096  3月 10  2012 welcome-content/

ちなみに、起動は以下のコマンドで。

$ $JBOSS_HOME/bin/standalone.sh

XA DataSourceの作成

JPAで、トランザクション管理可能なDataSourceが欲しかったので、XA DataSourceを作成することにしました。

…といっても、今回はトランザクション管理までやりませんでしたけどね。

最近のJBossの本を読むと、管理CLIからmoduleコマンドでJDBCドライバをモジュールとしてインストールできるようなのですが、コミュニティ版のJBoss AS 7は、それが入る前に止まったっぽいので、手動で作成します。

最初は、単にJDBCドライバをデプロイしようとしたのですが、それだとXAは作れないんですって。ここでまた、少々ハマりました…。

追加するドライバのJARは、MySQLのもので「mysql-connector-java-5.1.26.jar」とします。

「$JBOSS_HOME/modules」ディレクトリに移動します。

$ cd $JBOSS_HOME/modules/

MySQLのドライバ配置用のディレクトリを作成します。

$ mkdir -p com/mysql/main
$ cd com/mysql/main/

ここに、ドライバのJARをコピーします。

$ cp /path/to/mysql-connector-java-5.1.26.jar ./.

続いて、以下の内容で「module.xml」ファイルを作成します。
module.xml

<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="com.mysql">
    <resources>
        <resource-root path="mysql-connector-java-5.1.26.jar"/>
        <!-- Insert resources here -->
    </resources>

    <dependencies>
        <module name="javax.api"/>
        <module name="javax.transaction.api"/>
    </dependencies>
</module>

管理CLIに接続します。

$ $JBOSS_HOME/bin/jboss-cli.sh
You are disconnected at the moment. Type 'connect' to connect to the server or 'help' for the list of supported commands.
[disconnected /] connect
[standalone@localhost:9999 /]

XA DataSourceを登録します。管理上のDataSourceの名前は「mysqlXaDs」で、JNDI名は「java:jboss/datasources/mysqlXaDs」とします。

[standalone@localhost:9999 /] cd /subsystem=datasources

## 登録したモジュールを、JDBCドライバとして登録する
[standalone@localhost:9999 subsystem=datasources] ./jdbc-driver=mysql-driver:add(driver-name=mysql-driver,driver-module-name=com.mysql)
{"outcome" => "success"}

## XA DataSourceの作成
[standalone@localhost:9999 subsystem=datasources] xa-data-source add --name=mysqlXaDs --jndi-name=java:jboss/datasources/mysqlXaDs --driver-name=mysql-driver

## 作成したデータソースの、接続先などの設定
[standalone@localhost:9999 subsystem=datasources] cd /subsystem=datasources/xa-data-source=mysqlXaDs
[standalone@localhost:9999 xa-data-source=mysqlXaDs] ./xa-datasource-properties=ServerName:add(value=localhost)
{"outcome" => "success"}
[standalone@localhost:9999 xa-data-source=mysqlXaDs] ./xa-datasource-properties=PortNumber:add(value=3306)
{"outcome" => "success"}
[standalone@localhost:9999 xa-data-source=mysqlXaDs] ./xa-datasource-properties=DatabaseName:add(value=test)
{"outcome" => "success"}

[standalone@localhost:9999 xa-data-source=mysqlXaDs] xa-data-source --name=mysqlXaDs --user-name=kazuhira
[standalone@localhost:9999 xa-data-source=mysqlXaDs] xa-data-source --name=mysqlXaDs --password=password

[standalone@localhost:9999 xa-data-source=mysqlXaDs] xa-data-source enable --name=mysqlXaDs

ここまでできたら、1度コンソールを変えます。

登録したJDBCドライバを、XAとして設定します。以下のディレクトリに移動して

$ cd $JBOSS_HOME/standalone/configuration/

「standalone.xml」というファイルに、すでにH2用のドライバの設定と先ほど登録したMySQLのドライバの設定が書かれていると思います。ここで、MySQLの方に「xa-datasource-class」というタグを追加して、以下の内容とします。

                <drivers>
                    <driver name="h2" module="com.h2database.h2">
                        <xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
                    </driver>
                    <driver name="mysql-driver" module="com.mysql">
                        <xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>
                    </driver>
                </drivers>

ここまでできたら、JBoss ASを再起動します。

再起動ができたら、接続テストをしてみます。

[standalone@localhost:9999 /] /subsystem=datasources/xa-data-source=mysqlXaDs:test-connection-in-pool
{
    "outcome" => "success",
    "result" => [true]
}

「success」と返ってくれば、成功です。

ビルドツール

では、いよいよJava EEアプリケーションを作ろうというところですが、Scalaで書くはいいのですが、ビルドツールはどうしましょう…。

Web Profileの範囲しか使わないなら、xsbt-web-pluginでWARを作れば大丈夫だよね!ってことで、sbtでいくことにしました。ムリな場面が出てきたら、Mavenに乗り換えます。

build.sbt

name := "javaee6-web-jboss"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.10.3"

organization := "littlewings"

seq(webSettings: _*)

artifactName := { (version: ScalaVersion, module: ModuleID, artifact: Artifact) =>
  //artifact.name + "." + artifact.extension
  "javaee6-web." + artifact.extension
}

libraryDependencies ++= Seq(
  "org.eclipse.jetty" % "jetty-webapp" % "9.0.6.v20130930" % "container",
  "javax" % "javaee-web-api" % "6.0" % "provided",
  "mysql" % "mysql-connector-java" % "5.1.26" % "provided"
)

WARの名前が、「javaee6-web.war」となるように細工をしています。あと、プラグインの関係で、今回使いもしないJettyの依存関係が入っています…。

プラグインの設定。
project/plugins.sbt

addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "0.4.2")

これで、sbtのコンソールで

> packageWar

と入力することで「javaee6-web.war」が作成されます。

JPA

では、アプリケーションの作成へ。依存関係の都合上、下の階層から書いていくのがいいいでしょうか?

というわけで、JPAから。

設定。
src/main/resources/META-INF/persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<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="javaee6.web.pu" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>java:jboss/datasources/mysqlXaDs</jta-data-source>
    <properties>
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
      <property name="hibernate.show_sql" value="true" />
      <property name="hibernate.format_sql" value="true" />
    </properties>
  </persistence-unit>

Entityのコード。
src/main/scala/javaee6/web/entity/User.scala

package javaee6.web.entity

import scala.beans.BeanProperty

import javax.persistence.{Column, Entity, Id, Persistence, Table, Version}

object User {
  def apply(id: Int, firstName: String, lastName: String, age: Int): User = {
    val user = new User
    user.id = id
    user.firstName = firstName
    user.lastName = lastName
    user.age = age
    user
  }
}

@SerialVersionUID(1L)
@Entity
@Table(name = "user")
class User extends Serializable {
  @Id
  @BeanProperty
  var id: Int = _

  @Column(name = "first_name")
  @BeanProperty
  var firstName: String = _

  @Column(name = "last_name")
  @BeanProperty var lastName: String = _

  @Column
  @BeanProperty
  var age: Int = _

  @Column(name = "version_no")
  @Version
  @BeanProperty
  var versionNo: Int = _

  override def toString(): String =
    s"id = $id, firstName = $firstName, lastName = $lastName, age = $age, versionNo = $versionNo"
}

今回は、@Tableアノテーションを使用しました。@Tableアノテーションを使ったのは、JBoss ASにデプロイした時に、これまで書いていた@Entityでテーブル名のマッピングを変えている方法だと、JPQLの解釈がちょっと変わったからです…。

EJB3.1

ステートレス・セッションBeanをひとつ。EntityManager使っていますけど、Readしかしていませんが…。
src/main/scala/javaee6/web/service/UserService.scala

package javaee6.web.service

import scala.collection.JavaConverters._

import javax.ejb.{Local, LocalBean, Stateless}
import javax.persistence.{EntityManager, PersistenceContext}

import javaee6.web.entity.User

@Stateless
@LocalBean
class UserService {
  @PersistenceContext(unitName = "javaee6.web.pu")
  var entityManager: EntityManager = _

  def findAll: List[User] =
    entityManager
      .createQuery("SELECT u FROM User u")
      .getResultList
      .asScala
      .toList
      .asInstanceOf[List[User]]
}

JAX-RS

実装には、RESTEasyを使用します。今回、ここが1番てこずりました…。

@emaggameさんの指摘の結果、web.xmlはなくなりました!!ありがとうございます!

リソースクラス。
src/main/scala/javaee6/web/jaxrs/SimpleResource.scala

package javaee6.web.jaxrs

import javax.inject.Inject
import javax.ws.rs.{GET, Produces, Path}
import javax.ws.rs.core.{MediaType, Response}

import javaee6.web.service.UserService

@Path("/simple")
class SimpleResource {
  @Inject
  var userService: UserService = _

  @GET
  @Path("user")
  @Produces(Array(MediaType.TEXT_HTML))
  def users: Response = {
    val responseHtml =
      <html>
        <head>
          <meta charset="UTF-8" />
          <title>ユーザ一覧</title>
        </head>
      <body>
        <h1>ユーザ一覧</h1>
        <table border="1">
        <tr><th>ID</th><th>名前</th><th>年齢</th></tr>
        {userService.findAll.map { user =>
          <tr>
            <td>{user.id}</td>
            <td>{user.lastName + " " + user.firstName}</td>
            <td>{user.age}</td>
          </tr>
        }}
        </table>
      </body>
      </html>

    Response.ok(responseHtml.toString).build
  }
}

EJBは、CDIでインジェクションしています。

というわけでCDIを使っているので、以下のパスにファイルを作成。中身は空っぽです。

src/main/webapp/WEB-INF/beans.xml

デプロイするリソースを返すクラス。
src/main/scala/javaee6/web/jaxrs/SimpleApplication.scala

package javaee6.web.jaxrs

import scala.collection.JavaConverters._

import javax.ws.rs.ApplicationPath
import javax.ws.rs.core.Application

@ApplicationPath("/")
class SimpleApplication extends Application {
  override def getClasses: java.util.Set[Class[_]] =
    Set[Class[_]](classOf[SimpleResource]).asJava
}

見直しの結果、驚異的に短くなりました(笑)。

あとは、これをパッケージングしてJBoss ASにデプロイします。

デプロイ

パッケージング。

> packageWar

デプロイ。

$ cp /path/to/javaee6-web.war $JBOSS_HOME/standalone/deployments/

デプロイメントスキャナがあるので、上記ディレクトリ配下に置けば、JBoss ASがデプロイしてくれます。再デプロイ時も、上書きコピーすればOKです。

アクセスする際には、以下のURLにアクセスすればテーブルに登録されているユーザ一覧が返ります。

http://localhost:8080/javaee6-web/simple/user/

テーブルの状態がこうだとして、

mysql> DESC user;
+------------+-------------+------+-----+---------+-------+
| Field      | Type        | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| id         | int(9)      | NO   | PRI | 0       |       |
| first_name | varchar(10) | YES  |     | NULL    |       |
| last_name  | varchar(10) | YES  |     | NULL    |       |
| age        | int(3)      | YES  |     | NULL    |       |
| version_no | int(11)     | YES  |     | NULL    |       |
+------------+-------------+------+-----+---------+-------+
5 rows in set (0.34 sec)

mysql> SELECT * FROM user;
+----+------------+-----------+------+------------+
| id | first_name | last_name | age  | version_no |
+----+------------+-----------+------+------------+
|  1 | カツオ     | 磯野      |   11 |          0 |
|  2 | ワカメ     | 磯野      |    9 |          0 |
|  3 | タラオ     | フグ田    |    3 |          0 |
+----+------------+-----------+------+------------+
3 rows in set (0.01 sec)

生成されるHTMLは、こうなります。

<html>
        <head>
          <meta charset="UTF-8"/>
          <title>ユーザ一覧</title>
        </head>
      <body>
        <h1>ユーザ一覧</h1>
        <table border="1">
        <tr><th>ID</th><th>名前</th><th>年齢</th></tr>
        <tr>
            <td>1</td>
            <td>磯野 カツオ</td>
            <td>11</td>
          </tr><tr>
            <td>2</td>
            <td>磯野 ワカメ</td>
            <td>9</td>
          </tr><tr>
            <td>3</td>
            <td>フグ田 タラオ</td>
            <td>3</td>
          </tr>
        </table>
      </body>
      </html>

とりあえず、1本通しきりました…。

参考URL)
http://planet.jboss.org/post/how_to_create_an_manage_datasources_in_as7
http://rikutoto.blogspot.jp/2013/04/jboss-as-7-datasource.html
https://access.redhat.com/site/documentation/en-US/JBoss_Enterprise_Application_Platform/6/html/Administration_and_Configuration_Guide/Example_MySQL_XA_Datasource1.html
https://access.redhat.com/site/documentation/ja-JP/JBoss_Enterprise_Application_Platform/6/html/Migration_Guide/chap-Migrate_Your_Application.html

参考書籍)

JBoss Enterprise Application Platform6 構築・運用パーフェクトガイド

JBoss Enterprise Application Platform6 構築・運用パーフェクトガイド

Beginning Java EE 6~GlassFish 3で始めるエンタープライズJava (Programmer's SELECTION)

Beginning Java EE 6~GlassFish 3で始めるエンタープライズJava (Programmer's SELECTION)

JavaによるRESTfulシステム構築

JavaによるRESTfulシステム構築