3.2.6.2. DataManager

API 文档

DataManager 接口在中间层和客户端层提供 CRUD 功能,是一种通用工具,用于从数据库加载实体关系图并保存已更改的游离实体实例。



有关 DataManager 与 EntityManager 之间差异的信息,请参阅 DataManager 与 EntityManager

实际上,DataManager 只是委托给一个数据存储实现,并在需要时处理跨数据库引用。当使用标准 RdbmsStore 处理存储在关系型数据库中的实体时,下面描述的大多数实现细节都有效。对于另一种类型的数据存储,除接口方法名称之外的所有内容都可能不同。为简单起见,DataManager 在没有另外说明时指的是 基于 RdbmsStore 的 DataManager

下面列出了 DataManager 的方法:

  • load(Class) - 加载指定类的实体。此方法是流式 API 的入口点:
  1. @Inject
  2. private DataManager dataManager;
  3. private Book loadBookById(UUID bookId) {
  4. return dataManager.load(Book.class).id(bookId).view("book.edit").one();
  5. }
  6. private List<BookPublication> loadBookPublications(UUID bookId) {
  7. return dataManager.load(BookPublication.class)
  8. .query("select p from library$BookPublication p where p.book.id = :bookId")
  9. .parameter("bookId", bookId)
  10. .view("bookPublication.full")
  11. .list();
  12. }
  • loadValues(String query) - 通过查询纯数值加载键值对。此方法是流式 API 的入口点:
  1. List<KeyValueEntity> list = dataManager.loadValues(
  2. "select o.customer, sum(o.amount) from demo$Order o " +
  3. "where o.date >= :date group by o.customer")
  4. .store("legacy_db") (1)
  5. .properties("customer", "sum") (2)
  6. .parameter("date", orderDate)
  7. .list();

1- 指定实体所在的数据存储。 如果实体位于主数据存储,那么可以忽略这个方法。2- 指定返回的 KeyValueEntity 实体中的属性名称。 属性的顺序必须与查询结果集的列对应。

  • loadValue(String query, Class valueType) - 通过查询纯数值加载单个值。此方法是流式 API 的入口点:
  1. BigDecimal sum = dataManager.loadValue(
  2. "select sum(o.amount) from demo$Order o " +
  3. "where o.date >= :date group by o.customer", BigDecimal.class)
  4. .store("legacy_db") (1)
  5. .parameter("date", orderDate)
  6. .one();

1- 指定实体所在的数据存储。 如果实体位于主数据存储,那么可以忽略这个方法。

  • load(LoadContext), loadList(LoadContext) – 根据传递给它的 LoadContext 对象的参数加载实体。LoadContext 必须包含 JPQL 查询语句或实体标识符。如果两者都定义的话,则使用查询语句而忽略实体标识符。

例如:

  1. @Inject
  2. private DataManager dataManager;
  3. private Book loadBookById(UUID bookId) {
  4. LoadContext<Book> loadContext = LoadContext.create(Book.class)
  5. .setId(bookId).setView("book.edit");
  6. return dataManager.load(loadContext);
  7. }
  8. private List<BookPublication> loadBookPublications(UUID bookId) {
  9. LoadContext<BookPublication> loadContext = LoadContext.create(BookPublication.class)
  10. .setQuery(LoadContext.createQuery("select p from library$BookPublication p where p.book.id = :bookId")
  11. .setParameter("bookId", bookId))
  12. .setView("bookPublication.full");
  13. return dataManager.loadList(loadContext);
  14. }
  • loadValues(ValueLoadContext) - 加载键值对列表。该方法接受 ValueLoadContext,定义纯数值的查询语句和键值列表。返回包含 KeyValueEntity 实例的列表。例如:
  1. ValueLoadContext context = ValueLoadContext.create()
  2. .setQuery(ValueLoadContext.createQuery(
  3. "select o.customer, sum(o.amount) from demo$Order o " +
  4. "where o.date >= :date group by o.customer")
  5. .setParameter("date", orderDate))
  6. .addProperty("customer")
  7. .addProperty("sum");
  8. List<KeyValueEntity> list = dataManager.loadValues(context);
  • getCount(LoadContext) - 返回传递给方法的查询语句的记录数。可能的情况下,RdbmsStore 中的标准实现使用与原始查询相同的条件执行 select count() 查询,以获得最佳性能。

  • commit(CommitContext) – 将 CommitContext 中传递的一组实体保存到数据库中。必须分别指定用于更新和删除的实体的集合。

该方法返回 EntityManager.merge() 返回的实体实例集合,实际上这些就是刚刚在 DB 中更新的新实例。进一步的操作需要使用这些返回的实例,以防止数据丢失或造成乐观锁。通过使用 CommitContext.getViews() 获得的视图映射为每个保存的实例设置视图,这样可以确保返回实体中包含需要的属性。

保存实体集合的示例:

  1. @Inject
  2. private DataManager dataManager;
  3. private void saveBookInstances(List<BookInstance> toSave, List<BookInstance> toDelete) {
  4. CommitContext commitContext = new CommitContext(toSave, toDelete);
  5. dataManager.commit(commitContext);
  6. }
  7. private Set<Entity> saveAndReturnBookInstances(List<BookInstance> toSave, View view) {
  8. CommitContext commitContext = new CommitContext();
  9. for (BookInstance bookInstance : toSave) {
  10. commitContext.addInstanceToCommit(bookInstance, view);
  11. }
  12. return dataManager.commit(commitContext);
  13. }
  • reload(Entity, View) - 使用视图从数据库重新加载指定实例的便捷方法。委托给 load() 方法执行。

  • remove(Entity) - 从数据库中删除指定的实例。委托给 commit() 方法执行。

  • create(Class) - 在内存中创建给定实体的实例。这是一个便捷的方法,委托给了 Metadata.create()

  • getReference(Class, Object) - 返回一个实体实例,该实例可以用作对数据库中存在的对象的引用。

例如,如果要创建 User,则必须设置用户所属的 Group。如果知道 group ID,可以从数据库加载然后设置给用户。此方法可以避免不必要的数据库多次访问:

  1. user.setGroup(dataManager.getReference(Group.class, groupId));
  2. dataManager.commit(user);

引用也可用于通过 id 删除现有对象:

  1. dataManager.remove(dataManager.getReference(Customer.class, customerId));
查询




JPQL 查询的规则与 执行 JPQL 查询 中描述的规则类似。不同之处在于,通过 DataManager 执行的查询只能使用命名参数,不支持位置参数。



事务




DataManager 总是启动一个新的事务并在操作完成时提交事务,从而返回游离状态的实体。在中间层,如果需要实现复杂的事务行为,可以使用 TransactionalDataManager



部分实体




部分(Partial) 实体是一个实体实例,这个实例的属性可以是已加载的本地属性的一个子集。默认情况下,DataManager 根据视图加载部分实体(事实上,RdbmsStore 只是将视图的 loadPartialEntities 属性设置为 true 并将其传递给 EntityManager )。



在下面这些情况下,DataManager 会加载所有本地属性,视图仅用来获取引用:



-
加载的实体是可缓存的

-
为实体定义了内存 "读取" 约束

-
为实体设置了动态属性访问控制

-
LoadContextloadPartialEntities 属性设置为 false。