3. Ссылки и уникальные атрибуты
Следующая часть вашего приложения для онбординга - это управление отделами.
Новый сотрудник принадлежит к Отделу (Department
). Каждый отдел имеет уникальное название и ссылку на HR-менеджера.
В этой главе вы создадите:
-
Сущность
Department
со ссылкой наUser
. -
Таблицу базы данных с внешним ключом и уникальным ограничением.
-
CRUD-экраны, включающие UI-компонент для выбора связанной сущности.
Создание сущности Department
Сущность Department
имеет уникальный атрибут name
и атрибут hrManager
, который является ссылкой на сущность User
:
Если ваше приложение запущено, остановите его с помощью кнопки Stop () на главной панели инструментов.
В окне инструментов Jmix нажмите New () → JPA Entity, как вы делали в предыдущей главе. В диалоговом окне New JPA Entity введите Department
в поле Class и установите флажок Traits → Versioned:
Нажмите на кнопку OK.
Студия создаст класс сущности и откроет дизайнер сущностей:
Создание уникального атрибута
Давайте добавим атрибут name
к сущности.
Нажмите Add () на панели Attributes. В диалоговом окне New Attribute введите name
в поле Name и установите флажок Mandatory:
Теперь давайте определим уникальное ограничение для столбца NAME
в базе данных. Это гарантирует, что не будет отделов с одинаковым названием.
Перейдите на вкладку Indexes в нижнем части дизайнера и нажмите New Index () на панели инструментов Database Indexes. Studio добавит строку в список индексов:
Выберите NAME
в списке Available attributes и щелкните на панели инструментов, чтобы переместить атрибут в список Selected attributes.
Установите флажки Unique и Constraint в строке индекса и измените имя на IDX_DEPARTMENT_UNQ_NAME
:
Если вы переключитесь на вкладку Text в дизайнере сущностей, вы увидите уникальное ограничение, добавленное к аннотации @Table
:
@JmixEntity
@Table(name = "DEPARTMENT", uniqueConstraints = {
@UniqueConstraint(name = "IDX_DEPARTMENT_UNQ_NAME", columnNames = {"NAME"})
})
@Entity
public class Department {
// ...
Используя эту аннотацию, Studio добавит уникальное ограничение для таблицы базы данных в файлы Liquibase changelog.
Создание ссылочного атрибута
Теперь создайте ссылку на HR-менеджера отдела.
Нажмите Add () на панели Attributes. В диалоговом окне New Attribute введите hrManager
в поле Name. Затем выберите:
-
Attribute type:
ASSOCIATION
-
Type:
User
-
Cardinality:
Many to One
Нажмите на кнопку OK.
Вы можете найти новый атрибут и на вкладке Text:
@JoinColumn(name = "HR_MANAGER_ID")
@ManyToOne(fetch = FetchType.LAZY)
private User hrManager;
Кроме того, аннотация @Table
в заголовке класса теперь определяет индекс для столбца внешнего ключа:
@JmixEntity
@Table(name = "DEPARTMENT", indexes = {
@Index(name = "IDX_DEPARTMENT_HR_MANAGER", columnList = "HR_MANAGER_ID")
},
// ...
Вы также можете увидеть это на вкладке Indexes.
Создание CRUD-экранов
Давайте создадим CRUD-экраны для сущности Department
.
Нажмите Screens → Create screen на панели действий в верхней части дизайнера сущностей:
На первом шаге мастера создания экрана выберите шаблон Entity browser and editor screen
:
Нажмите Next.
Примите предложенные значения на первых двух шагах мастера.
На шаге Entity browser fetch plan добавьте атрибут hrManager
в фетч-план:
Теперь вы можете быть уверены, что ссылочная сущность User
будет загружена вместе с сущностью Department
и отображена на экране просмотра.
Если какой-либо атрибут не выбран в фетч-плане, Studio не создает для него визуальный компонент в генерируемых экранах. |
Нажмите кнопку Next.
На следующем шаге Entity editor fetch plan этот атрибут будет выбран автоматически:
Нажмите кнопку Next.
Оставьте значения по умолчанию на шаге Localizable messages и нажмите Create.
Studio сгенерирует два экрана: Department.browse
и Department.edit
и откроет их исходный код. Закройте пока все вкладки редактора - позже в этой главе вы внесете некоторые изменения в созданные экраны.
Запуск приложения
Нажмите на кнопку Debug () на главной панели инструментов.
Перед запуском приложения Studio сгенерирует Liquibase changelog:
Как вы можете видеть, changelog содержит команды для создания таблицы DEPARTMENT
, уникальное ограничение для столбца NAME
и внешнего ключа, а также индекс для столбца HR_MANAGER_ID
.
Нажмите на кнопку Save and run.
Студия выполнит changelog, затем соберет и запустит приложение.
Откройте http://localhost:8080
в вашем веб-браузере и войдите в приложение с учетными данными администратора (admin
/ admin
).
Раскройте меню Application и нажмите на подпункт Departments. Вы увидите экран Department.browse
:
Нажмите на кнопку Create. Откроется экран Department.edit
:
Вы можете выбрать HR-менеджера для отдела, нажав на кнопку с многоточием в поле выбора. Экран просмотра пользователей откроется над экраном редактирования отдела, что будет отображено в навигационной цепочке в верхней части экрана. Кнопка Select станет активной, когда вы выберете строку в таблице пользователей:
Выберите пользователя и нажмите на кнопку Select. Пользователь отобразится в поле выбора:
Нажмите на кнопку OK. Указанный пользователь также будет отображаться в таблице:
Имя экземпляра
Вы можете задаться вопросом, почему в поле выбора и таблице отображается строка [admin]
для выбранного пользователя?
В Jmix есть понятие имени экземпляра (instance name): понятный пользователю текст, представляющий экземпляр сущности. Он может быть определен для любой сущности с помощью аннотации @InstanceName
для поля или метода.
Сущность User
, созданная шаблоном проекта, имеет следующий метод, определяющий имя экземпляра:
public class User implements JmixUserDetails, HasTimeZone {
// ...
@InstanceName
@DependsOnProperties({"firstName", "lastName", "username"})
public String getDisplayName() {
return String.format("%s %s [%s]", (firstName != null ? firstName : ""),
(lastName != null ? lastName : ""), username).trim();
}
Таким образом, когда поля firstName
и lastName
пусты, имя экземпляра пользователя - это username
в квадратных скобках, как это видно в приложении на данный момент.
Дизайнер сущностей Studio автоматически генерирует аннотацию @InstanceName
, если он встречает атрибут с соответствующим именем: name
, description
и так далее. Например, ваша сущность Department
имеет @InstanceName
в своем атрибуте name
:
public class Department {
// ...
@InstanceName
@Column(name = "NAME", nullable = false)
@NotNull
private String name;
Таким образом, название отдела будет отображаться в пользовательском интерфейсе, если вы используете отдел в качестве ссылки в другой сущности. Вы увидите это позже в самоучителе.
Дизайнер сущностей также поможет вам определить имя экземпляра вручную. Вы можете выбрать в качестве имени экземпляра какой-либо атрибут или сгенерировать метод, используя поле Instance name и кнопку рядом с ним:
Простая кастомизация UI
Автоматически сгенерированный CRUD UI для отделов выглядит приемлемо, но есть некоторые недочеты, которые стоит исправить.
Изменение заголовка атрибута
Возможно, вы заметили, что сгенерированный заголовок для атрибута hrManager
не совсем корректен: он читается как Hr manager
. Давайте изменим его на HR Manager
.
Выберите атрибут hrManager
в дизайнере сущностей и нажмите на кнопку глобуса () рядом с именем атрибута:
Появится диалоговое окно Localized Message:
Измените текст и нажмите кнопку OK.
Вы можете просмотреть и отредактировать все сообщения вашего проекта, если дважды щелкните элемент User Interface → Message Bundle в окне инструментов Jmix. Сообщение, которое вы только что изменили, выделено ниже:
Переключитесь на приложение, запущенное в вашем веб-браузере. Закройте CRUD-экраны отдела и откройте их снова. Вы увидите новый заголовок для атрибута hrManager
.
Благодаря технологии Studio Hot deploy вам не нужно перезапускать приложение при внесении изменений в пользовательский интерфейс. Просто сохраните изменения в IDE (нажав комбинацию клавиш |
Обратите внимание, что обновление страницы веб-браузера не обновляет UI, так как состояние UI хранится на сервере. Для переоткрытия экрана закройте его вкладку внутри приложения и откройте ее снова из главного меню или из другого экрана. |
Настройка действий EntityPicker
По умолчанию, когда вы нажимаете кнопку с многоточием в поле выбора HR-менеджера, экран выбора пользователя полностью закрывает редактор отдела. Давайте изменим поведение поля выбора, чтобы отображать экран пользователей в диалоговом окне.
Найдите department-edit.xml
в окне инструментов Jmix и дважды щелкните по нему. Появится дизайнер экрана:
В зависимости от разрешения вашего дисплея вы можете захотеть одновременно отображать только редактор XML или экран предварительного просмотра. Используйте кнопки в верхней части панели редактора для переключения режима:
Переключитесь в режим Editor and Preview. Найдите поле hrManagerField
на панели иерархии Jmix UI. Компонент будет выбран в предварительном просмотре, в редакторе XML и на панели инспектора Jmix UI в нижнем правом углу:
Вы можете видеть, что элемент entityPicker
содержит вложенный элемент actions
с двумя действиями. Каждое действие соответствует кнопке поля выбора: действие entityLookup
показывает экран для выбора связанной сущности, а действие entityClear
очищает текущее значение поля выбора.
Действия можно настроить, указав специальные свойства.
Выберите действие entityLookup
на панели иерархии Jmix UI, затем выберите значение DIALOG
из выпадающего списка свойства openMode
в панели инспектора Jmix UI:
Ваши изменения будут отражены в XML.
Это работает и в противоположном направлении. Вы можете редактировать XML напрямую и просматривать результаты на панелях дизайнера и экране предварительного просмотра. |
Переключитесь на запущенное приложение. Выберите отдел и нажмите Edit чтобы открыть экран редактора отдела. Далее нажмите кнопку с многоточием в поле выбора HR-менеджера.
Экран просмотра пользователей откроется в подвижном диалоговом окне:
Изменение уникального сообщения о нарушении ограничений
Если вы попытаетесь создать другой отдел с тем же именем, вы увидите сообщение об ошибке нарушения уникального ограничения:
Сообщение по умолчанию не очень дружественное к пользователю, но вы можете легко изменить его.
Дважды щелкните элемент User Interface → Message Bundle в окне инструментов Jmix и добавьте следующую строку:
databaseUniqueConstraintViolation.IDX_DEPARTMENT_UNQ_NAME=A department with the same name already exists
Ключ сообщения должен начинаться с databaseUniqueConstraintViolation.
и заканчиваться именем уникального ограничения базы данных. Вы можете заметить, что файл уже содержит аналогичное сообщение для уникального ограничения атрибута username
сущности User
.
Переключитесь на приложение и протестируйте свои изменения. Теперь в тексте ошибки отображено ваше сообщение:
Резюме
В этом разделе вы реализовали вторую функцию: управление отделами.
Вы узнали, что:
-
Studio помогает создавать ссылочные атрибуты и генерирует Liquibase changelog с внешним ключом и индексом.
-
Чтобы показать ссылочный атрибут на экране просмотра или редактирования, он должен быть включен в фетч-план экрана.
-
Имя экземпляра используется для отображения ссылки в пользовательском интерфейсе.
-
Компонент выбора сущности (EntityPicker) используется по умолчанию для выбора связанной сущности в сгенерированном экране редактирования. Его действия можно настроить, например, показывать экран поиска в диалоговом окне.
-
Уникальность атрибутов сущностей поддерживается на уровне базы данных путем определения уникальных ограничений.
-
Уникальное сообщение о нарушении ограничений может быть легко кастомизировано.
-
Заголовки и сообщения, сгенерированные Studio, хранятся в пакете сообщений приложения.
-
Механизм Studio hot deploys изменяет экраны и сообщения в запущенном приложение, что избавляет от перезапуска приложения при разработке пользовательского интерфейса. Hot deploy не работает для классов сущностей.