数据加载器
数据加载器为 数据容器 加载数据。
根据交互的数据容器不同,数据加载器的接口有稍微的不同:
-
InstanceLoader
使用实体 id 或者 JPQL 查询语句加载单一实体到InstanceContainer
。 -
CollectionLoader
使用 JPQL 查询语句加载实体集合到CollectionContainer
。可以设置分页、排序以及其他可选的参数。 -
KeyValueCollectionLoader
加载KeyValueEntity
实体的集合到KeyValueCollectionContainer
。除了CollectionLoader
参数,还可以指定一个数据存储名称。
在视图的 XML 描述中,所有的类型的加载器都是使用 <loader>
元素定义,加载器的类型通过归属的数据容器类型确定。
数据加载器不是必要的,因为可以使用 DataManager
或者自定义的服务来加载数据,然后直接设置给容器。但通过在视图中声明式的定义加载器可以简化数据加载的过程。
通常,集合加载器从视图的描述文件中获得 JPQL 查询语句,之后创建 LoadContext
并调用 DataManager
加载实体。所以,典型的 XML 描述看起来是这样:
<collection id="departmentsDc"
class="com.company.onboarding.entity.Department">
<fetchPlan extends="_base">
<property name="hrManager" fetchPlan="_base"/>
</fetchPlan>
<loader id="departmentsDl" readOnly="true">
<query>
<![CDATA[select e from Department e]]>
</query>
</loader>
</collection>
在实体详情视图中,加载器的 XML 元素通常是空的,因为实例加载器需要一个实体的标识符,这个标识符通过编程的方式使用 StandardDetailView
基类指定。
<instance id="departmentDc"
class="com.company.onboarding.entity.Department">
<fetchPlan extends="_base">
<property name="hrManager" fetchPlan="_base"/>
</fetchPlan>
<loader id="departmentDl"/>
</instance>
如果加载器没有在 XML 中配置 readOnly="true"
,或者加载器通过编程的方式设置了 DataContext,则所有加载的实体都自动合并到数据上下文。
事件和处理器
本节介绍可以在视图控制器中处理的数据加载器生命周期事件。
如需使用 Jmix Studio 生成处理方法的桩代码,需要在视图 XML 描述或者 Jmix UI 结构面板选中元素,然后用 Jmix UI 组件面板的 Handlers 标签页生成。 或者可以使用视图控制器顶部面板的 Generate Handler 按钮。 |
loadDelegate
加载器可以将实际的加载动作代理给视图控制器的一个方法,方法中可以调用自定义服务,而不使用默认的 DataManager
。示例:
@Autowired
private DepartmentService departmentService;
@Install(to = "departmentsDl", target = Target.DATA_LOADER) (1)
private List<Department> departmentsDlLoadDelegate(final LoadContext<Department> loadContext) { (2)
LoadContext.Query query = loadContext.getQuery();
return departmentService.loadDepartments( (3)
query.getCondition(),
query.getSort(),
query.getFirstResult(),
query.getMaxResults()
);
}
1 | departmentsDl 加载器会使用 departmentsDlLoadDelegate() 方法来加载 Department 实体列表 |
2 | 此方法接收 LoadContext 参数,加载器会按照其参数(查询语句、过滤器等等)来创建这个参数。 |
3 | 数据加载是通过 DepartmentService.loadDepartments() 来完成的,该方法接收视图可视化组件为加载器设置的过滤条件、排序和分页参数。 |
除了调用自定义服务之外,加载代理方法还支持对加载的实体进行加载后处理。
查询条件
有时需要在运行时修改数据加载器的查询语句,以便在数据库级别过滤加载的数据。根据用户输入的参数进行过滤最简单的方法就是将 propertyFilter 属性过滤器 或 genericFilter 通用过滤器 可视化组件与数据加载器关联起来。
无论是否使用全局过滤器,都可以为加载器查询语句单独创建一组过滤条件。一个过滤条件是一组带参数的查询子句。子句中所有的参数都设置了之后,这些子句才会被添加到生成的查询语句文本中。
过滤条件会在数据存储级别处理,因此可以包含各个数据存储支持的不同语言的子句。框架会自动处理 JPQL 的过滤条件。
下面例子中,按照 Department
实体的两个属性:name
和 hrManager
对实体进行过滤。
加载器的查询过滤条件可以通过 <condition>
XML 元素进行声明式的定义,或者通过 setCondition()
方法编程式的定义。下面是在 XML 中配置条件的示例:
<view xmlns="http://jmix.io/schema/flowui/view"
xmlns:c="http://jmix.io/schema/flowui/jpql-condition"> (1)
<data readOnly="true">
<collection id="departmentsDc"
class="com.company.onboarding.entity.Department">
<fetchPlan extends="_base">
<property name="hrManager" fetchPlan="_base"/>
</fetchPlan>
<loader id="departmentsDl">
<query>
<![CDATA[select e from Department e]]>
<condition> (2)
<and> (3)
<c:jpql> (4)
<c:where>e.name like :name</c:where>
</c:jpql>
<c:jpql>
<c:where>e.hrManager = :hrManager</c:where>
</c:jpql>
</and>
</condition>
</query>
</loader>
</collection>
1 | 添加 JPQL condition 命名空间 |
2 | 在 query 内定义 condition 元素 |
3 | 如果有多个条件,添加 and 或 or 元素 |
4 | 使用可选的 join 元素和必需的 where 元素定义 JPQL 条件 |
假设视图有两个 UI 组件用来输入条件参数:nameFilterField
文本控件和 hrManagerFilterField
复选框:
<textField id="nameFilterField" label="Name"/>
<entityComboBox id="hrManagerFilterField" label="HR Manager"
metaClass="User" itemsContainer="usersDc">
<actions>
<action id="entityClear" type="entity_clear"/>
</actions>
</entityComboBox>
为了在用户改变输入值的时候刷新数据,需要在视图控制器添加事件监听器:
@ViewComponent
private CollectionLoader<Department> departmentsDl;
@Subscribe("nameFilterField")
public void onNameFilterFieldComponentValueChange(final AbstractField.ComponentValueChangeEvent<TextField, String> event) {
departmentsDl.setParameter("name", "(?i)%" + event.getValue() + "%");
departmentsDl.load();
}
@Subscribe("hrManagerFilterField")
public void onHrManagerFilterFieldComponentValueChange(final AbstractField.ComponentValueChangeEvent<EntityComboBox<User>, User> event) {
departmentsDl.setParameter("hrManager", event.getValue());
departmentsDl.load();
}
如上面所说,只有在条件的参数都设置了之后才会将条件添加到查询语句中。所以在数据库会执行什么样的查询语句依赖于在 UI 组件如何输入参数:
select e from Department e where e.name like :name
select e from Department e where e.hrManager = :hrManager
select e from Department e where (e.name like :name) and (e.hrManager = :hrManager)