数据上下文
DataContext
是跟踪加载到 UI 层的实体改动的接口。受跟踪实体的任何属性发生修改后,都标记成 “dirty”(表示发生变化),DataContext
会在调用 save()
方法的时候进行保存。
在 DataContext
内,具有唯一标识符的实体总是以单一的对象实例呈现,不管在此上下文的对象关系图中它在哪里被使用或者使用了多少次。
为了能跟踪实体变化,必须使用其 merge()
方法将实体放入 DataContext
中。如果上下文中不包含同样 id 的实体,则会创建一个新实例,将传递的实体状态拷贝至新实例,并将新实例返回。如果上下文已经有同样 id 的实例,则会将传递实例的状态拷贝至已经存在的实例并返回该实例。使用这个机制保证在数据上下文中对于同一个 id 始终只有一个实例。
当合并实体时,实体内包含根节点的整个实体对象关系图都会被合并。也就是说,所有的引用实体(包括集合)都会处于被跟踪状态。
使用 merge() 方法的重要原则就是,使用返回的实例进行继续操作而丢掉传入的那个实例。在很多情况下,返回的对象实例会跟传入的不同。唯一的例外是在给 merge() 方法传递实例时,如果该实例是在同一个数据上下文中调用另一个 merge() 或者 find() 返回的实例,则没有区别。
|
合并实体到 DataContext
的示例:
@ViewComponent
private CollectionContainer<Department> departmentsDc;
@Autowired
private DataManager dataManager;
@ViewComponent
private DataContext dataContext;
private void loadDepartment(Id<Department> departmentId) {
Department department = dataManager.load(departmentId).one();
Department trackedDepartment = dataContext.merge(department);
departmentsDc.getMutableItems().add(trackedDepartment);
}
对于一个特定的视图,只存在一个 DataContext
单例。在视图 XML 描述包含 <data>
元素的情况下创建。
在 XML 中定义的 数据加载器 会自动将加载的实体合并到 readOnly
属性不是 true
的 DataContext
中。默认情况下,Studio 生成的 实体列表视图 会带有该属性,因此,如果需要在实体列表视图跟踪实体改动并且提交改动的实体,需删除 XML 中相关数据加载器的 readOnly="true"
属性。
如果关联实体没有包含在视图的 fetch plan 中,而是通过 延迟加载 进行获取,那么关联实体不会合并到视图的 DataContext ,因此也无法追踪其改动。所以需要确保视图编辑的所有实体必须通过 fetch plan 预加载包含的引用实体。
|
获取 DataContext
-
视图的
DataContext
可以在控制器用注入的方式获取:@ViewComponent private DataContext dataContext;
-
如果只有视图的引用,则可以通过
UiControllerUtils
类获取其DataContext
:private void sampleMethod(View sampleView) { DataContext dataContext = ViewControllerUtils.getViewData(sampleView).getDataContext(); // ... }
父 DataContext
DataContext
实例可以形成父子关系。如果一个 DataContext
有父上下文,它会将改动的实体提交给父上下文而不是直接保存至数据存储。通过这个功能支持 编辑实体聚合关系,从实体只能跟主实体一起保存到数据库。如果一个实体属性使用 @Composition 注解,框架会自动在此属性的详情视图设置父上下文,从而该属性的改动会保存到主实体的数据上下文。
可以很容易为任何实体和视图提供与此相同的行为。
如果打开的详情视图需要提交数据到当前视图的数据上下文,可以使用 withParentDataContext()
方法:
private void detailViewWithCurrentDataContextAsParent() {
DialogWindow<DepartmentDetailView> dialogWindow = dialogWindows.detail(this, Department.class)
.withViewClass(DepartmentDetailView.class)
.withParentDataContext(dataContext)
.build();
dialogWindow.open();
}
事件和处理器
本章节介绍可以在视图控制器处理的 DataContext
生命周期事件。
如需使用 Jmix Studio 生成处理方法的桩代码,需要在视图 XML 描述或者 Jmix UI 结构面板选中元素,然后用 Jmix UI 组件面板的 Handlers 标签页生成。 或者可以使用视图控制器顶部面板的 Generate Handler 按钮。 |
SaveDelegate
默认情况下,DataContext
使用 DataManager.save(SaveContext) 方法保存实体改动和删除。而 saveDelegate
代理方法可以自定义保存数据的逻辑,在处理 DTO 实体 时非常有用。例如,可以用自定义的服务保存修改的实体:
@Autowired
private DepartmentService departmentService;
@Install(target = Target.DATA_CONTEXT)
private Set<Object> saveDelegate(final SaveContext saveContext) {
return departmentService.saveEntities(
saveContext.getEntitiesToSave(),
saveContext.getEntitiesToRemove());
}
代理方法需要返回一组已保存的实体实例。如果无法返回已保存的实例,那也可以返回保存前的实例,通过 saveContext.getEntitiesToSave()
获取,或者返回一个空集合。不要返回删除的实例。DataContext
会将返回的实例合并到视图中,使得视图能使用更新后的状态继续运行。
ChangeEvent
当跟踪实体变化时,或者合并了新实体或者删除了旧实体,就会发送该事件。
@Subscribe(target = Target.DATA_CONTEXT)
public void onChange(final DataContext.ChangeEvent event) {
log.debug("Changed entity: " + event.getEntity());
}
PreSaveEvent
该事件在保存改动之前发送。
在事件监听器中,可以在 getModifiedInstances()
和 getRemovedInstances()
方法返回的实体集合中添加任何实体,示例:
@Subscribe(target = Target.DATA_CONTEXT)
public void onPreSave(final DataContext.PreSaveEvent event) {
event.getModifiedInstances().add(department);
}
还可以用事件的 preventSave()
方法阻止数据保存,示例:
@Subscribe(target = Target.DATA_CONTEXT)
public void onPreSave2(DataContext.PreSaveEvent event) {
if (checkSomeCondition()) {
event.preventSave();
}
}
PostSaveEvent
该事件在保存改动之后发送。
在事件监听器中,可以获取通过 DataManager
或自定义 保存代理 保存后的实体集合。这些实体已经合并至 DataContext。示例:
@Subscribe(target = Target.DATA_CONTEXT)
public void onPostSave(final DataContext.PostSaveEvent event) {
log.debug("Saved: " + event.getSavedInstances());
}