3.2.6.2. DataManager
DataManager
接口在中间层和客户端层提供 CRUD 功能,是一种通用工具,用于从数据库加载实体关系图并保存已更改的游离实体实例。
有关 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 会加载所有本地属性,视图仅用来获取引用: