带区和数据集
报表的内部结构由 bands(带区) 组成。一个带区表示报表中的一个部分,该部分在最终文档中可能出现零次或多次。
带区中填充的数据由一个或多个与该带区关联的 datasets(数据集) 提供。
在报表模板中,带区的标记方式取决于文件格式。报表定义中包含带区的层次结构,与模板中的带区相对应。
下图展示了报表中使用的带区示例:
报表定义中的 Root 带区是预定义的,但模板中可能不会用到。
| 另请参阅 Report Generation 指南,其中包含具有复杂结构和各种输出类型的报表的实际示例。 |
带区
带区标签页
应用程序运行时,可以在报表编辑器中的 Bands 标签页定义带区结构和每个带区的数据集。
一个报表带区包含下列参数:
-
Name — 带区名称,在报表内唯一。只能包含拉丁字母、数字和下划线。
如果带区名称以 header 开头,则在使用 表格格式化器 时,其数据不会被打印。 -
Orientation — 带区方向:Horizontal、Vertical 或 Crosstab(双向)。
-
Horizontal 带区向下复制。可以包含子带区。
-
Vertical 带区向右复制。
-
Crosstab 带区以矩阵形式向右和向下复制。
-
-
Parent — 父带区。
-
Multiple Datasets — 如果想为带区定义多个数据集,请勾选。
-
Streaming — 勾选后将带区设置为 流式带区。
-
Dataset type — 数据集的 类型。
每个报表带区包括一个或多个数据集。在运行报表时,数据集将转换为行的列表,其中每行都是包含名值对(name-value)的映射。报表中出现的带区的次数与其最长数据集中的行数一样多。
报表模板中定义的字段在执行报表时会被数据集中的相应值替换。在定义数据集时,可以使用 报表外部参数 和其他带区的字段(用于关联带区)。
每个报表都有一个 Root 带区。可以在其中创建数据集,并从其他带区引用其字段。
请注意,Dataset name 列(在使用多个数据集时可用)仅供方便使用。
@BandDef 注解
在设计时可以用 @BandDef 定义带区。该注解有以下属性:
-
name— 带区名称,在报表内唯一。只能包含拉丁字母、数字和下划线。 -
parent— 父带区的名称。只有Root带区可以省略。 -
root— 对于Root带区必须设置为true。默认为false。 -
orientation— 带区的方向,由Orientation枚举定义:HORIZONTAL、VERTICAL、CROSS。默认为HORIZONTAL。 -
streaming— 如果为true,则带区为 流式带区。默认为false。 -
dataSets— 由一组@DataSetDef注解定义的数据集。
报表定义必须包含带有 name = "Root" 和 root = true 属性的 Root 带区。
报表定义 Java 类中 @BandDef 注解的顺序很重要。会影响同一层级带区的打印顺序。
|
流式带区
Jmix 3.1+
流式带区使用数据库游标逐行读取数据,并立即写入 XLSX 文档。报表引擎仅在内存中保留最后渲染的行,并将之前的行写入临时文件。其他带区的数据仍然加载到内存中。如需配置流式带区,在 Bands 标签页 中勾选 Streaming 复选框,或在 @BandDef 注解中设置 streaming = true。
有流式带区的报表必须满足以下要求:
报表详情视图不会保存违反这些要求的报表定义。当报表引擎渲染流式带区时,也会检查这些要求,并在不满足时直接失败,并列出相关的错误信息。
流式带区仅适用于非自定义的 .xlsx 模板。对于其他模板,报表引擎会忽略流式设置,向日志写入警告,并将所有数据加载到内存中。.xlsx 模板还必须遵循 具有流式带区的模板 中描述的规则。
流式带区的查询在独立的只读事务中运行,该事务在带区的写入期间保持开启。
数据集
SQL
SQL 数据集是通过执行 SQL 查询而生成的。建议使用 as 运算符为查询结果字段设置别名。最好将别名用双引号括起来,以防止 DBMS 进行可能的大小写转换:
select u.name as "userName", u.login as "userLogin"
from user_ u
可以在查询中使用报表输入参数和父带区字段。参数需要用 ${} 处理,例如 ${dateFrom}。父带区字段也可以类似地处理,通过在字段名称前面添加带区名称:${band1.field1}。
下面是一个使用从 group 父带区获取的 groupId 参数和外部 enabled 参数的 SQL 查询示例:
select u.name as "userName", u.login as "userLogin"
from user_ u
where u.group_id = ${group.groupId}
and u.active = ${active}
and u.deleted_date is null
|
SQL 查询中需要指定条件过滤软删除的记录。 |
- 合并多个数据集
-
Link field(链接字段) 用于合并来自同一带区内的多个数据集的数据。当单个查询或 Groovy 脚本无法获取报表所需的完整数据时使用。
- 选择数据存储
-
默认情况下,SQL 查询在主数据库上执行。如果要查询 附加数据存储,请在 Data store(数据存储) 字段中设置其名称。
- 报表带区中的查询预处理
-
如果需要根据报表输入参数或父带区中的参数值动态修改 SQL/JPQL 查询,则可以使用 SQL 预处理。模板引擎支持使用 Groovy 修改 SQL/JPQ 查询语句。如需使用,请在报表带区编辑界面下方选中 Preprocess query as Groovy template(将查询语句预处理为 Groovy 模板) 复选框。生成的查询将由
GStringTemplateEngine处理,该引擎可以访问:-
报表参数:
${<parameter_name>}, -
父带区中的值:
${<band_name>.<parameter_name>}。
考虑以下示例:根据是否传递
createTs2报表参数选择一个查询条件:e.create_ts < ${createTs2}或e.create_ts < current_timestamp。在这种情况下,查询应如下所示:
select e.create_ts, e.id, e.vin from ref_car e where e.create_ts >= ${createTs1} and <% out << (createTs2 != null ? 'e.create_ts < ${createTs2}' : 'e.create_ts < current_timestamp')%>因此,如果未传递
createTs2参数,则初始查询将转换为以下查询:select e.create_ts, e.id, e.vin from ref_car e where e.create_ts >= ${createTs1} and e.create_ts < current_timestamp如果传递
createTs2,则报表带区将使用以下查询:select e.create_ts, e.id, e.vin from ref_car e where e.create_ts >= ${createTs1} and e.create_ts < ${createTs2} -
JPQL
JPQL 数据集是通过执行 JPQL 查询而生成的。生成的查询字段必须使用 as 运算符提供别名。可以在 JPQL 查询中使用报表输入参数和父带区字段,类似 SQL 查询。
下面是一个使用从 group 父带区获取的 groupId 参数和外部 enabled 参数的 JPQL 查询示例:
select u.name as userName, u.login as userLogin
from User u
where u.group.id = ${group.groupId}
and u.active = ${active}
JPQL 查询自动支持软删除并仅返回未删除的记录。
|
请注意,当使用 JPQL 查询语句作为报表的数据源时,查询语句绕过 DataManager 直接在数据库执行。也就是说,不会使用 系统的 数据访问安全检查(行级别和资源级别)。因此,开发者必须确保 JPQL 查询仅检索用户被授权访问的数据。开发者有责任在查询中实施任何必要的安全检查。 |
AI 生成的 JPQL 数据集
Jmix 3.1+
AI 生成的 JPQL 数据集也是执行 JPQL 查询语句的结果。查询语句本身由大型语言模型(LLM)根据自然语言描述生成。查询语句与数据集一起存储。报表运行时,存储的查询语句通过 DataManager 执行,不会调用 LLM。
要求
查询语句由 AI 工具 扩展组件生成。如果应用程序中安装了该扩展组件并且启用了 数据加载功能,则报表详情视图中会提供 AI 生成的 JPQL 类型的数据集。Generate query 按钮还需要 配置一个模型。没有模型时,只能手动编辑存储的查询语句。
而运行包含此类数据集的报表时,不需要 AI 工具扩展组件,例如在 导入之后。
除了 AI 工具扩展组件之外,还可以使用实现 io.jmix.reports.llm.LlmDataQueryService 接口的自定义 bean 生成查询语句。
|
生成查询语句
模型通过 AI 工具扩展组件的 数据加载工具 读取数据模型,因此只能看到 AI 工具中 开放 给当前用户的实体。扩展组件会检查生成的查询语句,如果有问题,则要求模型进行修复,修复次数由 jmix.aitools.dataload.max-repair-attempts 属性配置。
如需创建该类型的数据集:
-
在 Dataset type 字段中选择 AI-generated JPQL。
-
在 Prompt 字段中描述带区需要的数据,例如:
Orders placed since :dateFrom with the customer name and the order amount,其中dateFrom是报表参数。请按 参数 部分介绍的名称引用参数。 -
点击 Generate query。最大生成时间由 jmix.reports.client.llm-query-generation-timeout 属性定义。
-
检查 Generated query 字段中的查询语句以及 Columns 列表中的带区字段。
Columns 列表是报表模板中带区字段的名称,例如 ${customerName}。每一列需要按位置与 select 子句的查询值匹配。
如需手动修改查询语句和列,请点击 Generate query 按钮旁边的 Edit 按钮(铅笔图标)。如果在 select 子句中添加、删除值或调整了值的位置,需要以相同方式修改 Columns 列表。否则,带区字段会写入错误值或空值。视图不会检查这个问题。注意,如果再次生成查询语句,会覆盖手动做的修改。
参数
查询语句通过 JPQL 命名参数引用值:
-
报表 输入参数 可通过别名使用,例如
:dateFrom。 -
父带区的字段可通过
:<band>_<field>使用,例如:Orders_customerId表示Orders带区的customerId字段。Root带区的字段不可用。仅当父带区也有 AI 生成 JPQL 数据集且保存了查询语句时,模型才知道父带区的字段。对于其他父数据集(如 JPQL 或 SQL),请在提示词中命名参数。
某些参数需要特殊处理:
-
空的可选参数会作为
null传递。如需使此类条件生效,查询语句必须检查该参数,例如(:customer is null or o.customer = :customer)。系统会要求模型添加这种检查。保存报表定义时,如果有未检查就比较的可选参数,视图提示警告。否则,带区中对应的值会为空且没有任何错误。 -
实体列表 类型的参数使用
in匹配。如果此类参数为空,报表运行会失败并报错。 -
不支持不区分大小写的参数语法
:(?i)name,报表运行会失败并报错。请改用lower(…) like :name。
如果提示词中对对结果的行数有要求,例如 the top 5 customers by revenue,该限制会与查询语句一起存储。在子带区中,限制在父带区的每一行都会执行。
在交叉表带区中,可以为单元格使用 AI 生成的 JPQL 数据集。列 header 数据集(<band>_dynamic_header)和行 header 数据集(<band>_master_data)的字段会作为列表参数传递给查询语句,命名为 :<dataset>_<field>,例如 :Sales_dynamic_header_month 表示 Sales_dynamic_header 数据集的 month 字段。为了让报表引擎将每个结果行与单元格关联,查询语句必须为列和行的 header 返回相同名称的列,例如 Sales_dynamic_header_month。
与 JPQL 数据集的差异
查询语句的语法和行为与 JPQL 数据集 不同:
| JPQL 数据集 | AI 生成的 JPQL 数据集 | |
|---|---|---|
报表参数 |
|
|
父带区字段 |
|
|
字段名 |
别名在 |
Columns 列表。别名保留在查询语句中,因此不能使用双引号,也不能是 JPQL 保留字,例如 |
查询预处理 |
不支持 |
|
数据存储 |
在 Data store 字段中设置。默认使用主数据存储。 |
从查询语句确定, |
数据访问检查 |
无,因为执行时绕过 |
有,参见 数据访问检查 |
数据访问检查
-
用户必须对查询语句中使用的每个实体都有读权限,包括关联实体和子查询。否则,报表运行会失败并报访问错误。
-
用户无法读取的属性会返回为
null。 -
读取的所有实体的 JPQL 策略会添加到查询语句中。
为了保证这些检查有效,如果存储的查询语句满足以下情况,报表运行会失败并报错:
-
不是
select查询,或使用了 EclipseLink 的SQL、FUNCTION、FUNC、OPERATOR、COLUMN或TABLE函数。 -
查找整个实体(如
select o或select o.customer)而不是实体属性。 -
读取有
READ操作的 谓词策略 的实体。请改用 JPQL 策略。 -
实体的行级策略无法应用于查询语句:该实体仅在子查询中使用或在多个别名下使用,或者其策略具有 join 子句且不是查询语句查找的实体。
限制
-
具有 AI 生成 JPQL 数据集的带区不能是 流式带区。
-
设计时创建的报表不能使用该数据集类型,因为查询语句在报表详情视图中生成并存储在报表定义中。
Groovy
Groovy 数据集是通过执行 Groovy 脚本而生成的。该脚本返回 List<Map<String, Object>> 类型的对象。此对象列表的每个元素都是 Map<String, Object> 类型的对象 - 对应于一个数据集记录。
脚本中可以访问下列对象:
-
params- 外部报表参数映射。以下是获取参数值的示例:def active = params['active'] -
parentBand- 父带区对象,类型为io.jmix.reports.yarg.structure.BandData。此对象支持通过调用getParameterValue()方法获取父带区字段值,例如:def groupId = parentBand.getParameterValue('groupId') -
currentAuthentication-io.jmix.core.security.CurrentAuthentication类型的对象,关联当前的认证用户。示例:def user = currentAuthentication.getUser() -
metadata-io.jmix.core.Metadata类型的对象,提供对应用程序元数据的访问。示例:def metaClass = metadata.getClass('User') -
dataManager-io.jmix.core.DataManager类型的对象,提供 CRUD 功能。示例:def book = dataManager.load(Book.class).id(bookId).fetchPlan("book.edit").one() -
timeSource-io.jmix.core.TimeSource类型的对象,用于获取当前时间。示例:def currentDate = timeSource.currentTimestamp() -
applicationContext-org.springframework.context.ApplicationContext类型的对象,提供访问托管 bean 的功能。示例:def accountService = applicationContext.getBean(AccountService);
实体
Entity(单一实体) 数据集仅包含一行数据,使用单个 JPA 实体实例的属性和与之相关的实体生成。
数据源由 Entity 类型的外部参数生成,必须在 Parameters(参数) 标签页中进行描述。Entity parameter name(实体参数名称) 字段中的值必须与参数名称匹配。
报表模板必须包含具有实体属性名称的字段。属性名称可以使用已有 fetch plan 中的名称,需要勾选 Use existing fetch plan(使用已有 fetch plan),或者用 Select entity attributes(选择实体属性) 按钮选择。
|
由于报表引擎每次都会重新加载 Entity 数据集,因此不能用于未在数据库保存的 DTO 实体或 JPA 实体。此时,可以使用 Groovy 数据集,将实体作为参数传入:
|
实体列表
List of entities(实体列表) 数据集由一组 JPA 实体实例生成。
数据源由 List of entities 类型的外部参数生成,必须在 Parameters(参数) 标签页中进行描述。Entity parameter name(实体参数名称) 字段中的值必须与参数名称匹配。
报表模板必须包含具有实体属性名称的字段。属性名称可以使用已有 fetch plan 中的名称,需要勾选 Use existing fetch plan(使用已有 fetch plan),或者用 Select entity attributes(选择实体属性) 按钮选择。
|
由于报表引擎每次都会重新加载 List of entities 数据集,因此不能用于未在数据库保存的 DTO 实体或 JPA 实体。此时,可以使用 Groovy 数据集,将实体列表作为参数传入:
|
JSON
JSON 数据集是通过 JSON 数据生成的。可以从以下来源获取此数据:
-
Groovy 脚本
用户提供的脚本应该以字符串形式返回 JSON 数据。
示例:
return ''' { "items": [ { "name": "Java Concurrency in practice", "price": 15000 }, { "name": "Clear code", "price": 13000 }, { "name": "Scala in action", "price": 12000 } ] } ''' -
URL
报表引擎将对 URL 执行 GET HTTP 查询。
示例:
https://jsonplaceholder.typicode.com/users -
参数
必须在 Parameters(参数) 标签页中描述包含 JSON 数据的 String 类型的报表外部参数。
使用 JsonPath 查询 JSON 树中的信息。例如,可以使用 $.store.book[*] JsonPath 返回以下 JSON 树中的所有书籍:
{
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99,
"isbn": "0-553-21311-3"
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}
有关 JsonPath 表达式的更多详细信息,请参阅 文档。
|
报表输出的字段如果是 如需使用这个格式,可以切换到报表详情视图的 Value formats(值格式) 标签页并打开 formatter 编辑器。例如,对于
|
@DataSetDef 注解
在设计时,使用 @DataSetDef 注解定义报表的数据集。该注解有以下属性:
-
name— 数据集名称。在DELEGATE类型的数据集中,用于关联数据集定义和返回代理的方法。在交叉表带区(
@BandDef.orientation = Orientation.CROSS)中,数据集必须使用以下名称:-
列标题:
${band_name}_dynamic_header -
行标题:
${band_name}_master_data -
主要内容:
${band_name}
-
-
type— 数据集类型,由DataSetType枚举定义。此枚举的值与上述运行时数据集的类型相对应。LLM值(AI 生成的 JPQL 数据集)无法在设计时使用。通常,设计时的报表使用
DELEGATE类型的数据集。此时,通过名称与使用@DataSetDelegate注解的方法关联。