Archive for 2012年5月24日

はじめての JavaFX エンタープライズ・アプリケーション : JavaFX と JPA で実装するアプリケーション

本日は少しだけ JavaFX を触ってみました。正直 JavaFX は櫻庭大先生や、関谷さん、他の Embedded チームの皆様に助けて頂いているので、私自身あまり JavaFX に触っておらず自信もないのですが、今日は、JavaFX から JPA を使って DB に接続するクライアント・サーバのアプリケーションの作成してみました。

開発環境:

  • JDK 1.7 (Mac OSX 1.7.0_04)
  • NetBeans 7.1.1 or later (Mac OSX 1.7.0_04)
  • JavaDB (今回は GlassFish に付属の JavaDB を使用しました)

 

必要なライブラリ:

  • eclipselink-2.3.0.jar
  • javax.persistence-2.0.jar
  • org.eclipse.persistence.jpa.jpql_1.0.0.jar
  • derbyclient.jar (JavaDB 用のドライバ)

アプリケーションの概要:

このアプリケーションは DB に存在する顧客情報を参照するだけの簡単なアプリケーションです。JavaFX のアプリケーションから JPA を使用して DB に接続し顧客名とメールアドレスをアプリケーション上に表示します。「データの取得」ボタンを押下すると DB よりデータを取得し、テーブル内に表示し、「データのクリア」ボタンを押下すると表示内容をクリアします。

 

 

 

それでは実際に作成してみましょう。まず、NetBeans の「新規プロジェクト」を作成します。

次に、「JavaFX アプリケーション」のプロジェクトを選択します。

選択すると下記の画面が表示されますので、「プロジェクト名 (N) :」と「アプリケーションクラスを作成 (C)」を編集しプロジェクトに対して適切な名前、パッケージ、クラス名を指定します。

以上で JavaFX アプリケーションのプロジェクトが生成されました。この状態で「主プロジェクトを構築 (B)」を選択した後、「主プロジェクトを実行 (R)」を実行するとボタンが一つだけ表示される Window が表示されます。ボタンを押下すると標準出力に ”Hello World !”が表示されます。今回はこの自動生成されたJavaFX プロジェクトのひな形を改造しアプリケーションを作成します。

デフォルトで生成される Main クラスのひな形ソースコード

public class Main extends Application {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }
    
    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Hello World!");
        Button btn = new Button();
        btn.setText("Say 'Hello World'");
        btn.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                System.out.println("Hello World!");
            }
        });
        
        StackPane root = new StackPane();
        root.getChildren().add(btn);
        primaryStage.setScene(new Scene(root, 300, 250));
        primaryStage.show();
    }
}

次にエンティティ・クラスを作成します。
※ 下記の方法は、既存で DB が存在し、既にテーブルが存在している場合の方法です。テーブルが存在しない状態でもエンティティクラスを生成できますが、簡単に説明するため今回は既存のテーブルを利用してエンティティクラスを生成します。
「新規」→「持続性」→「データベースからのエンティティクラス…」を選択します。

選択すると下記の画面が表示されます。ここで「関係する表を含める (I)」のチェックを外し「CUSTOMER」テーブルを選択した後、「次へ」ボタンを押下します。

ボタンを押下すると下記の画面が表示されます。ここでエンティティ・クラスのパッケージ名を適切に入力し「次へ」ボタンを押下します。

ボタンを押下すると下記の画面が表示されます。ここではデフォルトの設定のままで最後に「完了(F)」ボタンを押下します。

既存の DB テーブルを元に NetBeans で自動生成されたエンティティ・クラス

package jp.co.oracle.javafxsample.dao;

import java.io.Serializable;
import javax.persistence.*;
import javax.xml.bind.annotation.XmlRootElement;

/**
 *
 * @author Yoshio Terada
 */
@Entity
@Table(name = "CUSTOMER")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "Customer.findAll", query = "SELECT c FROM Customer c"),
    @NamedQuery(name = "Customer.findByCustomerId", query = "SELECT c FROM Customer c WHERE c.customerId = :customerId"),
    @NamedQuery(name = "Customer.findByName", query = "SELECT c FROM Customer c WHERE c.name = :name"),
    @NamedQuery(name = "Customer.findByAddressline1", query = "SELECT c FROM Customer c WHERE c.addressline1 = :addressline1"),
    @NamedQuery(name = "Customer.findByAddressline2", query = "SELECT c FROM Customer c WHERE c.addressline2 = :addressline2"),
    @NamedQuery(name = "Customer.findByCity", query = "SELECT c FROM Customer c WHERE c.city = :city"),
    @NamedQuery(name = "Customer.findByState", query = "SELECT c FROM Customer c WHERE c.state = :state"),
    @NamedQuery(name = "Customer.findByPhone", query = "SELECT c FROM Customer c WHERE c.phone = :phone"),
    @NamedQuery(name = "Customer.findByFax", query = "SELECT c FROM Customer c WHERE c.fax = :fax"),
    @NamedQuery(name = "Customer.findByEmail", query = "SELECT c FROM Customer c WHERE c.email = :email"),
    @NamedQuery(name = "Customer.findByCreditLimit", query = "SELECT c FROM Customer c WHERE c.creditLimit = :creditLimit")})
public class Customer implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @Column(name = "CUSTOMER_ID")
    private Integer customerId;
    @Column(name = "NAME")
    private String name;
    @Column(name = "ADDRESSLINE1")
    private String addressline1;
    @Column(name = "ADDRESSLINE2")
    private String addressline2;
    @Column(name = "CITY")
    private String city;
    @Column(name = "STATE")
    private String state;
    @Column(name = "PHONE")
    private String phone;
    @Column(name = "FAX")
    private String fax;
    @Column(name = "EMAIL")
    private String email;
    @Column(name = "CREDIT_LIMIT")
    private Integer creditLimit;

    public Customer() {
    }

    public Customer(Integer customerId) {
        this.customerId = customerId;
    }

    public Integer getCustomerId() {
        return customerId;
    }

    public void setCustomerId(Integer customerId) {
        this.customerId = customerId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddressline1() {
        return addressline1;
    }

    public void setAddressline1(String addressline1) {
        this.addressline1 = addressline1;
    }

    public String getAddressline2() {
        return addressline2;
    }

    public void setAddressline2(String addressline2) {
        this.addressline2 = addressline2;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getFax() {
        return fax;
    }

    public void setFax(String fax) {
        this.fax = fax;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getCreditLimit() {
        return creditLimit;
    }

    public void setCreditLimit(Integer creditLimit) {
        this.creditLimit = creditLimit;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (customerId != null ? customerId.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Customer)) {
            return false;
        }
        Customer other = (Customer) object;
        if ((this.customerId == null && other.customerId != null) || (this.customerId != null && !this.customerId.equals(other.customerId))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "jp.co.oracle.javafxsample.dao.Customer[ customerId=" + customerId + " ]";
    }
    
}

これで、JPA のエンティティクラスが生成されましたので、これを元に JavaFX で DB のデータを参照するアプリケーションを作成します。

まず、DB に接続し全データを取得する部分ですが、getData() メソッドで実装します。GlassFish の Java DB を利用した場合、上記までの手続きにより、自動的に Persistence Unit (持続性ユニット) の設定が行われています(※ その他の DB を利用する場合、別途 NetBeans の「新規」→「持続性」→「持続性ユニット…」を選択し持続性ユニットの設定を行ってください)。
持続性ユニットの設定が適切に行われている場合、プロジェクト内に persistence.xml というファイルが生成されています。この persistence.xml 設定ファイルには DB サーバへ接続するための情報(JDBC ドライバ、接続 URL、ユーザ名、パスワード)等が記載されていますが、プログラム上からどの持続性ユニットを利用するかを指定できるように持続性ユニット名が記載されています(この例では JavaFX-JPA-SamplePU)。

persistence.xml 中の持続性ユニット名の設定(全設定は最後にまとめて記載)

<persistence-unit name="JavaFX-JPA-SamplePU" transaction-type="RESOURCE_LOCAL">

JavaFX のアプリケーションではこの持続性ユニット名を指定してエンティティを管理するための、EntityManager を生成します。EntityManager を生成した後、JPQL の NamedQuery (NetBeans では上記エンティティ・クラスに記載されているように、基本的な Named クエリが複数自動生成。ここではCustomer に記載されている @NamedQuery(name = “Customer.findAll”, query = “SELECT c FROM Customer c” ),) を使用して全データを取得します。取得したデータは List にコピーし返します。

getData() の EntityManager 生成部分(全ソースコードは最後にまとめて記載)

        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("JavaFX-JPA-SamplePU");
        EntityManager em = emf.createEntityManager();
        TypedQuery tquery = em.createNamedQuery("Customer.findAll", Customer.class);
        List<Customer> list = tquery.getResultList();

「データの取得」ボタンが押下された際に、DB に接続しデータを JavaFX のテーブルに表示するため、ボタンが押下された際のイベントをハンドリングします。この際 JavaFX では javafx.scene.control.TableView の setItems(ObservableList < S > value) を利用してプロパティを設定する事ができます。List から ObservableList を生成するために、JavaFX で用意されている便利なユーティリティクラス FXCollections を使用して変換します。変換後 TableView#setItems() メソッドの引数に代入し実行します。

        btn.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                ObservableList<Customer> data = 
                FXCollections.observableArrayList(getData());
                table.setItems(data);
            }
        });

後は、説明の必要もないかと思いますが、TableColumn でどの項目を表示するかを記載して完了です。FXCollections という便利なユーティリティツールが用意されている事もあり、JPA のエンティティをとても簡単に扱う事ができます。もちろん昔ながらの JDBC で記載する事も可能かと思いますが、JPA の方がとても簡単に効率良く実装できる事がわかるかと思います。

さて実装が終わったので、プロジェクトを構築して実行するわけですが、実はこの状態でプログラムを実行するとエラーが発生します。なぜなら現時点で本プロジェクトに JDBC ドライバのライブラリを組み込んでいないためです。


[EL Severe]: 2012-05-24 18:42:32.336–ServerSession(1090664123)–Local Exception Stack:
Exception [EclipseLink-4003] (Eclipse Persistence Services – 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException
Exception Description: Configuration error. Class [org.apache.derby.jdbc.ClientDriver] not found.

そこで、JavaDB 用の JDBC ドライバを組み込みます。ライブラリを選択し「JAR/フォルダを選択…」を選択してください。

選択すると下記の画面が表示されますので、JavaDB 用の JDBC ドライバを選択します(derbyclient.jar)。

ライブラリを組み込むと下記のように表示されます。

ドライバを組み込んだ後、プロジェクトを構築します。

次にアプリケーションを実行します。


 

最後に、今回は JavaFX アプリケーションとして、アプリケーションを作成しました。つまりこのアプリケーションでは、コネクション・プール等を利用していない為、ボタンを押下する度に DB に対して接続が発生します。そこでこのアプリケーションを大量に配布した場合、DBに対して高負荷を与える可能性もあります。
そのような場合、もちろんこのアプリケーションをアプリケーション・クライアント・コンテナを含めて作成する事も可能です。アプリケーション・クライアント・コンテナ上で作成すると、アプリケーション・サーバ上のリソースをアノテーションを利用して注入できるようになる他、アプリケーション・サーバ上で EJB を作成し、それを参照するようにする事でコネクション・プールの利用もできるようになるため、DB に対する負荷も軽減できるようなります。要件に応じてはクライアント・サーバ型のアプリケーションの実装が必要になる場合もあるかと思いますが、場合によってはアプリケーション・クライアント・コンテナ上でアプリケーションを実行する事もご検討ください。

Main クラスの全ソースコード:

package jp.co.oracle.javafxsample;

import java.util.List;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javax.persistence.*;
import jp.co.oracle.javafxsample.dao.Customer;

/**
 *
 * @author Yoshio Terada
 */
public class Main extends Application {

    /**
     * @param args the command line arguments
     */
    
    private TableView table = new TableView();
    
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        Scene scene = new Scene(new Group(), 500, 400, Color.LIGHTGRAY);
        primaryStage.setTitle("顧客情報参照アプリケーション");

        final Label label = new Label("顧客情報の参照");

        //テーブルの初期化
        TableView table = initTable();
        
        //ボタンの初期化
        Button getBtn = initGetButton();
        Button clrBtn = initClearButton();
        
        VBox vbox = new VBox();
        vbox.setSpacing(2);
        vbox.getChildren().addAll(label, table,getBtn,clrBtn);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.setPrefSize(480, 380);

        ((Group) scene.getRoot()).getChildren().addAll(vbox);
        primaryStage.setScene(scene);
        primaryStage.show();

    }

    private TableView initTable() {
        TableColumn nameCol = new TableColumn("顧客名");
        nameCol.setMinWidth(200);
        nameCol.setCellValueFactory(new PropertyValueFactory<Customer, String>("name"));

        TableColumn emailCol = new TableColumn("電子メール");
        emailCol.setMinWidth(280);
        emailCol.setCellValueFactory(new PropertyValueFactory<Customer, String>("email"));

        table.setEditable(true);
        table.setPrefWidth(480);
        table.getColumns().addAll(nameCol, emailCol);

        return table;
    }

    private Button initGetButton() {
        Button btn = new Button();
        btn.setText("データの取得");
        btn.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                ObservableList<Customer> data = 
                FXCollections.observableArrayList(getData());
                table.setItems(data);
            }
        });
        return btn;
    }
    
    private Button initClearButton() {
        Button btn = new Button();
        btn.setText("データのクリア");
        btn.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                table.setItems(null);
            }
        });
        return btn;
    }

    // JPA を使用して DB よりデータの取得
    private List getData() {
        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("JavaFX-JPA-SamplePU");
        EntityManager em = emf.createEntityManager();
        TypedQuery tquery = em.createNamedQuery("Customer.findAll", Customer.class);
        List<Customer> list = tquery.getResultList();

        if(em != null)
            em.close();
        if(emf != null)
            emf.close();
        return list;
    }
}

persistence.xml の内容

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" 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">
  <persistence-unit name="JavaFX-JPA-SamplePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <class>jp.co.oracle.javafxsample.dao.Customer</class>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:derby://localhost:1527/sample"/>
      <property name="javax.persistence.jdbc.password" value="app"/>
      <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/>
      <property name="javax.persistence.jdbc.user" value="app"/>
    </properties>
  </persistence-unit>
</persistence>

2012年5月24日 at 8:26 午後 3件のコメント


Java Champion & Evangelist

Translate

ご注意

このエントリは個人の見解であり、所属する会社の公式見解ではありません

カレンダー

2012年5月
 123456
78910111213
14151617181920
21222324252627
28293031  

カテゴリー

clustermap

ブログ統計情報

  • 1,287,612 hits

Feeds

アーカイブ