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
.
Нажмите Views → Create view на панели действий в верхней части дизайнера сущностей:

На первом шаге мастера создания экрана выберите шаблон Entity list and detail views
:

Нажмите Next.
Примите предложенные значения на первых двух шагах мастера.
На шаге Entity list view fetch plan добавьте атрибут hrManager
в фетч-план:

Теперь вы можете быть уверены, что ссылочная сущность User
будет загружена вместе с сущностью Department
и отображена на экране списка.
Если какой-либо атрибут не выбран в фетч-плане, Studio не создает для него визуальный компонент в генерируемых экранах. |
Нажмите кнопку Next.
На следующем шаге Entity detail view plan этот атрибут будет выбран автоматически:

Нажмите кнопку Next.
Оставьте значения по умолчанию на шаге Localizable messages и нажмите Create.
Studio сгенерирует два экрана: Department.list
и Department.detail
и откроет их исходный код. Закройте пока все вкладки редактора - позже в этой главе вы внесете некоторые изменения в созданные экраны.
Запуск приложения
Нажмите на кнопку Debug () на главной панели инструментов.
Перед запуском приложения Studio сгенерирует Liquibase changelog:

Как вы можете видеть, changelog содержит команды для создания таблицы DEPARTMENT
, уникальное ограничение для столбца NAME
и внешнего ключа, а также индекс для столбца HR_MANAGER_ID
.
Нажмите на кнопку Save and run.
Студия выполнит changelog, затем соберет и запустит приложение.
Откройте http://localhost:8080
в вашем веб-браузере и войдите в приложение с учетными данными администратора (admin
/ admin
).
Раскройте меню Application и нажмите на подпункт Departments. Вы увидите экран Department.list
:

Нажмите на кнопку Create. Откроется экран Department.detail
:

Вы можете выбрать 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
Давайте произведем некоторые изменения в пользовательском интефейсе приложения, чтобы ближе познакомится с возможностями Jmix.
Изменение заголовка атрибута
Возможно, вы заметили, что сгенерированный заголовок для атрибута hrManager
не совсем корректен: он читается как Hr manager
. Давайте изменим его на HR Manager
.
Выберите атрибут hrManager
в дизайнере сущностей и нажмите на кнопку глобуса () рядом с именем атрибута:

Появится диалоговое окно Localized Message:

Измените текст и нажмите кнопку OK.
Вы можете просмотреть и отредактировать все сообщения вашего проекта, если дважды щелкните элемент User Interface → Message Bundle в окне инструментов Jmix. Сообщение, которое вы только что изменили, выделено ниже:

Переключитесь на приложение, запущенное в вашем веб-браузере. Обновите веб-страницу. Вы увидите новый заголовок для атрибута hrManager
.
Благодаря технологии Studio Hot deploy вам не нужно перезапускать приложение при внесении изменений в пользовательский интерфейс. Просто сохраните изменения в IDE (нажав комбинацию клавиш |
Сортировка в DataGrid
По умолчанию пользователи могут сортировать таблицу по одному столбцу. Давайте включим сортировку по нескольким столбцам.
Найдите department-list-view.xml
в окне инструментов Jmix и дважды щелкните по нему. Появится дизайнер экрана:

Studio позволяет предварительно просмотреть макет экрана прямо в IDE. Нажмите кнопку Start Preview:

Studio соберет фронтенд, и через несколько секунд вы увидите панель предварительного просмотра рядом с исходным кодом. В зависимости от разрешения вашего дисплея вам может быть удобно одновременно отображать только редактор XML или панель предварительного просмотра. Используйте кнопки в верхней части панели редактора для переключения режима:

Найдите departmentsDataGrid
в панели структуры Jmix UI. Компонент будет выбран в панель предварительного просмотра, в редакторе XML и в панели инспектора Jmix UI в правом нижнем углу:

Включите флажок для свойства multiSort
:

Studio добавит атрибут multiSort="true"
XML-элементу dataGrid
.
Это работает и в противоположном направлении. Вы можете редактировать XML напрямую и просматривать результаты на панелях дизайнера и предварительного просмотра. |
Переключитесь на запущенное приложение и обновите страницу с экраном списка отделов. Протестируйте сортировку, нажимая на заголовки колонок Name и HR Manager.
Изменение уникального сообщения о нарушении ограничений
Если вы попытаетесь создать другой отдел с тем же именем, вы увидите сообщение об ошибке нарушения уникального ограничения:

Сообщение по умолчанию не очень дружественное к пользователю, но вы можете легко изменить его.
Дважды щелкните элемент 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 не работает для классов сущностей.