Что нового

В данном разделе приведена информация о новой функциональности и возможных несовместимых изменениях в фреймворке Jmix и Jmix Studio версии 2.2. Примите их во внимание при обновлении с предыдущей версии фреймворка.

Для создания новых проектов в Jmix 2.2 или для апгрейда существующего проекта требуется Studio 2.2 или более поздней версии, поэтому в первую очередь обновите плагин Jmix Studio.

Минимальная требуемая версия IntelliJ IDEA - 2023.2.

Раздел Апгрейд проекта содержит информацию о том, как обновить проект с помощью Studio. Процедура автоматической миграции вносит следующие изменения в ваш проект:

  • Обновляет версию Jmix BOM, которая, в свою очередь, определяет версии всех зависимостей.

  • Обновляет версию Jmix Gradle plugin.

  • Обновляет версию Gradle wrapper до 8.6 в gradle/wrapper/gradle-wrapper.properties.

  • В проектах на Kotlin обновляет версию плагина Kotlin до 1.9.22.

  • В связи с перемещением функциональности пессимистической блокировки, заменяет использования пакетов io.jmix.core.pessimisticlocking на io.jmix.pessimisticlock, заменяет свойства jmix.core.pessimistic-lock.* на jmix.pslock.\* и добавляет зависимости на новый аддон в build.gradle.

  • Добавляет свойство приложения jmix.ui.view.prevent-browser-tab-closing = true. Подробнее см. ниже.

  • Добавляет свойство приложения spring.main.allow-circular-references = true. Подробнее см. ниже.

  • Добавляет свойство приложения jmix.core.skip-null-or-empty-conditions-by-default = true. Подробнее см. ниже.

  • Добавляет свойство приложения jmix.appsettings.check-permissions-for-app-settings-entity = true если проект содержит дополнение Application Settings. Подробнее см. ниже.

См. полный список опасных изменений, которые могут затронуть ваш проект после обновления.

Обновленные зависимости

Следующие важные зависимости были обновлены:

  • Spring Boot 3.2. См. его release notes для получения подробной информации.

  • Vaadin 24.3. См. release notes для Vaadin 24.2 и 24.3 для получения подробной информации. Studio удаляет файл .pnpmfile.cjs из проекта при миграции на Jmix 2.2 (по причине проблемы, описанной в vaadin/flow#17873). Убедитесь что данный файл более не существует после миграции.

Новая и улучшенная функциональность

Поддержка Java 21

Теперь вы можете использовать Java 21 и все её языковые возможности для написания приложений.

Фреймворк по-прежнему собирается с использованием Java 17, поэтому обе версии (и 17 и 21) доступны для прикладных проектов.

Дополнение Charts

Новое дополнение Charts интегрирует JavaScript-библиотеку Apache ECharts в пользовательский интерфейс Jmix.

Улучшения в дополнении Maps

  • Добавлена поддержка кластеризации и тепловых карт. См. High-Density Data Visualization.

  • Добавлена поддержка MultiPoint, MultiPolygon и MultiLineString. См. #2807.

Улучшения в дополнении BPM

  • Мастер форм BPM в Studio теперь поддерживает создание форм для стартового события и использование экземпляров сущностей вместо переменных процесса. См. выпадающие списки Form template and Form type на первом шаге мастера.

  • Флаг Async может быть установлен для сервисных задач в конструкторе BPMN в Studio.

  • Определения бизнес-процессов, разработанные в Studio, теперь могут быть развернуты в работающем приложении. Используйте кнопку Hot Deploy Process на верхней панели конструктора BPMN в Studio.

  • BPM-моделер в приложении теперь поддерживает элемент flowable:failedJobRetryTimeCycle. См. свойство Failed job retry time cycle выбранной сервисной задачи.

  • BPM-моделер в приложении теперь позволяет выбирать существующие переменные процесса для аргументов бина из выпадающих списков.

Компонент RichTextEditor

Новый компонент richTextEditor интегрирует JavaScript-библиотеку Quill в пользовательский интерфейс Jmix. Он доступен в стандартной палитре Add Component.

Горизонтальное главное меню

Новый компонент horizontalMenu позволяет создавать главный экран с горизонтальным меню.

Новый шаблон Main view with top menu доступен в мастере создания экранов. Если вы хотите использовать новый экран вместо существующего главного экрана, выберите флажок Use as default main view на первом шаге мастера. Тогда Studio заменит атрибут layout аннотации @Route во всех экранах и установит новый экран в свойство приложения jmix.ui.main-view-id.

Фильтрация главного меню

Новый компонент menuFilterField позволяет пользователям фильтровать элементы главного меню. Он доступен в стандартной палитре Add Component.

Атрибут menu должен указывать на компонент listMenu, который нужно фильтровать:

<menuFilterField menu="menu"
                 placeholder="Search..." classNames="ms-s me-s"/>
<nav id="navigation" classNames="jmix-main-view-navigation" ariaLabel="msg://navigation.ariaLabel">
    <listMenu id="menu"/>
</nav>

Обратите внимание, что фильтрация горизонтального меню не поддерживается.

Начальная компоновка в главном экране

Теперь вы можете декларативно определить начальную компоновку, которая будет отображаться, когда в главном экране не открыты никакие экраны. Используйте элемент initialLayout компонента appLayout:

<appLayout>
    <navigationBar .../>
    <drawerLayout .../>
    <initialLayout>
        <h2 text="Привет, мир!"/>
    </initialLayout>
</appLayout>

Подробнее см. #2213.

Улучшения таблицы данных

Обработка двойного щелчка

Компонент dataGrid теперь обрабатывает двойной щелчок в экранах списка: он либо открывает экран деталей, либо, в режиме поиска, завершает выбор. Подробнее см. #2582.

Параметры URL для фильтра в заголовках столбцов

Фильтр в заголовке столбцов dataGrid теперь может отражаться в URL для предоставления глубокой ссылки и сохранения состояния экрана при переходе к экрану деталей и обратно.

Используйте элемент dataGridFilter фасета urlQueryParameters, указывающий на таблицу данных:

<facets>
    <urlQueryParameters>
        <dataGridFilter component="usersDataGrid"/>
    </urlQueryParameters>
</facets>
<layout>
    <dataGrid id="usersDataGrid" dataContainer="usersDc">
        <columns>
            <column property="username" filterable="true" resizable="false" autoWidth="true"/>

Управление видимостью колонок

Новый компонент gridColumnVisibility позволяет пользователям скрывать и показывать колонки таблицы данных. Он состоит из кнопки и выпадающего меню со списком колонок.

Пример использования:

<hbox id="buttonsPanel" classNames="buttons-panel">
    <!-- ... -->
    <gridColumnVisibility icon="COG" themeNames="icon"
                          dataGrid="usersDataGrid" exclude="picture"/>
</hbox>
<dataGrid id="usersDataGrid" dataContainer="usersDc">
    <columns resizable="true">
        <column key="picture" sortable="false" flexGrow="0" resizable="false"/>
        <column property="username"/>
        <column property="firstName"/>

Свойства-коллекции в универсальном фильтре

Компонент genericFilter теперь позволяет создавать условия для свойств-коллекций (ссылок с отношением "один ко многим").

Например, в приложении Onboarding, вы можете фильтровать пользователей по свойству steps и его вложенным свойствам: steps.dueDate, steps.step.name и т.д. Хранилище данных с доступом через JPA автоматически создаст соответствующий JPQL-запрос с условием join. Ранее это можно было сделать только путем определения JPQL-условия вручную.

Подробнее см. #518.

Отправка событий всем сессиям пользователя

Теперь у бина UiEventPublisher есть метод publishEventForUsers(), который принимает экземпляр события и коллекцию имен пользователей. Этот метод позволяет отправлять события в сеансы определенных пользователей, независимо от того, подключены ли они к тому же серверу или к разным серверам в кластере.

Пример отправки события пользователю alice:

public class DepartmentListView extends StandardListView<Department> {
    @Autowired
    private UiEventPublisher uiEventPublisher;

    @Subscribe(id = "sendEventBtn", subject = "clickListener")
    public void onSendEventBtnClick(final ClickEvent<JmixButton> event) {
        uiEventPublisher.publishEventForUsers(new MyUiEvent(this), List.of("alice"));
    }

    public static class MyUiEvent extends ApplicationEvent {

        public MyUiEvent(Object source) {
            super(source);
        }
    }
}

Пример слушателя событий:

public class MainView extends StandardMainView {
    @Autowired
    private Notifications notifications;

    @EventListener
    public void onMyUiEvent(DepartmentListView.MyUiEvent event) {
        notifications.show("Event received");
    }
}

Если второй аргумент метода publishEventForUsers() равен null, событие отправляется всем подключенным пользователям.

Подробнее см. #1235.

Улучшена производительность сохранения

Теперь отредактированная сущность по умолчанию не перезагружается после действия save-and-close, если экран деталей был открыт через навигацию, потому что в этом случае экран списка все равно перезагружает весь список. Это улучшает производительность для сложных экранов, загружающих и сохраняющих большие графы объектов.

Вы можете явно контролировать перезагрузку сохраненных экземпляров, используя метод setReloadSaved() интерфейса DetailView, например:

@Subscribe
public void onInit(final InitEvent event) {
    setReloadSaved(true);
}

См. возможно опасные изменения и #1725 для получения дополнительной информации.

Сокращено время сборки

Теперь процесс сборки пропускает этап модификации байткода (enhancing) сущностей, если они не были изменены с момента последней сборки. Это значительно сокращает время сборки для проектов с большой моделью данных.

Например, если вы запустили сборку проекта, а затем модифицировали контроллер экрана и снова запустили сборку, вы должны увидеть следующее сообщение в консоли: Entities enhancing was skipped, because entity classes haven’t been changed since the last build.

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

jmix {
    entitiesEnhancing {
        skipUnmodifiedEntitiesEnhancing = false
    }
}

Улучшения в Studio

Начиная с версии Jmix Studio 2.2, премиальные RAD-функции доступны без активной подписки для небольших проектов, в которых число сущностей и ролей не превышает 10.

Сниппеты кода

В Studio теперь доступны новые сниппеты для функционала BPM, Отчетов, Уведомлений и Отправки электронной почты, если соответствующие дополнения включены в проект.

Добавление компонентов с помощью мастеров

Действие Add Component в Дизайнер экранов теперь имеет две вкладки:

  • Вкладка From Palette показывает палитру компонентов, как и раньше;

  • Вкладка Using Wizard содержит мастера, которые помогают решать сложные задачи, связанные с пользовательским интерфейсом. Например, мастер Edit entity attributes создает formLayout с полями для выбранных атрибутов сущности и контейнер данных с соответствующим фетч-планом.

    Список мастеров зависит от содержимого текущего экрана: например, если экран уже содержит dataGrid, то доступен мастер Add column to DataGrid.

Панель инспектора окна инструментов Jmix UI теперь показывает ссылку на документацию по выбранному компоненту UI. См. значок вопроса рядом с типом компонента.

Та же ссылка доступна как элемент Jmix Documentation в контекстном меню иерархии компонентов.

Генерация классов тестов

Studio теперь отображает элемент Tests в окне инструментов Jmix. Двойной щелчок на этом элементе открывает дерево Project в папке src/test/java.

Действия New → Advanced → Integration Test и New → Advanced → UI Integration Test позволяют быстро создавать классы для тестирования бизнес-логики и экранов.

Опасные изменения

Предотвращение закрытия вкладки браузера

Функциональность предотвращения случайного закрытия вкладки браузера, введенная в версии 2.0, теперь по умолчанию отключена. Ее можно включить для конкретного экрана методом setPreventBrowserTabClosing(true), или глобально для всего приложения с помощью следующего свойства:

jmix.ui.view.prevent-browser-tab-closing = true

Циклические зависимости между бинами Spring

Ранее циклические зависимости между бинами Spring были разрешены в Jmix на уровне фреймворка.

Jmix 2.2 больше не имеет циклических зависимостей и по умолчанию не разрешает их в прикладных проектах.

Есть вероятность того, что ваш проект содержит циклические зависимости между своими бинами, поэтому процедура миграции Studio автоматически добавляет следующее свойство в проект:

spring.main.allow-circular-references = true

Мы рекомендуем вам удалить это свойство и попробовать запустить приложение. Если произойдет ошибка инициализации, то либо переработайте свои бины, чтобы устранить циклические зависимости, либо верните данное свойство.

Подробнее см. #287.

Обработка пустых условий

Ранее условия по свойствам преобразовывались в true если параметр условия был пуст (null, пустая строка или пустая коллекция).

Начиная с Jmix 2.2, null или пустой параметр не приводит к пропуску условия. Для примера рассмотрим следующий код:

dataManager.load(User.class)
    .condition(PropertyCondition.contains("email", null))
    .list();

В Jmix 2.1 и ранее он выполнял следующий SQL:

SELECT ID, ACTIVE, EMAIL, <...> FROM USER_

В Jmix 2.2 по умолчанию он выполняет следующий SQL и передает null в качестве значения параметра:

SELECT ID, ACTIVE, EMAIL, <...> FROM USER_ WHERE EMAIL LIKE ?

В результате, в Jmix 2.1 возвращается список всех пользователей, а в Jmix 2.2 результирующий список будет пуст.

Чтобы вернуть предыдущее поведение, установите следующее свойство приложения:

jmix.core.skip-null-or-empty-conditions-by-default = true

Процедура миграции Studio автоматически добавляет это свойство в ваш проект.

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

dataManager.load(User.class)
    .condition(PropertyCondition.contains("email", null).skipNullOrEmpty())
    .list();

Подробнее см. #2490.

NoResultException

Исключение io.jmix.core.NoResultException теперь выбрасывается вместо java.lang.IllegalStateException, если метод one() fluent API загрузки сущностей DataManager не нашел ни одного экземпляра. См. #2682.

Пессимистическая блокировка

Функция пессимистическая блокировки была выделена в дополнение.

Пакет io.jmix.core.pessimisticlocking переименован в io.jmix.pessimisticlocking.

Следующие свойства приложения были изменены:

  • jmix.core.pessimistic-lock.use-default-quartz-configurationjmix.pslock.use-default-quartz-configuration

  • jmix.core.pessimistic-lock.expiration-cronjmix.pslock.expiration-cron

Процедура миграции Studio автоматически добавляет зависимости в ваш build.gradle и изменяет импорты и имена свойств.

Подробнее см. #1958.

Валидация в полях загрузки файлов

Метод isInvalid() компонентов fileUploadField и fileStorageUploadField теперь не вызывает валидацию, а только проверяет валидное состояние поля. См. #2821.

Сочетания клавиш действий

Клавиатурные сочетания действий, назначенных компонентам типа button или dataGrid теперь обрабатываются иначе. Подробнее см. #1758.

Проверка прав в Application Settings

Дополнение Application Settings теперь не требует наличия прав на сущность AppSettingsEntity для работы с настройками через бин AppSettings.

Чтобы вернуть предыдущее поведение, установите следующее свойство приложения:

jmix.appsettings.check-permissions-for-app-settings-entity = true

Процедура миграции Studio автоматически добавляет это свойство в ваш проект.

Подробнее см. #2710.

Экраны подсистемы безопасности

Компоновка стандартных экранов управления ресурсными ролями и ролями уровня строк была изменена для улучшения удобства использования. См. #2519.

Если вы расширили эти экраны в своем проекте, вам может потребоваться изменить свой код.

Интерфейсы DetailView и DataContext

Следующие методы были добавлены в интерфейсы в процессе реализации задачи улучшения производительности сохранения:

  • DataContext.save(boolean reloadSaved)

  • DetailView.isReloadSaved()

  • DetailView.setReloadSaved(boolean reloadSaved)

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

Кроме того, метод DataContext.PostSaveEvent.getSavedInstances() теперь возвращает пустую коллекцию, если сущности не были перезагружены. Это можно определить с помощью нового метода DataContext.PostSaveEvent.isEntitiesReloaded().

DTO-сущности в стандартных экранах

Фреймворк теперь не делает различий между JPA и DTO-сущностями при навигации к экрану деталей: он так же передает идентификатор сущности в параметре URL. Предполагается, что экран деталей для DTO-сущности получит этот идентификатор и загрузит экземпляр сущности из некоторого хранилища данных, используя делегат загрузки. Если вместо идентификатора передается константа "new", экран создает новый экземпляр.

Если вместо идентификатора передается вся сущность (например, при открытии в диалоговом окне), для определения режима (редактирования или создания) используется метод EntityStates.isNew(). Следовательно, важно установить сущность в состояние "не новая" после загрузки ее из хранилища данных. Для сущности DTO это можно сделать с помощью нового метода EntityStates.setNew(), для сущности JPA это делается стандартной реализацией хранилища данных JPA.

Если редактируемую сущность не нужно повторно загружать из хранилища перед установкой в контейнер данных, вызовите setReloadEdited(false) в конструкторе экрана деталей или обработчике события InitEvent. Это требуется в случае DTO-сущностей, существующих исключительно в памяти и не отображенных напрямую на внешние данные.

См. #2788 для получения дополнительной информации и рекомендаций, и проект External Data Sample для примера кода.

API дополнения Maps

Следующие изменения произведены в дополнении Maps:

  • io.jmix.mapsflowui.kit.component.model.style.text.Padding перемещен в пакет io.jmix.mapsflowui.kit.component.model. См. #2822.

  • Метод addStyles() классов Feature, PointFeature, MarkerFeature, LineStringFeature, PolygonFeature теперь возвращает void. Используйте вместо него метод withStyles(), если вам нужно вернуть экземпляр feature. См. #2807.

  • Метод addStyles() класса VectorLayer теперь возвращает void. Используйте вместо него метод withStyles(), если вам нужно вернуть экземпляр слоя. Кроме того, переименованы методы: isDeclutter()getDeclutter(), isUpdateWhileAnimating()getUpdateWhileAnimating(). См. #2790.

  • Метод addPointStyles() класса ClusterSource теперь возвращает void. Используйте вместо него метод withPointStyles(), если вам нужно вернуть экземпляр источника. См. #2790.

  • В классе Layer метод isVisible() переименован в getVisible(). См. #2790.

  • Тип свойств зума в классах VectorLayer, TileLayer, ImageLayer и GeoMapView изменен с Integer на Double. См. #2701.

Список изменений