| // |
| // 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。对于 NetBeans IDE 所支持的任何关系数据库,这些说明均适用。接下来,我们将把这些实体类和相关 JPA JAR 的模块一起包装到一个模块中。 |
| |
| 在以上模块成为应用程序的一部分之后,我们将创建一个为应用程序提供用户界面的新模块。该新模块将为用户提供一个显示数据库中数据的树状分层结构。然后,我们将创建另一个模块,以使用户能够编辑第一个模块显示的数据。我们将使用不同模块将查看器和编辑器分开,以使用户能够为同一查看器安装不同的编辑器,因为外部供应商会创建各种不同的编辑器,有些用于商业用途,有些则是免费提供的。正是 NetBeans 平台的模块化体系结构促成了这种灵活性。 |
| |
| 有了编辑器后,我们将开始添加 CRUD 功能。首先,上面所述的查看器将处理代表“读取”的 "R"。接下来,将处理代表“更新”的 "U",然后依次是代表“创建”的 "C" 和代表“删除”的 "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]`` ,以及一些 NetBeans 平台 Swing 组件,如 `` 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 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[Top 10 NetBeans APIs](最主要的 10 个 NetBeans API)。本教程中提到的许多概念都在该截屏视频系列中进行了详细讨论。 |
| |
| |
| |
| == 设置应用程序 |
| |
| 首先让我们来创建一个新的 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_zh_CN.html[连接 MySQL 数据库]。 |
| |
| |
| [start=2] |
| 1. 在 IDE 中,选择“文件”|“新建项目”,然后选择 "Java" |“Java 类库”以创建一个名为 ``CustomerLibrary`` 的新库项目。 |
| |
| |
| [start=3] |
| 1. 在“项目”窗口中,右键单击该库项目,选择“文件”|“新建文件”,然后从“数据库”中选择“持久性”|“实体类”。在向导中,选择数据库和所需的表。此处,我们选择 "Customer",将会自动添加 "Discount Code",因为这两个表是关联的。 |
| |
| |
| 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[] |
| |
| 现在,新应用程序中已包含第一个定制模块,它包装的 JAR 包含实体类和 persistence.xml 文件: |
| |
| |
| image::images/crud_68dbmanager-91.png[] |
| |
| |
| === 创建其他相关模块 |
| |
| 在此部分,将创建两个新模块,用来包装 EclipseLink JAR 和数据库连接器 JAR。 |
| |
| |
| [start=1] |
| 1. 执行与为实体类 JAR 创建库包装器相同的操作,但现在使用的是 EclipseLink JAR,它位于先前创建的 "CustomerLibrary" Java 库中: |
| |
| |
| image::images/crud_68dbmanager-94.png[] |
| |
| 在“库包装模块”向导中,您可以按住 Ctrl 键并单击以选择多个 JAR。 |
| |
| |
| [start=2] |
| 1. 接下来,再创建一个库包装模块,这次是为 Java DB 客户端 JAR 创建的,它位于 JDK 分发的 ``db/lib/derbyclient.jar`` 中。 |
| |
| |
| === 设计用户界面 |
| |
| 在此部分,将创建一个简单的原型用户界面,此界面提供了一个使用 ``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 客户端模块以及 EclipseLink 模块。右键单击 ``CustomerLibrary`` 模块,选择“属性”,然后使用“库”标签设置对 ``CustomerLibrary`` 模块所需的两个模块的依赖关系。 |
| |
| |
| [start=2] |
| 1. ``CustomerViewer`` 模块需要依赖于 EclipseLink 模块以及实体类模块。右键单击 ``CustomerViewer`` 模块,选择“属性”,然后使用“库”标签设置对 ``CustomerViewer`` 模块所需的两个模块的依赖关系。 |
| |
| |
| [start=3] |
| 1. 在“源”视图中打开 ``CustomerTopComponent`` ,在编辑器中单击鼠标右键,然后选择“修复导入”。IDE 现在可以添加所需的导入语句,因为提供所需类的模块现已可用于 ``CustomerTopComponent`` 。 |
| |
| 现在,您已在应用程序中的各模块之间设置了约定,从而可以控制不同代码段之间的依赖关系。 |
| |
| |
| === 运行原型 |
| |
| 在此部分,将运行该应用程序,以便查看能否正确访问数据库。 |
| |
| |
| [start=1] |
| 1. 启动数据库服务器。 |
| |
| |
| [start=2] |
| 1. 运行应用程序。您应看到如下所示的内容: |
| |
| |
| image::images/crud_68dbmanager-92.png[] |
| |
| 现在,您已具有一个简单原型,它包含的 NetBeans 平台应用程序将显示数据库中的数据,下一节将对其进行扩展。 |
| |
| |
| |
| == 集成 CRUD 功能 |
| |
| 为了创建与 NetBeans 平台顺利集成的 CRUD 功能,需要实现一些特定的 NetBeans 平台编码模式。以下部分详细介绍了这些模式。 |
| |
| |
| === 读取 |
| |
| 在此部分,将针对 NetBeans 平台资源管理器视图更改上一部分中引入的 ``JTextArea`` 。NetBeans 平台资源管理器视图是一种 Swing 组件,与标准 Swing 组件相比,此组件与 NetBeans 平台集成的效果更好。它们支持很多功能,其中之一是上下文概念,以便与上下文相关联。 |
| |
| 为了表示数据,NetBeans 平台 ``Node`` 类将提供一个通用的分层结构模型,此模型可通过任何 NetBeans 平台资源管理器视图显示。本部分末尾说明了如何将资源管理器视图与 NetBeans 平台“属性”窗口进行同步。 |
| |
| |
| [start=1] |
| 1. 对于 ``TopComponent`` ,在“设计”视图中删除 ``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[Top 10 NetBeans APIs](最主要的 10 个 NetBeans API),特别是处理节点 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`` 不会与“属性”窗口(可通过选择“窗口”|“属性”打开该窗口)保持同步。换言之,在树状分层结构中上下移动时,“属性”窗口中不会显示任何内容。 |
| |
| |
| [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`` ,其中含有两个 ``JTextField`` (分别用于允许用户编辑的两个列)。您将需要使查看器模块与编辑器模块进行通信。每当在查看器模块中选择新的 ``Node`` 时,都会将当前的 ``Customer`` 对象添加到 ``Lookup`` 中。在编辑器模块中,将需要侦听 ``Lookup`` 以确定是否引入了 ``Customer`` 对象。每当将新的 ``Customer`` 对象引入到 ``Lookup`` 时,都会在编辑器中更新 ``JTextField`` 。 |
| |
| 接下来,将 ``JTextField`` 与 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) 向新窗口中添加两个 ``JLabel`` 和两个 ``JTextField`` 。将标签的文本设置为 "Name" 和 "City",并将两个 ``JTextField`` 的变量名称设置为 ``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`` 类中创建 ``AbstractNode`` ,而不是 ``BeanNode`` 。这样,您就可以将当前 ``Customer`` 对象添加到该 Node 的 ``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 的模块的依赖关系。 |
| |
| * 接下来,更改 ``EditorTopComponent`` 类签名以实现 ``LookupListner`` : |
| |
| [source,java] |
| ---- |
| |
| public final class EditorTopComponent extends TopComponent implements LookupListener |
| ---- |
| |
| * 覆盖 ``resultChanged`` ,以便在将新的 ``Customer`` 对象引入 ``Lookup`` 中时,对 ``JTextField`` 进行更新: |
| |
| [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`` 中,这意味着 "Ford Motor Co" 的 ``Customer`` 对象当前在全局上下文中可用(因为该节点为当前选定的 ``Node`` )。随后即会将此对象传递到 ``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`` 源代码中找到。上述方法有一个缺点,即, ``EditorTopComponent`` 仅在找到 ID 为 "CustomerTopComponent" 的 ``TopComponent`` 时才发挥作用。可通过以下两种方法解决此问题:明确记录此问题,以使其他编辑器的开发人员了解他们需要这样标识查看器 ``TopComponent`` ,或者重写该选定模型,如 Tim Boudreau link:http://weblogs.java.net/blog/timboudreau/archive/2007/01/how_to_replace.html[在此处所述]。 |
| |
| 如果使用上述方法之一,您会发现将焦点切换到 ``EditorTopComponent`` 时,上下文并未丢失,如下所示: |
| |
| |
| image::images/crud_68dbmanager-991.png[] |
| |
| 由于您现在使用的是 ``AbstractNode`` 而不是 ``BeanNode`` ,“属性”窗口中不会显示任何属性。您需要自行提供这些属性,如 link:https://netbeans.apache.org/tutorials/nbm-nodesapi2.html[节点 API 教程]中所述。 |
| |
| |
| [start=7] |
| 1. 然后,让我们来处理撤销/重做功能。当用户更改某个 ``JTextField`` 时,我们希望启用“撤销”按钮、“重做”按钮以及“编辑”菜单中的相关菜单项。为此,NetBeans 平台提供了 link:http://bits.netbeans.org/dev/javadoc/org-openide-awt/org/openide/awt/UndoRedo.Manager.html[UndoRedo.Manager]。 |
| * 在 ``EditorTopComponent`` 项部声明并实例化一个新的 UndoRedoManager: |
| |
| [source,java] |
| ---- |
| |
| private UndoRedo.Manager manager = new UndoRedo.Manager(); |
| ---- |
| |
| * 接下来,覆盖 ``EditorTopComponent`` 中的 ``getUndoRedo()`` 方法: |
| |
| [source,java] |
| ---- |
| |
| @Override |
| public UndoRedo getUndoRedo() { |
| return manager; |
| } |
| ---- |
| |
| * 在 ``EditorTopComponent`` 的构造函数中,向 ``JTextField`` 中添加一个 ``KeyListener`` ,并在需要实现的相关方法中,添加 ``UndoRedoListener`` : |
| |
| [source,java] |
| ---- |
| |
| jTextField1.getDocument().addUndoableEditListener(manager); |
| jTextField2.getDocument().addUndoableEditListener(manager); |
| |
| ---- |
| |
| * 运行应用程序并显示运行中的撤销和重做功能,即相关按钮和菜单项。功能将按预期方式运行。您可能需要更改 ``KeyListener`` ,以免任何键都可启用撤销/重做功能。例如,当按下 Enter 键时,您可能不希望启用撤销/重做功能。因此,请调整上述代码以满足您的业务需求。 |
| |
| [start=8] |
| 1. 接下来,我们需要集成 NetBeans 平台的保存功能。 |
| * 缺省情况下,NetBeans 平台工具栏中提供了“全部保存”按钮。在当前情况下,我们并不希望保存“全部”,因为“全部”指许多不同的文档。在本例中,只有一个“文档”,即供树状分层结构中所有节点重复使用的编辑器。删除“全部保存”按钮,然后添加“保存”按钮,方法是向 ``CustomerEditor`` 模块的层文件中添加以下代码: |
| |
| [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> |
| ---- |
| |
| 现在运行应用程序时,将在工具栏中看到一个不同的图标。现在我们可以使用“保存”按钮,而非“全部保存”按钮。 |
| |
| * 设置对于对话框 API 和节点 API 的依赖关系。 |
| |
| * 在 ``EditorTopComponent`` 构造函数中添加一个调用,以便在每次检测到更改时触发一个方法(将在下一步骤中定义): |
| |
| [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); |
| } |
| }); |
| |
| //Create a new instance of our SaveCookie implementation: |
| impl = new SaveCookieImpl(); |
| |
| //Create a new instance of our dynamic object: |
| content = new InstanceContent(); |
| |
| //Add the dynamic object to the TopComponent Lookup: |
| associateLookup(new AbstractLookup(content)); |
| |
| } |
| |
| ... |
| ... |
| ... |
| |
| ---- |
| |
| * 以下是上面提到的两种方法。首先,每当检测到更改,就会触发该方法。每次检测到更改时,就会将节点 API 中的 ``SaveCookie`` 实现添加到 ``InstanceContent`` 中: |
| |
| [source,java] |
| ---- |
| |
| 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 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); |
| //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. |
| } |
| } |
| } |
| |
| ---- |
| |
| * 运行应用程序并注意“保存”按钮的启用/禁用情况。 |
| |
| |
| image::images/crud_68dbmanager-992.png[] |
| |
| 现在,单击上面对话框中的“确定”时什么也不会发生。在下一个步骤中,我们将添加一些 JPA 代码,用于处理更改的持久性。 |
| |
| * 接下来,我们会添加 JPA 代码以持久保留更改。要执行此操作,请替换注释 "//Implement your save functionality here."。应使用以下代码替换该注释: |
| |
| [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. 第四,我们需要添加刷新客户查看器的功能。您可能希望添加一个定期刷新查看器的 ``Timer`` 。而在此例中,我们将向根节点添加一个“刷新”菜单项,以便用户可以手动刷新查看器。 |
| * 在 ``CustomerViewer`` 模块的主包中,创建一个新的 ``Node`` ,用于替换在查看器中用作子对象根的 ``AbstractNode`` 。注意,我们还会将一个“刷新”操作绑定到新的根节点。 |
| |
| [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`` 构造函数中的以上代码。在上面的突出显示部分,我们可以看到现在使用的是 ``CustomerRootNode`` ,而不是 ``AbstractNode`` 。 ``CustomerRootNode`` 包括“刷新”操作,此操作将调用以上代码。 |
| |
| * 在保存功能中,添加对上述方法的调用,以便每次保存数据时,都会自动进行刷新。可以使用不同方法为保存功能实现此扩展。例如,您可能希望创建一个包含刷新操作的新模块。然后,将在查看器模块和编辑器模块之间共享该模块,以便为两者提供相同的功能。 |
| |
| * 再次运行应用程序,注意,您拥有了一个新的根节点,其中带有“刷新”操作。 |
| |
| |
| image::images/crud_68dbmanager-994.png[] |
| |
| * 更改一些数据并保存,调用“刷新”操作,注意,将更新查看器。 |
| |
| 现在,您已学会了如何让 NetBeans 平台处理对 ``JTextField`` 所做的更改。当文本发生更改时,即会启用或禁用 NetBeans 平台的“撤销”和“重做”按钮。此外,还会正确启用和禁用“保存”按钮,让用户将更改的数据保存到数据库。 |
| |
| |
| === 创建 |
| |
| 在此部分,将允许用户在数据库中创建一个新的条目。 |
| |
| |
| [start=1] |
| 1. 右键单击 ``CustomerEditor`` 模块,然后选择“新建操作”。使用“新建操作”向导创建一个新的“始终启用”操作。新的操作应显示在工具栏和/或菜单栏中的任意位置。在向导的下一步中,调用操作 ``NewAction`` 。 |
| |
| 确保有一个 16x16 的图标,当希望从工具栏调用此操作时,向导将强制选择此图标。 |
| |
| |
| [start=2] |
| 1. 在新建操作中,使 ``TopComponent`` 处于打开状态,并使 ``JTextField`` 保留空白: |
| |
| [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`` 中,添加以下方法以重置 ``JTextField`` 并创建新的 ``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); |
| |
| //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(); |
| *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()); |
| //add more fields that will populate all the other columns in the table! |
| entityManager.persist(customer); |
| entityManager.getTransaction().commit();* |
| } |
| } |
| |
| } |
| ---- |
| |
| |
| [start=4] |
| 1. 再次运行应用程序,并向数据库中添加一个新客户: |
| |
| |
| === 删除 |
| |
| 在此部分,将使用户能够删除数据库中选定的条目。使用上面介绍的概念和代码,自己实现“删除”操作。 |
| |
| |
| [start=1] |
| 1. 创建一个新的操作 ``DeleteAction`` 。确定要将其绑定到 "Customer" 节点,还是绑定到工具栏、菜单栏、快捷键或上述内容的组合。根据要绑定到的位置,您需要在代码中使用不同的方法。再次阅读教程以获取帮助,特别参见如何创建“新建”操作的部分,并将其与根节点上的“刷新”操作进行比较。 |
| |
| |
| [start=2] |
| 1. 获取当前 ``Customer`` 对象,返回“您是否确定?”对话框,然后删除该条目。有关此阶段的相关帮助,请再次阅读教程,重点查看实现“保存”功能的部分。现在不是保存,而是从数据库中删除条目。 |
| |
| |
| == 另请参见 |
| |
| NetBeans 平台 CRUD 教程到此结束。本文档介绍了如何针对给定数据库创建一个带有 CRUD 功能的新 NetBeans 平台应用程序。有关创建和开发应用程序的更多信息,请参见以下资源: |
| |
| * link:https://netbeans.apache.org/kb/docs/platform_zh_CN.html[NetBeans 平台学习资源] |
| * link:http://bits.netbeans.org/dev/javadoc/[NetBeans API Javadoc] |