Programming Diary.

ソフトウェアエンジニアの技術blog

MySQLサンプルデータを用いたJava EEアプリケーション開発入門

はじめに

Java EE初学者が入門として簡単なWebアプリケーションを作成して学習を進めていきます。今回は、MySQL公式サイトで配布されているworldデータベースを使用して、各大陸の国一覧表示アプリを作成します。

開発環境

※現状ではJava EEアプリケーション開発を最も容易に始められると思われるNetBeans + GlassFishの組み合わせを選択しました。(ネットや書籍の情報が豊富なうえに、NetBeansの自動生成機能が便利なようです)

開発するアプリの概要

今回は、MySQLのサンプルworldデータベースを用いて、各大陸の国の情報を一覧で表示するアプリを開発します。

初期表示画面 f:id:smys0515:20150517202312p:plain

プルダウンで大陸名を選択すると、該当する国名、人口、首都が表示されます。 f:id:smys0515:20150517202117p:plain

MySQL worldデータベースの導入

まずは、MySQL公式サイトのExample Databasesからworld databaseをダウンロードします。ストレージエンジンがMyISAM versionとInnoDB versionが存在しますが、今回はInnoDB versionを使用することにしました。コマンドラインからMySQLへ接続し、データベースを作成後、ダウンロードしたファイルを読み込みます。

mysql> create database world;
mysql> use world;
mysql> source world_innodb.sql;

下記テーブルが作成されていることを確認します。

mysql> show tables;
+-----------------+
| Tables_in_world |
+-----------------+
| City            |
| Country         |
| CountryLanguage |
+-----------------+

ERMasterのインポート機能でER図を自動生成すると下図のようになります。CountryテーブルのContinentカラムに格納されうる値が、定められた文字列定数のいずれかであることがわかります。(今回はそれらの値をプルダウンに用います) f:id:smys0515:20150517115422p:plain

ちなみにMySQL公式サイトによると、当サンプルデータはフィンランドの国家統計局によるデータとのことです。

NetBeansMySQL接続設定

続いて、NetBeansからMySQLへ接続するための設定を行います。NetBeansを起動し、「サービス」タブの「データベース」を右クリックで「MySQLサーバーを登録」を選択します。すると、「MySQLサーバー・プロパティ」ダイアログが開くので、適切な値を入力し「OK」をクリックします。

f:id:smys0515:20150517204428p:plain f:id:smys0515:20150517204433p:plain

問題なく接続できればデータベースの設定は完了です。引き続き、Webアプリケーションプロジェクトを作成していきます。

Webアプリケーションプロジェクトの作成

NetBeansの「ファイル」>「新規プロジェクト」から、カテゴリ:[Maven]、プロジェクト:[Webアプリケーション]を選択し「次へ」をクリックします。 f:id:smys0515:20150517234614p:plain プロジェクト名(今回はWorldDataApp)等を入力して、「次へ」をクリックし、サーバ:[GlassFish Server]、Java EEバージョン:[Java EE 7 Web]となっていることを確認して、「終了」をクリックするとプロジェクトが作成されます。

さらに、今回使用する各パッケージを作成しておきます。「プロジェクト」タブの「ソース・パッケージ」を右クリックで「新規」>「その他」を選択します。カテゴリ:[Java]、ファイル・タイプ:[Javaパッケージ]を選択、「次へ」をクリックし、パッケージ名を入力して下記パッケージを作成します。

  • sample.javaee.worlddataapp.bean(CDI管理用パッケージ)
  • sample.javaee.worlddataapp.ejbEJB用パッケージ)
  • sample.javaee.worlddataapp.entity(JPAエンティティ用パッケージ)

エンティティクラスの自動生成

はじめにエンティティクラスを作成します。JPAエンティティ用パッケージ(sample.javaee.worlddataapp.entity)を右クリックで、「新規」>「その他」を選択して、カテゴリ:[持続性]、ファイル・タイプ:[データベースからのエンティティ・クラス]を選択し、「次へ」をクリックします。データ・ソースで新しいデータソースを選択し、JNDI名を入力して、データベース接続を選択し、「OK」をクリックします。

すると、下図の画面で使用可能な表が表示されるので、今回はCityとCountryを追加し、「次へ」で進めていき「終了」をクリックします。 f:id:smys0515:20150518014725p:plain これで、City.javaおよびCountry.javaが自動生成されます。また、JPA設定ファイルsrc/main/resources/META-INF/persistence.xmlも生成されています。

ただし、今回はデフォルトで自動生成されたCountry.javaのフィールドの

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "countryCode")
    private Collection<City> cityCollection;

となっている箇所を下記のように変更し、首都名の取得メソッドを追加しました。

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "countryCode")
    @MapKey(name = "id")
    private Map<Integer, City> cityMap;

    public String getCapitalName() {
        if (cityMap == null || capital == null) {
            return "";
        }
        return cityMap.get(capital).getName();
    }

これは、Countryに紐づくCityをCollectionで保持していたのを、City.idをキーとするMapで保持するよう変更し、Country.capital(=City.id)からCity.nameを取得できるようにするためです。

続いて、EJB用パッケージ(sample.javaee.worlddataapp.ejb)を右クリックで、「新規」>「その他」を選択して、カテゴリ:[持続性]、ファイル・タイプ:[エンティティ・クラスのセッションBean]を選択し、「次へ」をクリックします。使用可能なエンティティ・クラスに先ほど作成したクラスが表示されるので、追加して「次へ」「終了」をクリックします。 f:id:smys0515:20150518021658p:plain

すると、データアクセス用のAbstractFacade.java, CityFacade.java, CountryFacade.javaが生成されています。今回は、CountryFacade.javaに下記メソッドを追加して、continentからも取得できるようにしました。

    public List<Country> findByContinent(String continent) {
        return em.createNamedQuery("Country.findByContinent").setParameter("continent", continent).getResultList();
    }

CDI管理Beanの生成

つぎに、CDI管理Beanを作成します。CDI管理Bean用パッケージ(sample.javaee.worlddataapp.bean)を右クリックで、「新規」>「その他」を選択して、カテゴリ:[JavaServer Faces]、ファイル・タイプ:[JSF管理対象Bean]を選択し、「次へ」をクリックします。クラス名を入力し、スコープにviewを選択して「終了」をクリックします。

下記のように、大陸プルダウンの生成処理およびプルダウン選択時の国一覧情報の取得処理などを記述します。

// package・importは省略

/**
 * 国一覧表示画面 管理Bean
 */
@Named(value = "worldDataBean")
@ViewScoped
public class WorldDataBean implements Serializable {

    /* 選択された大陸 */
    private Continent continent;

    /* 大陸プルダウン */
    private List<Continent> continentList;

    /* 表示する国一覧 */
    private List<Country> countryList;

    /* データベースアクセス用EJB */
    @Inject
    private CountryFacade countryFacade;

    /**
     * 初期化処理
     */
    @PostConstruct
    public void init() {
        this.continent = Continent.NONE;
        this.continentList = Arrays.asList(Continent.values());
        this.countryList = getCountryByContinent();
    }

    /**
     * 大陸から国一覧を取得
     * @return 
     */
    public List<Country> getCountryByContinent() {
        if (this.continent == Continent.NONE) {
            return new ArrayList<>();
        } else {
            return countryFacade.findByContinent(this.continent.getLabelName());
        }
    }
    // getter・setterは省略

    /**
     * 大陸を表す列挙型
     */
    public static enum Continent {

        NONE("-"), ASIA("Asia"), EUROPE("Europe"), NORTH_AMERICA("North America"), //
        AFRICA("Africa"), OCEANIA("Oceania"), ANTARCTICA("Antarctica"), SOUTH_AMERICA("South America");

        /* ラベル名 */
        private String labelName;

        Continent(String labelName) {
            this.labelName = labelName;
        }
        // getter・setterは省略
    }

}

JSFページの作成

最後にJSFページを作成します。Webページフォルダを右クリックで、「新規」>「その他」を選択して、カテゴリ:[JavaServer Faces]、ファイル・タイプ:[JSFページ]を選択し、「次へ」をクリックします。ファイル名(world)を入力し、「終了」をクリックします。ここで、デフォルトで生成されたindex.htmlは不要なため削除し、WEB-INF/web.xmlのwelcome-fileのindex.htmlはworld.xhtmlに変更します。

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:head>
        <title>World Data</title>
        <style type="text/css">
            .numberColumn  {text-align: right;}
            .oddRow  {background-color: lightcyan;}
            .evenRow  {background-color: lightyellow}
        </style>
    </h:head>
    <h:body>
        <h:form prependId="false">
            <h:outputText value="continent: " />
            <h:selectOneMenu value="#{worldDataBean.continent}">
                <f:selectItems value="#{worldDataBean.continentList}" var="continent" itemValue="#{continent}" itemLabel="#{continent.labelName}"/>
                <f:ajax execute="txt" event="change" render="countryTable" listener="#{worldDataBean.getCountryByContinent()}" />
            </h:selectOneMenu>
            <h:dataTable id="countryTable" var="country" value="#{worldDataBean.countryList}" rowClasses="oddRow, evenRow" columnClasses="numberColumn, ,numberColumn, ">
                <h:column>
                    <f:facet name="header">
                        <h:outputText value="#" rendered="#{worldDataBean.countryList.size() > 0}" />
                    </f:facet>
                    <h:outputText value="#{worldDataBean.countryList.indexOf(country) + 1} " />
                </h:column>
                <h:column>
                    <f:facet name="header">
                        <h:outputText value="name" rendered="#{worldDataBean.countryList.size() > 0}" />
                    </f:facet>
                    <h:outputText value="#{country.name} " />
                </h:column>
                <h:column>
                    <f:facet name="header">
                        <h:outputText value="population" rendered="#{worldDataBean.countryList.size() > 0}" />
                    </f:facet>
                    <h:outputText value="#{country.population}" />
                </h:column>
                <h:column>
                    <f:facet name="header">
                        <h:outputText value="capital" rendered="#{worldDataBean.countryList.size() > 0}" />
                    </f:facet>
                    <h:outputText value="#{country.capitalName}" />
                </h:column>
            </h:dataTable>
        </h:form>
    </h:body>
</html>

主なポイントは以下の通りです。

  • <h:dataTable>タグのrowClassesおよびcolumnClassesでスタイルの指定が可能。それぞれカンマ区切りで複数指定すると、行ごとまたは列ごとに適用するスタイルを変えることができる。(今回のように奇数行と偶数行の背景色の色分けや特定列のみ右寄せで表示するなど)
  • <f:ajax>タグを用いることで画面の一部のみを更新することが可能。event属性でAjaxが実行されるタイミングを、render属性でレンダリング対象を指定し、listener属性に記述したCDI管理Beanのメソッドが実行される。

以上で完成です。アプリケーションを実行し、想定通りの動作となることを確認します。

まとめ

MySQLサンプルデータを用いたJava EEアプリケーション開発を通じて、JSF, EJB, JPA, CDIなどについて簡単に確認しました。NetBeans + GlassFishで意外と手軽に始められると感じたので、引き続き学習を続けていきたいと思います。

※『Javaエンジニア養成読本』特集3「現場で役立つJava EE」を参考にしました。

Javaエンジニア養成読本 [現場で役立つ最新知識、満載!] (Software Design plus)

Javaエンジニア養成読本 [現場で役立つ最新知識、満載!] (Software Design plus)