数据上下文
DataContext
是跟踪加载到 UI 层的实体改动的接口。跟踪实体的任何属性修改后都标记成 “dirty”(表示发生变化),然后 DataContext
会在调用 commit()
方法的时候将发生变化的实体发送到中间件进行保存。
在 DataContext
内,具有唯一标识符的实体总是以单一的对象实例呈现,不管对象关系图中它在哪里被使用或者使用了多少次。
为了能跟踪实体变化,必须使用其 merge()
方法将实体放入 DataContext
中。如果数据上下文不包含同样id的实体,则会创建一个新实例,将传递的实体状态拷贝至新实例,并将新实例返回。如果上下文已经有同样id的实例,则会将传递实例的状态拷贝至已经存在的实例并返回。使用这个机制保证在数据上下文中对于同一个 id 始终只有一个实例。
当合并实体时,实体内包含根节点的整个实体对象关系图都会被合并。也就是说,所有的引用实体(包括集合)都会处于被跟踪状态。
使用 merge() 方法的重要原则就是,使用返回的实例进行继续操作而丢掉传入的那个实例。在很多情况下,返回的对象实例会跟传入的不同。唯一的例外是在给 merge() 方法传递实例时,如果该实例是在同一个数据上下文中调用另一个 merge() 或者 find() 返回的实例,则没有区别。
|
合并实体到 DataContext
的示例:
@Autowired
private DataContext dataContext;
@Autowired
private DataManager dataManager;
@Autowired
private CollectionContainer<Department> departmentsDc;
private void loadDepartment(Id<Department> departmentId) {
Department department = dataManager.load(departmentId).one();
Department trackedDepartment = dataContext.merge(department);
departmentsDc.getMutableItems().add(trackedDepartment);
}
对于一个特定的界面和它所有的内部 fragment 来说,只存在一个 DataContext
单例。在界面 XML 描述存在 <data>
元素的情况下创建。
<data>
元素可以有 readOnly="true"
属性,此时会使用一个特殊的 “不操作” 的 DataContext
实现,该实现不跟踪实体的改动,因此不会影响性能。默认情况下,Studio 生成的实体浏览界面会有只读的数据上下文,所以如果需要在实体浏览界面跟踪实体改动并且提交改动的实体,需删除 XML 的 readOnly="true"
属性。
如果关联实体没有包含在界面的 fetch plan 中,而是通过 延迟加载 进行获取,那么关联实体不会合并到界面的 DataContext ,因此也无法追踪其改动。所以需要确保界面编辑的所有实体必须通过 fetch plan 预加载包含的引用实体。
|
获取 DataContext
-
界面的
DataContext
可以在控制器用注入的方式获取:@Autowired private DataContext dataContext;
-
如果只有界面的引用,则可以通过
UiControllerUtils
类获取其DataContext
:private void sampleMethod(Screen sampleScreen) { DataContext dataContext = UiControllerUtils.getScreenData(sampleScreen).getDataContext(); // ... }
-
UI 组件可以通过下面的方法获取当前界面的
DataContext
:DataContext dataContext = UiControllerUtils.getScreenData(getFrame().getFrameOwner()).getDataContext();
父 DataContext
DataContext
实例可以形成父子关系。如果一个 DataContext
有父上下文,它会将改动的实体提交给父上下文而不是直接保存至数据存储。通过这个功能支持编辑实体的组合关系,从实体只能跟主实体一起保存到数据库。如果一个实体属性使用 @Composition
注解,框架会自动在此属性的编辑界面设置父上下文,从而该属性的改动会保存到主实体的数据上下文。
可以很容易为任何实体和界面提供与此相同的行为。
如果打开的编辑界面需要提交数据到当前界面的数据上下文,可以使用 builder 的 withParentDataContext()
方法:
@Autowired
private ScreenBuilders screenBuilders;
@Autowired
private DataContext dataContext;
private void editScreenWithCurrentDataContextAsParent() {
PersonEdit personEdit = screenBuilders.editor(Person.class, this)
.withScreenClass(PersonEdit.class)
.withParentDataContext(dataContext)
.build();
personEdit.show();
}
如果使用 Screens
bean 打开简单界面,需要提供 setter 方法接收父数据上下文:
public class SmplScreen extends Screen {
@Autowired
private DataContext dataContext;
public void setParentDataContext(DataContext parentDataContext) {
dataContext.setParent(parentDataContext);
}
}
然后在创建了界面之后使用:
@Autowired
private DataContext dataContext;
@Autowired
private Screens screens;
private void openSmplScreenWithCurrentDataContextAsParent() {
SmplScreen smplScreen = screens.create(SmplScreen.class);
smplScreen.setParentDataContext(dataContext);
smplScreen.show();
}
确保父数据上下文没有使用 readOnly="true" 属性。否则在使用其作为父上下文的时候会抛出异常。
|
事件和事件处理器
本章节介绍可以在界面控制器处理的 DataContext 生命周期事件。
如需使用 Jmix Studio 生成处理器的桩代码,需要在界面 XML 描述或者 Component Hierarchy 面板选中 data 元素,然后用 Component Inspector 面板的 Handlers 标签页生成。 或者可以使用界面控制器顶部面板的 Generate Handler 按钮。 |
ChangeEvent
该事件在上下文检测到跟踪实体变化时发送,合并了新实体或者删除了旧实体。
@Subscribe(target = Target.DATA_CONTEXT)
public void onChange(DataContext.ChangeEvent event) {
log.debug("Changed entity: " + event.getEntity());
indicatorLabel.setValue("Changed");
}
PostCommitEvent
该事件在提交改动之后发送。
在事件监听器中,可以获取从 DataManager
或自定义 提交代理 提交之后的实体集合。这些实体已经合并至 DataContext。示例:
@Subscribe(target = Target.DATA_CONTEXT)
public void onPostCommit(DataContext.PostCommitEvent event) {
log.debug("Committed: " + event.getCommittedInstances());
}
PreCommitEvent
该事件在提交改动之前发送。
在对应的事件监听器中,可以在 getModifiedInstances()
和 getRemovedInstances()
方法返回的实体机和中添加任何实体,示例:
@Subscribe(target = Target.DATA_CONTEXT)
public void onPreCommit(DataContext.PreCommitEvent event) {
event.getModifiedInstances().add(user);
}
还可以用事件的 preventCommit()
方法阻止提交,示例:
@Subscribe(target = Target.DATA_CONTEXT)
public void onPreCommit(DataContext.PreCommitEvent event) {
if (checkSomeCondition()) {
event.preventCommit();
}
}
CommitDelegate
默认情况下,DataContext
使用 DataManager.save(SaveContext) 方法提交更改和删除的实体。commitDelegate
处理器支持自定义保存数据的逻辑,这在使用 DTO 实体 时特别有用。例如,可以使用自定义服务保存更改的实体:
@Autowired
private SampleService service;
@Install(target = Target.DATA_CONTEXT)
private Set<Object> commitDelegate(SaveContext saveContext) {
return service.saveEntities(
saveContext.getEntitiesToSave(),
saveContext.getEntitiesToRemove());
}