3.5.3.3. 数据上下文
DataContext
是跟踪加载到客户端层实体改动的接口。跟踪实体的任何属性修改后都标记成 “dirty”(表示发生变化),然后 DataContext
会在调用 commit()
方法的时候将发生变化的实体发送到中间件进行保存。
在 DataContext
内,具有唯一标识符的实体总是以单一的对象实例呈现,不管对象关系图中它在哪里被使用或者使用了多少次。
为了能跟踪实体变化,必须使用其 merge()
方法将实体放入 DataContext
中。如果数据上下文不包含同样id的实体,则会创建一个新实例,将传递的实体状态拷贝至新实例,并将新实例返回。如果上下文已经有同样id的实例,则会将传递实例的状态拷贝至已经存在的实例并返回。使用这个机制保证在数据上下文中对于同一个实例id始终只有一个实例。
当合并实体时,实体内包含根节点的整个实体对象关系图都会被合并。也就是说,所有的引用实体(包括集合)都会处于被跟踪状态。
使用 |
合并实体到 DataContext
的示例:
@Inject
private DataContext dataContext;
private void loadCustomer(Id<Customer, UUID> customerId) {
Customer customer = dataManager.load(customerId).one();
Customer trackedCustomer = dataContext.merge(customer);
customersDc.getMutableItems().add(trackedCustomer);
}
对于一个特定的界面和它所有的内嵌的组件来说,只存在一个 DataContext
单例,在界面 XML 描述存在 <data>
元素的情况下创建。
<data>
元素可以有 readOnly="true"
属性,此时会使用一个特殊的 “不操作“ 的实现,此实现不需要跟踪实体的改动,因此不会影响性能。默认情况下,Studio 生成的实体浏览界面会有只读的数据上下文,所以如果需要在实体浏览界面跟踪实体改动并且提交脏实体,需要再删除 XML 的 readOnly="true"
属性。
- 获取 DataContext
-
界面的
DataContext
可以在控制器用注入的方式获取:@Inject private DataContext dataContext;
如果只有界面的引用,则可以通过
UiControllerUtils
类获取其DataContext
:DataContext dataContext = UiControllerUtils.getScreenData(screenOrFrame).getDataContext();
UI 组件可以通过下面的方法获取当前界面的
DataContext
:DataContext dataContext = UiControllerUtils.getScreenData(getFrame().getFrameOwner()).getDataContext();
- 父数据上下文
-
DataContext
实例支持父子关系。如果一个DataContext
有父上下文,它会将改动的实体提交给父上下文而不是提交给中间件。通过这个功能支持编辑组合关系,从实体只能跟主实体一起保存到数据库。如果一个实体属性使用 @Composition 注解,平台会自动在此属性的编辑界面设置父上下文,从而该属性的改动会保存到主实体的数据上下文。可以很容易为任何实体和界面提供与此相同的行为。
如果打开的编辑界面需要提交数据到当前界面的数据上下文,可以使用 builder 的
withParentDataContext()
方法:@Inject private ScreenBuilders screenBuilders; @Inject private DataContext dataContext; private void editFooWithCurrentDataContextAsParent() { FooEdit fooEdit = screenBuilders.editor(Foo.class, this) .withScreenClass(FooEdit.class) .withParentDataContext(dataContext) .build(); fooEdit.show(); }
如果使用
Screens
bean 打开简单界面,需要提供 setter 方法接收父数据上下文:public class FooScreen extends Screen { @Inject private DataContext dataContext; public void setParentDataContext(DataContext parentDataContext) { dataContext.setParent(parentDataContext); } }
然后在创建了界面之后使用:
@Inject private Screens screens; @Inject private DataContext dataContext; private void openFooScreenWithCurrentDataContextAsParent() { FooScreen fooScreen = screens.create(FooScreen.class); fooScreen.setParentDataContext(dataContext); fooScreen.show(); }
确保父数据上下文没有使用
readOnly="true"
属性。否则在使用这个上下文作为父上下文的时候会抛出异常。