5. UI 中处理数据

开发进行到这个阶段,我们已经有了入职步骤和部门的管理功能,以及默认的用户管理功能,并且为用户添加了 Onboarding status 属性。现在我们需要将用户与入职步骤和部门关联起来。

本节中,我们将完成:

  • User 实体添加 departmentjoiningDate 属性,并在 UI 展示。

  • 创建 UserStep 实体,关联一个用户和一个入职步骤。

  • User 实体添加 UserStep 实体集合,并在 User.detail 视图展示。

  • User.detail 视图实现生成和保存 UserStep 实例的功能。

下图展示本节要用到的实体和属性:

data in ui diagram

添加关联属性

我们已经在之前的章节完成了类似的任务:为 Department 实体添加关联至 User 实体的 HR 经理属性。现在我们需要创建一个反向连接:一个用户需要属于某个部门。

data in ui diagram 2

如果你的应用程序正在运行,先通过主工具栏的 Stopsuspend)按钮停止运行。

Jmix 工具窗口双击 User 实体并选择其最后一个属性(我们要在最后添加新属性):

Attributes 工具栏中,点击 Addadd)。

弹出的 New Attribute 对话框中,Name 字段填写 department。然后选择:

  • Attribute typeASSOCIATION

  • TypeDepartment

  • CardinalityMany to One

add attr 1

点击 OK

然后选中 department 属性,在 Attributes 工具栏中点击 Add to viewsadd attribute to screens)按钮:

add attr 2

出现的对话框中会显示 User.detailUser.list 视图。我们都选上,然后点击 OK

Studio 会在 User.list 视图的 dataGrid 组件和 User 视图的 formLayout 组件中添加 department 属性。

你可能会注意,Studio 在 user-list-view.xml 中添加了下面的代码:

<data>
    <collection id="usersDc"
                class="com.company.onboarding.entity.User">
        <fetchPlan extends="_base">
            <property name="department" fetchPlan="_base"/> <!-- added -->
        </fetchPlan>

以及 user-detail-view.xml

<data>
    <instance id="userDc"
              class="com.company.onboarding.entity.User">
        <fetchPlan extends="_base">
            <property name="department" fetchPlan="_base"/> <!-- added -->
        </fetchPlan>

有了这些代码,关联的部门实体会与用户实体在同一个数据库查询中加载。

即使 fetch plan 中没有包含 department,基于关联实体的 懒加载 机制,视图也能正常工作。但是在懒加载时,关联实体是通过单独的数据库请求进行加载。因此,懒加载在列表视图会造成性能问题,该视图首先执行一个加载所有用户的请求,然后针对每个用户,执行单独的请求加载部门(N+1 查询问题)。

运行应用程序可以查看新的属性。

点击主工具栏中的 Debugstart debugger)按钮启动应用程序。

Studio 会生成 Liquibase 更改日志,添加了 USER_ 表的 DEPARTMENT_ID 列,并创建外键和索引。确认这个改动。

Studio 会执行更改日志,然后构建并运行应用程序。

在浏览器打开 http://localhost:8080 并使用 admin / admin 凭证登录。

点击主菜单的 ApplicationUsers,打开 User.list 视图,可以看到新加的 Department 列。创建用户时,可以看到 User.detail 中的 Department 选择控件:

add attr 3

使用下拉框选择

默认情况下,Studio 会生成 entityPicker 组件选择关联实体。可以在 User.detail 视图中看到这样的组件。打开 user-detail-view.xml 并在 formLayout 中找到 entityPicker 组件:

<layout ...>
    <formLayout id="form" dataContainer="userDc">
        ...
        <entityPicker id="departmentField" property="department">
            <actions>
                <action id="entityLookup" type="entity_lookup"/>
                <action id="entityClear" type="entity_clear"/>
            </actions>
        </entityPicker>
    </formLayout>

该组件支持通过一个列表视图选择实体,支持过滤、排序或者分页。但是当备选的记录相对比较少时(比如少于 1000),通过简单的下拉框列表选择会更加方便。

我们将修改 User.detail 视图,使用 entityComboBox 组件选择用户的部门。

将组件的 XML 元素修改为 entityComboBox 并删除内部的 actions 元素:

<entityComboBox id="departmentField" property="department"/>

切换至运行的应用程序,重新打开用户详情视图。

可以看到,Department 字段现在变成了下拉框,但是无法打开,即便已经创建了一些部门。

dropdown 2

创建选项数据容器

我们为 entityComboBox 组件提供一组选项,用于选择关联的部门实体。选项列表包含所有的部门,按名称排序。

在操作面板点击 Add Component,选择 Data components,然后双击 Collection。在 Data Container Properties Editor 窗口的 Entity 字段选择 Department,点击 OK

options container 1

然后在 Jmix UI 结构和 XML 的 data 元素下会创建 id 为 departmentsDccollection 元素:

<data>
    ...
    <collection id="departmentsDc" class="com.company.onboarding.entity.Department">
        <fetchPlan extends="_base"/>
        <loader id="departmentsDl" readOnly="true">
            <query>
                <![CDATA[select e from Department e]]>
            </query>
        </loader>
    </collection>
</data>

该元素定义一个 集合数据容器(collection data container),以及容器关联的一个 数据加载器(loader)。数据容器包含由加载器加载的部门实体列表,加载使用的查询语句在加载器中指定。

可以在 XML 中直接编辑查询语句,或者通过 JPQL 设计器进行编辑。在 Jmix UI 中的组件面板中,找到数据组件的 query 属性,然后点击右侧的链接打开设计器:

options container 2

JPQL Query Designer 窗口中,切换至 ORDER 标签页并添加 name 属性:

options container 3

点击 OK

在 XML 中生成的查询语句如下:

<data>
    ...
    <collection id="departmentsDc" class="com.company.onboarding.entity.Department">
        <fetchPlan extends="_base"/>
        <loader id="departmentsDl" readOnly="true">
            <query>
                <![CDATA[select e from Department e
                order by e.name asc]]>
            </query>
        </loader>
    </collection>
</data>

现在需要将 entityComboBox 组件与 departmentsDc 数据容器进行关联。

Jmix UI 的结构面板选中 departmentField,然后在 Jmix UI 的组件面板中为 itemsContainer 属性选择 departmentsDc

options container 4

切换至运行的应用程序并重新打开用户详情视图。

可以看到 Department 下拉框现在已经有了选项:

dropdown 3
entityComboBox 组件支持直接在组件内输入的方式对选项进行过滤。过滤的过程是在服务端的内存中进行,所有的选项已经一次从数据加载出来了。

创建 UserStep 实体

本小节中,我们将创建 UserStep 实体,用来表示特定用户的入职步骤:

data in ui diagram 3

如果你的应用程序正在运行,先通过主工具栏的 Stopsuspend)按钮停止运行。

Jmix 工具窗口中,点击 Newadd)→ JPA Entity 并与 之前 的步骤一样创建带有 Versioned 特性的 UserStep 实体。

为实体添加下列属性:

Name Attribute type Type Cardinality Mandatory

user

ASSOCIATION

User

Many to One

true

step

ASSOCIATION

Step

Many to One

true

dueDate

DATATYPE

LocalDate

-

true

completedDate

DATATYPE

LocalDate

-

false

sortValue

DATATYPE

Integer

-

true

实体设计器的最终状态如下:

create user step 1

添加组合属性

我们来看看 UserUserStep 实体的关系。UserStep 实例仅当特定的 User 实例存在时才有意义(即,属于该用户)。一个 UserStep 实例不能修改其所有者;此外,其他数据模型也没有关联 UserStep,也就是说 UserStep 实例都是包含在 User 实例内的。

在 Jmix 中,这种实体间的关系被称为 组合(composition)User 由一组 UserSteps 和其他的属性共同组成。

Jmix 中的组合实现了 DDD(Domain-Driven Design)中的聚合(Aggregate)模式。

在父实体中创建包含一组组合子实体的属性也很方便。

我们在 User 实体中创建 steps 属性:

data in ui diagram 4

如果你的应用程序正在运行,先通过主工具栏的 Stopsuspend)按钮停止运行。

User 实体设计器的 Attributes 工具栏中,点击 Addadd)。弹出的 New Attribute 对话框中,Name 字段填写 steps,然后选择:

  • Attribute typeCOMPOSITION

  • TypeUserStep

  • CardinalityOne to Many

composition 1

注意,Mapped by 字段会自动选择 user。这是 UserStep 实体中的一个属性,映射至一个数据库列,用于维护 UserStepsUsers 的关系(外键)。

点击 OK

该属性的源代码会带有 @Composition 注解:

@Composition
@OneToMany(mappedBy = "user")
private List<UserStep> steps;

UserSteps 需要在用户详情视图展示,因此,选中 steps 属性并点击 Attributes 工具栏中的 Add to Viewsadd attribute to screens)按钮,选择 User.detail 视图,点击 OK

Studio 会修改 user-detail-view.xml

<data>
    <instance id="userDc"
              class="com.company.onboarding.entity.User">
        <fetchPlan extends="_base">
            <property name="department" fetchPlan="_base"/>
            <property name="steps" fetchPlan="_base"/> (1)
        </fetchPlan>
        <loader/>
        <collection id="stepsDc" property="steps"/> (2)
    </instance>
    ...
<layout ...>
    <formLayout id="form" dataContainer="userDc">
        ...
    </formLayout>
    <hbox id="buttonsPanel" classNames="buttons-panel">
        <button action="stepsDataGrid.create"/>
        <button action="stepsDataGrid.edit"/>
        <button action="stepsDataGrid.remove"/>
    </hbox>
    <dataGrid id="stepsDataGrid" dataContainer="stepsDc" ...> (3)
        <actions>
            <action id="create" type="list_create"/>
            <action id="edit" type="list_edit"/>
            <action id="remove" type="list_remove"/>
        </actions>
        <columns>
            <column property="version"/>
            <column property="dueDate"/>
            <column property="completedDate"/>
            <column property="sortValue"/>
        </columns>
    </dataGrid>
1 Fetch plan 添加了 steps 属性,确保 UserSteps 与 User 一起进行预加载。
2 内部的 stepsDc 集合数据容器用于将可视化组件与 steps 集合属性做绑定。
3 dataGrid 组件用于展示 stepsDc 数据容器中的数据。

运行应用程序查看这些改动。

点击主工具栏中的 Debugstart debugger)按钮启动应用程序。

Studio 会生成 Liquibase 更改日志,包含创建 USER_STEP 表、关联至 USER_STEP 的外键约束和索引。确认这些改动。

Studio 会执行更改日志,然后运行应用程序。

应用程序准备好后,在浏览器打开 http://localhost:8080 并使用 admin / admin 凭证登录。

打开一个用户进行编辑。可以看到数据网格展示 UserStep 实体:

composition 2

如果点击数据网格中的 Create 按钮,系统会抛出异常:View 'UserStep.detail' is not defined。是的,我们还没有为 UserStep 实体创建详情视图。但是实际上我们不需要创建这个视图,因为 UserStep 实例可以通过给用户分配预定义的 Step 实体生成。

为用户生成 UserSteps

本节中,我们将为编辑的 User 实体生成并展示 UserStep 实例。

添加 JoiningDate 属性

首先,为 User 实体添加 joiningDate 属性:

data in ui diagram 5

该属性将用于计算 UserStep 实体中的 dueDate 属性:UserStep.dueDate = User.joiningDate + Step.duration

如果你的应用程序正在运行,先通过主工具栏的 Stopsuspend)按钮停止运行。

User 实体设计器的 Attributes 工具栏中,点击 Addadd)。弹出的 New Attribute 对话框中,Name 字段填写 joiningDate,然后在 Type 下拉框中选择 LocalDate

joining date 1

点击 OK

然后选中新创建的 joiningDate 属性,在 Attributes 工具栏中点击 Add to Viewsadd attribute to screens)按钮。在弹窗中选择 User.detailUser.list 视图并点击 OK

点击主工具栏中的 Debugstart debugger)按钮启动应用程序。

Studio 会生成 Liquibase 更改日志,为 USER_ 表添加 JOINING_DATE 列。确认此改动。

Studio 会执行更改日志,然后运行应用程序。在浏览器打开 http://localhost:8080,登录后在用户列表视图和详情视图确认新属性已经添加上了。

添加自定义按钮

现在我们需要删除管理 UserSteps 的默认操作按钮,然后添加一个按钮用于启动自定义逻辑创建实体。

打开 user-detail-view.xml 并删除 dataGrid 内的 actions 元素和 hbox 内的全部 button 元素:

<hbox id="buttonsPanel" classNames="buttons-panel">
</hbox>
<dataGrid id="stepsDataGrid" dataContainer="stepsDc" width="100%" height="100%">
    <columns>
        <column property="version"/>
        <column property="dueDate"/>
        <column property="completedDate"/>
        <column property="sortValue"/>
    </columns>
</dataGrid>

然后在 Jmix UI 结构面板中选择 buttonsPanel,右键点击节点,在菜单中选择 Add Component,添加一个按钮。在组件工具箱中找到 Components → Button,并双击。然后在组件属性面板中,设置其 idgenerateButtontextGenerate。切换至 Handlers 标签页,创建一个 ClickEvent 的处理器方法:

button 1

按下 Ctrl/Cmd+S 保存修改然后切换至运行中的程序。重新打开用户详情视图,查看我们新添加的 Generate 按钮正常展示而不是展示默认的 CRUD 按钮:

button 2

创建并保存 UserStep 实例

现在实现生成 UserStep 实例的逻辑。

UserDetailView 控制器中添加下列字段:

public class UserDetailView extends StandardDetailView<User> {

    @Autowired
    private DataManager dataManager;

    @Autowired
    private Notifications notifications;

    @ViewComponent
    private DataContext dataContext;

    @ViewComponent
    private CollectionPropertyContainer<UserStep> stepsDc;
}

如果直接复制粘贴上面的代码,IDE 会对这些代码报错,因为还需要添加相关类的 import 语句。将光标放置于这些出错的代码上,IDE 会建议合适的 import 类。如果没有提示,可以关闭 UserDetailView.java 后再次打开。

可以通过操作面板中的 Inject 按钮注入视图中的组件和 Spring bean:

inject 1

generateButton 点击处理方法中添加创建和保存 UserStep 的逻辑:

@Subscribe("generateButton")
public void onGenerateButtonClick(final ClickEvent<Button> event) {
    User user = getEditedEntity(); (1)

    if (user.getJoiningDate() == null) { (2)
        notifications.create("Cannot generate steps for user without 'Joining date'")
               .show();
        return;
    }

    List<Step> steps = dataManager.load(Step.class)
            .query("select s from Step s order by s.sortValue asc")
            .list(); (3)

    for (Step step : steps) {
        if (stepsDc.getItems().stream().noneMatch(userStep ->
                userStep.getStep().equals(step))) { (4)
            UserStep userStep = dataContext.create(UserStep.class); (5)
            userStep.setUser(user);
            userStep.setStep(step);
            userStep.setDueDate(user.getJoiningDate().plusDays(step.getDuration()));
            userStep.setSortValue(step.getSortValue());
            stepsDc.getMutableItems().add(userStep); (6)
        }
    }
}
1 使用 StandardDetailView 父类中的 getEditedEntity() 方法获取正在编辑的 User 实体。
2 如果 joiningDate 属性未设置,展示消息并退出。
3 加载已经添加的入职步骤。
4 忽略 stepsDc 集合容器中已经存在的步骤。
5 DataContext.create() 方法创建新的 UserStep 实例。
6 将新的 UserStep 实例添加至 stepsDc 集合容器,以便在 UI 展示。
当通过 DataContext 对象创建实例时,实例自动由 DataContext 进行管理,并会在视图提交时(点击视图的 OK 按钮时)自动保存至数据库。

按下 Ctrl/Cmd+S 保存修改然后切换至运行中的程序。重新打开用户详情视图,此时我们点击 Generate 按钮,会创建对应入职步骤的几条记录。

如果通过点击 OK 提交视图,所有创建的 UserSteps 实例都会自动保存。如果点击 Cancel,则不会保存。因为在上面的代码中,我们没有显式地保存创建的实体。而是通过 DataContext.create() 将实体合并至视图的 DataContext 内,只有在整个视图的 DataContext 提交时才会保存新创建的实例。

优化 UserSteps 数据网格

下面的小节中,我们将完善处理生成的 UserSteps 的 UI 视图。

内部集合的排序

你可能已经注意到,当打开一个带有生成 UserSteps 的用户视图时,这些步骤并没有按照 sortValue 属性排序:

ordering 1

数据网格展示 User 实体的 steps 集合属性,因此我们可以在数据模型级别引入排序。

打开 User 实体,选择 steps 属性并在 Order by 字段输入 sortValue

ordering 2

如果切换至 Text 标签页,可以看到 steps 属性上添加了 @OrderBy 注解:

@OrderBy("sortValue")
@Composition
@OneToMany(mappedBy = "user")
private List<UserStep> steps;

现在当加载 User 实体时,实体内部的 steps 集合会按照 UserStep.sortValue 属性进行排序。

如果应用程序正在运行,请重新启动。

打开用户详情视图。可以看到步骤的顺序现在对了:

ordering 3

数据网格的列重排

此时,UserSteps 数据网格提供的信息并不是很有用。我们需要删除 VersionSort value 列并添加展示步骤名称的列。

删除列很简单,在 Jmix UI 的结构面板中选择这些列并按下 Delete,或直接从 XML 代码里删除相应的元素即可。

添加列时,在 Jmix UI 的结构面板中选择 columns 元素,然后在组件面板中点击 AddColumn,会出现 Add Column 弹窗:

columns 2

可以看到,这里并不允许添加步骤名称。这是因为 step 属性是一个关联属性,而我们没有定义一个合适的 fetch plan 去加载这个实体。

Jmix UI 的结构面板中,选择 userDc 数据容器,然后可以在 Jmix UI 组件面板中 fetchPlan 属性处或者直接在 XML 编辑器的装订线栏中点击 Editedit)按钮:

columns 3

Edit Fetch Plan 窗口中,选中 stepsstep 属性,然后点击 OK

columns 4

内部的 step 属性会被添加至 fetch plan XML:

<instance id="userDc"
          class="com.company.onboarding.entity.User">
    <fetchPlan extends="_base">
        <property fetchPlan="_base" name="department"/>
        <property fetchPlan="_base" name="steps">
            <property name="step" fetchPlan="_base"/>
        </property>
    </fetchPlan>
    <loader/>
    <collection id="stepsDc" property="steps"/>
</instance>

现在 UserSteps 集合会与 Step 实例一起从数据库进行预加载。

Jmix UI 的结构面板中选择 columns 元素并在组件面板中点击 AddColumn。现在 Add Column 弹窗中包含了关联的 Step 实体和属性了:

columns 5

选择 stepname,然后点击 OK。新列会添加在最后面:

<dataGrid id="stepsDataGrid" dataContainer="stepsDc" ...>
    <columns>
        <column property="dueDate"/>
        <column property="completedDate"/>
        <column property="step.name"/>
    </columns>

除了 step.name 之外,也可以直接使用 step。此时,表格列中将展示实体的 实例名称。对于 Step 实体,实例名称是从 name 属性获取,所以结果没有不同。

也可以在 XML 中直接添加 step 列而不修改 fetch plan,由于关联实体的懒加载机制,UI 视图也能正常工作。但是此时 Step 会在单独的请求中加载,集合中的每个 UserStep 会触发一次(N+1 查询性能问题)。

step.name 移到最前面,可以直接在 XML 中修改或者在 Jmix UI 结构面板中拖动元素:

<dataGrid id="stepsDataGrid" dataContainer="stepsDc" width="100%" height="100%">
    <columns>
        <column property="step.name"/>
        <column property="dueDate"/>
        <column property="completedDate"/>
    </columns>
</dataGrid>

按下 Ctrl/Cmd+S 保存修改然后切换至运行中的程序。重新打开用户详情视图,确保 Steps 表格现在展示步骤名称:

columns 6

添加组件列

本小节中,我们将实现:通过点击数据网格行中的一个复选框,即可标记 UserStep 完成。复选框在数据网格左侧新增一列中显示。

stepsDataGrid 添加一个新的列:

<dataGrid id="stepsDataGrid" ...>
    <columns>
        <column key="completed" sortable="false" width="4em" flexGrow="0"/>

该列没有绑定任何实体属性,因此,这里使用了 key 属性,而非 property 属性。

选中 completed 列,在组件属性面板切换至 Handlers 标签,并创建 renderer 处理方法:

@Supply(to = "stepsDataGrid.completed", subject = "renderer")
private Renderer<UserStep> stepsDataGridCompletedRenderer() {
    return null;
}

在控制器类中注入 UiComponents

@Autowired
private UiComponents uiComponents;
可以通过编辑器顶部操作面板中的 Inject 按钮注入视图中的组件和 Spring bean。

实现 stepsDataGridCompletedRenderer 方法:

@Supply(to = "stepsDataGrid.completed", subject = "renderer")
private Renderer<UserStep> stepsDataGridCompletedRenderer() {
    return new ComponentRenderer<>(userStep -> { (1)
        Checkbox checkbox = uiComponents.create(Checkbox.class); (2)
        checkbox.setValue(userStep.getCompletedDate() != null);
        checkbox.addValueChangeListener(e -> { (3)
            if (userStep.getCompletedDate() == null) {
                userStep.setCompletedDate(LocalDate.now());
            } else {
                userStep.setCompletedDate(null);
            }
        });
        return checkbox; (4)
    });
}
1 方法返回一个 Renderer 对象,用于创建一个可以在列中渲染的 UI 组件。参数为当前行对应的实体实例。
2 使用 UiComponents 工厂创建 Checkbox 组件实例。
3 当点击复选框时,复选框的值会发生变化,并且调用其 ValueChangeEvent 监听器。监听器中,为 UserStep 实体设置了 completedDate 属性。
4 返回列单元格中显示的组件。

按下 Ctrl/Cmd+S 保存修改然后切换至运行中的程序。重新打开用户详情视图并点击某些行的复选框。Completed date 列会做相应的更改:

generated column 5

UserStep 实例的改动会在点击视图的 OK 按钮后保存到数据库。由视图的 DataContext 负责:能跟踪所有实体的改动并保存修改的实例。

响应更改

当为用户生成入职步骤、标记一个 UserStep 完成或者删除一个步骤时,Onboarding status 字段应该也做相应的调整。

我们现在实现根据 UserSteps 集合变动做出响应的逻辑。

打开 UserDetailView 控制器并在顶部的操作面板中点击 Generate Handler。收起所有的树结构,然后在 Data containers handlersstepsDc 下面选择 ItemPropertyChangeEventCollectionChangeEvent

container listener 1

点击 OK

Studio 会生成两个方法桩代码:onStepsDcItemPropertyChange()onStepsDcCollectionChange()。实现如下:

@Subscribe(id = "stepsDc", target = Target.DATA_CONTAINER)
public void onStepsDcCollectionChange(final CollectionContainer.CollectionChangeEvent<UserStep> event) {
    updateOnboardingStatus(); (1)
}

@Subscribe(id = "stepsDc", target = Target.DATA_CONTAINER)
public void onStepsDcItemPropertyChange(final InstanceContainer.ItemPropertyChangeEvent<UserStep> event) {
    updateOnboardingStatus(); (2)
}

private void updateOnboardingStatus() {
    User user = getEditedEntity(); (3)

    long completedCount = user.getSteps() == null ? 0 :
            user.getSteps().stream()
                    .filter(us -> us.getCompletedDate() != null)
                    .count();
    if (completedCount == 0) {
        user.setOnboardingStatus(OnboardingStatus.NOT_STARTED); (4)
    } else if (completedCount == user.getSteps().size()) {
        user.setOnboardingStatus(OnboardingStatus.COMPLETED);
    } else {
        user.setOnboardingStatus(OnboardingStatus.IN_PROGRESS);
    }
}
1 ItemPropertyChangeEvent 处理器在实体的属性发生更改时调用。
2 CollectionChangeEvent 处理器在容器中添加或者删除数据时调用。
3 获取当前编辑的 User 实例。
4 更新 onboardingStatus 属性。由于数据绑定机制,对该属性的更新会立即展示在 UI 组件中。

按下 Ctrl/Cmd+S 保存修改然后切换至运行中的程序。刷新用户详情视图,然后在 UserStep 数据网格中做一些改动,并查看 Onboarding status 字段的值。

小结

在本节中,我们实现了下面两个功能:

  1. 为用户指定部门。

  2. 为用户生成并管理入职步骤。

学习内容:

  • 关联属性应该包含在视图的 fetch plan 中,以避免 N+1 查询性能问题。

  • entityComboBox 实体下拉框 可以用来在下拉框中选择关联实体。该组件需要一个 集合容器,选项列表在 itemsContainer 属性设置。

  • UserUserStep 实体的关系是 组合 的一个很好的示例,关联实体(UserStep)仅作为其所有者(User)的一部分存在。这种关联通过 @Composition 注解标记。

  • 关联实体的集合可以用关联属性的 @OrderBy 注解进行排序。

  • button 按钮 组件的 ClickEvent 处理器用来处理按钮的点击事件。可以在 Jmix UI 组件面板的 Handlers 标签页自动生成。

  • 实体详情视图控制器的 getEditedEntity() 方法返回正在编辑的实体。

  • Notifications 接口用来展示弹出通知消息。

  • DataManager 接口可以用来从数据库加载数据。

  • 内部的嵌套关联实体集合会被加载到 CollectionPropertyContainer。容器的 getItems()getMutableItems() 方法可以用来遍历集合或者添加/删除集合项。

  • DataContext 跟踪实体的更改,并在用户点击视图的 OK 按钮时将改动的实例保存至数据库。

  • UI 数据网格可以添加列,用来展示任意的可视化组件。

  • ItemPropertyChangeEventCollectionChangeEvent 可以用来对数据容器中的实体变更做出响应。