// 
//     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 для платформы NetBeans
: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 для платформы NetBeans - Apache NetBeans
:keywords: Apache NetBeans Platform, Platform Tutorials, Учебный курс по NetBeans CRUD для платформы NetBeans

В этом курсе описана интеграция базы данных Java DB в приложение платформы NetBeans. Вначале рассматривается база данных Java DB, исходя из которой создается класс сущностей. Следует отметить, что эти указания применимы не только для Java DB. Они могут использоваться для любой реляционной базы данных, поддерживаемой средой IDE NetBeans. Затем классы сущностей помещаются в оболочку модуля наряду с модулями для связанного компонента JPA JARS.

После того как модули становятся частью приложения, создается новый модуль, который обеспечивает пользовательский интерфейс приложения. Новый модуль предоставляет пользователю древовидную иерархию, отображающую данные из базы данных. Затем создается другой модуль, позволяющий пользователю изменять данные, отображаемые первым модулем. Выделение средства просмотра и средства изменения в отдельные модули позволяет устанавливать различные редакторы для одного средства просмотра, так как различные редакторы могут создаваться внешними производителями как на коммерческой, так и на бесплатной основе. Таким образом, модульная архитектура платформы NetBeans обеспечивает гибкость.

После установки редактора выполняется добавление функций CRUD. Первый компонент "R" ("Read", чтение) обрабатывается описанным выше средством просмотра. Затем выполняется компонент "U" ("Update", обновление), затем – "C" ("Create", создание) и "D" ("Delete", удаление).

Этот курс позволит получить сведения о различных функциях платформы 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]`` .

NOTE:  В этом документе используется среда IDE NetBeans версии 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[10 лучших интерфейсов API NetBeans] перед началом работы с этим курсом. Многие используемые в этом курсе понятия более подробно рассматриваются в демо-роликах.



== Настройка приложения

Начнем с создания нового приложения платформы NetBeans.


[start=1]
1. Выберите в меню "Файл" команду "Новый проект" (CTRL+SHIFT+N). В разделе "Категории" выберите параметр "Модули NetBeans". В разделе "Проекты" выберите параметр "Приложение платформы NetBeans". Нажмите кнопку "Далее".

[start=2]
1. На панели "Имя и местоположение" введите в поле "Имя проекта" текст  ``DBManager`` . Нажмите кнопку "Готово".

В среде IDE будет создан проект  ``DBManager`` . Проект является контейнером для всех остальных создаваемых модулей.


image::images/crud_68dbmanager-1.png[]




== Интеграция базы данных

Для интеграции базы данных следует создать классы сущностей из базы данных и интегрировать эти классы вместе со связанными файлами JAR в модули, которые входят в состав приложения NetBeans Platform.


=== Создание классов сущностей

В этом разделе выполняется создание классов сущностей из выбранной базы данных.


[start=1]
1. В рамках этого примера окно "Службы" используется для соединения с демонстрационной базой данных, поставляемой вместе со средой IDE NetBeans:


image::images/crud_68dbmanager-2.png[]

Возможно также выбрать любую другую базу и применить адаптировать действия к конкретному случаю. Если используется MySQL, обратитесь к разделу  link:https://netbeans.apache.org/kb/docs/ide/mysql_ru.html[Подключение к базе данных MySQL].


[start=2]
1. В среде IDE выберите пункт меню "Файл | Новый проект", а затем – "Java | Библиотека классов Java" для создания нового проекта библиотеки с именем  ``CustomerLibrary`` .


[start=3]
1. В окне "Проекты" правой кнопкой мыши щелкните проект библиотеки и выберите в меню "Файл" пункт "Новый файл", а затем – "Сохранение состояния" | "Классы сущностей из базы данных". В мастере выберите базу данных и требуемые таблицы. Здесь следует выбрать "Клиент", после чего "Код скидки" будет добавлен автоматически, поскольку между этими двумя таблицами установлена связь.


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, после чего файл JAR будет размещен в папке "dist", которую можно просмотреть в окне "Файлы":


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[]


=== Создание других связанных модулей

В этом разделе создаются два новых модуля, которые помещают файлы JAR EclipseLink в оболочку, а также соединитель баз данных JAR.


[start=1]
1. Выполните те же действия, что и при создании оболочки библиотеки для файла JAR класса сущностей, но на этот раз для файлов JAR EclipseLink, которые расположены в библиотеке Java "CustomerLibrary", созданной ранее:


image::images/crud_68dbmanager-94.png[]

В мастере "Модуль-обёртка вокруг библиотеки" можно выбрать несколько архивов JAR щелчком кнопки мыши при нажатой клавише CTRL.


[start=2]
1. Затем следует создать еще один модуль-обертку вокруг библиотеки для файла JAR клиента базы данных Java DB, предоставляемого в дистрибутиве 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 Client, а также от модуля 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

Чтобы создать функциональность CRUD, которая тесно интегрируется с платформой NetBeans, необходимо реализовать некоторые очень специфические приемы программирования этой платформы. В следующих разделах эти шаблоны рассматриваются более подробно.


=== Чтение

В этом разделе производится изменение введенного в предыдущем разделе элемента  ``JTextArea``  для представления проводника платформы NetBeans. Представления проводника платформы 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 Nodes и интерфейса API Explorer &amp; Property Sheet.


[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[10 лучших интерфейсов API NetBeans] для получения подробных сведений о приведенном выше коде, в особенности демо-ролик об интерфейсе Nodes API и интерфейсе Explorer &amp; Property Sheet API.


[start=4]
1. Откройте представление проектирования для  ``TopComponent`` , щелкните правой кнопкой палитру, выберите в меню "Менеджер палитры" команду "Добавить из файла JAR". Затем перейдите к элементу  ``org-openide-explorer.jar`` , который находится в папке  ``platform11/modules``  внутри каталога установки среды IDE NetBeans. Выберите элемент 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. Синхронизация окна "Свойства" с элементом  ``BeanTreeView``  осуществляется путем добавления в конструктор элемента  ``TopComponent``  следующего кода:

[source,java]
----

associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
----

Здесь элементы  ``TopComponent``   ``ActionMap``  и  ``ExplorerManager``  добавляются в элемент  ``Lookup``   ``TopComponent`` . Это также приводит к тому, что в окне "Свойства" появляется отображаемое имя и текст всплывающей подсказки выбранного элемента  ``Node`` .


[start=9]
1. Повторно запустите приложение и обратите внимание на то, что диалоговое окно "Свойства" теперь синхронизировано с представлением проводника:


image::images/crud_68dbmanager-95.png[]

Теперь данные можно просмотреть в древовидной иерархии, как и в случае с элементом  ``JTree`` . В то же время возможен переход в другое представление проводника без изменения модели, так как  ``ExplorerManager``  выполняет роль посредника между моделью и представлением. Кроме того, теперь будет возможна синхронизация представления с окном "Свойства".


=== Обновление

В этом разделе вначале создается редактор. Редактор будет предоставлен новым модулем NetBeans. Таким образом, вначале будет создан новый модуль. Затем в этом модуле будет создан новый элемент  ``TopComponent`` , содержащий два поля  ``JTextFields``  (для каждого столбца, который доступен пользователю для редактирования). Модулю средства просмотра необходимо разрешить взаимодействие с модулем редактора. Каждый раз при выборе нового элемента  ``Node``  в модуле средства просмотра текущий объект  ``Customer``  будет добавлен в  ``Lookup`` . В модуле редактора будет осуществляться прослушивание  ``Lookup``  на появление объектов  ``Customer`` . При появлении нового объекта  ``Customer``  в  ``Lookup``  будет выполнено обновление текстовых полей  ``JTextField``  в редакторе.

Затем поля  ``JTextFields``  будут синхронизированы с функциями платформы NetBeans "Отменить", "Вернуть" и "Сохранить". Другими словами, при внесении пользователем изменения в поле  ``JTextField``  существующая функциональность платформы NetBeans должна быть доступной, чтобы обеспечивать поддержку платформы NetBeans, исключающую необходимость создания новых функций. Для этого необходимо использовать элемент  ``UndoRedoManager``  наряду с элементом  ``SaveCookie`` .


[start=1]
1. Создайте новый модуль с именем  ``CustomerEditor``  и основой кодового имени  ``org.shop.editor`` .


[start=2]
1. Правой кнопкой мыши щелкните модуль  ``CustomerEditor``  и выберите команду "Создать | Оконный компонент". Убедитесь в том, что в настройках указано отображать окно в положении ``editor``  и открывать его при запуске приложения. На последней панели мастера задайте префикс имени класса Editor.


[start=3]
1. Используйте палитру (CTRL+SHIFT+8) для добавления двух меток  ``JLabels``  и двух полей  ``JTextFields``  в новое окно. Задайте тексты меток "Имя" и "Город", а затем установите имена переменных полей  ``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`` .

Следует также убедиться в том, что  ``BeanTreeView``  в  ``CustomerViewer``  будет растягиваться по горизонтали и вертикали при изменении размера приложения пользователем. Для проверки этого откройте окно, выберите элемент  ``BeanTreeView`` , а затем нажмите кнопки со стрелками на панели инструментов Конструктора GUI.


[start=5]
1. Выполните приложение и проверьте, выводятся ли следующие данные при запуске приложения:


image::images/crud_68dbmanager-97.png[]


[start=6]
1. Теперь можно приступить к добавлению кода. Сначала необходимо открыть выбранный в настоящий момент объект Customer в редакторе:
* Сначала настройте модуль  ``CustomerViewer``  таким образом, чтобы текущий объект  ``Customer``  добавлялся в окно средства просмотра  ``Lookup``  при каждом выборе элемента  ``Node`` . Для этого создайте  ``AbstractNode``  вместо  ``BeanNode``  в классе  ``CustomerChildFactory`` . В этом случае текущий объект  ``Customer``  можно будет добавить к  ``Lookup``  узла следующим образом (обратите внимание на текст, выделенный полужирным шрифтом):

[source,java]
----

@Override
protected Node createNodeForKey(Customer c) {
    Node node = new AbstractNode(Children.LEAF, Lookups.singleton(c));
    node.setDisplayName(c.getName());
    node.setShortDescription(c.getCity());
    return node;
//        try {
//            return new BeanNode(c);
//        } catch (IntrospectionException ex) {
//            Exceptions.printStackTrace(ex);
//            return null;
//        }
}
----

Теперь при каждом создании элемента  ``Node`` , которое выполняется при выборе нового клиента в средстве просмотра, новый объект  ``Customer``  добавляется в окно  ``Lookup``  элемента  ``Node`` .

* Теперь следует изменить модуль редактора таким образом, чтобы его окно отслеживало объекты  ``Customer`` , добавляемые в окно  ``Lookup`` . Вначале установите в модуле редактора зависимость от модуля, который предоставляет класс сущностей, а также от модуля, предоставляющего файлы состояния JAR.

* Затем настройте подпись класса  ``EditorTopComponent``  для внедрения  ``LookupListener`` :

[source,java]
----

public final class EditorTopComponent extends TopComponent implements LookupListener
----

* Переопределите  ``resultChanged``  таким образом, чтобы текстовые поля  ``JTextField``  обновлялись при вставке нового объекта  ``Customer``  в окно  ``Lookup`` :

[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", то объект  ``Customer``  для значения "Ford Motor Co" добавляется в окно  ``Lookup``  элемента  ``Node`` . Так как элемент  ``Node``  является выбранным в настоящее время компонентом, объект  ``Customer``  для значения "Ford Motor Co" становится доступным в глобальном контексте. Это передается в элемент  ``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``  также доступен при запуске приложения.

* Затем необходимо следующим образом объявить переменную result в начале класса:

[source,java]
----

private Lookup.Result result = null;
----

* Запустите приложение повторно и обратите внимание на то, что окно редактора обновляется при выборе нового элемента  ``Node`` :


image::images/crud_68dbmanager-98.png[]

В то же время следует отметить операции, выполняемые при переходе в окно редактора:


image::images/crud_68dbmanager-99.png[]

Так как элемент  ``Node``  больше не является текущим, объект  ``Customer``  покидает глобальный контекст. Как сказано выше, это происходит потому, что глобальный контекст выступает посредником для окна  ``Lookup``  текущего элемента  ``Node`` . Таким образом, в этом случае глобальный контекст не может использоваться. Вместо этого будет использоваться локальное окно  ``Lookup`` , предоставленное окном Customer.

Замените строку


[source,java]
----

result = Utilities.actionsGlobalContext().lookupResult(Customer.class);
----

следующей строкой:


[source,java]
----

result = WindowManager.getDefault().findTopComponent("CustomerTopComponent").getLookup().lookupResult(Customer.class);
----

Строка "CustomerTopComponent" является идентификатором  ``CustomerTopComponent`` , который представляет собой строковую константу, находящуюся в исходном коде компонента  ``CustomerTopComponent`` . Недостатком описанного выше подхода является то, что элемент  ``EditorTopComponent``  при этом может работать только в том случае, если обнаруживается компонент  ``TopComponent``  с идентификатором "CustomerTopComponent". Это должно явным образом документироваться, чтобы информировать пользователей других редакторов о том, что идентификация  ``TopComponent``  средства просмотра выполняется именно таким образом. Разработчик также может изменить модель выбора,  link:http://weblogs.java.net/blog/timboudreau/archive/2007/01/how_to_replace.html[как описано здесь] Тимом Будро.

При использовании описанных подходов контекст не будет потерян при переключении фокусировки на компонент  ``EditorTopComponent`` , как показано ниже:


image::images/crud_68dbmanager-991.png[]

Поскольку теперь вместо  ``BeanNode``  используется  ``AbstractNode`` , в окне "Свойства" нет свойств. Их придется задать вручную, в соответствии с описанием в документе  link:https://netbeans.apache.org/tutorials/nbm-nodesapi2.html[Руководство по интерфейсу API для узлов].


[start=7]
1. Теперь следует перейти к функциям "Отменить" и "Вернуть". Необходимо добиться того, чтобы при изменении пользователем одного из полей  ``JTextFields``  стали доступны кнопки "Отменить" и "Вернуть", а также связанные с ними команды меню "Правка". Для этого платформа NetBeans предоставляет  link:http://bits.netbeans.org/dev/javadoc/org-openide-awt/org/openide/awt/UndoRedo.Manager.html[UndoRedo.Manager].
* Объявите новый элемент UndoRedoManager и создайте его экземпляр в начале элемента  ``EditorTopComponent`` :

[source,java]
----

private UndoRedo.Manager manager = new UndoRedo.Manager();
----

* Затем следует переопределить метод  ``getUndoRedo()``  в компоненте  ``EditorTopComponent`` :

[source,java]
----

@Override
public UndoRedo getUndoRedo() {
    return manager;
}
----

* В конструкторе элемента  ``EditorTopComponent``  следует добавить элемент  ``KeyListener``  в поля  ``JTextFields`` . Затем добавьте прослушиватели  ``UndoRedoListeners``  в связанные методы, которые необходимо внедрить:

[source,java]
----

jTextField1.getDocument().addUndoableEditListener(manager);
jTextField2.getDocument().addUndoableEditListener(manager);

----

* Запустите приложение и проверьте функции "Отменить" и "Вернуть" в действии, а также кнопки и команды меню. Функции будут работать ожидаемым образом. Можно изменить прослушиватель  ``KeyListener``  таким образом, чтобы не все клавиши вызывали включение функций "Отменить" и "Вернуть". Например, при нажатии клавиши ВВОД включение функций "Отменить" и "Вернуть", скорее всего, не потребуется. Следовательно, необходимо именить код, приведенный выше, чтобы он соответствовал требованиям предприятия.

[start=8]
1. Необходимо также выполнить интеграцию с функциональностью платформы NetBeans "Сохранить":
* По умолчанию на панели инструментов платформы NetBeans доступна кнопка "Сохранить все". В рассматриваемой ситуации сохранение "всех" элементов не требуется, так как понятие "все" подразумевает наличие различных документов. Здесь имеется только один "документ", то есть редактор, который используется для всех узлов древовидной иерархии. Удалите кнопку "Сохранить все" и добавьте кнопку "Сохранить". Для этого добавьте следующий код в файл layer модуля  ``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 Dialogs и интерфейса API Nodes.

* В конструкторе  ``EditorTopCompontn``  добавьте вызов метода (определяемого на следующем этапе) при каждом обнаружении изменений:

[source,java]
----

public EditorTopComponent() {

        ...
        ...
        ...

        jTextField1.getDocument().addDocumentListener(new DocumentListener() {
            public void insertUpdate(DocumentEvent arg0) {
                fire(true);
            }
            public void removeUpdate(DocumentEvent arg0) {
                fire(true);
            }
            public void changedUpdate(DocumentEvent arg0) {
                fire(true);
            }
        });

        jTextField2.getDocument().addDocumentListener(new DocumentListener() {
            public void insertUpdate(DocumentEvent arg0) {
                fire(true);
            }
            public void removeUpdate(DocumentEvent arg0) {
                fire(true);
            }
            public void changedUpdate(DocumentEvent arg0) {
                fire(true);
            }
        });

        //Создание экземпляра реализации SaveCookie:
        impl = new SaveCookieImpl();

        //Создание экземпляра динамического объекта:
        content = new InstanceContent();

        //Добавление динамического объекта в верхнюю часть TopComponent Lookup:
        associateLookup(new AbstractLookup(content));

    }

    ...
    ...
    ...

----

* Здесь приведены два метода, упоминавшиеся выше. Первый метод вызывается при каждом обнаружении изменений. При обнаружении изменений к  ``InstanceContent``  добавляется реализация  ``SaveCookie``  из интерфейса API узлов:

[source,java]
----

    public void fire(boolean modified) {
        if (modified) {
            //Если текст изменен,
            //добавить реализацию SaveCookie к Lookup:
            content.add(impl);
        } else {
            //В противном случае удалим реализацию SaveCookie из lookup:
            content.remove(impl);
        }
    }

    private class SaveCookieImpl implements SaveCookie {

        @Override
        public void save() throws IOException {

           Confirmation message = new NotifyDescriptor.Confirmation("Сохранить \""
                    + jTextField1.getText() + " (" + jTextField2.getText() + ")\"?",
                    NotifyDescriptor.OK_CANCEL_OPTION,
                    NotifyDescriptor.QUESTION_MESSAGE);

            Object result = DialogDisplayer.getDefault().notify(message);
            //Если пользователь намерен сохранить, и нажимает "Да",
            //необходимо отключить действие Save,
            //таким образом оно будет доступно только при наличии изменений
            //текстового поля:
            if (NotifyDescriptor.YES_OPTION.equals(result)) {
                fire(false);
                //Реализация функций сохранения.
            }
        }
    }

----

* Запустите приложение и обратите внимание на включение и отключение кнопки "Сохранить":


image::images/crud_68dbmanager-992.png[]

В настоящий момент при нажатии кнопки "ОК" в приведенном выше диалоговом окне не происходит. На следующем этапе будет добавлен код JPA, обрабатывающий состояние изменений.

* Затем следует добавить код JPA для сохранения изменений. Для этого замените комментарий "//Реализация функций сохранения". Этот комментарий необходимо заменить следующим кодом.

[source,java]
----

EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager();
entityManager.getTransaction().begin();
Customer c = entityManager.find(Customer.class, customer.getCustomerId());
c.setName(jTextField1.getText());
c.setCity(jTextField2.getText());
entityManager.getTransaction().commit();
----

Значение "customer" в  ``customer.getCustomerId()()``  в настоящее время не определено. Добавьте строку, выделенную жирным шрифтом, в  ``resultChanged``  ниже, сразу после определения  ``Customer customer;``  в верхней части класса, таким образом текущий объект  ``Customer``  будет определять значение  ``customer`` , которое затем используется в коде сохранения состояния, определенном выше, для получения идентификатора текущего объекта  ``Customer`` .


[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("[имя не указано]");
        jTextField2.setText("[город не указан]");
    }
}
----

* Запустите приложение и измените данные. В настоящее время функциональность "Обновить" отсутствует (она будет добавлена на следующем этапе), поэтому для просмотра обновленной информации следует перезапустить приложение. Например, в данном случае древовидная иерархия отображает сохраненное имя клиента "Toyota Motor Co":


image::images/crud_68dbmanager-993.png[]


[start=9]
1. Затем следует добавить функцию для обновления средства просмотра Customer. Разработчик может добавить элемент  ``Timer`` , который периодически обновляет средство просмотра. В этом примере в узел Root будет добавлена команда меню "Обновить", позволяющая пользователю вручную обновить средство просмотра.
* В основном пакете модуля  ``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, "Обновить");
        }

        public void actionPerformed(ActionEvent e) {
            CustomerTopComponent.refreshNode();
        }
    }

}
----

* Добавьте этот метод в компонент  ``CustomerTopComponent``  для обновления представления:

[source,java]
----

public static void refreshNode() {
    EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager();
    Query query = entityManager.createQuery("SELECT c FROM Customer c");
    List<Customer> resultList = query.getResultList();
    em.setRootContext(new *CustomerRootNode*(Children.create(new CustomerChildFactory(resultList), true)));
} 
----

Теперь следует заменить приведенный выше код в конструкторе  ``CustomerTopComponent``  на вызов упомянутого элемента. Как видно из выделенной части кода, теперь вместо элемента  ``AbstractNode``  используется элемент  ``CustomerRootNode`` . Элемент  ``CustomerRootNode``  содержит действие "Обновить", вызывающее приведенный выше код.

* Добавьте к коду сохранения вызов привденного выше метода, чтобы при каждом сохранении данных происходило автоматическое обновление. Реализация этого расширения кода сохранения возможна несколькими разными способами. Например, можно создать новый модуль, содержащий действие обновления. Этот модуль затем будет совместно исползоваться модулем просмотра и модулем редактора, предоставляя общие функциональные возможности.

* Повторно запустите приложение и обратите внимание на то, что появился новый корневой узел с действием "Обновить":


image::images/crud_68dbmanager-994.png[]

* Измените данные, сохраните их, выполните действие "Обновить" и убедитесь в том, что средство просмотра обновляется.

В этом разделе был рассмотрен способ обработки изменений полей  ``JTextFields``  в платформе NetBeans. При изменении текста кнопки платформы NetBeans "Отменить" и "Вернуть" будут включены или отключены. Кроме того, кнопка "Сохранить" также корректно включается и отключается, что позволяет пользователю сохранять измененные данные в базу данных.


=== Создание

В этом разделе пользователю предоставляется возможность создания новой записи базы данных.


[start=1]
1. Правой кнопкой мыши щелкните модуль  ``CustomerEditor``  и выберите команду "Создать действие". Используйте мастер создания действия для создания нового действия "Всегда включено". Новое действие должно отображаться в любом положении на панели инструментов и/или в строке меню. На следующем шаге мастера вызовите действие  ``NewAction`` .

Убедитесь в наличии значка размером 16 на 16, который необходимо выбрать в мастере, если действие должно выбираться с панели инструментов.


[start=2]
1. В новом действии следует открыть компонент  ``TopComponent``  вместе с пустыми полями  ``JTextFields`` :

[source,java]
----

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public final class NewAction implements ActionListener {

    public void actionPerformed(ActionEvent e) {
        EditorTopComponent tc = EditorTopComponent.getDefault();
        tc.resetFields();
        tc.open();
        tc.requestActive();
    }

}
----

Это действие внедряет класс  ``ActionListener`` , который привязан к приложению через записи в файле layer. Эти записи создаются мастером нового действия. Представьте себе легкость переноса существующего приложения Swing на платформу NetBeans при наличии возможности использования тех же классов  ``Action`` , которые использовались в исходном приложении, без необходимости их изменения в соответствии с классами  ``Action`` , предоставляемыми платформой NetBeans!

В компоненте  ``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("Сохранить \""
                    + jTextField1.getText() + " (" + jTextField2.getText() + ")\"?",
                    NotifyDescriptor.OK_CANCEL_OPTION,
                    NotifyDescriptor.QUESTION_MESSAGE);

    Object result = DialogDisplayer.getDefault().notify(msg);

    //Когда пользователь щелкает кнопку "Yes", подтверждая сохранение,
            //следует отключить кнопку "Сохранить" и команду меню "Сохранить",
            //чтобы они могли использоваться только при внесении следующего изменения 
            //в текстовое поле:
    if (NotifyDescriptor.YES_OPTION.equals(result)) {
        fire(false);
        EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager();
        entityManager.getTransaction().begin();
        *if (customer.getCustomerId() != null)* {
            Customer c = entityManager.find(Customer.class, cude.getCustomerId());
            c.setName(jTextField1.getText());
            c.setCity(jTextField2.getText());
            entityManager.getTransaction().commit();
        } else {
            *Query query = entityManager.createQuery("SELECT c FROM Customer c");
            List<Customer> resultList = query.getResultList();
            customer.setCustomerId(resultList.size()+1);
            customer.setName(jTextField1.getText());
            customer.setCity(jTextField2.getText());
            //Добавить дополнительные поля, заполняющие оставшиеся столбцы в таблице!
            entityManager.persist(customer);
            entityManager.getTransaction().commit();*
        }
    }

}
----


[start=4]
1. Повторно запустите приложение и добавьте нового клиента в базу данных.


=== Удаление

В этом разделе пользователю предоставляется возможность удалять выбранную запись в базе данных. С помощью описанных выше приемов и кода внедрите действие "Удалить" самостоятельно.


[start=1]
1. Создайте новое действие  ``DeleteAction`` . Следует определить необходимость привязки действия к узлу Customer, к панели инструментов, к строке меню, к сочетанию клавиш или к комбинации этих вариантов. В зависимости от привязки следует использовать различные подходы к написанию кода. Повторно изучите учебный курс, обратив особое внимание на способ создания действия "Новое" в сравнении с действием корневого узла "Обновить".


[start=2]
1. Получите текущий объект  ``Customer`` , возвратите диалоговое окно "Вы уверены?", а затем удалите запись. Для получения дополнительных сведений повторно прочитайте учебный курс, в особенности ту часть, в которой реализуется функция "Сохранить". Вместо сохранения записи теперь производится ее удаление из базы данных.


== Дополнительная информация

На этом учебный курс по функциям CRUD в платформе NetBeans завершен. В этом документе описано создание нового приложения платформы NetBeans с функциональностью CRUD для определенной базы данных. Дополнительные сведения о создании и разработке приложений приведены в следующих ресурсах:

*  link:https://netbeans.apache.org/kb/docs/platform.html[Учебная карта по платформе NetBeans]
*  link:http://bits.netbeans.org/dev/javadoc/[Документация Javadoc по интерфейсам API в среде NetBeans]
