MVC 1.0ではじめる簡単Java EE開発入門!

この記事の想定読者

  • Struts 1.xの代替技術を模索している方
  • Java EEという言葉は知っているが、中身はあまり知らないという方
  • Java EEってそもそも何?という方

今回は、「そもそもJava EEって何?」「なぜJava EEが注目されているの?」ということを解説するとともに、Java EE 8で導入される「MVC 1.0」を利用して、Java EEの技術で簡単な検索Webアプリの開発する方法を解説します。

これまでのJavaによる開発

これまで、Javaシステム開発を行う際には、複数オープンソースフレームワークを組み合わせて使うことが多かったと思います。
たとえば、Struts 1.x + Spring + Hibernate(or MyBatis)は、良く聞く組み合わせです。
しかし、Struts 1.xは2008年からアップデートされておらず、2013年にはサポート終了が公式にアナウンスされました。
Apache Struts 1 EOL Press Release
また、各フレームワークごとに異なる設定ファイル(XMLなど)があります。いわゆる「XML地獄」です。
さらに、複数の異なるフレームワークを組み合わせるためには特殊な設定(XMLJavaコード)が必要となり、プロジェクトの雛形を作るだけでも相当な工数がかかります。

そもそも、Java EEって?

Java Enterprise Edition(Java EE)は、Webを中心とした大規模システム向けの「標準技術」の「仕様」です。
昔は「J2EE」と呼ばれていて、EJBEnterprise JavaBeans)の重厚長大さで敬遠されていたようです。
そのアンチテーゼとして、Struts・Spring・Hibernateなどが生まれてきた経緯があります。
しかし、Java EEも負けじと進化を続け、かなり実用的なものになってきました。
2006年にはJava EE 5、2009年にはJava EE 6、2013年にはJava EE 7がリリースされ、実用性や開発の生産性が飛躍的に高まっています。
そして、来年(2016年)の第3四半期(おそらく9月末くらい?)には、Java EE 8が登場する予定です。

Java EEの良いところ

総じていえば、「安心して使えて、生産性が高く、開発が楽」ということです。

フルスタックフレームワーク

Webフレームワーク・DIコンテナ・ORマッパー・その他など、システム開発に必要となるものが一括で提供されています。
また、各技術はシームレスに連携できるようになっており、特殊な設定をする必要はありません。
プロジェクトの雛形作成は非常に楽です。

ほぼXMLレス

Java EEの各技術は、ほとんどXMLを書きません。これは、Java SE 5で導入されたアノテーションの活用や、CoC(設定よりも規約)によるものです。

標準技術

Java EEは標準技術なので、Oracle社などの明確なサポートを受けることができます。
また、世界中の人が標準技術を使うことで、ノウハウがどんどん共有されていきます。

Java EEによるWebシステムの基本アーキテクチャ

f:id:MasatoshiTada:20150403125639p:plain

データアクセス

JPAJava Persistence API)がORマッパーです。データベースにアクセスし、CRUD操作を行います。エンティティクラスでデータをやりとりします。

ビジネスロジック

CDI(Context and Dependency Injection)がDIコンテナです。
基本的にはPOJO(Plain Old Java Object)で作りますが、これをCDI管理対象にし、プレゼンテーション層にDIします。
EJBを使うことも可能ですが、EE 7以降、トランザクション管理などもCDIおよびJTAJava Transaction API)でできるようになっており、EJBレスで開発も可能です。
ただし、EJBにしかない機能(タイマーなど)もあり、場合によって使い分ければOKです。
EJBアノテーションベースで、かなり使うのが楽になっています。

プレゼンテーション

HTMLを返すのがMVC(EE 8新機能)・JSFJSONXMLを返すREST Webサービスを構築するのがJAX-RS、リアルタイム通信を行うのがWebSocketです。
MVCはアクションベース、JSFコンポーネントベースです。CSSJavaScriptをガリガリ活用したいならMVC、簡単にリッチな画面を作りたいならJSF、という使い分けになるでしょう。

MVC 1.0 + CDI + JPAで簡単な検索アプリを作る

環境

インストール方法などは、下記の記事をご覧ください。とても楽チンです。
GlassFish 4.0のインストールとNetBeans 8.0の連携 - Java EE 事始め!

作るアプリ

f:id:MasatoshiTada:20150403132156p:plain
製造者のIDを入力したら、検索結果が表示されるWebアプリです。

プロジェクトの作成

NetBeansで[ファイル]→[新規プロジェクト]→[Maven]→[Webアプリケーション]としてください。
プロジェクトができたら、pom.xmlに下記の依存性を追加します。

<dependencies>
  <!-- MVC 1.0の実装。まだEarly Draft版 -->
  <dependency>
    <groupId>com.oracle.ozark</groupId>
    <artifactId>ozark</artifactId>
    <version>1.0.0-m01</version>
    <scope>compile</scope>
  </dependency>
  <!-- JPAの実装 -->
  <dependency>
    <groupId>org.eclipse.persistence</groupId>
    <artifactId>eclipselink</artifactId>
    <version>2.5.2</version>
    <scope>provided</scope>
  </dependency>
  <!-- エンティティ自動生成ツール。自動で追記されるので自分で書く必要はなし -->
  <dependency>
    <groupId>org.eclipse.persistence</groupId>
    <artifactId>org.eclipse.persistence.jpa.modelgen.processor</artifactId>
    <version>2.5.2</version>
    <scope>provided</scope>
  </dependency>
  <!-- OzarkでThymeleafを使うための拡張 -->
  <dependency>
    <groupId>com.oracle.ozark.ext</groupId>
    <artifactId>ozark-thymeleaf</artifactId>
    <version>1.0.0-m01</version>
  </dependency>
  <!-- Java DBのドライバ -->
  <dependency>
    <groupId>org.apache.derby</groupId>
    <artifactId>derbyclient</artifactId>
    <version>10.11.1.1</version>
  </dependency>
  <!-- Java EE 7 Web ProfileのAPI -->
  <dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-web-api</artifactId>
    <version>7.0</version>
    <scope>provided</scope>
  </dependency>
</dependencies>

データアクセス

エンティティを作成します。NetBeansを使えば、自動生成が可能です。
ちなみに、「Manufacturer」は「製造者」という意味です。Java DBに最初から入っているテーブルです。

package ozarksample.entity;

import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entity
public class Manufacturer implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @NotNull
    @Column(name = "MANUFACTURER_ID")
    private Integer manufacturerId;
    @Size(max = 30)
    private String name;
    @Size(max = 30)
    private String addressline1;
    @Size(max = 30)
    private String addressline2;
    ・・・
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "manufacturer", fetch = FetchType.LAZY)
    private List<Product> productList;

    public Manufacturer() {
    }
    
    // 以下、setter/getterなど

また、エンティティを自動生成すると、同時にperisistence.xmlという、JPAの設定ファイルも自動生成されます。
場所はsrc/main/resources/META-INFです。
自動生成されたXMLに、ユニット名、キャッシュ、ログなどの設定を加えるだけです。

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" ・・・>
  <!-- あとで呼び出す際のユニット名 -->
  <persistence-unit name="ozarkPU" transaction-type="JTA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>jdbc/sample</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <!-- キャッシュとバリデーションをOFF -->
    <shared-cache-mode>NONE</shared-cache-mode>
    <validation-mode>NONE</validation-mode>
    <properties>
      <!-- ログ出力の設定など -->
      <property name="eclipselink.logging.level" value="FINE"/>
      <property name="eclipselink.logging.thread" value="true"/>
      <property name="eclipselink.logging.session" value="false"/>
      <property name="eclipselink.logging.timestamp" value="false"/>
      <property name="eclipselink.target-database" value="JavaDB"/>
    </properties>
  </persistence-unit>
</persistence>

ビジネスロジック

package ozarksample.service;

import javax.enterprise.context.RequestScoped;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import ozarksample.entity.Manufacturer;

@RequestScoped
public class ManufacturerService {
    @PersistenceContext(unitName = "ozarkPU")
    private EntityManager manager;
    
    @Transactional(Transactional.TxType.REQUIRED)
    public Manufacturer findById(Integer id) {
        Manufacturer manufacturer = manager.find(Manufacturer.class, id);
        return manufacturer;
    }
    
    // other methods for CRUD operations
}

EntityManagerがDBアクセスを仲介します。EntityManagerにはCRUD操作用のメソッドが定義されています。また、JPQLという問い合わせ言語、ネイティブSQLの利用も可能です。
@PersistenceContextアノテーションで、EntityManagerをDIします。
クラスについている@RequestScopedは、このクラスをCDI管理対象にするためのものです。次のプレゼンテーション層に、このクラスをDIします。
メソッドに@Transactionalを付けることで、トランザクション管理を自動で行ってくれます。具体的には、対象メソッドにインターセプターをかませることで、メソッド開始前にトランザクションの開始、メソッド終了後にコミット(例外発生時は自動ロールバック)してくれます。

また、ここまでのコードは、プレゼンテーション技術に依存しません。
MVCでもJSFでもJAX-RSでもWebSocketでも、ビジネスロジック以下は変える必要がありません。
そういう意味で、再利用性も高いです。

プレゼンテーション

コントローラーはMVC、ビューは今回はThymeleafで作ります。もちろん、JSPやFacelets(XHTML形式で書く、JSFのビュー)も使えます。
ThymeleafはJava EE標準ではないのですが、独自タグを使わずピュアなHTML5で書けるので、CSSJavaScriptも書きやすくなり、おすすめです。
ただ、Thymeleafは整形式のXHTMLで書く必要があるので、注意してください。
つまり、<br>はダメで、<br/>ならOKです。

JAX-RSの有効化

MVCは、JAX-RSベースのフレームワークですので、まずはJAX-RSを有効化する必要があります。
そのためには、javax.ws.rs.core.Applicationのサブクラスを作ります。クラスの中身は空でOKです。

package ozarksample;

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

@ApplicationPath("api")
public class MyApplication extends Application {
}
コントローラー

MVCのコントローラーは、ビューの技術に依存しません。これも嬉しいですね。

★追記(2015/04/22)
JSR 371によると、コントローラークラスは、CDI管理ビーンでなければなりません。
下記のコントローラークラスに@RequestScopedが付いているのは、そのためです。

package ozarksample.controller;

import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.mvc.Models;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import ozarksample.entity.Manufacturer;
import ozarksample.service.ManufacturerService;

@Path("manufacturer")
@RequestScoped
public class ManufacturerController {
    @Inject
    private ManufacturerService manufacturerService;
    
    @Inject
    private Models models;

    @GET
    @Path("input")
    @Controller
    public String input() {
        return "manufacturer/input.html";
    }
    
    @GET
    @Path("find")
    @Controller
    public String findById(@QueryParam("id") Integer id) {
        Manufacturer manufacturer = manufacturerService.findById(id);
        models.put("manufacturer", manufacturer);
        return "manufacturer/result.html";
    }
}
入力画面(input.html)

HTMLやJSPは、デフォルトでは「WEB-INF/views」配下に作成します。サブフォルダを作ることも可能です。
今回は、WEB-INF/views/manufacturerフォルダ内にHTMLを作成します。

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>input</title>
        <meta charset="UTF-8"/>
    </head>
    <body>
        <p>input manufacturer id</p>
        <form action="./find" method="get">
            <input type="text" name="id"/>
            <input type="submit" value="search"/>
        </form>
    </body>
</html>
検索結果表示画面(result.html)
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>result</title>
        <meta charset="UTF-8"/>
    </head>
    <body>
        <p th:text="${manufacturer.manufacturerId}">Manufacturer ID</p>
        <p th:text="${manufacturer.name}">Manufacturer Name</p>
    </body>
</html>

実行!

NetBeans上でプロジェクトを右クリックー>[実行]とすると、MavenがプロジェクトをWARにビルドし、GlassFishにデプロイされます。
ブラウザも自動で起動します。

入力画面

f:id:MasatoshiTada:20150403133519p:plain

結果表示画面

f:id:MasatoshiTada:20150403133534p:plain

ここまでのまとめ

僕がこのアプリを作るのにかかった時間は、だいたい10分です。
もちろん僕がJava EENetBeansに慣れているということもありますが、これくらいのアプリであれば非常に簡単です。
もちろん、CDIJPAなどの各技術は奥が深いですし、もっと複雑なことをやろうとすると手間はかかるのですが、それはどんなフレームワークでも一緒ではないでしょうか。

ここまでのコードは、GitHubにアップしておきましたので、ご覧ください。
MasatoshiTada/OzarkSample · GitHub

話はそれますが、NetBeansならGitHub連携もすごく簡単です。一切gitコマンドを打たずにコミット・プッシュできます。
NetBeans IDEを使用してGitHubリポジトリを設定するためのビデオ

Java EEの改善してほしい点

さて、ここまで「Java EE良いよね!」という話をしてきましたが、改善点の無いものなどありませんので、いくつか挙げます。

商用アプリケーションサーバーが出るのに時間がかかる

Java EEは仕様が膨大(今回紹介したものは一部に過ぎません)なため、全機能フル対応した商用のアプリケーションサーバーが出てくるのに、だいたい2年くらいかかっています。
ただ、商用サポートはありませんが、オープンソースGlassFishWildFly(旧JBossオープンソース版)は、タイムリーに出てきます。
特にGlassFishOracleが開発しているため、Java EEの正式リリースと同時に出されます。
また、Oracle WebLogic Serverは、EE 7あたりから、少しずつEEの仕様に対応する「段階的対応」を取っています。
Java EE 8は2016年秋リリース予定ですが、特にMVC 1.0はEE 8の目玉機能ですので、WebLogicは真っ先に対応するのではないでしょうか(と、期待しています^^;)。

仕様の進化がスロー

僕の私見ですが、進化の速さ・生産性の高さ・機能の豊富さは、まだSpringや.NETの方が優位だと思います。
これは、Springや.NETは特定企業(Pivotal社・Microsoft社)が主体で開発を進めるのに対して、Javaはコミュニティによる合議制で仕様の策定・開発を進めている点が大きいのでしょう。
ただ、これはどちらが良いとは一概には言えないと思います。
Java後方互換性を大切にしていますし、標準技術による安定性・信頼性を求めるのであれば、Java EEがいいのではないでしょうか。場合によりけり、だと思います。
ただ、最低でも、新規開発でStruts 1.xを使い続けるのは、止めたほうがよいでしょうね(^^;

最後に

長い記事にお付き合いいただき、ありがとうございました。
この記事を読んだ方に、「思ってたよりだいぶ簡単じゃん!Java EEちょっとやってみようかな~」と思っていただければ嬉しいです。

また、4/11(土)のJJUG CCCに、スピーカーとしての登壇が決まりました!
CCCへの登壇は、3回連続3回目になりました。
タイトルは「はまる!JPA(初学者向けライト版)」です。
JPAは初めてという方が対象ですので、他のORマッパーは使ったことあるけど、JPAは全く知らないという方でも大丈夫です。
Sessions / JJUG CCC 2015 Spring(4月11日開催) | 日本Javaユーザーグループ

過去2回の発表資料はこちらです。
JPAの同時実行制御とロック20140518 #ccc_r15 #jjug_ccc
ステップ・バイ・ステップで学ぶラムダ式・Stream api入門 #jjug ccc #ccc h2

それでは、Enjoy Java EE