5. UI 中处理数据
开发进行到这个阶段,我们已经有了入职步骤和部门的管理功能,以及默认的用户管理功能,并且为用户添加了 Onboarding status 属性。现在我们需要将用户与入职步骤和部门关联起来。
本节中,我们将完成:
- 
为 User实体添加department和joiningDate属性,并在 UI 展示。
- 
创建 UserStep实体,关联一个用户和一个入职步骤。
- 
为 User实体添加UserStep实体集合,并在User.detail视图展示。
- 
在 User.detail视图实现生成和保存UserStep实例的功能。
下图展示本节要用到的实体和属性:
添加关联属性
我们已经在之前的章节完成了类似的任务:为 Department 实体添加关联至 User 实体的 HR 经理属性。现在我们需要创建一个反向连接:一个用户需要属于某个部门。
如果你的应用程序正在运行,先通过主工具栏的 Stop()按钮停止运行。
在 Jmix 工具窗口双击 User 实体并选择其最后一个属性(我们要在最后添加新属性):
在 Attributes 工具栏中,点击 Add()。
弹出的 New Attribute 对话框中,Name 字段填写 department。然后选择:
- 
Attribute type: ASSOCIATION
- 
Type: Department
- 
Cardinality:Many to One 
 
点击 OK。
然后选中 department 属性,在 Attributes 工具栏中点击 Add to views()按钮:
 
出现的对话框中会显示 User.detail 和 User.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 查询问题)。 | 
运行应用程序可以查看新的属性。
点击主工具栏中的 Debug()按钮启动应用程序。
Studio 会生成 Liquibase 更改日志,添加了 USER_ 表的 DEPARTMENT_ID 列,并创建外键和索引。确认这个改动。
Studio 会执行更改日志,然后构建并运行应用程序。
在浏览器打开 http://localhost:8080 并使用 admin / admin 凭证登录。
点击主菜单的 Application → Users,打开 User.list 视图,可以看到新加的 Department 列。创建用户时,可以看到 User.detail 中的 Department 选择控件:
 
使用下拉框选择
默认情况下,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 字段现在变成了下拉框,但是无法打开,即便已经创建了一些部门。
 
创建选项数据容器
我们为 entityComboBox 组件提供一组选项,用于选择关联的部门实体。选项列表包含所有的部门,按名称排序。
在操作面板点击 Add Component,选择 Data components,然后双击 Collection。在 Data Container Properties Editor 窗口的 Entity 字段选择 Department,点击 OK:
 
然后在 Jmix UI 结构和 XML 的 data 元素下会创建 id 为 departmentsDc 的 collection 元素:
<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 属性,然后点击右侧的链接打开设计器:
 
在 JPQL Query Designer 窗口中,切换至 ORDER 标签页并添加 name 属性:
 
点击 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:
 
切换至运行的应用程序并重新打开用户详情视图。
可以看到 Department 下拉框现在已经有了选项:
 
| entityComboBox组件支持直接在组件内输入的方式对选项进行过滤。过滤的过程是在服务端的内存中进行,所有的选项已经一次从数据加载出来了。 | 
创建 UserStep 实体
本小节中,我们将创建 UserStep 实体,用来表示特定用户的入职步骤:
如果你的应用程序正在运行,先通过主工具栏的 Stop()按钮停止运行。
在 Jmix 工具窗口中,点击 New()→ 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 | 
实体设计器的最终状态如下:
 
添加组合属性
我们来看看 User 和 UserStep 实体的关系。UserStep 实例仅当特定的 User 实例存在时才有意义(即,属于该用户)。一个 UserStep 实例不能修改其所有者;此外,其他数据模型也没有关联 UserStep,也就是说 UserStep 实例都是包含在 User 实例内的。
在 Jmix 中,这种实体间的关系被称为 组合(composition):User 由一组 UserSteps 和其他的属性共同组成。
| Jmix 中的组合实现了 DDD(Domain-Driven Design)中的聚合(Aggregate)模式。 | 
在父实体中创建包含一组组合子实体的属性也很方便。
我们在 User 实体中创建 steps 属性:
如果你的应用程序正在运行,先通过主工具栏的 Stop()按钮停止运行。
在 User 实体设计器的 Attributes 工具栏中,点击 Add()。弹出的 New Attribute 对话框中,Name 字段填写 
steps,然后选择:
- 
Attribute type: COMPOSITION
- 
Type: UserStep
- 
Cardinality:One to Many 
 
注意,Mapped by 字段会自动选择 user。这是 UserStep 实体中的一个属性,映射至一个数据库列,用于维护 UserSteps 和 Users 的关系(外键)。
点击 OK。
该属性的源代码会带有 @Composition 注解:
@Composition
@OneToMany(mappedBy = "user")
private List<UserStep> steps;UserSteps 需要在用户详情视图展示,因此,选中 steps 属性并点击 Attributes 工具栏中的 Add to Views()按钮,选择 
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.createAction"/>
        <button action="stepsDataGrid.editAction"/>
        <button action="stepsDataGrid.removeAction"/>
    </hbox>
    <dataGrid id="stepsDataGrid" dataContainer="stepsDc" ...> (3)
        <actions>
            <action id="createAction" type="list_create"/>
            <action id="editAction" type="list_edit"/>
            <action id="removeAction" 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数据容器中的数据。 | 
运行应用程序查看这些改动。
点击主工具栏中的 Debug()按钮启动应用程序。
Studio 会生成 Liquibase 更改日志,包含创建 USER_STEP 表、关联至 USER_ 和 STEP 的外键约束和索引。确认这些改动。
Studio 会执行更改日志,然后运行应用程序。
应用程序准备好后,在浏览器打开 http://localhost:8080 并使用 admin / admin 凭证登录。
打开一个用户进行编辑。可以看到数据网格展示 UserStep 实体:
 
如果点击数据网格中的 Create 按钮,系统会抛出异常:View 'UserStep.detail' is not defined。是的,我们还没有为 UserStep 实体创建详情视图。但是实际上我们不需要创建这个视图,因为 UserStep 实例可以通过给用户分配预定义的 Step 实体生成。
为用户生成 UserSteps
本节中,我们将为编辑的 User 实体生成并展示 UserStep 实例。
添加 JoiningDate 属性
首先,为 User 实体添加 joiningDate 属性:
该属性将用于计算 UserStep 实体中的 dueDate 属性:UserStep.dueDate = User.joiningDate + Step.duration。
如果你的应用程序正在运行,先通过主工具栏的 Stop()按钮停止运行。
在 User 实体设计器的 Attributes 工具栏中,点击 Add()。弹出的 New Attribute 对话框中,Name 字段填写 
joiningDate,然后在 Type 下拉框中选择 LocalDate:
 
点击 OK。
然后选中新创建的 joiningDate 属性,在 Attributes 工具栏中点击 Add to Views()按钮。在弹窗中选择 
User.detail 和 User.list 视图并点击 OK。
点击主工具栏中的 Debug()按钮启动应用程序。
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,并双击。然后在组件属性面板中,设置其 id 为 generateButton,text 为 Generate。切换至 Handlers 标签页,创建一个 ClickEvent 的处理器方法:
 
按下 Ctrl/Cmd+S 保存修改然后切换至运行中的程序。重新打开用户详情视图,查看我们新添加的 Generate 按钮正常展示而不是展示默认的 CRUD 按钮:

创建并保存 UserStep 实例
现在实现生成 UserStep 实例的逻辑。
在 UserDetailView 控制器中添加下列字段:
public class UserDetailView extends StandardDetailView<User> {
    @Autowired
    private DataManager dataManager;
    @ViewComponent
    private DataContext dataContext;
    @ViewComponent
    private CollectionPropertyContainer<UserStep> stepsDc;| 如果直接复制粘贴上面的代码,IDE 会对这些代码报错,因为还需要添加相关类的  | 
| 可以通过操作面板中的 Inject 按钮注入视图中的组件和 Spring bean:   | 
在 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 属性排序:
 
数据网格展示 User 实体的 steps 集合属性,因此我们可以在数据模型级别引入排序。
打开 User 实体,选择 steps 属性并在 Order by 字段输入 sortValue:
 
如果切换至 Text 标签页,可以看到 steps 属性上添加了 @OrderBy 注解:
@OrderBy("sortValue")
@Composition
@OneToMany(mappedBy = "user")
private List<UserStep> steps;现在当加载 User 实体时,实体内部的 steps 集合会按照 UserStep.sortValue 属性进行排序。
如果应用程序正在运行,请重新启动。
打开用户详情视图。可以看到步骤的顺序现在对了:
 
数据网格的列重排
此时,UserSteps 数据网格提供的信息并不是很有用。我们需要删除 Sort value 列并添加展示步骤名称的列。
删除列很简单,在 Jmix UI 的结构面板中选择这些列并按下 Delete,或直接从 XML 代码里删除相应的元素即可。
添加列时,在 Jmix UI 的结构面板中选择 columns 元素,然后在组件面板中点击 Add → Column,会出现 Add Column 弹窗:
 
选择 step → name 并点击 OK。新的列会添加至列表的最后:
<dataGrid id="stepsDataGrid" dataContainer="stepsDc" ...>
    <columns>
        <column property="dueDate"/>
        <column property="completedDate"/>
        <column property="step.name"/>
    </columns>内部属性会被添加至 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 实例一起从数据库进行预加载。
除了 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 表格现在展示步骤名称:
 
添加组件列
本小节中,我们将实现:通过点击数据网格行中的一个复选框,即可标记 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 列会做相应的更改:
 
UserStep 实例的改动会在点击视图的 OK 按钮后保存到数据库。由视图的 DataContext 负责:能跟踪所有实体的改动并保存修改的实例。
响应更改
当为用户生成入职步骤、标记一个 UserStep 完成或者删除一个步骤时,Onboarding status 字段应该也做相应的调整。
我们现在实现根据 UserSteps 集合变动做出响应的逻辑。
打开 UserDetailView 控制器并在顶部的操作面板中点击 Generate Handler。收起所有的树结构,然后在 Data containers handlers → stepsDc 下面选择 ItemPropertyChangeEvent 和 CollectionChangeEvent:
 
点击 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 字段的值。
小结
在本节中,我们实现了下面两个功能:
- 
为用户指定部门。 
- 
为用户生成并管理入职步骤。 
学习内容:
- 
关联属性应该包含在视图的 fetch plan 中,以避免 N+1 查询性能问题。 
- 
entityComboBox 实体下拉框 可以用来在下拉框中选择关联实体。该组件需要一个 集合容器,选项列表在 itemsContainer属性设置。
- 
User和UserStep实体的关系是 组合 的一个很好的示例,关联实体(UserStep)仅作为其所有者(User)的一部分存在。这种关联通过 @Composition 注解标记。
- 
关联实体的集合可以用关联属性的 @OrderBy注解进行排序。
- 
button 按钮 组件的 ClickEvent处理器用来处理按钮的点击事件。可以在 Jmix UI 组件面板的 Handlers 标签页自动生成。
- 
实体详情视图控制器的 getEditedEntity()方法返回正在编辑的实体。
- 
Notifications 接口用来展示弹出通知消息。 
- 
DataManager 接口可以用来从数据库加载数据。 
- 
内部的嵌套关联实体集合会被加载到 CollectionPropertyContainer。容器的 getItems()和getMutableItems()方法可以用来遍历集合或者添加/删除集合项。
- 
DataContext 跟踪实体的更改,并在用户点击视图的 OK 按钮时将改动的实例保存至数据库。 
- 
UI 数据网格可以添加列,用来展示任意的可视化组件。 
- 
ItemPropertyChangeEvent 和 CollectionChangeEvent 可以用来对数据容器中的实体变更做出响应。