3.2.6.2. DataManager

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

参考 在 CUBA 中进行数据处理 指南,了解在 CUBA 中如何使用 DataManager API 处理不同的数据访问。

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

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

下面列出了 DataManager 的方法:

  • load(Class) - 加载指定类的实体。此方法是流式 API 的入口点:

    @Inject
    private DataManager dataManager;
    
    private Book loadBookById(UUID bookId) {
        return dataManager.load(Book.class).id(bookId).view("book.edit").one();
    }
    
    private List<BookPublication> loadBookPublications(UUID bookId) {
        return dataManager.load(BookPublication.class)
            .query("select p from library_BookPublication p where p.book.id = :bookId")
            .parameter("bookId", bookId)
            .view("bookPublication.full")
            .list();
    }
  • loadValues(String query) - 通过查询纯数值加载键值对。此方法是流式 API 的入口点:

    List<KeyValueEntity> list = dataManager.loadValues(
            "select o.customer, sum(o.amount) from demo_Order o " +
            "where o.date >= :date group by o.customer")
        .store("legacy_db") (1)
        .properties("customer", "sum") (2)
        .parameter("date", orderDate)
        .list();
    1 - 指定实体所在的数据存储。 如果实体位于主数据存储,那么可以忽略这个方法。
    2 - 指定返回的 KeyValueEntity 实体中的属性名称。 属性的顺序必须与查询结果集的列对应。
  • loadValue(String query, Class valueType) - 通过查询纯数值加载单个值。此方法是流式 API 的入口点:

    BigDecimal sum = dataManager.loadValue(
            "select sum(o.amount) from demo_Order o " +
            "where o.date >= :date group by o.customer", BigDecimal.class)
        .store("legacy_db") (1)
        .parameter("date", orderDate)
        .one();
    1 - 指定实体所在的数据存储。 如果实体位于主数据存储,那么可以忽略这个方法。
  • load(LoadContext), loadList(LoadContext) – 根据传递给它的 LoadContext 对象的参数加载实体。LoadContext 必须包含 JPQL 查询语句或实体标识符。如果两者都定义的话,则使用查询语句而忽略实体标识符。

    例如:

    @Inject
    private DataManager dataManager;
    
    private Book loadBookById(UUID bookId) {
        LoadContext<Book> loadContext = LoadContext.create(Book.class)
                .setId(bookId).setView("book.edit");
        return dataManager.load(loadContext);
    }
    
    private List<BookPublication> loadBookPublications(UUID bookId) {
        LoadContext<BookPublication> loadContext = LoadContext.create(BookPublication.class)
                .setQuery(LoadContext.createQuery("select p from library_BookPublication p where p.book.id = :bookId")
                    .setParameter("bookId", bookId))
                .setView("bookPublication.full");
        return dataManager.loadList(loadContext);
    }
  • loadValues(ValueLoadContext) - 加载键值对列表。该方法接受 ValueLoadContext,定义纯数值的查询语句和键值列表。返回包含 KeyValueEntity 实例的列表。例如:

    ValueLoadContext context = ValueLoadContext.create()
            .setQuery(ValueLoadContext.createQuery(
                        "select o.customer, sum(o.amount) from demo_Order o " +
                        "where o.date >= :date group by o.customer")
                .setParameter("date", orderDate))
            .addProperty("customer")
            .addProperty("sum");
    List<KeyValueEntity> list = dataManager.loadValues(context);
  • getCount(LoadContext) - 返回传递给方法的查询语句的记录数。可能的情况下,RdbmsStore 中的标准实现使用与原始查询相同的条件执行 select count() 查询,以获得最佳性能。

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

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

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

    保存实体集合的示例:

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

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

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

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

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

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

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

    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() 方法。

示例:

// named parameter
dataManager.load(Customer.class)
        .query("e.name like :name")
        .parameter("name", value)
        .list();

// positional parameter
dataManager.load(Customer.class)
        .query("e.name like ?1", value)
        .list();

// case-insensitive positional parameter
dataManager.load(Customer.class)
        .query("e.name like ?1 or e.email like ?1", "(?i)%joe%")
        .list();

// multiple positional parameters
dataManager.load(Order.class)
        .query("e.date between ?1 and ?2", date1, date2)
        .list();

// omitting "select" and using a positional parameter
dataManager.load(Order.class)
        .query("from sales_Order o, sales_OrderLine l " +
                "where l.order = o and l.product.name = ?1", productName)
        .list();

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

事务

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

部分实体

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

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

  • 加载的实体是可缓存的

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

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

  • LoadContextloadPartialEntities 属性设置为 false。