3.5.3.3. 数据上下文

DataContext 是跟踪加载到客户端层实体改动的接口。跟踪实体的任何属性修改后都标记成 “dirty”(表示发生变化),然后 DataContext 会在调用 commit() 方法的时候将发生变化的实体发送到中间件进行保存。

DataContext 内,具有唯一标识符的实体总是以单一的对象实例呈现,不管对象关系图中它在哪里被使用或者使用了多少次。

为了能跟踪实体变化,必须使用其 merge() 方法将实体放入 DataContext 中。如果数据上下文不包含同样id的实体,则会创建一个新实例,将传递的实体状态拷贝至新实例,并将新实例返回。如果上下文已经有同样id的实例,则会将传递实例的状态拷贝至已经存在的实例并返回。使用这个机制保证在数据上下文中对于同一个实例id始终只有一个实例。

当合并实体时,实体内包含根节点的整个实体对象关系图都会被合并。也就是说,所有的引用实体(包括集合)都会处于被跟踪状态。

使用 merge() 方法的重要原则就是,使用返回的实例进行继续操作而丢掉传入的那个实例。在很多情况下,返回的对象实例会跟传入的不同。唯一的例外是在给 merge() 方法传递实例时,如果该实例是在同一个数据上下文中调用另一个 merge() 或者 find() 返回的实例,则没有区别。

合并实体到 DataContext 的示例:

  1. @Inject
  2. private DataContext dataContext;
  3. private void loadCustomer(Id<Customer, UUID> customerId) {
  4. Customer customer = dataManager.load(customerId).one();
  5. Customer trackedCustomer = dataContext.merge(customer);
  6. customersDc.getMutableItems().add(trackedCustomer);
  7. }

对于一个特定的界面和它所有的内嵌的组件来说,只存在一个 DataContext 单例,在界面 XML 描述存在 <data> 元素的情况下创建。

<data> 元素可以有 readOnly="true" 属性,此时会使用一个特殊的 “不操作“ 的实现,此实现不需要跟踪实体的改动,因此不会影响性能。默认情况下,Studio 生成的实体浏览界面会有只读的数据上下文,所以如果需要在实体浏览界面跟踪实体改动并且提交脏实体,需要再删除 XML 的 readOnly="true" 属性。

父数据上下文

DataContext 实例支持父子关系。如果一个 DataContext 有父上下文,它会将改动的实体提交给父上下文而不是提交给中间件。通过这个功能支持编辑组合关系,从实体只能跟主实体一起保存到数据库。如果一个实体属性使用 @Composition 注解,平台会自动在此属性的编辑界面设置父上下文,从而该属性的改动会保存到主实体的数据上下文。

可以很容易为任何实体和界面提供与此相同的行为。

如果打开的编辑界面需要提交数据到当前界面的数据上下文,可以使用 builder 的 withParentDataContext() 方法:

  1. @Inject
  2. private ScreenBuilders screenBuilders;
  3. @Inject
  4. private DataContext dataContext;
  5. private void editFooWithCurrentDataContextAsParent() {
  6. FooEdit fooEdit = screenBuilders.editor(Foo.class, this)
  7. .withScreenClass(FooEdit.class)
  8. .withParentDataContext(dataContext)
  9. .build();
  10. fooEdit.show();
  11. }

如果使用 Screens bean 打开简单界面,需要提供 setter 方法接收父数据上下文:

  1. public class FooScreen extends Screen {
  2. @Inject
  3. private DataContext dataContext;
  4. public void setParentDataContext(DataContext parentDataContext) {
  5. dataContext.setParent(parentDataContext);
  6. }
  7. }

然后在创建了界面之后使用:

  1. @Inject
  2. private Screens screens;
  3. @Inject
  4. private DataContext dataContext;
  5. private void openFooScreenWithCurrentDataContextAsParent() {
  6. FooScreen fooScreen = screens.create(FooScreen.class);
  7. fooScreen.setParentDataContext(dataContext);
  8. fooScreen.show();
  9. }

确保父数据上下文没有使用 readOnly=”true” 属性。否则在使用这个上下文作为父上下文的时候会抛出异常。