数据加载器
数据加载器为 数据容器 加载数据。
根据交互的数据容器不同,数据加载器的接口有稍微的不同:
-
InstanceLoader
使用实体 id 或者 JPQL 查询语句加载单一实体到InstanceContainer
。 -
CollectionLoader
使用 JPQL 查询语句加载实体集合到CollectionContainer
。可以设置分页、排序以及其他可选的参数。 -
KeyValueCollectionLoader
加载KeyValueEntity
实体的集合到KeyValueCollectionContainer
。除了CollectionLoader
参数,还可以指定一个数据存储名称。
在界面的 XML 描述中,所有的加载器都用同一个 <loader>
元素中定义,加载器的类型通过包裹它的容器类型确定。
数据加载器不是必要的,因为可以使用 DataManager
或者自定义的服务来加载数据,然后直接设置给容器。但通过在界面中声明式的定义加载器可以简化数据加载的过程,特别是要使用 过滤器 组件的情况下。
通常,集合加载器从界面的描述文件中获得 JPQL 查询语句,然后从过滤器组件拿到查询参数,之后创建 LoadContext
并调用 DataManager
加载实体。所以,典型的 XML 描述看起来是这样:
<data readOnly="true">
<collection id="customersDc"
class="ui.ex1.entity.Customer">
<fetchPlan extends="_base"/>
<loader id="customersDl" >
<query>
<![CDATA[select e from uiex1_Customer e]]>
</query>
</loader>
</collection>
</data>
<layout expand="customersTable" spacing="true">
<filter id="filter"
dataLoader="customersDl">
<properties include=".*"/>
</filter>
<!-- ... -->
</layout>
在实体编辑界面,加载器的 XML 元素通常是空的,因为实例加载器需要一个实体的标识符,这个标识符通过编程的方式使用 StandardEditor
基类指定。
<data>
<instance id="customerDc"
class="ui.ex1.entity.Customer">
<fetchPlan extends="_base"/>
<loader/>
</instance>
</data>
加载器也可以编程式创建和配置,示例:
@Autowired
private DataComponents dataComponents;
private CollectionLoader<Customer> customersDl;
private void createCustomerLoader(CollectionContainer<Customer> container) {
customersDl = dataComponents.createCollectionLoader();
customersDl.setQuery("select e from uiex1_Customer e");
customersDl.setContainer(container);
customersDl.setDataContext(getScreenData().getDataContext());
}
当加载器设置了 DataContext(当使用 XML 描述定义加载器的时候是默认设置的),所有加载的实体都自动合并到数据上下文(data context)。
事件和处理器
本节介绍可以在界面控制器中处理的数据加载器生命周期事件。
如需使用 Jmix Studio 生成处理器的桩代码,在界面 XML 描述或者 Component Hierarchy(组件层级结构) 面板中选择数据容器元素,然后使用 Component Inspector(组件探查器) 面板的 Handlers 标签页。 或者,可以使用界面控制器顶部面板的 Generate Handler 按钮。 |
loadDelegate
加载器可以将实际的加载动作代理给界面控制器的一个方法,方法中可以调用自定义服务,而不使用默认的 DataManager
。示例:
@Autowired
private CustomerService customerService;
@Install(to = "customersDl", target = Target.DATA_LOADER) (1)
protected List<Customer> customersDlLoadDelegate(LoadContext<Customer> loadContext) { (2)
LoadContext.Query query = loadContext.getQuery();
return customerService.loadCustomers( (3)
query.getCondition(),
query.getSort(),
query.getFirstResult(),
query.getMaxResults()
);
}
1 | customersDl 加载器会使用 customersDlLoadDelegate() 方法来加载 Customer 实体列表 |
2 | 此方法接收 LoadContext 参数,加载器会按照其参数(查询语句、过滤器等等)来创建这个参数。 |
3 | 数据加载是通过 CustomerService.loadCustomers() 来完成的,该方法接收界面可视化组件为加载器设置的过滤条件、排序和分页参数。 |
除了调用自定义服务之外,加载代理方法还支持对加载的实体进行加载后处理。
如果使用代理自定义数据加载过程,并在表格中使用分页器组件(Pagination 或 SimplePagination)显示加载的数据,那么还需要自定义逻辑来统计数据的总行数。请参阅与表格关联分页器组件的 TotalCountDelegate 处理器。
查询条件
有时需要在运行时修改数据加载器的查询语句,以便过滤数据库级别加载的数据。需要根据用户输入的参数进行过滤,最简单的方法就是将 过滤器 可视化组件与数据加载器关联起来。
不需要使用全局过滤器或者添加全局过滤器,而是可以为加载器查询语句单独创建一组过滤条件。一个过滤条件是一组带有参数的查询子句。子句中所有的参数都设置了之后,这些子句才会被添加到生成的查询语句文本中。过滤条件会在数据存储级别传递,因此可以包含各个数据存储支持的不同语言的子句。框架会提供 JPQL 的过滤条件。
下面例子中,按照 Person
实体的两个属性:string name
和 boolean status
对实体进行过滤。
加载器的查询过滤条件可以通过 <condition>
XML 元素进行声明式的定义,或者通过 setCondition()
方法编程式的定义。下面是在 XML 中配置条件的示例:
<window xmlns="http://jmix.io/schema/ui/window"
xmlns:c="http://jmix.io/schema/ui/jpql-condition">(1)
<data readOnly="true">
<collection id="personsDc"
class="ui.ex1.entity.Person">
<fetchPlan extends="_base"/>
<loader id="personsDl">
<query>
<![CDATA[select e from uiex1_Person e]]>
<condition> (2)
<and>(3)
<c:jpql>(4)
<c:where>e.name like :name</c:where>
</c:jpql>
<c:jpql>
<c:where>e.status = :status</c:where>
</c:jpql>
</and>
</condition>
</query>
</loader>
</collection>
</data>
1 | 添加 JPQL condition 命名空间 |
2 | 在 query 内定义 condition 元素 |
3 | 如果有多个条件,添加 and 或 or 元素 |
4 | 使用可选的 join 元素和必需的 where 元素定义 JPQL 条件 |
假设界面有两个 UI 组件用来输入条件参数:nameFilterField
文本控件和 statusFilterField
复选框。为了在用户改变它们值的时候刷新数据,需要在界面控制器添加事件监听器:
@Autowired
private CollectionLoader<Person> personsDl;
@Subscribe("nameFilterField")
public void onNameFilterFieldValueChange1(HasValue.ValueChangeEvent event) {
if (event.getValue() != null) {
personsDl.setParameter("name", "(?i)%" + event.getValue() + "%");
} else {
personsDl.removeParameter("name");
}
personsDl.load();
}
@Subscribe("statusFilterField")
public void onStatusFilterFieldValueChange(HasValue.ValueChangeEvent<Boolean> event) {
if (event.getValue()) {
personsDl.setParameter("status", true);
} else {
personsDl.removeParameter("status");
}
personsDl.load();
}
如上面所说,只有在条件的参数都设置了之后才会将条件添加到查询语句中。所以在数据库会执行什么样的查询语句依赖于在 UI 组件如何输入参数:
select e from uiex1_Person e where e.name like :name
select e from uiex1_Person e where e.status = :status
select e from uiex1_Person e where (e.name like :name) and (e.status = :status)