Учебный курс по 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, которые способствуют созданию приложений такого рода. Например, в курсе описаны средства UndoRedo.Manager и ExplorerManager, а также компоненты платформы NetBeans Swing, например, TopComponent и BeanTreeView.
Примечание. В этом документе используется среда IDE NetBeans версии 6.8. Если установлена более ранняя версия, обратитесь к версии 6.7 этого документа.
Содержание

Для работы с этим руководством требуется программное обеспечение и ресурсы, перечисленные в следующей таблице.
| Программное обеспечение или ресурс | Требуемая версия |
|---|---|
| Среда IDE NetBeans | версия 6.8 |
| Комплект для разработчика на языке Java (JDK) | версия 6 или версия 5 |
Создаваемое в этом курсе приложение выглядит следующим образом:
Рекомендуется просмотреть серию демо-роликов 10 лучших интерфейсов API NetBeans перед началом работы с этим курсом. Многие используемые в этом курсе понятия более подробно рассматриваются в демо-роликах.
Настройка приложения
Начнем с создания нового приложения платформы NetBeans.
- Выберите в меню "Файл" команду "Новый проект" (CTRL+SHIFT+N). В разделе "Категории" выберите параметр "Модули NetBeans". В разделе "Проекты" выберите параметр "Приложение платформы NetBeans". Нажмите кнопку "Далее".
- На панели "Имя и местоположение" введите в поле "Имя проекта" текст DBManager. Нажмите кнопку "Готово".
В среде IDE будет создан проект DBManager. Проект является контейнером для всех остальных создаваемых модулей.
Интеграция базы данных
Для интеграции базы данных следует создать классы сущностей из базы данных и интегрировать эти классы вместе со связанными файлами JAR в модули, которые входят в состав приложения NetBeans Platform.
Создание классов сущностей
В этом разделе выполняется создание классов сущностей из выбранной базы данных.
- В рамках этого примера окно "Службы" используется для соединения с демонстрационной базой данных, поставляемой вместе со средой IDE NetBeans:
Возможно также выбрать любую другую базу и применить адаптировать действия к конкретному случаю. Если используется MySQL, обратитесь к разделу Подключение к базе данных MySQL.
- В среде IDE выберите пункт меню "Файл | Новый проект", а затем – "Java | Библиотека классов Java" для создания нового проекта библиотеки с именем CustomerLibrary.
- В окне "Проекты" правой кнопкой мыши щелкните проект библиотеки и выберите в меню "Файл" пункт "Новый файл", а затем – "Сохранение состояния" | "Классы сущностей из базы данных". В мастере выберите базу данных и требуемые таблицы. Здесь следует выбрать "Клиент", после чего "Код скидки" будет добавлен автоматически, поскольку между этими двумя таблицами установлена связь.
- Укажите стратегию постоянства, выбрав один из доступных вариантов: Поскольку выбор одного из вариантов является обязательным, выберем EclipseLink:
- Укажите имя demo для пакета, в котором будут созданы классы сущностей.
- Нажмите кнопку "Готово". После выполнения этого действия просмотрите созданный код и обратите внимание на то, что теперь в папке META-INF располагается файл persistence.xml, а также классы сущностей для всех таблиц:
- Создайте библиотеку Java, после чего файл JAR будет размещен в папке "dist", которую можно просмотреть в окне "Файлы":
Помещение файла JAR класса сущностей в оболочку модуля
В этом разделе рассматривается добавление первого модуля к приложению. Новый модуль NetBeans помещает в обертку файл JAR, созданный в предыдущем разделе.
- Правой кнопкой мыши щелкните узел "Модули DBManager" в окне "Проекты" и выберите команду "Добавить новую библиотеку".
- Выберите файл JAR, созданный в предыдущем подразделе, а затем завершите работу мастера, указав любые значения. Предположим, что приложение предназначено для работы с посетителями веб-сайта shop.org; в таком случае для основы кодового имени подходит уникальный идентификатор "org.shop.model":
Теперь первый собственный модуль в новом приложении обертывает архив JAR, содержащий классы сущностей и файл persistence.xml:
Создание других связанных модулей
В этом разделе создаются два новых модуля, которые помещают файлы JAR EclipseLink в оболочку, а также соединитель баз данных JAR.
- Выполните те же действия, что и при создании оболочки библиотеки для файла JAR класса сущностей, но на этот раз для файлов JAR EclipseLink, которые расположены в библиотеке Java "CustomerLibrary", созданной ранее:

В мастере "Модуль-обёртка вокруг библиотеки" можно выбрать несколько архивов JAR щелчком кнопки мыши при нажатой клавише CTRL.
- Затем следует создать еще один модуль-обертку вокруг библиотеки для файла JAR клиента базы данных Java DB, предоставляемого в дистрибутиве JDK: db/lib/derbyclient.jar.
Проектирование пользовательского интерфейса
В этом разделе создается простой прототип пользовательского интерфейса, который предоставляет окно JTextArea для отображения данных, извлеченных из базы данных.
- Правой кнопкой мыши щелкните узел "Модули DBManager" в окне "Проекты" и выберите команду "Добавить новый". Создайте новый модуль с именем CustomerViewer с основой кодового имени org.shop.ui.
- В окне "Проекты" щелкните правой кнопкой мыши новый модуль и выберите команду "Создать" | "Оконный компонент". Укажите, что компонент должен быть создан в положении editor и должен открываться при запуске приложения. Установите Customer в качестве префикса имени класса окна.
- Используйте палитру (CTRL+SHIFT+8) для перетаскивания JTextArea в новое окно:

- Добавьте этот код в конец конструктора TopComponent:
EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager(); Query query = entityManager.createQuery("SELECT c FROM Customer c"); List<Customer> resultList = query.getResultList(); for (Customer c : resultList) { jTextArea1.append(c.getName() + " (" + c.getCity() + ")" + "\n"); }Так как не установлены зависимости модулей, которые предоставляют объект Customer и файлы состояния JAR, указанные выше операторы будут отмечены красным подчеркиванием. Это будет устранено в следующем разделе.
Выше приведены ссылки на единицу сохранения состояния CustomerLibraryPU, имя которой установлено в файле persistence.xml. Кроме того, здесь имеется ссылка на один из классов сущностей с именем Customer, который находится в модуле классов сущностей. Если эти элементы отличаются от приведенных выше, их можно соответствующим образом адаптировать.
Установка зависимостей
В этом разделе будет показано, как в одних модулях использовать код из других модулей. Для этого нужно совершенно явным образом установить соглашения между связанными модулями. Строгая модульная архитектура платформы NetBeans предотвращает случайное и хаотическое использование кода разных модулей, что нередко происходит на других платформах.
- Модуль классов сущностей должен иметь зависимости от модуля Derby Client, а также от модуля EclipseLink. Правой кнопкой мыши щелкните модуль CustomerLibrary, выберите команду "Свойства", а затем перейдите на вкладку "Библиотеки" для задания зависимостей от двух модулей, необходимых модулю CustomerLibrary.
- Модуль CustomerViewer требует наличия зависимости от модуля EclipseLink, а также от модуля классов сущностей. Правой кнопкой мыши щелкните модуль CustomerViewer, выберите команду "Свойства", а затем перейдите на вкладку "Библиотеки" для задания зависимостей от двух модулей, необходимых модулю CustomerViewer.
- Откройте элемент CustomerTopComponent в представлении "Исходный код", правой кнопкой мыши щелкните окно редактора и выберите команду "Исправить выражения импорта". Среда IDE теперь может добавлять необходимые операторы импорта, так как для компонента CustomerTopComponent теперь доступны модули, содержащие необходимые классы.
Теперь между модулями приложения установлены соглашения, которые дают возможность управления зависимостями в отдельных частях кода.
Запуск прототипа
В этом разделе вы выполните запуск приложения, которое поможет проверить, верно ли установлено соединение с базой данных.
- Запустите сервер базы данных.
- Запустите приложение. На экране должно отобразиться следующее изображение:

Таким образом, создан простой прототип, состоящий из приложения платформы NetBeans, и выводящий данные из базы данных, который будет расширен в следующем разделе.
Интеграция функциональности CRUD
Чтобы создать функциональность CRUD, которая тесно интегрируется с платформой NetBeans, необходимо реализовать некоторые очень специфические приемы программирования этой платформы. В следующих разделах эти шаблоны рассматриваются более подробно.
Чтение
В этом разделе производится изменение введенного в предыдущем разделе элемента JTextArea для представления проводника платформы NetBeans. Представления проводника платформы NetBeans являются компонентами Swing, которые, по сравнению со стандартными компонентами Swing, лучше всего интегрируются с платформой NetBeans. Среди прочего поддерживается понятие контекста, позволяющее представлениям обладать чувствительностью к контексту.
Данные будут представлены в общей иерархической модели, предоставленной классом платформы NetBeans Node, который отображается во всех представлениях проводника платформы NetBeans. В конце данного раздела описан процесс синхронизации представления проводника с диалоговым окном "Свойства" платформы NetBeans.
- В компоненте TopComponent удалите элемент JTextArea в представлении проектирования и закомментируйте связанный с ним код в представлении исходного кода:
EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager(); Query query = entityManager.createQuery("SELECT c FROM Customer c"); List<Customer> resultList = query.getResultList(); //for (Customer c : resultList) { // jTextArea1.append(c.getName() + " (" + c.getCity() + ")" + "\n"); //} - Правой кнопкой мыши щелкните модуль CustomerViewer, выберите команду "Свойства", а затем перейдите на вкладку "Библиотеки" для задания зависимостей интерфейса API Nodes и интерфейса API Explorer & Property Sheet.
- Затем измените подпись класса для реализации элемента ExplorerManager.Provider:
final class CustomerTopComponent extends TopComponent implements ExplorerManager.Provider
Необходимо переопределить getExplorerManager()
@Override public ExplorerManager getExplorerManager() { return em; }В начале класса следует объявить и инициализировать ExplorerManager:
private static ExplorerManager em = new ExplorerManager();
Обратитесь к ролику 10 лучших интерфейсов API NetBeans для получения подробных сведений о приведенном выше коде, в особенности демо-ролик об интерфейсе Nodes API и интерфейсе Explorer & Property Sheet API.
- Откройте представление проектирования для TopComponent, щелкните правой кнопкой палитру, выберите в меню "Менеджер палитры" команду "Добавить из файла JAR". Затем перейдите к элементу org-openide-explorer.jar, который находится в папке platform11/modules внутри каталога установки среды IDE NetBeans. Выберите элемент BeanTreeView и завершите работу мастера. Теперь элемент BeanTreeView должен отображаться на палитре. Перетащите его с палитры в окно.
- Создайте класс фабрики, создающий новый экземпляр BeanNode для каждого клиента в базе данных:
import demo.Customer; import java.beans.IntrospectionException; import java.util.List; import org.openide.nodes.BeanNode; import org.openide.nodes.ChildFactory; import org.openide.nodes.Node; import org.openide.util.Exceptions; public class CustomerChildFactory extends ChildFactory<Customer> { private List<Customer> resultList; public CustomerChildFactory(List<Customer> resultList) { this.resultList = resultList; } @Override protected boolean createKeys(List<Customer> list) { for (Customer Customer : resultList) { list.add(Customer); } return true; } @Override protected Node createNodeForKey(Customer c) { try { return new BeanNode(c); } catch (IntrospectionException ex) { Exceptions.printStackTrace(ex); return null; } } } - Повторно выберите компонент CustomerTopComponent и используйте ExplorerManager для передачи результата из запроса JPA в элемент Node:
EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager(); Query query = entityManager.createQuery("SELECT c FROM Customer c"); List<Customer> resultList = query.getResultList(); em.setRootContext(new AbstractNode(Children.create(new CustomerChildFactory(resultList), true))); //for (Customer c : resultList) { // jTextArea1.append(c.getName() + " (" + c.getCity() + ")" + "\n"); //} - Запустите приложение. После запуска приложения откройте диалоговое окно "Свойства". Обратите внимание на то, что, несмотря на доступность данных, отображаемых в BeanTreeView, элемент BeanTreeView не синхронизирован с диалоговым окном "Свойства", которое открывается в меню "Окно" с помощью команды "Свойства. Другими словами, при перемещении по древовидной иерархии в окне "Свойства" элементы не отображаются.
- Синхронизация окна "Свойства" с элементом BeanTreeView осуществляется путем добавления в конструктор элемента TopComponent следующего кода:
associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
Здесь элементы TopComponent ActionMap и ExplorerManager добавляются в элемент Lookup TopComponent. Это также приводит к тому, что в окне "Свойства" появляется отображаемое имя и текст всплывающей подсказки выбранного элемента Node.
- Повторно запустите приложение и обратите внимание на то, что диалоговое окно "Свойства" теперь синхронизировано с представлением проводника:
Теперь данные можно просмотреть в древовидной иерархии, как и в случае с элементом JTree. В то же время возможен переход в другое представление проводника без изменения модели, так как ExplorerManager выполняет роль посредника между моделью и представлением. Кроме того, теперь будет возможна синхронизация представления с окном "Свойства".
Обновление
В этом разделе вначале создается редактор. Редактор будет предоставлен новым модулем NetBeans. Таким образом, вначале будет создан новый модуль. Затем в этом модуле будет создан новый элемент TopComponent, содержащий два поля JTextFields (для каждого столбца, который доступен пользователю для редактирования). Модулю средства просмотра необходимо разрешить взаимодействие с модулем редактора. Каждый раз при выборе нового элемента Node в модуле средства просмотра текущий объект Customer будет добавлен в Lookup. В модуле редактора будет осуществляться прослушивание Lookup на появление объектов Customer. При появлении нового объекта Customer в Lookup будет выполнено обновление текстовых полей JTextField в редакторе.
Затем поля JTextFields будут синхронизированы с функциями платформы NetBeans "Отменить", "Вернуть" и "Сохранить". Другими словами, при внесении пользователем изменения в поле JTextField существующая функциональность платформы NetBeans должна быть доступной, чтобы обеспечивать поддержку платформы NetBeans, исключающую необходимость создания новых функций. Для этого необходимо использовать элемент UndoRedoManager наряду с элементом SaveCookie.
- Создайте новый модуль с именем CustomerEditor и основой кодового имени org.shop.editor.
- Правой кнопкой мыши щелкните модуль CustomerEditor и выберите команду "Создать | Оконный компонент". Убедитесь в том, что в настройках указано отображать окно в положенииeditor и открывать его при запуске приложения. На последней панели мастера задайте префикс имени класса Editor.
- Используйте палитру (CTRL+SHIFT+8) для добавления двух меток JLabels и двух полей JTextFields в новое окно. Задайте тексты меток "Имя" и "Город", а затем установите имена переменных полей JTextField равными jTextField1 и jTextField2.
В Конструкторе GUI окно теперь должно выглядеть следующим образом:

- Вернитесь к модулю CustomerViewer и измените его файл layer.xml, указав в нем, что окно CustomerTopComponent выводится в режиме explorer.
Правой кнопкой мыши щелкните проект и выберите команду "Очистить", перейдя в файл layer.xml. Зачем это нужно? При каждом запуске приложения и его закрытии положения окон сохраняются в пользовательском каталоге. Таким образом, если элемент CustomerViewer изначально отображался в режиме editor, он останется в режиме editor до выполнения команды "Очистить", которая сбрасывает пользовательский каталог (т.е. удаляет его) и позволяет отображать CustomerViewer в положении, установленном в настоящий момент в файле layer.xml.
Следует также убедиться в том, что BeanTreeView в CustomerViewer будет растягиваться по горизонтали и вертикали при изменении размера приложения пользователем. Для проверки этого откройте окно, выберите элемент BeanTreeView, а затем нажмите кнопки со стрелками на панели инструментов Конструктора GUI.
- Выполните приложение и проверьте, выводятся ли следующие данные при запуске приложения:

- Теперь можно приступить к добавлению кода. Сначала необходимо открыть выбранный в настоящий момент объект Customer в редакторе:
- Сначала настройте модуль CustomerViewer таким образом, чтобы текущий объект Customer добавлялся в окно средства просмотра Lookup при каждом выборе элемента Node. Для этого создайте AbstractNode вместо BeanNode в классе CustomerChildFactory. В этом случае текущий объект Customer можно будет добавить к Lookup узла следующим образом (обратите внимание на текст, выделенный полужирным шрифтом):
@Override protected Node createNodeForKey(Customer c) { Node node = new AbstractNode(Children.LEAF, Lookups.singleton(c)); node.setDisplayName(c.getName()); node.setShortDescription(c.getCity()); return node; // try { // return new BeanNode(c); // } catch (IntrospectionException ex) { // Exceptions.printStackTrace(ex); // return null; // } }Теперь при каждом создании элемента Node, которое выполняется при выборе нового клиента в средстве просмотра, новый объект Customer добавляется в окно Lookup элемента Node.
- Теперь следует изменить модуль редактора таким образом, чтобы его окно отслеживало объекты Customer, добавляемые в окно Lookup. Вначале установите в модуле редактора зависимость от модуля, который предоставляет класс сущностей, а также от модуля, предоставляющего файлы состояния JAR.
- Затем настройте подпись класса EditorTopComponent для внедрения LookupListener:
public final class EditorTopComponent extends TopComponent implements LookupListener
- Переопределите resultChanged таким образом, чтобы текстовые поля JTextField обновлялись при вставке нового объекта Customer в окно Lookup:
@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 становится активным при каждом открытии окна редактора, как показано ниже:
@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 в начале класса:
private Lookup.Result result = null;
- Запустите приложение повторно и обратите внимание на то, что окно редактора обновляется при выборе нового элемента Node:

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

Так как элемент Node больше не является текущим, объект Customer покидает глобальный контекст. Как сказано выше, это происходит потому, что глобальный контекст выступает посредником для окна Lookup текущего элемента Node. Таким образом, в этом случае глобальный контекст не может использоваться. Вместо этого будет использоваться локальное окно Lookup, предоставленное окном Customer.
Замените строку
result = Utilities.actionsGlobalContext().lookupResult(Customer.class);
следующей строкой:
result = WindowManager.getDefault().findTopComponent("CustomerTopComponent").getLookup().lookupResult(Customer.class);Строка "CustomerTopComponent" является идентификатором CustomerTopComponent, который представляет собой строковую константу, находящуюся в исходном коде компонента CustomerTopComponent. Недостатком описанного выше подхода является то, что элемент EditorTopComponent при этом может работать только в том случае, если обнаруживается компонент TopComponent с идентификатором "CustomerTopComponent". Это должно явным образом документироваться, чтобы информировать пользователей других редакторов о том, что идентификация TopComponent средства просмотра выполняется именно таким образом. Разработчик также может изменить модель выбора, как описано здесь Тимом Будро.
При использовании описанных подходов контекст не будет потерян при переключении фокусировки на компонент EditorTopComponent, как показано ниже:

Поскольку теперь вместо BeanNode используется AbstractNode, в окне "Свойства" нет свойств. Их придется задать вручную, в соответствии с описанием в документе Руководство по интерфейсу API для узлов.
- Сначала настройте модуль CustomerViewer таким образом, чтобы текущий объект Customer добавлялся в окно средства просмотра Lookup при каждом выборе элемента Node. Для этого создайте AbstractNode вместо BeanNode в классе CustomerChildFactory. В этом случае текущий объект Customer можно будет добавить к Lookup узла следующим образом (обратите внимание на текст, выделенный полужирным шрифтом):
- Теперь следует перейти к функциям "Отменить" и "Вернуть". Необходимо добиться того, чтобы при изменении пользователем одного из полей JTextFields стали доступны кнопки "Отменить" и "Вернуть", а также связанные с ними команды меню "Правка". Для этого платформа NetBeans предоставляет UndoRedo.Manager.
- Объявите новый элемент UndoRedoManager и создайте его экземпляр в начале элемента EditorTopComponent:
private UndoRedo.Manager manager = new UndoRedo.Manager();
- Затем следует переопределить метод getUndoRedo() в компоненте EditorTopComponent:
@Override public UndoRedo getUndoRedo() { return manager; } - В конструкторе элемента EditorTopComponent следует добавить элемент KeyListener в поля JTextFields. Затем добавьте прослушиватели UndoRedoListeners в связанные методы, которые необходимо внедрить:
jTextField1.getDocument().addUndoableEditListener(manager); jTextField2.getDocument().addUndoableEditListener(manager);
- Запустите приложение и проверьте функции "Отменить" и "Вернуть" в действии, а также кнопки и команды меню. Функции будут работать ожидаемым образом. Можно изменить прослушиватель KeyListener таким образом, чтобы не все клавиши вызывали включение функций "Отменить" и "Вернуть". Например, при нажатии клавиши ВВОД включение функций "Отменить" и "Вернуть", скорее всего, не потребуется. Следовательно, необходимо именить код, приведенный выше, чтобы он соответствовал требованиям предприятия.
- Объявите новый элемент UndoRedoManager и создайте его экземпляр в начале элемента EditorTopComponent:
- Необходимо также выполнить интеграцию с функциональностью платформы NetBeans "Сохранить":
- По умолчанию на панели инструментов платформы NetBeans доступна кнопка "Сохранить все". В рассматриваемой ситуации сохранение "всех" элементов не требуется, так как понятие "все" подразумевает наличие различных документов. Здесь имеется только один "документ", то есть редактор, который используется для всех узлов древовидной иерархии. Удалите кнопку "Сохранить все" и добавьте кнопку "Сохранить". Для этого добавьте следующий код в файл layer модуля CustomerEditor:
<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 добавьте вызов метода (определяемого на следующем этапе) при каждом обнаружении изменений:
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 узлов:
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); //Реализация функций сохранения. } } } - Запустите приложение и обратите внимание на включение и отключение кнопки "Сохранить":

В настоящий момент при нажатии кнопки "ОК" в приведенном выше диалоговом окне не происходит. На следующем этапе будет добавлен код JPA, обрабатывающий состояние изменений.
- Затем следует добавить код JPA для сохранения изменений. Для этого замените комментарий "//Реализация функций сохранения". Этот комментарий необходимо заменить следующим кодом.
EntityManager entityManager = Persistence.createEntityManagerFactory("CustomerLibraryPU").createEntityManager(); entityManager.getTransaction().begin(); Customer c = entityManager.find(Customer.class, customer.getCustomerId()); c.setName(jTextField1.getText()); c.setCity(jTextField2.getText()); entityManager.getTransaction().commit();Значение "customer" в customer.getCustomerId()() в настоящее время не определено. Добавьте строку, выделенную жирным шрифтом, в resultChanged ниже, сразу после определения Customer customer; в верхней части класса, таким образом текущий объект Customer будет определять значение customer, которое затем используется в коде сохранения состояния, определенном выше, для получения идентификатора текущего объекта Customer.
@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":

- По умолчанию на панели инструментов платформы NetBeans доступна кнопка "Сохранить все". В рассматриваемой ситуации сохранение "всех" элементов не требуется, так как понятие "все" подразумевает наличие различных документов. Здесь имеется только один "документ", то есть редактор, который используется для всех узлов древовидной иерархии. Удалите кнопку "Сохранить все" и добавьте кнопку "Сохранить". Для этого добавьте следующий код в файл layer модуля CustomerEditor:
- Затем следует добавить функцию для обновления средства просмотра Customer. Разработчик может добавить элемент Timer, который периодически обновляет средство просмотра. В этом примере в узел Root будет добавлена команда меню "Обновить", позволяющая пользователю вручную обновить средство просмотра.
- В основном пакете модуля CustomerViewer необходимо создать новый элемент Node, заменяющий элемент AbstractNode, который в настоящее время используется в качестве корневого элемента нижестоящих элементов средства просмотра. Обратите внимание на то, что действие "Обновить" также привязывается к новому корневому узлу.
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 для обновления представления:
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 содержит действие "Обновить", вызывающее приведенный выше код.
- Добавьте к коду сохранения вызов привденного выше метода, чтобы при каждом сохранении данных происходило автоматическое обновление. Реализация этого расширения кода сохранения возможна несколькими разными способами. Например, можно создать новый модуль, содержащий действие обновления. Этот модуль затем будет совместно исползоваться модулем просмотра и модулем редактора, предоставляя общие функциональные возможности.
- Повторно запустите приложение и обратите внимание на то, что появился новый корневой узел с действием "Обновить":

- Измените данные, сохраните их, выполните действие "Обновить" и убедитесь в том, что средство просмотра обновляется.
- В основном пакете модуля CustomerViewer необходимо создать новый элемент Node, заменяющий элемент AbstractNode, который в настоящее время используется в качестве корневого элемента нижестоящих элементов средства просмотра. Обратите внимание на то, что действие "Обновить" также привязывается к новому корневому узлу.
В этом разделе был рассмотрен способ обработки изменений полей JTextFields в платформе NetBeans. При изменении текста кнопки платформы NetBeans "Отменить" и "Вернуть" будут включены или отключены. Кроме того, кнопка "Сохранить" также корректно включается и отключается, что позволяет пользователю сохранять измененные данные в базу данных.
Создание
В этом разделе пользователю предоставляется возможность создания новой записи базы данных.
- Правой кнопкой мыши щелкните модуль CustomerEditor и выберите команду "Создать действие". Используйте мастер создания действия для создания нового действия "Всегда включено". Новое действие должно отображаться в любом положении на панели инструментов и/или в строке меню. На следующем шаге мастера вызовите действие NewAction.
Убедитесь в наличии значка размером 16 на 16, который необходимо выбрать в мастере, если действие должно выбираться с панели инструментов.
- В новом действии следует открыть компонент TopComponent вместе с пустыми полями JTextFields:
import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public final class NewAction implements ActionListener { public void actionPerformed(ActionEvent e) { EditorTopComponent tc = EditorTopComponent.getDefault(); tc.resetFields(); tc.open(); tc.requestActive(); } }Это действие внедряет класс ActionListener, который привязан к приложению через записи в файле layer. Эти записи создаются мастером нового действия. Представьте себе легкость переноса существующего приложения Swing на платформу NetBeans при наличии возможности использования тех же классов Action, которые использовались в исходном приложении, без необходимости их изменения в соответствии с классами Action, предоставляемыми платформой NetBeans!
В компоненте EditorTopComponent необходимо добавить следующий метод для сброса полей текстовых полей JTextField и создания нового объекта Customer:
public void resetFields() { customer = new Customer(); jTextField1.setText(""); jTextField2.setText(""); } - В элементе SaveCookie следует убедиться в том, что возврат значения null указывает на сохранение новой записи, а не на обновление существующей записи:
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(); } } } - Повторно запустите приложение и добавьте нового клиента в базу данных.
Удаление
В этом разделе пользователю предоставляется возможность удалять выбранную запись в базе данных. С помощью описанных выше приемов и кода внедрите действие "Удалить" самостоятельно.
- Создайте новое действие DeleteAction. Следует определить необходимость привязки действия к узлу Customer, к панели инструментов, к строке меню, к сочетанию клавиш или к комбинации этих вариантов. В зависимости от привязки следует использовать различные подходы к написанию кода. Повторно изучите учебный курс, обратив особое внимание на способ создания действия "Новое" в сравнении с действием корневого узла "Обновить".
- Получите текущий объект Customer, возвратите диалоговое окно "Вы уверены?", а затем удалите запись. Для получения дополнительных сведений повторно прочитайте учебный курс, в особенности ту часть, в которой реализуется функция "Сохранить". Вместо сохранения записи теперь производится ее удаление из базы данных.
Дополнительная информация
На этом учебный курс по функциям CRUD в платформе NetBeans завершен. В этом документе описано создание нового приложения платформы NetBeans с функциональностью CRUD для определенной базы данных. Дополнительные сведения о создании и разработке приложений приведены в следующих ресурсах:
