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 プラットフォームのさまざまな機能について理解できます。たとえば、UndoRedo.Manager や ExplorerManager、さらに、TopComponent や BeanTreeView などの NetBeans プラットフォーム Swing コンポーネントについて理解できます。
注: このドキュメントでは NetBeans IDE 6.8 リリースを使用します。これ以前のバージョンを使用している場合、このドキュメントの 6.7 バージョンを参照してください。
目次

このチュートリアルを行うには、次の表に示すソフトウェアおよびリソースが必要です。
| ソフトウェアまたはリソース | 必須バージョン |
|---|---|
| NetBeans IDE | version 6.8 |
| Java Developer Kit (JDK) | version 6 または version 5 |
このチュートリアルで作成するアプリケーションは、次のようになります。
このチュートリアルの操作を開始する前に、スクリーンキャストシリーズの「NetBeans API のトップ 10」を視聴いただくことをお勧めします。このスクリーンキャストシリーズでは、このチュートリアルで紹介する概念の多くがより詳細に説明されています。
アプリケーションの設定
まず、NetBeans プラットフォームアプリケーションを新しく作成します。
- 「ファイル」>「新規プロジェクト」(Ctrl-Shift-N) を選択します。「カテゴリ」で「NetBeans モジュール」を選択します。「プロジェクト」で「NetBeans プラットフォームアプリケーション」を選択します。「次へ」をクリックします。
- 「名前と場所」パネルで、「プロジェクト名」フィールドに「DBManager」と入力します。「完了」をクリックします。
IDE によって DBManager プロジェクトが作成されます。このプロジェクトは、これから作成するほかのモジュールすべてのコンテナになります。
データベースの統合
データベースを統合するには、データベースからエンティティークラスを作成し、それらのエンティティークラスとその関連 JAR を、NetBeans プラットフォームアプリケーションを構成するモジュールに統合する必要があります。
エンティティークラスの作成
この節では、選択したデータベースからエンティティークラスを生成します。
- この例では、「サービス」ウィンドウを使用して、NetBeans IDE に含まれているサンプルデータベースに接続します。
別の方法として、任意のデータベースを使用して、使用するユースケースに合わせて手順を適宜変更してもかまいません。MySQL の場合、MySQL データベースへの接続を参照してください。
- IDE で「ファイル」>「新規プロジェクト」を選択します。次に「Java」>「Java クラスライブラリ」を選択し、「CustomerLibrary」という名前の新しいライブラリプロジェクトを作成します。
- 「プロジェクト」ウィンドウで、作成したライブラリプロジェクトを右クリックし、「ファイル」>「新規ファイル」を選択します。次に、「持続性」>「データベースからのエンティティークラス」を選択します。ウィザードで、必要なデータベースと表を選択します。ここで「Customer」を選択すると、自動的に「Discount Code」が追加されます。これは、これらの 2 つの表の間に関係があるためです。
- 使用可能な任意のオプションである、持続性の方針を指定します。何か選択する必要があるため、ここでは EclipseLink を選択します。
- エンティティークラスの生成先となるパッケージの名前として「demo」を指定します。
- 「完了」をクリックします。この手順を完了したら、生成されたコードを確認します。また、特に、「META-INF」というフォルダに persistence.xml ファイルが作成されていること、および表ごとにエンティティークラスが作成されていることに注目します。
- Java ライブラリを構築すると、ライブラリプロジェクトの「dist」フォルダに JAR ファイルが生成されます。このファイルは、「ファイル」ウィンドウで確認できます。
エンティティークラス JAR のモジュールへのラップ
この節では、アプリケーションに最初のモジュールを追加します。新規 NetBeans モジュールは、前の節で作成した JAR ファイルをラップします。
- 「プロジェクト」ウィンドウで DBManager の「モジュール」ノードを右クリックし、「新規ライブラリを追加」を選択します。
- 前の節で作成した JAR を選択し、任意の値を指定してウィザードを終了します。このアプリケーションを、shop.org で顧客に対応するためのものと仮定します。その場合、コード名ベースとして、一意の識別子「org.shop.model」が適しています。
これで、新しいアプリケーション内に、エンティティークラスと persistence.xml ファイルを含む JAR をラップする、最初のカスタムモジュールが作成されました。
その他の関連モジュールの作成
この節では、EclipseLink JAR とデータベースコネクタ JAR をラップする新しいモジュールを 2 個作成します。
- エンティティークラス JAR 用にライブラリラッパーを作成したときと同じようにします。ただし今回は、以前に作成した「CustomerLibrary」 Java ライブラリ内にある EclipseLink JAR 用です。

「ライブラリラッパーモジュール」ウィザードで Ctrl キーを押しながらクリックすると、複数の JAR を選択できます。
- 次に、ライブラリラッパーモジュールをもう一つ作成します。これは Java DB クライアント JAR 用で、db/lib/derbyclient.jar の JDK ディストリビューションで使用できます。
ユーザーインタフェースのデザイン
この節では、単純なプロトタイプのユーザーインタフェースを作成します。このユーザーインタフェースのウィンドウに、データベースから取得したデータを JTextArea を使用して表示します。
- 「プロジェクト」ウィンドウで DBManager の「モジュール」ノードを右クリックし、「新規を追加」を選択します。「CustomerViewer」という名前で新しいモジュールを作成し、コード名ベースに「org.shop.ui」を指定します。
- 「プロジェクト」ウィンドウで、新しく作成したモジュールを右クリックし、「新規」>「ウィンドウコンポーネント」を選択します。このコンポーネントが editor 位置に作成され、アプリケーションが起動するときに開くように指定します。ウィンドウのクラス名の接頭辞として Customer を設定します。
- パレット (Ctrl-Shift-8) を使用して、新しいウィンドウに JTextArea をドラッグ&ドロップします。

- 次の行を TopComponent コンストラクタの最後に追加します。
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 プラットフォームによって提供されるような厳密なモジュールアーキテクチャーを持たない場合に起こることがよくあります。
- エンティティークラスモジュールには、Derby Client モジュールと EclipseLink モジュールに対する依存関係が必要です。CustomerLibrary モジュールを右クリックして「プロパティー」を選択し、「ライブラリ」タブを使用して、CustomerLibrary モジュールに必要な 2 つのモジュールに対する依存関係を設定します。
- CustomerViewer モジュールには、EclipseLink モジュールとエンティティークラスモジュールに対する依存関係が必要です。CustomerViewer モジュールを右クリックして「プロパティー」を選択し、「ライブラリ」タブを使用して、CustomerViewer モジュールに必要な 2 つのモジュールに対する依存関係を設定します。
- CustomerTopComponent をソースビューで開き、エディタを右クリックして「インポートを修正」を選択します。必要なクラスを提供するモジュールが CustomerTopComponent に用意されたため、IDE は必要なインポート文を追加できるようになりました。
これで、アプリケーションのモジュール間にコントラクトが設定されました。これにより、コードの異なる部分間の依存関係を管理できます。
プロトタイプの実行
この節では、アプリケーションを実行し、データベースに適切にアクセスすることを確認できます。
- データベースサーバーを起動します。
- アプリケーションを実行します。次のように表示されます。

これで、データベースからデータを表示する NetBeans プラットフォームアプリケーションからなる、単純なプロトタイプを作成しました。次の節で、これを拡張します。
CRUD 機能の統合
NetBeans プラットフォームにスムースに統合する CRUD 機能を作成するには、NetBeans プラットフォームの特定のコーディングパターンをいくつか実装する必要があります。以降の節では、このパターンを詳細に説明します。
読み取り
この節では、前の節で説明した JTextArea を NetBeans プラットフォームのエクスプローラビュー用に変更します。NetBeans プラットフォームのエクスプローラビューは Swing コンポーネントの一種ですが、標準の Swing コンポーネントよりも NetBeans プラットフォームに緊密に統合されます。特に、コンテキスト依存にすることができる、コンテキストの概念をサポートしています。
データの表示には、NetBeans プラットフォームの Node クラスによって提供される汎用的な階層モデルが使用されます。このモデルは、NetBeans プラットフォームのすべてのエクスプローラビューで表示できます。この節の最後で、エクスプローラビューを NetBeans プラットフォームのプロパティーウィンドウと同期させる方法について説明します。
- TopComponent で、デザインビューから JTextArea を削除し、ソースビューで JTextArea に関連する次のコードをコメントアウトします。
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"); //} - CustomerViewer モジュールを右クリックして「プロパティー」を選択し、「ライブラリ」タブを使用して、「ノード API」と「エクスプローラおよびプロパティーシート API」に対する依存関係を設定します。
- 次に、ExplorerManager.Provider を実装するように、クラスの署名を次のように変更します。
final class CustomerTopComponent extends TopComponent implements ExplorerManager.Provider
getExplorerManager() をオーバーライドする必要があります。
@Override public ExplorerManager getExplorerManager() { return em; }クラスの先頭で、ExplorerManager を宣言して初期化します。
private static ExplorerManager em = new ExplorerManager();
前述のコードの詳細については、「NetBeans API のトップ 10」で、特にノード API とエクスプローラおよびプロパティーシート API について取り上げているスクリーンキャストを視聴してください。
- TopComponent のデザインビューに切り替えてパレット内を右クリックし、「パレットマネージャー」>「JAR から追加」を選択します。次に、NetBeans IDE のインストールディレクトリの platform11/modules フォルダにある org-openide-explorer.jar を参照します。BeanTreeView を選択し、ウィザードを終了します。パレットに BeanTreeView が表示されます。これをパレットからウィンドウにドラッグ&ドロップします。
- データベース内の各顧客に新しい BeanNode を作成する、ファクトリクラスを作成します。
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; } } } - CustomerTopComponent に戻り、ExplorerManager を使用して JPA クエリーの結果リストを Node に渡します。
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"); //} - アプリケーションを実行します。アプリケーションが起動したら、プロパティーウィンドウを開きます。BeanTreeView に表示されるデータがあるとしても、この BeanTreeView はプロパティーウィンドウ (「Window」>「Properties」から表示) と同期しません。つまり、ツリー階層を上下に移動しても、プロパティーウィンドウには何も表示されません。
- 次のコードを TopComponent 内のコンストラクタに追加して、プロパティーウィンドウを BeanTreeView と同期させます。
associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
これで、TopComponent の ActionMap と ExplorerManager を TopComponent の Lookup に追加しました。これには、選択した Node の表示名とツールチップテキストがプロパティーウィンドウに表示されるようになるという副次的な効果があります。
- アプリケーションを再度実行し、今度は、プロパティーウィンドウがエクスプローラビューと同期することを確認します。
これで、JTree を使用した場合と同じように、ツリー階層でデータを表示できるようになりました。別のエクスプローラビューに切り替えることもできますが、その際にモデルを変更する必要はまったくありません。これは、ExplorerManager がモデルとビューを媒介するためです。最後に、ビューとプロパティーウィンドウを同期することもできるようになりました。
更新
この節では、まずエディタを作成します。エディタは、新しい NetBeans モジュールによって提供されます。このため、まず、新しいモジュールを作成します。次に、この新しいモジュール内で、新しい TopComponent を作成し、ユーザーが編集する各列に対して JTextFields を 2 つ含めます。ビューアモジュールがエディタモジュールと通信できるようにする必要があります。ビューアモジュール内で新しい Node が選択されるたびに、現在の Customer オブジェクトを Lookup に追加します。エディタモジュールで、Customer オブジェクトを挿入する Lookup を待機します。新しい Customer オブジェクトが Lookup に挿入されるたびに、エディタで JTextFields を更新します。
次に、JTextFields を、NetBeans プラットフォームの元に戻す、再実行、および保存の機能と同期させます。つまり、ユーザーが JTextField を変更したときに、NetBeans プラットフォームの既存の機能を利用できるようにします。このようにすると、新しい機能を作成せずに、NetBeans プラットフォームのサポートに取り込むことができます。これを実現するには、UndoRedoManager を SaveCookie とともに使用する必要があります。
- 「CustomerEditor」という名前で新しいモジュールを作成し、コード名ベースに「org.shop.editor」を指定します。
- CustomerEditor モジュールを右クリックして、「新規」>「ウィンドウコンポーネント」を選択します。ウィンドウが editor の位置に表示され、アプリケーションが起動するときに開くように指定する必要があります。ウィザードの最後のパネルで、クラス名の接頭辞として「Editor」を設定します。
- パレット (Ctrl-Shift-8) を使用して、JLabels を 2 個と JTextFields を 2 個、新しいウィンドウに追加します。ラベルのテキストに「Name」と「City」を設定し、2 個の JTextFields の変数名にそれぞれ jTextField1 と jTextField2 を設定します。
GUI ビルダーで、ウィンドウが次のように表示されます。

- CustomerViewer モジュールに戻り、layer.xml ファイルを変更して CustomerTopComponent ウィンドウが explorer モードで表示されるように指定します。
layer.xml ファイルを変更したあとは、アプリケーションプロジェクトを右クリックし、「生成物を削除」を選択します。これには理由があります。アプリケーションを実行して終了するたびに、ウィンドウの位置がユーザーディレクトリに保存されるからです。このため、CustomerViewer が当初 editor モードで表示されていた場合、「生成物を削除」を実行するまで、editor モードのままになります。「生成物を削除」により、ユーザーディレクトリがリセットされ (つまり、ユーザーディレクトリが削除され)、CustomerViewer が有効になり、現在 layer.xml ファイルに設定されている位置に表示されます。
また、ユーザーによってアプリケーションのサイズが変更されたときに、CustomerViewer の BeanTreeView が縦または横に伸縮するかどうかを確認します。この確認を行うには、ウィンドウを開いて BeanTreeView を選択してから、GUI ビルダーのツールバーの矢印ボタンをクリックします。
- アプリケーションを実行し、アプリケーションの起動時に次のようになるかを確認します。

- これで、コードの追加を開始できます。最初に、現在選択されている Customer オブジェクトをエディタに表示する必要があります。
- まず、CustomerViewer モジュールを調整し、新しい Node が選択されるたびに、現在の Customer オブジェクトがビューアのウィンドウの Lookup に追加されるようにします。CustomerChildFactory クラスに、BeanNode ではなく AbstractNode を作成することによって、これを実現します。次に示すように、現在の Customer オブジェクトをノードの Lookup に追加できます (ボールドの部分)。
@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 クラスの署名を次のように変更します。
public final class EditorTopComponent extends TopComponent implements LookupListener
- resultChanged をオーバーライドし、新しい Customer オブジェクトが Lookup に挿入されるたびに JTextFields が更新されるようにします。
@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 がアクティブになるなど) はすべて、次に示すように、エディタウィンドウが開かれるたびに開始されます。
@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 はアプリケーションの起動時に使用可能になります。
- 最後に、次に示すように、クラスの先頭で結果変数を宣言します。
private Lookup.Result result = null;
- アプリケーションを再度実行し、新しい Node を選択するたびに、エディタウィンドウが更新されることを確認します。

一方、フォーカスをエディタウィンドウに切り替えるときに何が起こるかを確認します。

Node は選択を解除されたため、Customer オブジェクトはグローバルコンテキストでなくなります。これは、すでに指摘したとおり、グローバルコンテキストが現在の Node の Lookup の代わりとして機能しているためです。このため、この例では、グローバルコンテキストを使用できません。代わりに、「Customer」ウィンドウによって提供されるローカル Lookup を使用します。
次のコードを書き換えます。
result = Utilities.actionsGlobalContext().lookupResult(Customer.class);
次のようにします。
result = WindowManager.getDefault().findTopComponent("CustomerTopComponent").getLookup().lookupResult(Customer.class);文字列「CustomerTopComponent」は CustomerTopComponent の ID であり、CustomerTopComponent のソースコードで確認できる文字列定数です。この方法の欠点は、ID が「CustomerTopComponent」の TopComponent を見つけることができなければ、EditorTopComponent が動作しないという点です。この点については、明確な文書を作成し、このようにしてビューアの TopComponent を識別する必要があることを代替エディタの開発者に示すか、Tim Boudreau がここで説明しているように、選択モデルを書き換える必要があります。
これらの方法のどちらかを採用した場合は、次に示すように、フォーカスを EditorTopComponent に切り替えてもコンテキストが失われません。

BeanNode の代わりに AbstractNode を使用しているため、「プロパティー」ウィンドウにプロパティーは表示されません。ノード API のチュートリアルに説明されているように、自分で指定する必要があります。
- まず、CustomerViewer モジュールを調整し、新しい Node が選択されるたびに、現在の Customer オブジェクトがビューアのウィンドウの Lookup に追加されるようにします。CustomerChildFactory クラスに、BeanNode ではなく AbstractNode を作成することによって、これを実現します。次に示すように、現在の Customer オブジェクトをノードの Lookup に追加できます (ボールドの部分)。
- 次に、元に戻す/再実行の機能に取り組みます。具体的には、ユーザーが JTextFields のいずれかを変更するたびに、「Undo」ボタンと「Redo」ボタン、および「Edit」メニューの関連するメニュー項目が有効になるようにします。これを実現するために、NetBeans プラットフォームは UndoRedo.Manager を使用可能にします。
- 新しい UndoRedoManager を EditorTopComponent の先頭で宣言し、インスタンス化します。
private UndoRedo.Manager manager = new UndoRedo.Manager();
- 次に、EditorTopComponent 内の getUndoRedo() メソッドをオーバーライドします。
@Override public UndoRedo getUndoRedo() { return manager; } - EditorTopComponent のコンストラクタで、KeyListener を JTextFields に追加し、実装する必要のある関連メソッド内に UndoRedoListeners を追加します。
jTextField1.getDocument().addUndoableEditListener(manager); jTextField2.getDocument().addUndoableEditListener(manager);
- アプリケーションを再度実行し、「Undo」と「Redo」のボタンとメニュー項目の機能が動作することを確認します。この機能は、予想どおりに動作します。必要な場合は、KeyListener を変更して、一部のキーに対して元に戻す/再実行の機能を無効にすることもできます。たとえば、Enter キーが押されたときに、元に戻す/再実行の機能が有効になるようにはしないでしょう。このため、前出のコードは、ビジネス要件に合わせて調整します。
- 新しい UndoRedoManager を EditorTopComponent の先頭で宣言し、インスタンス化します。
- 3 つ目に、NetBeans プラットフォームの保存機能と統合する必要があります。
- デフォルトで、「Save All」ボタンは NetBeans プラットフォームツールバーで使用できます。このシナリオでは、「すべて」を保存するのではありません。「すべて」は複数のドキュメントがあることを示すからです。ここでは、「ドキュメント」は 1 個しかありません。それは、ツリー階層内の全ノードで再利用されるエディタです。CustomerEditor モジュールのレイヤーファイルに次のコードを追加して、「Save All」ボタンを削除し、代わりに「Save」ボタンを追加します。
<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 コンストラクタで、変更が検出されたときにメソッドを起動する呼び出しを追加します (次の手順で定義)。
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 に追加されます。
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」ボタンが有効か無効かを確認します。

ここで、上のダイアログの「OK」をクリックしても、何も行われません。次の手順で、変更を維持するための、いくつかの JPA コードを追加します。
- 次に、変更を維持するための JPA コードを追加します。これを行うには、「//保存の機能をここに実装」のコメントを置き換えます。このコメントを、次のコードで置き換えます。
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 を取得します。
@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」を示す永続化された顧客名が表示されています。

- デフォルトで、「Save All」ボタンは NetBeans プラットフォームツールバーで使用できます。このシナリオでは、「すべて」を保存するのではありません。「すべて」は複数のドキュメントがあることを示すからです。ここでは、「ドキュメント」は 1 個しかありません。それは、ツリー階層内の全ノードで再利用されるエディタです。CustomerEditor モジュールのレイヤーファイルに次のコードを追加して、「Save All」ボタンを削除し、代わりに「Save」ボタンを追加します。
- 4 つ目に、Customer ビューアを更新する機能を追加する必要があります。ビューアを定期的に更新する Timer を追加することができます。しかし、この例では、ルートノードに「Refresh」メニュー項目を追加し、ユーザーがビューアを手動で更新できるようにします。
- CustomerViewer モジュールのメインパッケージで、新しい Node を作成し、ビューアの子ルートとして現在使用している AbstractNode を置き換えます。さらに、「Refresh」アクションを、作成したルートノードにバインドします。
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 に追加します。
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」アクションを備えた新しいルートノードがあることを確認します。

- 一部のデータを変更して保存し、「Refresh」アクションを呼び出して、ビューアが更新されていることを確認します。
- CustomerViewer モジュールのメインパッケージで、新しい Node を作成し、ビューアの子ルートとして現在使用している AbstractNode を置き換えます。さらに、「Refresh」アクションを、作成したルートノードにバインドします。
ここでは、NetBeans プラットフォームが JTextFields に加えられた変更を処理する方法を学習しました。テキストが変更されるたびに、NetBeans プラットフォームの「Undo」ボタンと「Redo」ボタンが有効化または無効化されます。さらに、変更したデータをユーザーがデータベースに保存できるように、「Save」ボタンが適切に有効化または無効化されます。
作成
この節では、データベース内にユーザーが新しいエントリを作成できるようにします。
- CustomerEditor モジュールを右クリックし、「新規アクション」を選択します。「新規アクション」ウィザードを使用して、「常に有効」アクションを新しく作成します。新しいアクションは、ツールバーまたはメニューバーの任意の場所、あるいはその両方に表示されるはずです。ウィザードの次の手順で、NewAction アクションを呼び出します。
16x16 のアイコンを使用できることを確認します。このアイコンは、ツールバーから呼び出されるアクションを指定する場合に、ウィザードで選択する必要があります。
- 新規アクションで、TopComponent と空の JTextFields が開くようにします。
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 オブジェクトを作成する次のメソッドを追加します。
public void resetFields() { customer = new Customer(); jTextField1.setText(""); jTextField2.setText(""); } - SaveCookie で、null の戻り値が、既存のエントリの更新ではなく、新しいエントリが保存されたことを示すようにします。
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(); } } } - アプリケーションを再度実行し、データベースに新しい顧客を追加します。
削除
この節では、ユーザーがデータベースで選択したエントリを削除できるようにします。前述の概念とコードを使用して、削除アクションを自分で実装してください。
- 新規アクションの DeleteAction を作成します。作成したアクションを Customer ノードにバインドするか、ツールバー、メニューバー、キーボードショートカット、またはそれらの組み合わせにバインドするかを決定します。バインドする場所によって、コード内で異なる方法を使用する必要があります。ヘルプについては、再度このチュートリアルを読んでください。特に「新規」アクションの作成方法を読んで、この方法とルートノードに「Refresh」アクションを作成する方法とを比較してください。
- 現在の Customer オブジェクトを取得し、「Are you sure?」ダイアログを返して、エントリを削除します。このやり方に関するヘルプについては、「保存」機能が実装される部分を中心に、再度チュートリアルを読んでください。保存する代わりに、ここでは、データベースからエントリを削除します。
関連項目
これで、NetBeans プラットフォーム CRUD チュートリアルを終了します。このドキュメントは、指定されたデータベースに、CRUD 機能を備えた新しい NetBeans プラットフォームアプリケーションを作成する方法について説明しました。アプリケーションの作成と開発の詳細については、次のリソースを参照してください。
