blob: 73e6e7cb39191cf87d915a29100f75de66174c32 [file] [log] [blame]
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<!-- -*- xhtml -*- -->
<title>NetBeans CRUD Application Tutorial for NetBeans Platform 7.0</title>
<meta name="AUDIENCE" content="NBUSER">
<meta name="TYPE" content="ARTICLE">
<meta name="EXPIRES" content="N">
<meta name="developer" content="gwielenga@netbeans.org">
<meta name="indexed" content="y">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="description"
content="A guide describing how to create a CRUD application on
NetBeans Platform 7.0.">
<link rel="stylesheet" type="text/css" href="https://netbeans.org/netbeans.css">
<!-- Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. -->
<!-- Use is subject to license terms.-->
</head>
<body>
<h1><a name="top"></a>NetBeans Platform CRUD Application Tutorial</h1>
<p>This tutorial shows you how to integrate a Java DB database into a
NetBeans Platform application. We start by exploring a Java DB database,
from which we create entity classes. Though the starting point
of this tutorial is Java DB, be aware that these instructions
are not applicable to Java DB only. Rather, they are relevant to any
relational database supported by NetBeans IDE.
<p><b class="notes">Note:</b> Though the database access part of this tutorial
is fine for smaller applications, there are many other factors
to be aware of in the context of larger real-life scenarios. It is
important to be aware of these factors, aspects of which are described
<a href="http://blog.schauderhaft.de/2008/09/28/hibernate-sessions-in-two-tier-rich-client-applications/">here</a>.</p>
<p>Once we have code for accessing the database, we wrap the entity classes
into a module, together with modules for the related JPA JARS. Once
the data access module is part of our application, we create a new
module that provides the user interface for our application. The
new module gives the user a tree hierarchy showing data from the database.
We then create another module that lets the user edit the data displayed
by the first module. By separating the viewer from the editor in distinct modules, we will enable
the user to install a different editor for the same viewer, since different
editors could be created by external vendors, some commercially and some
for free. It is this flexibility that the modular architecture of the
NetBeans Platform makes possible.
<p>Once we have an editor, we begin adding CRUD functionality. First, the
"R", standing for "Read", is handled by the viewer described above. Next,
the "U" for "Update" is handled, followed by the "C" for "Create", and the
"D" for "Delete".
<p>At the end of the tutorial, you will have learned about a range of NetBeans
Platform features that help you in creating applications of this kind. For
example, you will have learned about the <tt><a href="http://bits.netbeans.org/dev/javadoc/org-openide-awt/org/openide/awt/UndoRedo.Manager.html">UndoRedo.Manager</a></tt> and the <tt><a href="http://bits.netbeans.org/dev/javadoc/org-openide-explorer/org/openide/explorer/ExplorerManager.html">ExplorerManager</a></tt>,
as well as NetBeans Platform Swing components, such as <tt><a href="http://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/TopComponent.html">TopComponent</a></tt> and <tt><a href="http://bits.netbeans.org/dev/javadoc/org-openide-explorer/org/openide/explorer/view/BeanTreeView.html">BeanTreeView</a></tt>.
<p><b class="notes">Note:</b> This document assumes the NetBeans IDE 7.0 Release. If you
are using an earlier version, see the <a href="691/nbm-crud.html">previous version
of this document</a>.</p>
<p><b>Contents</b></p>
<p><img src="../../images/articles/70/netbeans-stamp.gif" class="stamp" width="114" height="114" alt="Content on this page applies to NetBeans IDE 6.9 and 7.0" title="Content on this page applies to NetBeans IDE 6.9 and 7.0"/></p>
<ul class="toc">
<li><a href="#creating-app">Setting Up the Application</a></li>
<li><a href="#integrating-database">Integrating the Database</a>
<ul>
<li><a href="#creating-entity">Creating Entity Classes from a Database</a></li>
<li><a href="#wrapping-entity">Wrapping the Entity Class JAR in a Module</a></li>
<li><a href="#creating-other">Creating Other Related Modules</a></li>
<li><a href="#designing-ui">Designing the User Interface</a></li>
<li><a href="#setting-dependencies">Setting Dependencies</a></li>
<li><a href="#running-prototype">Running the Prototype</a></li>
</ul>
<li><a href="#integrating-crud">Integrating CRUD Functionality</a>
<ul>
<li><a href="#read">Read</a></li>
<li><a href="#update">Update</a></li>
<li><a href="#create">Create</a></li>
<li><a href="#delete">Delete</a></li>
</ul>
</li>
<li><a href="#nextsteps">Further Reading</a></li>
</ul>
<p><b>To follow this tutorial, you need the software and resources listed in the following
table.</b></p>
<table>
<tbody>
<tr>
<th class="tblheader" scope="col">Software or Resource</th>
<th class="tblheader" scope="col">Version Required</th>
</tr>
<tr>
<td class="tbltd1"><a href="https://netbeans.org/downloads/index.html">NetBeans IDE</a></td>
<td class="tbltd1">version 7.0 or above</td>
</tr>
<tr>
<td class="tbltd1"><a href="http://java.sun.com/javase/downloads/index.jsp">Java Developer Kit (JDK)</a></td>
<td class="tbltd1">Version 6</td>
</tr>
</tbody>
</table>
<p>The application you create in this tutorial will
look as follows:</p>
<p><img alt="the final state of the application" src="../../images/tutorials/crud/70/dbmanager-99c.png"/> </p>
<p class="tips"> It is advisable to watch the screencast series
<a href="https://platform.netbeans.org/tutorials/nbm-10-top-apis.html">Top 10 NetBeans APIs</a>
before beginning to work on this tutorial. Many of the concepts addressed in this tutorial
are discussed in more detail within the screencast series.
<p>
<!-- ===================================================================================== -->
<br>
<h2 class="tutorial"><a name="creating-app"></a>Setting up the Application</h2>
<p>Let's start by creating a new NetBeans Platform application.
<div class="indent">
<ol>
<li>Choose File &gt; New Project (Ctrl+Shift+N). Under Categories, select NetBeans Modules.
Under Projects, select NetBeans Platform Application. Click Next.</li>
<li>In the Name and Location panel, type <tt>DBManager</tt> in the Project Name field.
Click Finish.</li>
</ol>
<p>The IDE creates the <tt>DBManager</tt> project. The project
is a container for all the other modules you will create:
<p><img alt="NetBeans Platform container" src="../../images/tutorials/crud/70/dbmanager-1.png"/> </p>
</div>
<!-- ===================================================================================== -->
<h2><a name="integrating-database"></a>Integrating the Database</h2>
<p>In order to integrate the database, you need to create entity classes
from your database and integrate those entity classes, together with
their related JARs, into modules as part of your NetBeans Platform
application.</p>
<h3 class="tutorial"><a name="creating-entity"></a>Creating the Entity Classes</h3>
<p>In this section, you generate entity classes from a selected database.
<div class="indent">
<ol>
<li><p>Let's start by using the IDE to inspect the database that we will
use in our application. Use the Services window (Ctrl-5) to
connect to the sample database that is included with
NetBeans IDE:</p>
<p><img alt="Services window" src="../../images/tutorials/crud/70/dbmanager-2.png"/> </p>
<p class="tips"> Alternatively, use
any database you like and adapt the steps that follow to your particular
use case. For Oracle Database, see <a href="https://netbeans.org/kb/docs/ide/oracle-db.html">Connecting to an Oracle Database</a>; for
MySQL, see <a href="https://netbeans.org/kb/docs/ide/mysql.html">Connecting to a MySQL Database</a> for help.</p>
<p><li><p>Now we will create a library that will contain entity classes for the
tables that we're interested in for our application.
In the IDE, choose File | New Project, followed by
Java | Java Class Library and create a new library project, anywhere on disk,
named <tt>CustomerLibrary</tt>:</p>
<p><img alt="Services window" src="../../images/tutorials/crud/70/dbmanager-2a.png"/> </p>
<p><li><p>In the Projects window, right-click the <code>CustomerLibrary</code> project
and choose File | New File, followed by Persistence | "Entity
Classes from Database". In the wizard, select your database and the tables you
need. Here we choose "Customer", and then "Discount Code" is added automatically, since
there is a relationship between these two tables:</p>
<p><img style="border: 1px solid black" alt="adding tables" src="../../images/tutorials/crud/70/dbmanager-3.png"/> </p>
<p>Click Next.
<p><li><p>Type "demo" as the name of
the package where the entity classes will be generated and leave everything
else unchanged:</p>
<p><img alt="name of package" src="../../images/tutorials/crud/70/dbmanager-4.png"/> </p>
<p><li><p>Click Finish. Once you have completed this step, look at the generated code and notice
that, among other files, you now have a <code>persistence.xml</code> file in a folder
called META-INF, as well as entity classes for each of your tables:</p>
<p><img alt="entity classes" src="../../images/tutorials/crud/70/dbmanager-7.png"/> </p>
<p><li><p>Right-click and then build the <code>CustomerLibrary</code>. Switch to the
Files window (Ctrl-2) and notice that you have a JAR file in the library
project's "dist" folder:</p>
<p><img alt="dist folder" src="../../images/tutorials/crud/70/dbmanager-8.png"/> </p>
</li>
</ol>
</div>
<h3 class="tutorial"><a name="wrapping-entity"></a>Wrapping the Entity Class JAR in a Module</h3>
<p>In this section, you add your first module to your application!
The new NetBeans module will wrap the JAR file
you created in the previous section.
<div class="indent">
<ol>
<li><p>Right-click the <tt>DBManager</tt>'s "Modules" node in the Projects window
and choose Add New Library.</p>
<p><li><p>In the "New Library Wrapper Module Project" dialog,
select the JAR you created in the previous subsection. No need to
include a license; leave the License field empty.
Complete the wizard, specifying any values you like. Let's assume
the application is for dealing with customers at shop.org, in which case
a unique identifier <code>org.shop.model</code> is appropriate for the code name base, since
this module provides the model (also known as "domain") of the application:</p>
<p><img alt="unique id for module" style="border: 1px black solid" src="../../images/tutorials/crud/70/dbmanager-9.png"/> </p>
</ol>
<p>You now have your first custom module in your new application, which
wraps the JAR containing the entity classes and
the persistence.xml file:</p>
<p><img alt="persistence.xml" src="../../images/tutorials/crud/70/dbmanager-91.png"/> </p>
</div>
<h3 class="tutorial"><a name="creating-other"></a>Creating Other Related Modules</h3>
<p>In this section, you create two new modules, wrapping the EclipseLink JARs,
as well as the database connector JAR.</p>
<div class="indent">
<ol>
<li><p>Do the same as you did when creating the library wrapper for the
entity class JAR, but this time for the EclipseLink JARs, which are in
the NetBeans IDE installation directory, within the "<tt>java/modules/ext</tt>"
folder, as shown below:</p>
<p><img alt="wrapping a library" src="../../images/tutorials/crud/70/dbmanager-94.png"/></p>
<p class="tips"> In the Library Wrapper Module wizard,
you can use Ctrl-Click to select multiple JARs.</p>
<p><li><p>Next, create yet another library wrapper module, this time for the Java DB client JAR,
which is named <tt>derbyclient.jar</tt>.
The location of this JAR depends on your version of the JDK, as well as on your operating system.
For example, on Linux systems, this JAR could be found within your JDK
distribution at <tt>"db/lib/derbyclient.jar"</tt>. On Windows systems, depending on your version
of the JDK, you could find this JAR here, instead, i.e., at "<tt>C:\Program Files\Sun\JavaDB\lib</tt>":</p>
<p><img alt="wrapping a library" src="../../images/tutorials/crud/70/dbmanager-94a.png"/></p>
<p class="tips"> To use an embedded Java DB database, instead
of the external Java DB database used in this tutorial,
<a href="http://blogs.oracle.com/geertjan/entry/embedded_database_for_netbeans_platform">read this article</a>.</p>
<p><li><p>Your application structure should now be as shown below. You should
see that you have an application that contains three modules. One module
contains the customer library, while the other two contain the <tt>EclipeLink</tt> JARs
and the Derby Client JAR:</p>
<p><img alt="wrapping a library" src="../../images/tutorials/crud/70/dbmanager-94b.png"/></p>
</li>
</ol>
</div>
<p>Now it is, finally, time to do some coding!</p>
<h3 class="tutorial"><a name="designing-ui"></a>Designing the User Interface</h3>
<p>In this section, you create a simple prototype user interface, providing
a window that uses a <tt>JTextArea</tt> to display data retrieved
from the database.
<div class="indent">
<ol>
<li><p>Right-click the <tt>DBManager</tt>'s Modules node in the Projects window
and choose Add New. Create a new module named <tt>CustomerViewer</tt>, with
the code name base <tt>org.shop.viewer</tt>. Click Finish. You now have a fourth
module in your application.</p>
<p><li>In the Projects window, right-click the new module and choose
New | Window. Specify that it should be created in the <tt>explorer</tt>
position and that it should open when the application starts. Set <tt>CustomerViewer</tt>
as the window's class name prefix. Click Finish.
<p><li><p>Use the Palette (Ctrl-Shift-8) to drag
and drop a <tt>JTextArea</tt> on the new window:</p>
<p><img alt="JTextArea dropped" style="border: 1px solid black" src="../../images/tutorials/crud/70/dbmanager-95.png"/></p>
<p><li>Click the "Source" tab and the source code of the <tt>TopComponent</tt> opens. Add this
to the end of the TopComponent constructor:
<pre class="examplecode">EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager();
Query query = entityManager.createQuery("SELECT c FROM Customer c");
List&lt;Customer&gt; resultList = query.getResultList();
for (Customer c : resultList) {
jTextArea1.append(c.getName() + " (" + c.getCity() + ")" + "\n");
}</pre>
<p class="tips"> Since you have not set dependencies on the modules that
provide the Customer object and the persistence JARs, the statements
above will be marked with red error underlines. These will be fixed
in the section that follows.</p>
<p>Above, you can see references to a persistence unit named "CustomerLibraryPU",
which is the name set in the <tt>persistence.xml</tt> file. In addition, there is a reference
to one of the entity classes, called <tt>Customer</tt>, which is in the entity classes module.
Adapt these bits to your needs, if they are different to the above.
</ol>
</div>
<h3 class="tutorial"><a name="setting-dependencies"></a>Setting Dependencies</h3>
<p>In this section, you enable some of the modules to use code from
some of the other modules. You do this very explicitly by setting intentional
contracts between related modules, i.e., as opposed
to the accidental and chaotic reuse of code that tends to happen when
you do not have a strict modular architecture such as that provided by
the NetBeans Platform.
<div class="indent">
<ol>
<li>The entity classes module needs to have dependencies on the Derby Client
module as well as on the EclipseLink module. Right-click the
<tt>CustomerLibrary</tt> module, choose Properties, and use the
Libraries tab to set dependencies on the two modules that the
<tt>CustomerLibrary</tt> module needs.
<p><li>The <tt>CustomerViewer</tt> module needs a dependency
on the EclipseLink module as well as on the entity classes module.
Right-click the
<tt>CustomerViewer</tt> module, choose Properties, and use the
Libraries tab to set dependencies on the two modules that the
<tt>CustomerViewer</tt> module needs.
<p><li>Open the <tt>CustomerViewerTopComponent</tt> in the Source view, right-click
in the editor, and choose "Fix Imports". The IDE is now able to add
the required import statements, because the modules that provide
the required classes are now available to the <tt>CustomerViewerTopComponent</tt>. The
import statememts you should now have are as follows:
<pre class="examplecode">import demo.Customer;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Persistence;
import javax.persistence.Query;
import org.openide.util.NbBundle;
import org.openide.windows.TopComponent;
import org.netbeans.api.settings.ConvertAsProperties;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;</pre>
</ol>
</div>
<p>You now have set contracts between the modules
in your application, giving you control
over the dependencies between distinct pieces
of code.</p>
<h3 class="tutorial"><a name="running-prototype"></a>Running the Prototype</h3>
<p>In this section, you run the application so that you can see
that you're correctly accessing your database.
<div class="indent">
<ol>
<li>Start your database server.
<p><li><p>Run the application. You should see this:</p>
<p><img alt="running the prototype" src="../../images/tutorials/crud/70/dbmanager-92.png"/></p>
</ol>
</div>
<p>You now have a simple prototype, consisting of a NetBeans Platform
application that displays data from your database,
which you will extend in the next
section.</p>
<h2><a name="integrating-crud"></a>Integrating CRUD Functionality</h2>
<p>In order to create CRUD functionality that integrates smoothly
with the NetBeans Platform, some very specific NetBeans Platform coding patterns need to
be implemented. The sections that follow describe these patterns
in detail.</p>
<h3 class="tutorial"><a name="read"></a>Read</h3>
<p>In this section, you change the <tt>JTextArea</tt>, introduced in the
previous section, for a NetBeans Platform explorer view. NetBeans Platform
explorer views are Swing components that integrate better with the
NetBeans Platform than standard Swing components do. Among other things,
they support the notion of a context, which enables them to be
context sensitive.
<p>Representing your data,
you will have a generic hierarchical model provided by a NetBeans Platform
<tt>Node</tt> class, which can be displayed by any of the NetBeans Platform
explorer views. This section ends with an explanation of how to synchronize
your explorer view with the NetBeans Platform Properties window.
<div class="indent">
<ol>
<li>In your <tt>TopComponent</tt>, delete the <tt>JTextArea</tt>
in the Design view and comment out its
related code in the Source view:
<pre class="examplecode">EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager();
Query query = entityManager.createQuery("SELECT c FROM Customer c");
List&lt;Customer&gt; resultList = query.getResultList();
//for (Customer c : resultList) {
// jTextArea1.append(c.getName() + " (" + c.getCity() + ")" + "\n");
//}</pre>
<p><li>Right-click the <tt>CustomerViewer</tt> module, choose Properties,
and use the Libraries tab to set dependencies on the Nodes API and
the Explorer & Property Sheet API.
<p><li>Next, change the class signature to implement <tt>ExplorerManager.Provider</tt>:
<pre class="examplecode">final class CustomerViewerTopComponent extends TopComponent implements ExplorerManager.Provider</pre>
<p>You will need to override <tt>getExplorerManager()</tt>
<pre class="examplecode">@Override
public ExplorerManager getExplorerManager() {
return em;
}</pre>
<p>At the top of the class, declare and initialize the <tt>ExplorerManager</tt>:
<pre class="examplecode">private static ExplorerManager em = new ExplorerManager();</pre>
<p class="tips"> Watch <a href="https://platform.netbeans.org/tutorials/nbm-10-top-apis.html">Top 10 NetBeans APIs</a>
for details on the above code, especially the screencast dealing with the Nodes API
and the Explorer & Property Sheet API.
<p><li>Switch to the <tt>TopComponent</tt> Design view,
right-click in the Palette, choose Palette Manager | Add from JAR. Then browse to
the <tt>org-openide-explorer.jar</tt>, which is in <tt>platform/modules</tt> folder,
within the NetBeans IDE installation directory. Choose the BeanTreeView and complete
the wizard. You should now see <tt>BeanTreeView</tt> in the Palette. Drag it from
the Palette and drop it on the window.
<p><li>Create a factory class that will create a new <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-db/org/netbeans/api/db/explorer/node/BaseNode.html">BeanNode</a>
for each customer in your database:
<pre class="examplecode">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&lt;Customer&gt; {
private List&lt;Customer&gt; resultList;
public CustomerChildFactory(List&lt;Customer&gt; resultList) {
this.resultList = resultList;
}
@Override
protected boolean createKeys(List&lt;Customer&gt; 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;
}
}
}</pre>
<p><li>Back in the <tt>CustomerViewerTopComponent</tt>,
use the <tt>ExplorerManager</tt> to pass the result list
from the JPA query in to the <tt>Node</tt>:
<pre class="examplecode">EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager();
Query query = entityManager.createQuery("SELECT c FROM Customer c");
List&lt;Customer&gt; resultList = query.getResultList();
<b>em.setRootContext(new AbstractNode(Children.create(new CustomerChildFactory(resultList), true)));</b>
//for (Customer c : resultList) {
// jTextArea1.append(c.getName() + " (" + c.getCity() + ")" + "\n");
//}</pre>
<p><li>Run the application. Once the application is running,
open the Properties window. Notice that even though the data
is available, displayed in a <tt>BeanTreeView</tt>, the <tt>BeanTreeView</tt>
is not synchronized with the Properties window, which is available
via Window | Properties. In other words, nothing is displayed
in the Properties window when you move up and down the tree hierarchy.
<p><li>Synchronize the Properties window with the <tt>BeanTreeView</tt>
by adding the following to the constructor in the <tt>TopComponent</tt>:
<pre class="examplecode">associateLookup(ExplorerUtils.createLookup(em, getActionMap()));</pre>
<p>Here we add the <tt>TopComponent</tt>'s <tt>ActionMap</tt> and <tt>ExplorerManager</tt>
to the <tt>Lookup</tt> of the <tt>TopComponent</tt>. A side effect of this is
that the Properties window starts displaying the display name and tooltip text
of the selected <tt>Node</tt>.
<p><li>Run the application again and notice that the Properties window (available
from the Window menu) is now synchronized with the explorer view:
<p><img alt="synchronization" src="../../images/tutorials/crud/70/dbmanager-95a.png"/>
</ol>
</div>
<p>Now you are able to view your data in a tree hierarchy, as you would
be able to do with a <tt>JTree</tt>. However, you're also able to swap
in a different explorer view without needing to change the model at all
because the <tt>ExplorerManager</tt> mediates between the model and the
view. Finally, you are now also able to synchronize the view with the
Properties window.
<h3 class="tutorial"><a name="update"></a>Update</h3>
<p>In this section, you first create an editor. The editor will be provided
by a new NetBeans module. So, you will first create a new module. Then, within
that new module, you will create a new <tt>TopComponent</tt>, containing two <tt>JTextFields</tt>,
for each of the columns you want to let the user edit. You will need to
let the viewer module communicate with the editor module. Whenever a new
<tt>Node</tt> is selected in the viewer module, you will add the current <tt>Customer</tt>
object to the <tt>Lookup</tt>. In the editor module, you will listen to the
<tt>Lookup</tt> for the introduction of <tt>Customer</tt> objects. Whenever a
new <tt>Customer</tt> object is introduced into the <tt>Lookup</tt>, you will
update the <tt>JTextFields</tt> in the editor.
<p>Next, you will synchronize your <tt>JTextFields</tt>
with the NetBeans Platform's Undo, Redo, and Save functionality. In other words,
when the user makes changes to a <tt>JTextField</tt>, you want the
NetBeans Platform's existing functionality to become available so that,
instead of needing to create new functionality, you'll simply be able to
hook into the NetBeans Platform's support. To
this end, you will need to use the <tt>UndoRedoManager</tt>, together with the
<tt>SaveCookie</tt>.
<div class="indent">
<ol>
<li>Create a new module, named <tt>CustomerEditor</tt>, with <tt>org.shop.editor</tt> as
its code name base.
<p><li>Right-click the <tt>CustomerEditor</tt> module and choose New | Window.
Make sure to specify that the window should appear in the <tt>editor</tt> position and
that it should open when the application starts. In the final panel of the wizard,
set "CustomerEditor" as the class name prefix.
<p><li><p>Use the Palette (Ctrl-Shift-8) to add two <tt>JLabels</tt> and two <tt>JTextFields</tt>
to the new window. Set the texts of the labels to "Name" and "City" and
set the variable names of the two <tt>JTextFields</tt> to
<tt>nameField</tt>
and <tt>cityField</tt>. In
the GUI Builder, the window should now look something like this:</p>
<p><img alt="designing the user interface" src="../../images/tutorials/crud/70/dbmanager-96.png"/></p>
<li><p>Run the application and make sure that you see the following when the
application starts up:</p>
<p><img alt="running the new UI" src="../../images/tutorials/crud/70/dbmanager-97.png"/></p>
<li><p>Now we can start adding some code.
Firstly, we need to show the currently selected Customer object in the editor:</p>
<ul>
<li>Start by tweaking the <tt>CustomerViewer</tt> module so that the current <tt>Customer</tt> object
is added to the viewer window's <tt>Lookup</tt> whenever a new <tt>Node</tt>
is selected. Do this by adding the
current <tt>Customer</tt> object to the <tt>Lookup</tt> of the Node,
as follows (note the parts in bold):
<pre class="examplecode">@Override
protected Node createNodeForKey(Customer c) {
try {
return <b>new CustomerBeanNode(c);</b>
} catch (IntrospectionException ex) {
Exceptions.printStackTrace(ex);
return null;
}
}
<b>private class CustomerBeanNode extends BeanNode {
public CustomerBeanNode(Customer bean) throws IntrospectionException {
super(bean, Children.LEAF, Lookups.singleton(bean));
}
}</b></pre>
<p>Now, whenever a new <tt>Node</tt> is created, which
happens when the user selects a new customer in the viewer, a new
<tt>Customer</tt> object is added to the <tt>Lookup</tt> of the <tt>Node</tt>.
<p><li>Let's
now change the editor module in such a way that its window
will end up listening for <tt>Customer</tt> objects being added to the <tt>Lookup</tt>. First,
set a dependency in the editor module on the module that provides
the entity class, as well as the module that provides the persistence
JARs.
<p><li>Next, change the <tt>CustomerEditorTopComponent</tt> class signature
to implement <tt>LookupListener</tt>:
<pre class="examplecode">public final class CustomerEditorTopComponent extends TopComponent implements LookupListener</pre>
<p><li>Override the
<tt>resultChanged</tt> so that the <tt>JTextFields</tt> are updated whenever
a new <tt>Customer</tt> object is introduced into the <tt>Lookup</tt>:
<pre class="examplecode">@Override
public void resultChanged(LookupEvent lookupEvent) {
Lookup.Result r = (Lookup.Result) lookupEvent.getSource();
Collection&lt;Customer&gt; coll = r.allInstances();
if (!coll.isEmpty()) {
for (Customer cust : coll) {
nameField.setText(cust.getName());
cityField.setText(cust.getCity());
}
} else {
nameField.setText("[no name]");
cityField.setText("[no city]");
}
}</pre>
<p><li>Now that the <tt>LookupListener</tt> is defined,
we need to add it to something. Here, we add it to
the <tt>Lookup.Result</tt> obtained from the global context.
The global context proxies the context of the selected <tt>Node</tt>.
For example, if "Ford Motor Co" is selected in the tree hierarchy,
the <tt>Customer</tt> object for "Ford Motor Co" is added to the <tt>Lookup</tt>
of the <tt>Node</tt> which, because it is the currently selected <tt>Node</tt>,
means that the <tt>Customer</tt> object for "Ford Motor Co" is now available in
the global context. That is what is then passed to the <tt>resultChanged</tt>,
causing the text fields to be populated.
<p>All of the above starts happening, i.e., the <tt>LookupListener</tt>
becomes active, whenever the editor window is opened, as you can see below:</p>
<pre class="examplecode">@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;
}</pre>
<p>Since the editor window is opened when the application starts, the
<tt>LookupListener</tt> is available at the time that the application starts up.
<p><li>Finally, declare the result variable at the top of the class, like this:
<pre class="examplecode">private Lookup.Result result = null;</pre>
<p><li>Run the application again and notice that the editor window
is updated whenever you select a new <tt>Node</tt>:
<p><img alt="updated editor window" src="../../images/tutorials/crud/70/dbmanager-98.png"/></p>
<p>However, note what happens when you switch the focus to the editor window:</p>
<p><img alt="switch focus" src="../../images/tutorials/crud/70/dbmanager-99.png"/></p>
<p>Because the <tt>Node</tt> is no longer current, the <tt>Customer</tt> object
is no longer in the global context. This is the case because, as pointed out
above, the global context proxies the <tt>Lookup</tt> of the current <tt>Node</tt>.
Therefore, in this case, we cannot use the global context. Instead, we will
use the local <tt>Lookup</tt> provided by the Customer window.</p>
<p>Rewrite this line:
<pre class="examplecode">result = Utilities.actionsGlobalContext().lookupResult(Customer.class);</pre>
<p>To this:</p>
<pre class="examplecode">result = WindowManager.getDefault().findTopComponent("CustomerViewerTopComponent").getLookup().lookupResult(Customer.class);</pre>
<p>The string "CustomerViewerTopComponent" is the ID of the <tt>CustomerViewerTopComponent</tt>, which
is a string constant that you can find in the source code of the <tt>CustomerViewerTopComponent</tt>.</p>
<p class="tips"> One
drawback of the approach above is that now our <tt>CustomerEditorTopComponent</tt> only works if it
can find a <tt>TopComponent</tt> with the ID "CustomerViewerTopComponent". Either this needs to
be explicitly documented, so that developers of alternative editors can know that they
need to identify the viewer <tt>TopComponent</tt> this way, or you need to rewrite the
selection model, <a href="http://weblogs.java.net/blog/timboudreau/archive/2007/01/how_to_replace.html">as described here</a> by Tim Boudreau.
</ul>
<li>Secondly, let's work on the Undo/Redo functionality. What we'd
like to have happen is that whenever the user makes a change to one
of the <tt>JTextFields</tt>, the "Undo" button and the "Redo" button,
as well as the related menu items in the Edit menu, become enabled. To
that end, the NetBeans Platform makes the <a href="http://bits.netbeans.org/dev/javadoc/org-openide-awt/org/openide/awt/UndoRedo.Manager.html">UndoRedo.Manager</a> available.
<ul>
<li>Declare and instantiate a new <tt>UndoRedoManager</tt> at the top of the
<tt>CustomerEditorTopComponent</tt>:
<pre class="examplecode">private UndoRedo.Manager manager = new UndoRedo.Manager();</pre>
<p><li>Next, override the <tt>getUndoRedo()</tt> method in the <tt>CustomerEditorTopComponent</tt>:
<pre class="examplecode">@Override
public UndoRedo getUndoRedo() {
return manager;
}</pre>
<p><li>In the constructor of the <tt>CustomerEditorTopComponent</tt>, add
a <tt>KeyListener</tt> to the <tt>JTextFields</tt> and, within
the related methods that you need to implement, add the <tt>UndoRedoListeners</tt>:
<pre class="examplecode">nameField.getDocument().addUndoableEditListener(manager);
cityField.getDocument().addUndoableEditListener(manager);
</pre>
<p><li>Run the application and try out the Undo and Redo features,
the buttons as well as the menu items. The functionality works exactly as you would expect:</p>
<p><img alt="updated editor window" src="../../images/tutorials/crud/70/dbmanager-99a.png"/>
<p class="tips"> You might want to
change the <tt>KeyListener</tt> so that not ALL keys cause the undo/redo
functionality to be enabled. For example, when Enter is pressed, you probably
do not want the undo/redo functionality to become available. Therefore, tweak
the code above to suit your business requirements.</p>
</li>
</ul>
<li>Thirdly, we need to
integrate with the NetBeans Platform's Save functionality:
<ul>
<li>Set dependencies on the Dialogs API and the Nodes API.
<p><li>In the <tt>CustomerEditorTopComponent</tt> constructor, add a
call to fire a method (which will be defined in the next step)
whenever a change is detected:
<pre class="examplecode"> private final InstanceContent content;
private final CustomerSaveCapability impl;
public CustomerEditorTopComponent() {
...
...
...
nameField.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);
}
});
cityField.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);
}
});
//Create a new instance of our SaveCookie implementation:
impl = new CustomerSaveCapability();
//Create a new instance of our dynamic object:
content = new InstanceContent();
//Add the dynamic object to the TopComponent Lookup:
associateLookup(new AbstractLookup(content));
}
...
...
...
</pre>
<p><li>Here is the method and inner class referred to above. First, the method
that is fired whenever a change is detected. Then, an implementation of
the <tt>SaveCookie</tt> from the Nodes API is dynamically added to the <tt>InstanceContent</tt>
whenever a change is detected:
<pre class="examplecode">public void fire(boolean modified) {
if (modified) {
//If the text is modified,
//we add SaveCookie impl to Lookup:
content.add(impl);
} else {
//Otherwise, we remove the SaveCookie impl from the lookup:
content.remove(impl);
}
}
private class CustomerSaveCapability implements SaveCookie {
@Override
public void save() throws IOException {
Confirmation message = new NotifyDescriptor.Confirmation("Do you want to save \""
+ nameField.getText() + " (" + cityField.getText() + ")\"?",
NotifyDescriptor.OK_CANCEL_OPTION,
NotifyDescriptor.QUESTION_MESSAGE);
Object result = DialogDisplayer.getDefault().notify(message);
//When user clicks "Yes", indicating they really want to save,
//we need to disable the Save action,
//so that it will only be usable when the next change is made
//to the JTextArea:
if (NotifyDescriptor.YES_OPTION.equals(result)) {
fire(false);
//Implement your save functionality here.
}
}
}
</pre>
<p><li>Run the application and notice the enablement/disablement of the
Save menu item.
<p><img alt="enabled save button" src="../../images/tutorials/crud/70/dbmanager-99c.png"/></p>
<p class="tips"> Do not confuse the "Save All" button and menu item with the "Save" button
and menu item. In this tutorial, we're focusing on the "Save" action, which by default is invoked
from the File menu. If needed, you can tweak the central registry to register the Save action
so that it is displayed in the toolbar.</p>
<p class="tips"> Right now, nothing happens when you click OK in the "Question" dialog
above. In the next step, we add some JPA code for handling persistence of
our changes.
<p><li>Next, we add JPA code for persisting our change.
Do so by replacing the comment "//Implement your save functionality here."
The comment should be replaced by all of the following:
<pre class="examplecode">EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager();
entityManager.getTransaction().begin();
Customer c = entityManager.find(Customer.class, customer.getCustomerId());
c.setName(nameField.getText());
c.setCity(cityField.getText());
entityManager.getTransaction().commit();</pre>
<p class="tips"> The "customer" in <tt>customer.getCustomerId()()</tt> is currently undefined. Add the line
in bold in the <tt>resultChanged</tt> below, after declaring <tt>Customer customer;</tt> at the top
of the class, so that the current <tt>Customer</tt> object sets the <tt>customer</tt>,
which is then used in the persistence code above to obtain the ID of
the current <tt>Customer</tt> object.
<pre class="examplecode">@Override
public void resultChanged(LookupEvent lookupEvent) {
Lookup.Result r = (Lookup.Result) lookupEvent.getSource();
Collection&lt;Customer&gt; c = r.allInstances();
if (!c.isEmpty()) {
for (Customer customer : c) {
<b>customer = cust;</b>
nameField.setText(customer.getName());
cityField.setText(customer.getCity());
}
} else {
nameField.setText("[no name]");
cityField.setText("[no city]");
}
}</pre>
<p><li><p>Run the application and change some data. Currently, we have no "Refresh"
functionality (that will be added in the next step) so, to see the changed data, restart the application.</p></li>
</ul>
<li>Fourthly, we need to
add functionality for refreshing the Customer viewer. You might want to
add a <tt>Timer</tt> which periodically refreshes the viewer.
However, in this example, we will add
a "Refresh" menu item to the Root node so that the user will be able
to manually refresh the viewer.
<ul>
<li>In the main package of the <tt>CustomerViewer</tt> module,
create a new <tt>Node</tt>, which will replace the <tt>AbstractNode</tt>
that we are currently using as the root of the children in the
viewer. Note that we also bind all actions in the "Actions/Customer" folder
to the context menu of our
new root node.
<pre class="examplecode">import java.util.List;
import javax.swing.Action;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.util.NbBundle.Messages;
import org.openide.util.Utilities;
import static org.shop.viewer.Bundle.*;
public class CustomerRootNode extends AbstractNode {
@Messages("CTRL_RootName=Root")
public CustomerRootNode(Children kids) {
super(kids);
setDisplayName(CTRL_RootName());
}
@Override
public Action[] getActions(boolean context) {
List<? extends Action> actionsForCustomer = Utilities.actionsForPath("Actions/Customer");
return actionsForCustomer.toArray(new Action[actionsForCustomer.size()]);
}
}</pre>
<p><li>Then create a new Java class and register a refresh Action in the
"Actions/Customer" folder, which means it will appear in the
context menu of the root node that you created above:</p>
<pre class="java">import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import org.openide.awt.ActionID;
import org.openide.awt.ActionRegistration;
import org.openide.util.NbBundle.Messages;
@ActionID(id="org.shop.viewer.CustomerRootRefreshAction", category="Customer")
@ActionRegistration(displayName="#CTL_CustomerRootRefreshAction")
@Messages("CTL_CustomerRootRefreshAction=Refresh")
public class CustomerRootRefreshAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
CustomerViewerTopComponent.refreshNode();
}
}</pre>
<p><li>Add this method to the <tt>CustomerViewerTopComponent</tt>, for refreshing
the view:
<pre class="examplecode">public static void refreshNode() {
EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager();
Query query = entityManager.createQuery("SELECT c FROM Customer c");
List&lt;Customer&gt; resultList = query.getResultList();
em.setRootContext(new <b>CustomerRootNode</b>(Children.create(new CustomerChildFactory(resultList), true)));
} </pre>
<p class="tips"> Now replace the code above in the constructor
of the <tt>CustomerViewerTopComponent</tt> with a call to the above. As
you can see in the highlighted part above, we are now using our <tt>CustomerRootNode</tt> instead
of the <tt>AbstractNode</tt>. The <tt>CustomerRootNode</tt> includes
the "Refresh" action, which calls the code above.
<p><li>In your save functionality, add the call to the method above so that,
whenever data is saved, an automatic refresh takes place. You can
take different approaches when implementing this extension to
the save functionality. For example, you might want to create a
new module that contains the refresh action. That module would
then be shared between the viewer module and the editor module,
providing functionality that is common to both.
<p><li>Run the application again and notice that you have a new root node,
with a "Refresh" action.
<p><img alt="the final state of the application" src="../../images/tutorials/crud/70/dbmanager-99d.png"/> </p>
<p><li>Make a change to some data, save it, invoke the Refresh action, and notice
that the viewer is updated.
</ul>
</ol>
</div>
<p>You have now learned how to let the NetBeans Platform handle changes
to the <tt>JTextFields</tt>. Whenever the text changes, the NetBeans
Platform Undo and Redo buttons are enabled or disabled. Also, the
Save button is enabled and disabled correctly, letting the user
save changed data back to the database.
<h3 class="tutorial"><a name="create"></a>Create</h3>
<p>In this section, you allow the user to create a new entry in the database.
<div class="indent">
<ol>
<li>In the <tt>CustomerEditor</tt>
module create a new Java class named "CustomerNewAction". Let
the <tt>TopComponent</tt> be opened via this Action,
together with emptied <tt>JTextFields</tt>:
<pre class="examplecode">import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import org.openide.awt.ActionID;
import org.openide.awt.ActionRegistration;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.WindowManager;
@ActionID(id="org.shop.editor.CustomerNewAction", category="File")
@ActionRegistration(displayName="#CTL_CustomerNewAction")
@Messages("CTL_CustomerNewAction=Refresh")
public final class CustomerNewAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
CustomerEditorTopComponent tc = (CustomerEditorTopComponent) WindowManager.getDefault().findTopComponent("CustomerEditorTopComponent");
tc.resetFields();
tc.open();
tc.requestActive();
}
}</pre>
<p>In the <tt>CustomerEditorTopComponent</tt>, add the following method for resetting
the <tt>JTextFields</tt> and creating a new <tt>Customer</tt> object:
<pre class="examplecode">public void resetFields() {
customer = new Customer();
nameField.setText("");
cityField.setText("");
}</pre>
<p><li>In the <tt>SaveCookie</tt>, ensure that a return of <tt>null</tt>
indicates that a new entry is saved, instead of an existing entry
being updated:
<pre>public void save() throws IOException {
Confirmation message = new NotifyDescriptor.Confirmation("Do you want to save \""
+ nameField.getText() + " (" + cityField.getText() + ")\"?",
NotifyDescriptor.OK_CANCEL_OPTION,
NotifyDescriptor.QUESTION_MESSAGE);
Object result = DialogDisplayer.getDefault().notify(msg);
//When user clicks "Yes", indicating they really want to save,
//we need to disable the Save button and Save menu item,
//so that it will only be usable when the next change is made
//to the text field:
if (NotifyDescriptor.YES_OPTION.equals(result)) {
fire(false);
EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager();
entityManager.getTransaction().begin();
<b>if (customer.getCustomerId() != null)</b> {
Customer c = entityManager.find(Customer.class, cude.getCustomerId());
c.setName(nameField.getText());
c.setCity(cityField.getText());
entityManager.getTransaction().commit();
} else {
<b>Query query = entityManager.createQuery("SELECT c FROM Customer c");
List&lt;Customer&gt; resultList = query.getResultList();
customer.setCustomerId(resultList.size()+1);
customer.setName(nameField.getText());
customer.setCity(cityField.getText());
//add more fields that will populate all the other columns in the table!
entityManager.persist(customer);
entityManager.getTransaction().commit();</b>
}
}
}</pre>
<p><li>Run the application again and add a new customer to the database.
</ol>
</div>
<h3 class="tutorial"><a name="delete"></a>Delete</h3>
<p>In this section, let the user delete a selected entry in the database. Using
the concepts and code outlined above, implement the Delete action yourself.
<div class="indent">
<ol>
<li>Create a new action, <tt>DeleteAction</tt>. Decide whether you
want to bind it to a Customer node or whether you'd rather bind it
to the toolbar, the menu bar, keyboard shortcut, or combinations of
these. Depending on where you want to bind
it, you will need to use a different approach in your code. Read the
tutorial again for help, especially by looking at how the "New" action
was created, while comparing it to the "Refresh" action on the root
node.
<p><li>Get the current <tt>Customer</tt> object, return an 'Are you sure?' dialog,
and then delete the entry. For help on this point, read the tutorial
again, focusing on the part where the "Save" functionality is implemented.
Instead of saving, you now want to delete an entry from the database.
</ol>
</div>
<!-- ======================================================================================== -->
<h2><a name="nextsteps"></a>Further Reading</h2>
<p>This concludes the NetBeans Platform CRUD Tutorial. This document has described
how to create a new NetBeans Platform application with CRUD functionality for
a given database.</p>
<p>A problem with the design of the application you created in this tutorial
is that the data access code is embedded within the user interface.
For example, the calls to the <code>EntityManager</code> for persisting
changes are found within the <code>TopComponent</code>. To work towards
an architecture that enables a clean separation between data access code
and the user interface, see this series of articles:</p>
<div class="indent">
<ul>
<li><a href="http://netbeans.dzone.com/loosely-coupled-reloadable-capabilities">Loosely Coupled Reloadable Capabilities for CRUD Applications</a>
<li><a href="http://netbeans.dzone.com/loosely-coupled-saveable-capabilities">Loosely Coupled Saveable Capabilities for CRUD Applications</a>
<li><a href="http://netbeans.dzone.com/loosely-coupled-creatable-capabilities">Loosely Coupled Creatable Capabilities for CRUD Applications</a>
<li><a href="http://netbeans.dzone.com/loosely-coupled-deletable-capabilities">Loosely Coupled Deletable Capabilities for CRUD Applications</a>
<li><a href="http://netbeans.dzone.com/loosely-coupled-data-layers">Loosely Coupled Data Layers for CRUD Applications</a>
</ul>
</div>
<p>For information on embedding a database in a NetBeans Platform application,
see <a href="http://blogs.oracle.com/geertjan/entry/embedded_database_for_netbeans_platform">Embedded Database for NetBeans Platform CRUD Tutorial </a>.
<p>For more information about creating and developing applications, see the following resources:
<div class="indent">
<ul>
<li><a href="https://netbeans.org/kb/trails/platform.html">NetBeans Platform Learning Trail</a></li>
<li><a href="http://bits.netbeans.org/dev/javadoc/">NetBeans API Javadoc</a></li>
</ul>
</div>
<!-- ======================================================================================== -->
</body>
</html>