blob: 0f87568c47534ce321e9bfaf57f6048b203ad1c5 [file] [log] [blame]
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
= NetBeans プラットフォーム CRUD アプリケーションのチュートリアル
:jbake-type: platform_tutorial
:jbake-tags: tutorials
:jbake-status: published
:syntax: true
:source-highlighter: pygments
:toc: left
:toc-title:
:icons: font
:experimental:
:description: NetBeans プラットフォーム CRUD アプリケーションのチュートリアル - Apache NetBeans
:keywords: Apache NetBeans Platform, Platform Tutorials, NetBeans プラットフォーム CRUD アプリケーションのチュートリアル
このチュートリアルでは、Java DB データベースを NetBeans プラットフォームアプリケーションに統合する方法を説明します。まず、Java DB データベースの操作から開始します。この Java DB データベースからエンティティークラスを作成します。ただし、この手順は、Java DB だけに該当するわけではありません。むしろ、NetBeans IDE によってサポートされるすべてのリレーショナルデータベースに該当します。次に、関連する JPA JAR 用のモジュールとともに、エンティティークラスをモジュールにラップします。
前述のモジュールがアプリケーションに組み込まれたら、アプリケーションのユーザーインタフェースを提供する新しいモジュールを作成します。この新しいモジュールにより、ユーザーはデータベースからのデータをツリー階層で表示できます。次に、別のモジュールを作成し、最初のモジュールによって表示されるデータをユーザーが編集できるようにします。ビューアとエディタを別々のモジュールに分けることによって、ユーザーが 1 つのビューアに別のエディタをインストールできるようにします。これは、商用のものや無償のものなど、外部ベンダーによってさまざまなエディタが作成されているためです。このような柔軟性は、NetBeans プラットフォームのモジュールアーキテクチャーによって実現されます。
エディタを作成したら、CRUD 機能の追加を開始します。まず、読み取り (Read) を表す「R」が、前述のビューアで処理されます。次に、更新 (Update) を表す「U」が処理され、続いて、作成 (Create) の「C」、および削除 (Delete)の「D」が処理されます。
このチュートリアルを終えると、この種のアプリケーションの作成に役立つ NetBeans プラットフォームのさまざまな機能について理解できます。たとえば、 `` link:http://bits.netbeans.org/dev/javadoc/org-openide-awt/org/openide/awt/UndoRedo.Manager.html[UndoRedo.Manager]`` や `` link:http://bits.netbeans.org/dev/javadoc/org-openide-explorer/org/openide/explorer/ExplorerManager.html[ExplorerManager]`` 、さらに、 `` link:http://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/TopComponent.html[TopComponent]`` や `` link:http://bits.netbeans.org/dev/javadoc/org-openide-explorer/org/openide/explorer/view/BeanTreeView.html[BeanTreeView]`` などの NetBeans プラットフォーム Swing コンポーネントについて理解できます。
NOTE: このドキュメントでは NetBeans IDE 6.8 リリースを使用します。これ以前のバージョンを使用している場合、 link:67/nbm-crud.html[このドキュメントの 6.7 バージョン]を参照してください。
このチュートリアルで作成するアプリケーションは、次のようになります。
image::http://netbeans.dzone.com/sites/all/files/customer-app-on-nb.png[]
このチュートリアルの操作を開始する前に、スクリーンキャストシリーズの「 link:https://netbeans.apache.org/tutorials/nbm-10-top-apis.html[NetBeans API のトップ 10]」を視聴いただくことをお勧めします。このスクリーンキャストシリーズでは、このチュートリアルで紹介する概念の多くがより詳細に説明されています。
== アプリケーションの設定
まず、NetBeans プラットフォームアプリケーションを新しく作成します。
[start=1]
1. 「ファイル」>「新規プロジェクト」(Ctrl-Shift-N) を選択します。「カテゴリ」で「NetBeans モジュール」を選択します。「プロジェクト」で「NetBeans プラットフォームアプリケーション」を選択します。「次へ」をクリックします。
[start=2]
1. 「名前と場所」パネルで、「プロジェクト名」フィールドに「 ``DBManager`` 」と入力します。「完了」をクリックします。
IDE によって ``DBManager`` プロジェクトが作成されます。このプロジェクトは、これから作成するほかのモジュールすべてのコンテナになります。
image::images/crud_68dbmanager-1.png[]
== データベースの統合
データベースを統合するには、データベースからエンティティークラスを作成し、それらのエンティティークラスとその関連 JAR を、NetBeans プラットフォームアプリケーションを構成するモジュールに統合する必要があります。
=== エンティティークラスの作成
この節では、選択したデータベースからエンティティークラスを生成します。
[start=1]
1. この例では、「サービス」ウィンドウを使用して、NetBeans IDE に含まれているサンプルデータベースに接続します。
image::images/crud_68dbmanager-2.png[]
別の方法として、任意のデータベースを使用して、使用するユースケースに合わせて手順を適宜変更してもかまいません。MySQL の場合、 link:https://netbeans.apache.org/kb/docs/ide/mysql_ja.html[MySQL データベースへの接続]を参照してください。
[start=2]
1. IDE で「ファイル」>「新規プロジェクト」を選択します。次に「Java」>「Java クラスライブラリ」を選択し、「 ``CustomerLibrary`` 」という名前の新しいライブラリプロジェクトを作成します。
[start=3]
1. 「プロジェクト」ウィンドウで、作成したライブラリプロジェクトを右クリックし、「ファイル」>「新規ファイル」を選択します。次に、「持続性」>「データベースからのエンティティークラス」を選択します。ウィザードで、必要なデータベースと表を選択します。ここで「Customer」を選択すると、自動的に「Discount Code」が追加されます。これは、これらの 2 つの表の間に関係があるためです。
image::images/crud_68dbmanager-3.png[]
[start=4]
1. 使用可能な任意のオプションである、持続性の方針を指定します。何か選択する必要があるため、ここでは EclipseLink を選択します。
image::images/crud_68dbmanager-4.png[]
[start=5]
1. エンティティークラスの生成先となるパッケージの名前として「demo」を指定します。
image::images/crud_68dbmanager-5.png[]
[start=6]
1. 「完了」をクリックします。この手順を完了したら、生成されたコードを確認します。また、特に、「META-INF」というフォルダに ``persistence.xml`` ファイルが作成されていること、および表ごとにエンティティークラスが作成されていることに注目します。
image::images/crud_68dbmanager-7.png[]
[start=7]
1. Java ライブラリを構築すると、ライブラリプロジェクトの「dist」フォルダに JAR ファイルが生成されます。このファイルは、「ファイル」ウィンドウで確認できます。
image::images/crud_68dbmanager-8.png[]
=== エンティティークラス JAR のモジュールへのラップ
この節では、アプリケーションに最初のモジュールを追加します。新規 NetBeans モジュールは、前の節で作成した JAR ファイルをラップします。
[start=1]
1. 「プロジェクト」ウィンドウで ``DBManager`` の「モジュール」ノードを右クリックし、「新規ライブラリを追加」を選択します。
[start=2]
1. 前の節で作成した JAR を選択し、任意の値を指定してウィザードを終了します。このアプリケーションを、shop.org で顧客に対応するためのものと仮定します。その場合、コード名ベースとして、一意の識別子「org.shop.model」が適しています。
image::images/crud_68dbmanager-9.png[]
これで、新しいアプリケーション内に、エンティティークラスと persistence.xml ファイルを含む JAR をラップする、最初のカスタムモジュールが作成されました。
image::images/crud_68dbmanager-91.png[]
=== その他の関連モジュールの作成
この節では、EclipseLink JAR とデータベースコネクタ JAR をラップする新しいモジュールを 2 個作成します。
[start=1]
1. エンティティークラス JAR 用にライブラリラッパーを作成したときと同じようにします。ただし今回は、以前に作成した「CustomerLibrary Java ライブラリ内にある EclipseLink JAR 用です。
image::images/crud_68dbmanager-94.png[]
「ライブラリラッパーモジュール」ウィザードで Ctrl キーを押しながらクリックすると、複数の JAR を選択できます。
[start=2]
1. 次に、ライブラリラッパーモジュールをもう一つ作成します。これは Java DB クライアント JAR 用で、 ``db/lib/derbyclient.jar`` JDK ディストリビューションで使用できます。
=== ユーザーインタフェースのデザイン
この節では、単純なプロトタイプのユーザーインタフェースを作成します。このユーザーインタフェースのウィンドウに、データベースから取得したデータを ``JTextArea`` を使用して表示します。
[start=1]
1. 「プロジェクト」ウィンドウで ``DBManager`` の「モジュール」ノードを右クリックし、「新規を追加」を選択します。「 ``CustomerViewer`` 」という名前で新しいモジュールを作成し、コード名ベースに「 ``org.shop.ui`` 」を指定します。
[start=2]
1. 「プロジェクト」ウィンドウで、新しく作成したモジュールを右クリックし、「新規」>「ウィンドウコンポーネント」を選択します。このコンポーネントが ``editor`` 位置に作成され、アプリケーションが起動するときに開くように指定します。ウィンドウのクラス名の接頭辞として ``Customer`` を設定します。
[start=3]
1. パレット (Ctrl-Shift-8) を使用して、新しいウィンドウに ``JTextArea`` をドラッグ&ドロップします。
image::images/crud_68dbmanager-93.png[]
[start=4]
1. 次の行を TopComponent コンストラクタの最後に追加します。
[source,java]
----
EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager();
Query query = entityManager.createQuery("SELECT c FROM Customer c");
List<Customer> resultList = query.getResultList();
for (Customer c : resultList) {
jTextArea1.append(c.getName() + " (" + c.getCity() + ")" + "\n");
}
----
Customer オブジェクトと持続性 JAR を提供するモジュールに対して依存関係を設定していないため、前出の文はエラーを示す赤い下線でマークされます。このエラーの修正は、次の節で行います。
前出の行には、「CustomerLibraryPU」という名前の持続性ユニットへの参照があります。これは、 ``persistence.xml`` ファイル内で設定された名前です。さらに、 ``Customer`` というエンティティークラスへの参照があります。これは、エンティティークラスモジュール内にあります。これらの記述が前出のものと異なる場合、必要に応じて適宜変更します。
=== 依存関係の設定
この節では、いくつかのモジュールで、別のモジュールのコードを利用できるようにします。これは、関連するモジュール間に意図的なコントラクトを設定することによって、明示的に行います。つまり、意図せずに無秩序にコードが再利用されるような状況とは対照的です。そのような無秩序な再利用は、NetBeans プラットフォームによって提供されるような厳密なモジュールアーキテクチャーを持たない場合に起こることがよくあります。
[start=1]
1. エンティティークラスモジュールには、Derby Client モジュールと EclipseLink モジュールに対する依存関係が必要です。 ``CustomerLibrary`` モジュールを右クリックして「プロパティー」を選択し、「ライブラリ」タブを使用して、 ``CustomerLibrary`` モジュールに必要な 2 つのモジュールに対する依存関係を設定します。
[start=2]
1. ``CustomerViewer`` モジュールには、EclipseLink モジュールとエンティティークラスモジュールに対する依存関係が必要です。 ``CustomerViewer`` モジュールを右クリックして「プロパティー」を選択し、「ライブラリ」タブを使用して、 ``CustomerViewer`` モジュールに必要な 2 つのモジュールに対する依存関係を設定します。
[start=3]
1. ``CustomerTopComponent`` をソースビューで開き、エディタを右クリックして「インポートを修正」を選択します。必要なクラスを提供するモジュールが ``CustomerTopComponent`` に用意されたため、IDE は必要なインポート文を追加できるようになりました。
これで、アプリケーションのモジュール間にコントラクトが設定されました。これにより、コードの異なる部分間の依存関係を管理できます。
=== プロトタイプの実行
この節では、アプリケーションを実行し、データベースに適切にアクセスすることを確認できます。
[start=1]
1. データベースサーバーを起動します。
[start=2]
1. アプリケーションを実行します。次のように表示されます。
image::images/crud_68dbmanager-92.png[]
これで、データベースからデータを表示する NetBeans プラットフォームアプリケーションからなる、単純なプロトタイプを作成しました。次の節で、これを拡張します。
== CRUD 機能の統合
NetBeans プラットフォームにスムースに統合する CRUD 機能を作成するには、NetBeans プラットフォームの特定のコーディングパターンをいくつか実装する必要があります。以降の節では、このパターンを詳細に説明します。
=== 読み取り
この節では、前の節で説明した ``JTextArea`` NetBeans プラットフォームのエクスプローラビュー用に変更します。NetBeans プラットフォームのエクスプローラビューは Swing コンポーネントの一種ですが、標準の Swing コンポーネントよりも NetBeans プラットフォームに緊密に統合されます。特に、コンテキスト依存にすることができる、コンテキストの概念をサポートしています。
データの表示には、NetBeans プラットフォームの ``Node`` クラスによって提供される汎用的な階層モデルが使用されます。このモデルは、NetBeans プラットフォームのすべてのエクスプローラビューで表示できます。この節の最後で、エクスプローラビューを NetBeans プラットフォームのプロパティーウィンドウと同期させる方法について説明します。
[start=1]
1. ``TopComponent`` で、デザインビューから ``JTextArea`` を削除し、ソースビューで JTextArea に関連する次のコードをコメントアウトします。
[source,java]
----
EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager();
Query query = entityManager.createQuery("SELECT c FROM Customer c");
List<Customer> resultList = query.getResultList();
//for (Customer c : resultList) {
// jTextArea1.append(c.getName() + " (" + c.getCity() + ")" + "\n");
//}
----
[start=2]
1. ``CustomerViewer`` モジュールを右クリックして「プロパティー」を選択し、「ライブラリ」タブを使用して、「ノード API」と「エクスプローラおよびプロパティーシート API」に対する依存関係を設定します。
[start=3]
1. 次に、 ``ExplorerManager.Provider`` を実装するように、クラスの署名を次のように変更します。
[source,java]
----
final class CustomerTopComponent extends TopComponent implements ExplorerManager.Provider
----
``getExplorerManager()`` をオーバーライドする必要があります。
[source,java]
----
@Override
public ExplorerManager getExplorerManager() {
return em;
}
----
クラスの先頭で、 ``ExplorerManager`` を宣言して初期化します。
[source,java]
----
private static ExplorerManager em = new ExplorerManager();
----
前述のコードの詳細については、「 link:https://netbeans.apache.org/tutorials/nbm-10-top-apis.html[NetBeans API のトップ 10]」で、特にノード API とエクスプローラおよびプロパティーシート API について取り上げているスクリーンキャストを視聴してください。
[start=4]
1. ``TopComponent`` のデザインビューに切り替えてパレット内を右クリックし、「パレットマネージャー」>「JAR から追加」を選択します。次に、NetBeans IDE のインストールディレクトリの ``platform11/modules`` フォルダにある ``org-openide-explorer.jar`` を参照します。BeanTreeView を選択し、ウィザードを終了します。パレットに ``BeanTreeView`` が表示されます。これをパレットからウィンドウにドラッグ&ドロップします。
[start=5]
1. データベース内の各顧客に新しい link:http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-db/org/netbeans/api/db/explorer/node/BaseNode.html[BeanNode] を作成する、ファクトリクラスを作成します。
[source,java]
----
import demo.Customer;
import java.beans.IntrospectionException;
import java.util.List;
import org.openide.nodes.BeanNode;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;
public class CustomerChildFactory extends ChildFactory<Customer> {
private List<Customer> resultList;
public CustomerChildFactory(List<Customer> resultList) {
this.resultList = resultList;
}
@Override
protected boolean createKeys(List<Customer> list) {
for (Customer Customer : resultList) {
list.add(Customer);
}
return true;
}
@Override
protected Node createNodeForKey(Customer c) {
try {
return new BeanNode(c);
} catch (IntrospectionException ex) {
Exceptions.printStackTrace(ex);
return null;
}
}
}
----
[start=6]
1. ``CustomerTopComponent`` に戻り、 ``ExplorerManager`` を使用して JPA クエリーの結果リストを ``Node`` に渡します。
[source,java]
----
EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager();
Query query = entityManager.createQuery("SELECT c FROM Customer c");
List<Customer> resultList = query.getResultList();
*em.setRootContext(new AbstractNode(Children.create(new CustomerChildFactory(resultList), true)));*
//for (Customer c : resultList) {
// jTextArea1.append(c.getName() + " (" + c.getCity() + ")" + "\n");
//}
----
[start=7]
1. アプリケーションを実行します。アプリケーションが起動したら、プロパティーウィンドウを開きます。 ``BeanTreeView`` に表示されるデータがあるとしても、この ``BeanTreeView`` はプロパティーウィンドウ (「Window」>「Properties」から表示) と同期しません。つまり、ツリー階層を上下に移動しても、プロパティーウィンドウには何も表示されません。
[start=8]
1. 次のコードを ``TopComponent`` 内のコンストラクタに追加して、プロパティーウィンドウを ``BeanTreeView`` と同期させます。
[source,java]
----
associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
----
これで、 ``TopComponent`` ``ActionMap`` ``ExplorerManager`` ``TopComponent`` ``Lookup`` に追加しました。これには、選択した ``Node`` の表示名とツールチップテキストがプロパティーウィンドウに表示されるようになるという副次的な効果があります。
[start=9]
1. アプリケーションを再度実行し、今度は、プロパティーウィンドウがエクスプローラビューと同期することを確認します。
image::images/crud_68dbmanager-95.png[]
これで、 ``JTree`` を使用した場合と同じように、ツリー階層でデータを表示できるようになりました。別のエクスプローラビューに切り替えることもできますが、その際にモデルを変更する必要はまったくありません。これは、 ``ExplorerManager`` がモデルとビューを媒介するためです。最後に、ビューとプロパティーウィンドウを同期することもできるようになりました。
=== 更新
この節では、まずエディタを作成します。エディタは、新しい NetBeans モジュールによって提供されます。このため、まず、新しいモジュールを作成します。次に、この新しいモジュール内で、新しい ``TopComponent`` を作成し、ユーザーが編集する各列に対して ``JTextFields`` 2 つ含めます。ビューアモジュールがエディタモジュールと通信できるようにする必要があります。ビューアモジュール内で新しい ``Node`` が選択されるたびに、現在の ``Customer`` オブジェクトを ``Lookup`` に追加します。エディタモジュールで、 ``Customer`` オブジェクトを挿入する ``Lookup`` を待機します。新しい ``Customer`` オブジェクトが ``Lookup`` に挿入されるたびに、エディタで ``JTextFields`` を更新します。
次に、 ``JTextFields`` を、NetBeans プラットフォームの元に戻す、再実行、および保存の機能と同期させます。つまり、ユーザーが ``JTextField`` を変更したときに、NetBeans プラットフォームの既存の機能を利用できるようにします。このようにすると、新しい機能を作成せずに、NetBeans プラットフォームのサポートに取り込むことができます。これを実現するには、 ``UndoRedoManager`` ``SaveCookie`` とともに使用する必要があります。
[start=1]
1. ``CustomerEditor`` 」という名前で新しいモジュールを作成し、コード名ベースに「 ``org.shop.editor`` 」を指定します。
[start=2]
1. ``CustomerEditor`` モジュールを右クリックして、「新規」>「ウィンドウコンポーネント」を選択します。ウィンドウが ``editor`` の位置に表示され、アプリケーションが起動するときに開くように指定する必要があります。ウィザードの最後のパネルで、クラス名の接頭辞として「Editor」を設定します。
[start=3]
1. パレット (Ctrl-Shift-8) を使用して、 ``JLabels`` 2 個と ``JTextFields`` 2 個、新しいウィンドウに追加します。ラベルのテキストに「Name」と「City」を設定し、2 個の ``JTextFields`` の変数名にそれぞれ ``jTextField1`` ``jTextField2`` を設定します。
GUI ビルダーで、ウィンドウが次のように表示されます。
image::images/crud_68dbmanager-96.png[]
[start=4]
1. ``CustomerViewer`` モジュールに戻り、 ``layer.xml`` ファイルを変更して ``CustomerTopComponent`` ウィンドウが ``explorer`` モードで表示されるように指定します。
``layer.xml`` ファイルを変更したあとは、アプリケーションプロジェクトを右クリックし、「生成物を削除」を選択します。これには理由があります。アプリケーションを実行して終了するたびに、ウィンドウの位置がユーザーディレクトリに保存されるからです。このため、 ``CustomerViewer`` が当初 ``editor`` モードで表示されていた場合、「生成物を削除」を実行するまで、 ``editor`` モードのままになります。「生成物を削除」により、ユーザーディレクトリがリセットされ (つまり、ユーザーディレクトリが_削除_され)、 ``CustomerViewer`` が有効になり、現在 ``layer.xml`` ファイルに設定されている位置に表示されます。
また、ユーザーによってアプリケーションのサイズが変更されたときに、 ``CustomerViewer`` ``BeanTreeView`` が縦または横に伸縮するかどうかを確認します。この確認を行うには、ウィンドウを開いて ``BeanTreeView`` を選択してから、GUI ビルダーのツールバーの矢印ボタンをクリックします。
[start=5]
1. アプリケーションを実行し、アプリケーションの起動時に次のようになるかを確認します。
image::images/crud_68dbmanager-97.png[]
[start=6]
1. これで、コードの追加を開始できます。最初に、現在選択されている Customer オブジェクトをエディタに表示する必要があります。
* まず、 ``CustomerViewer`` モジュールを調整し、新しい ``Node`` が選択されるたびに、現在の ``Customer`` オブジェクトがビューアのウィンドウの ``Lookup`` に追加されるようにします。 ``CustomerChildFactory`` クラスに、 ``BeanNode`` ではなく ``AbstractNode`` を作成することによって、これを実現します。次に示すように、現在の ``Customer`` オブジェクトをノードの ``Lookup`` に追加できます (ボールドの部分)。
[source,java]
----
@Override
protected Node createNodeForKey(Customer c) {
Node node = new AbstractNode(Children.LEAF, Lookups.singleton(c));
node.setDisplayName(c.getName());
node.setShortDescription(c.getCity());
return node;
// try {
// return new BeanNode(c);
// } catch (IntrospectionException ex) {
// Exceptions.printStackTrace(ex);
// return null;
// }
}
----
これにより、ユーザーがビューアで新規顧客を選択することで新しい ``Node`` が作成されるたびに、新しい ``Customer`` オブジェクトが ``Node`` ``Lookup`` に追加されるようになります。
* 次に、エディタモジュールを変更し、 ``Lookup`` に追加される ``Customer`` オブジェクトをエディタモジュールのウィンドウが最終的に待機するようにします。まず、エディタモジュールで、エンティティークラスを提供するモジュールと持続性 JAR を提供するモジュールに対する依存関係を設定します。
* 次に、 ``LookupListener`` を実装するように ``EditorTopComponent`` クラスの署名を次のように変更します。
[source,java]
----
public final class EditorTopComponent extends TopComponent implements LookupListener
----
* ``resultChanged`` をオーバーライドし、新しい ``Customer`` オブジェクトが ``Lookup`` に挿入されるたびに ``JTextFields`` が更新されるようにします。
[source,java]
----
@Override
public void resultChanged(LookupEvent lookupEvent) {
Lookup.Result r = (Lookup.Result) lookupEvent.getSource();
Collection<Customer> coll = r.allInstances();
if (!coll.isEmpty()) {
for (Customer cust : coll) {
jTextField1.setText(cust.getName());
jTextField2.setText(cust.getCity());
}
} else {
jTextField1.setText("[no name]");
jTextField2.setText("[no city]");
}
}
----
* これで、 ``LookupListener`` が定義されたので、これをどこかに追加する必要があります。ここでは、グローバルコンテキストから取得した ``Lookup.Result`` に追加します。グローバルコンテキストは、選択された ``Node`` のコンテキストの代わりとして機能します。たとえば、ツリー階層で「Ford Motor Co」が選択された場合、「Ford Motor Co」の ``Customer`` オブジェクトが ``Node`` ``Lookup`` に追加されます。これは、現在選択されている ``Node`` が「Ford Motor Co」であり、「Ford Motor Co」を表す ``Customer`` オブジェクトがグローバルコンテキストで利用可能になったことを意味します。次に、このオブジェクトが ``resultChanged`` に渡され、テキストフィールドに値が取り込まれます。
前出の処理 ( ``LookupListener`` がアクティブになるなど) はすべて、次に示すように、エディタウィンドウが開かれるたびに開始されます。
[source,java]
----
@Override
public void componentOpened() {
result = Utilities.actionsGlobalContext().lookupResult(Customer.class);
result.addLookupListener(this);
resultChanged(new LookupEvent(result));
}
@Override
public void componentClosed() {
result.removeLookupListener(this);
result = null;
}
----
アプリケーションが起動するとエディタウィンドウが開くので、 ``LookupListener`` はアプリケーションの起動時に使用可能になります。
* 最後に、次に示すように、クラスの先頭で結果変数を宣言します。
[source,java]
----
private Lookup.Result result = null;
----
* アプリケーションを再度実行し、新しい ``Node`` を選択するたびに、エディタウィンドウが更新されることを確認します。
image::images/crud_68dbmanager-98.png[]
一方、フォーカスをエディタウィンドウに切り替えるときに何が起こるかを確認します。
image::images/crud_68dbmanager-99.png[]
``Node`` は選択を解除されたため、 ``Customer`` オブジェクトはグローバルコンテキストでなくなります。これは、すでに指摘したとおり、グローバルコンテキストが現在の ``Node`` ``Lookup`` の代わりとして機能しているためです。このため、この例では、グローバルコンテキストを使用できません。代わりに、「Customer」ウィンドウによって提供されるローカル ``Lookup`` を使用します。
次のコードを書き換えます。
[source,java]
----
result = Utilities.actionsGlobalContext().lookupResult(Customer.class);
----
次のようにします。
[source,java]
----
result = WindowManager.getDefault().findTopComponent("CustomerTopComponent").getLookup().lookupResult(Customer.class);
----
文字列「CustomerTopComponent」は ``CustomerTopComponent`` ID であり、 ``CustomerTopComponent`` のソースコードで確認できる文字列定数です。この方法の欠点は、ID が「CustomerTopComponent」の ``TopComponent`` を見つけることができなければ、 ``EditorTopComponent`` が動作しないという点です。この点については、明確な文書を作成し、このようにしてビューアの ``TopComponent`` を識別する必要があることを代替エディタの開発者に示すか、Tim Boudreau link:http://weblogs.java.net/blog/timboudreau/archive/2007/01/how_to_replace.html[ここ]で説明しているように、選択モデルを書き換える必要があります。
これらの方法のどちらかを採用した場合は、次に示すように、フォーカスを ``EditorTopComponent`` に切り替えてもコンテキストが失われません。
image::images/crud_68dbmanager-991.png[]
``BeanNode`` の代わりに ``AbstractNode`` を使用しているため、「プロパティー」ウィンドウにプロパティーは表示されません。 link:https://netbeans.apache.org/tutorials/nbm-nodesapi2.html[ノード API のチュートリアル]に説明されているように、自分で指定する必要があります。
[start=7]
1. 次に、元に戻す/再実行の機能に取り組みます。具体的には、ユーザーが ``JTextFields`` のいずれかを変更するたびに、「Undo」ボタンと「Redo」ボタン、および「Edit」メニューの関連するメニュー項目が有効になるようにします。これを実現するために、NetBeans プラットフォームは link:http://bits.netbeans.org/dev/javadoc/org-openide-awt/org/openide/awt/UndoRedo.Manager.html[UndoRedo.Manager] を使用可能にします。
* 新しい UndoRedoManager ``EditorTopComponent`` の先頭で宣言し、インスタンス化します。
[source,java]
----
private UndoRedo.Manager manager = new UndoRedo.Manager();
----
* 次に、 ``EditorTopComponent`` 内の ``getUndoRedo()`` メソッドをオーバーライドします。
[source,java]
----
@Override
public UndoRedo getUndoRedo() {
return manager;
}
----
* ``EditorTopComponent`` のコンストラクタで、 ``KeyListener`` ``JTextFields`` に追加し、実装する必要のある関連メソッド内に ``UndoRedoListeners`` を追加します。
[source,java]
----
jTextField1.getDocument().addUndoableEditListener(manager);
jTextField2.getDocument().addUndoableEditListener(manager);
----
* アプリケーションを再度実行し、「Undo」と「Redo」のボタンとメニュー項目の機能が動作することを確認します。この機能は、予想どおりに動作します。必要な場合は、 ``KeyListener`` を変更して、一部のキーに対して元に戻す/再実行の機能を無効にすることもできます。たとえば、Enter キーが押されたときに、元に戻す/再実行の機能が有効になるようにはしないでしょう。このため、前出のコードは、ビジネス要件に合わせて調整します。
[start=8]
1. 3 つ目に、NetBeans プラットフォームの保存機能と統合する必要があります。
* デフォルトで、「Save All」ボタンは NetBeans プラットフォームツールバーで使用できます。このシナリオでは、「すべて」を保存するのではありません。「すべて」は複数のドキュメントがあることを示すからです。ここでは、「ドキュメント」は 1 個しかありません。それは、ツリー階層内の全ノードで再利用されるエディタです。 ``CustomerEditor`` モジュールのレイヤーファイルに次のコードを追加して、「Save All」ボタンを削除し、代わりに「Save」ボタンを追加します。
[source,xml]
----
<folder name="Toolbars">
<folder name="File">
<file name="org-openide-actions-SaveAction.shadow">
<attr name="originalFile" stringvalue="Actions/System/org-openide-actions-SaveAction.instance"/>
<attr name="position" intvalue="444"/>
</file>
<file name="org-openide-actions-SaveAllAction.shadow_hidden"/>
</folder>
</folder>
----
次にアプリケーションを実行すると、ツールバーに別のアイコンが表示されます。「Save All」ボタンの代わりに、「Save」ボタンが使用できるようになります。
* 「ダイアログ API」と「ノード API」に対して、依存関係を設定します。
* ``EditorTopCompontn`` コンストラクタで、変更が検出されたときにメソッドを起動する呼び出しを追加します (次の手順で定義)。
[source,java]
----
public EditorTopComponent() {
...
...
...
jTextField1.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent arg0) {
fire(true);
}
public void removeUpdate(DocumentEvent arg0) {
fire(true);
}
public void changedUpdate(DocumentEvent arg0) {
fire(true);
}
});
jTextField2.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent arg0) {
fire(true);
}
public void removeUpdate(DocumentEvent arg0) {
fire(true);
}
public void changedUpdate(DocumentEvent arg0) {
fire(true);
}
});
//SaveCookie 実装の新しいインスタンスを作成:
impl = new SaveCookieImpl();
//動的オブジェクトの新しいインスタンスを作成:
content = new InstanceContent();
//動的コンポーネントを TopComponent Lookup に追加:
associateLookup(new AbstractLookup(content));
}
...
...
...
----
* これらを参照する 2 つのメソッドは次のとおりです。1 つ目は、変更が検出されたときに起動されるメソッドです。ノード API からの ``SaveCookie`` の実装は、変更が検出されたときに ``InstanceContent`` に追加されます。
[source,java]
----
public void fire(boolean modified) {
if (modified) {
//テキストが変更されたら、
//Lookup に SaveCookie 実装を追加
content.add(impl);
} else {
//そうでない場合、Lookup から SaveCookie 実装を削除
content.remove(impl);
}
}
private class SaveCookieImpl implements SaveCookie {
@Override
public void save() throws IOException {
Confirmation message = new NotifyDescriptor.Confirmation("Do you want to save \""
+ jTextField1.getText() + " (" + jTextField2.getText() + ")\"?",
NotifyDescriptor.OK_CANCEL_OPTION,
NotifyDescriptor.QUESTION_MESSAGE);
Object result = DialogDisplayer.getDefault().notify(message);
//ユーザーが「Yes」をクリックした場合、保存の意思を示しているので、
//保存アクションを無効にする必要があり、
//JTextArea に次回変更が加えられるまでは
//使用できないようにする
if (NotifyDescriptor.YES_OPTION.equals(result)) {
fire(false);
//保存の機能をここに実装
}
}
}
----
* アプリケーションを実行し、「Save」ボタンが有効か無効かを確認します。
image::images/crud_68dbmanager-992.png[]
ここで、上のダイアログの「OK」をクリックしても、何も行われません。次の手順で、変更を維持するための、いくつかの JPA コードを追加します。
* 次に、変更を維持するための JPA コードを追加します。これを行うには、「//保存の機能をここに実装」のコメントを置き換えます。このコメントを、次のコードで置き換えます。
[source,java]
----
EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager();
entityManager.getTransaction().begin();
Customer c = entityManager.find(Customer.class, customer.getCustomerId());
c.setName(jTextField1.getText());
c.setCity(jTextField2.getText());
entityManager.getTransaction().commit();
----
``customer.getCustomerId()()`` 」の「customer」は、現在定義されていません。次の ``resultChanged`` のボールドで表示された行を、クラスの最上位にある ``Customer customer;`` 宣言のあとに追加すると、現在の ``Customer`` オブジェクトが ``customer`` を設定します。これは前出の持続性コード内で使用され、現在の ``Customer`` オブジェクトの ID を取得します。
[source,java]
----
@Override
public void resultChanged(LookupEvent lookupEvent) {
Lookup.Result r = (Lookup.Result) lookupEvent.getSource();
Collection<Customer> c = r.allInstances();
if (!c.isEmpty()) {
for (Customer customer : c) {
*customer = cust;*
jTextField1.setText(customer.getName());
jTextField2.setText(customer.getCity());
}
} else {
jTextField1.setText("[no name]");
jTextField2.setText("[no city]");
}
}
----
* アプリケーションを実行し、一部のデータを変更します。現時点では、まだ「更新」機能はありません。次回追加される予定です。そのため、変更されたデータを確認するには、アプリケーションを再起動してください。ここでは、たとえば、ツリー階層に「Toyota Motor Co」を示す永続化された顧客名が表示されています。
image::images/crud_68dbmanager-993.png[]
[start=9]
1. 4 つ目に、Customer ビューアを更新する機能を追加する必要があります。ビューアを定期的に更新する ``Timer`` を追加することができます。しかし、この例では、ルートノードに「Refresh」メニュー項目を追加し、ユーザーがビューアを手動で更新できるようにします。
* ``CustomerViewer`` モジュールのメインパッケージで、新しい ``Node`` を作成し、ビューアの子ルートとして現在使用している ``AbstractNode`` を置き換えます。さらに、「Refresh」アクションを、作成したルートノードにバインドします。
[source,java]
----
public class CustomerRootNode extends AbstractNode {
public CustomerRootNode(Children kids) {
super(kids);
setDisplayName("Root");
}
@Override
public Action[] getActions(boolean context) {
Action[] result = new Action[]{
new RefreshAction()};
return result;
}
private final class RefreshAction extends AbstractAction {
public RefreshAction() {
putValue(Action.NAME, "Refresh");
}
public void actionPerformed(ActionEvent e) {
CustomerTopComponent.refreshNode();
}
}
}
----
* ビューを更新するために、次のメソッドを ``CustomerTopComponent`` に追加します。
[source,java]
----
public static void refreshNode() {
EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager();
Query query = entityManager.createQuery("SELECT c FROM Customer c");
List<Customer> resultList = query.getResultList();
em.setRootContext(new *CustomerRootNode*(Children.create(new CustomerChildFactory(resultList), true)));
}
----
ここで、 ``CustomerTopComponent`` のコンストラクタ内の前出のコードを、前出のコードへの呼び出しと置き換えます。前出の強調表示されている部分で確認できるように、現在 ``AbstractNode`` の代わりに ``CustomerRootNode`` が使用されています。 ``CustomerRootNode`` には「Refresh」アクションが含まれます。このアクションが、前出のコードを呼び出します。
* 保存機能に、前出のメソッドの呼び出しを追加して、データが保存されたときに自動で再表示が行われるようにします。別の方法で、この拡張を保存機能に実装することができます。たとえば、「Refresh」アクションを含む新しいモジュールを作成するとします。このモジュールはビューアモジュールとエディタモジュールとの間で共有され、両方に共通する機能を提供します。
* アプリケーションを再度実行し、「Refresh」アクションを備えた新しいルートノードがあることを確認します。
image::images/crud_68dbmanager-994.png[]
* 一部のデータを変更して保存し、「Refresh」アクションを呼び出して、ビューアが更新されていることを確認します。
ここでは、NetBeans プラットフォームが ``JTextFields`` に加えられた変更を処理する方法を学習しました。テキストが変更されるたびに、NetBeans プラットフォームの「Undo」ボタンと「Redo」ボタンが有効化または無効化されます。さらに、変更したデータをユーザーがデータベースに保存できるように、「Save」ボタンが適切に有効化または無効化されます。
=== 作成
この節では、データベース内にユーザーが新しいエントリを作成できるようにします。
[start=1]
1. ``CustomerEditor`` モジュールを右クリックし、「新規アクション」を選択します。「新規アクション」ウィザードを使用して、「常に有効」アクションを新しく作成します。新しいアクションは、ツールバーまたはメニューバーの任意の場所、あるいはその両方に表示されるはずです。ウィザードの次の手順で、 ``NewAction`` アクションを呼び出します。
16x16 のアイコンを使用できることを確認します。このアイコンは、ツールバーから呼び出されるアクションを指定する場合に、ウィザードで選択する必要があります。
[start=2]
1. 新規アクションで、 ``TopComponent`` と空の ``JTextFields`` が開くようにします。
[source,java]
----
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public final class NewAction implements ActionListener {
public void actionPerformed(ActionEvent e) {
EditorTopComponent tc = EditorTopComponent.getDefault();
tc.resetFields();
tc.open();
tc.requestActive();
}
}
----
このアクションは、 ``ActionListener`` クラスを実装します。このクラスは、レイヤーファイル内のエントリを通じてアプリケーションにバインドされ、「新規アクション」ウィザードによってアプリケーションに挿入されます。既存の Swing アプリケーションを NetBeans プラットフォームに移植することがどれほど容易かを想像してみてください。移植では、元のアプリケーションで使用していたのと同じ ``Action`` クラスをそのまま使用できます。NetBeans プラットフォームによって提供される ``Action`` クラスに適合させるために書き直す必要はありません。
``EditorTopComponent`` で、 ``JTextFields`` をリセットし、新しい ``Customer`` オブジェクトを作成する次のメソッドを追加します。
[source,java]
----
public void resetFields() {
customer = new Customer();
jTextField1.setText("");
jTextField2.setText("");
}
----
[start=3]
1. ``SaveCookie`` で、 ``null`` の戻り値が、既存のエントリの更新ではなく、新しいエントリが保存されたことを示すようにします。
[source,java]
----
public void save() throws IOException {
Confirmation message = new NotifyDescriptor.Confirmation("Do you want to save \""
+ jTextField1.getText() + " (" + jTextField2.getText() + ")\"?",
NotifyDescriptor.OK_CANCEL_OPTION,
NotifyDescriptor.QUESTION_MESSAGE);
Object result = DialogDisplayer.getDefault().notify(msg);
//ユーザーが「Yes」をクリックした場合、保存の意思を示しているので、
//「Save」ボタンと「Save」メニュー項目を無効にして、
//テキストフィールドに次回変更が加えられるまでは
//使用できないようにする
if (NotifyDescriptor.YES_OPTION.equals(result)) {
fire(false);
EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager();
entityManager.getTransaction().begin();
*if (customer.getCustomerId() != null)* {
Customer c = entityManager.find(Customer.class, cude.getCustomerId());
c.setName(jTextField1.getText());
c.setCity(jTextField2.getText());
entityManager.getTransaction().commit();
} else {
*Query query = entityManager.createQuery("SELECT c FROM Customer c");
List<Customer> resultList = query.getResultList();
customer.setCustomerId(resultList.size()+1);
customer.setName(jTextField1.getText());
customer.setCity(jTextField2.getText());
//表のほかの列すべてを生成するフィールドを追加
entityManager.persist(customer);
entityManager.getTransaction().commit();*
}
}
}
----
[start=4]
1. アプリケーションを再度実行し、データベースに新しい顧客を追加します。
=== 削除
この節では、ユーザーがデータベースで選択したエントリを削除できるようにします。前述の概念とコードを使用して、削除アクションを自分で実装してください。
[start=1]
1. 新規アクションの ``DeleteAction`` を作成します。作成したアクションを Customer ノードにバインドするか、ツールバー、メニューバー、キーボードショートカット、またはそれらの組み合わせにバインドするかを決定します。バインドする場所によって、コード内で異なる方法を使用する必要があります。ヘルプについては、再度このチュートリアルを読んでください。特に「新規」アクションの作成方法を読んで、この方法とルートノードに「Refresh」アクションを作成する方法とを比較してください。
[start=2]
1. 現在の ``Customer`` オブジェクトを取得し、「Are you sure?」ダイアログを返して、エントリを削除します。このやり方に関するヘルプについては、「保存」機能が実装される部分を中心に、再度チュートリアルを読んでください。保存する代わりに、ここでは、データベースからエントリを削除します。
== 関連項目
これで、NetBeans プラットフォーム CRUD チュートリアルを終了します。このドキュメントは、指定されたデータベースに、CRUD 機能を備えた新しい NetBeans プラットフォームアプリケーションを作成する方法について説明しました。アプリケーションの作成と開発の詳細については、次のリソースを参照してください。
* link:https://netbeans.apache.org/kb/docs/platform_ja.html[NetBeans プラットフォームの学習]
* link:http://bits.netbeans.org/dev/javadoc/[NetBeans API Javadoc]