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() 获得的视图映射为每个保存的实例设置视图,这样可以确保返回实体中包含需要的属性。

    DataManager 可以为保存的实体进行 bean 验证

    保存实体集合的示例:

    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 函数不区分大小写的子串搜索JPQL 中的宏 章节了解 CUBA 中的 JPQL 和 JPA 标准之间的差异。另外需要注意,DataManager 只能执行 “select” 查询。

流式接口的 query() 方法可以接收完整的或者省略的查询语句。如果使用省略的查询语句,需要符合下面的规则:

  • 可以省略 "select <alias>" 子句。

  • 如果 "from" 子句包含单一实体,而且又不想用实体别名,则可以省略 "from <entity> <alias> where" 子句。此时,框架会使用 e 作为该实体的别名。

  • 可以使用位置标记查询条件并同时将调价值作为额外的参数传递给 query() 方法。

示例:

  1. // named parameter
  2. dataManager.load(Customer.class)
  3. .query("e.name like :name")
  4. .parameter("name", value)
  5. .list();
  6. // positional parameter
  7. dataManager.load(Customer.class)
  8. .query("e.name like ?1", value)
  9. .list();
  10. // case-insensitive positional parameter
  11. dataManager.load(Customer.class)
  12. .query("e.name like ?1 or e.email like ?1", "(?i)%joe%")
  13. .list();
  14. // multiple positional parameters
  15. dataManager.load(Order.class)
  16. .query("e.date between ?1 and ?2", date1, date2)
  17. .list();
  18. // omitting "select" and using a positional parameter
  19. dataManager.load(Order.class)
  20. .query("from sales_Order o, sales_OrderLine l " +
  21. "where l.order = o and l.product.name = ?1", productName)
  22. .list();

需要注意的是,位置标记条件参数只在流式接口支持。LoadContext.Query 还是只支持命名条件参数。

事务

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

部分实体

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

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

  • 加载的实体是可缓存的

  • 为实体定义了内存 “读取” 约束

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

  • LoadContextloadPartialEntities 属性设置为 false。