带区和数据集

报表的内部结构由 bands(带区) 组成。一个带区表示报表中的一个部分,该部分在最终文档中可能出现零次或多次。

带区中填充的数据由一个或多个与该带区关联的 datasets(数据集) 提供。

在报表模板中,带区的标记方式取决于文件格式。报表定义中包含带区的层次结构,与模板中的带区相对应。

下图展示了报表中使用的带区示例:

bands 1
Figure 1. 示例报表带区

报表定义中的 Root 带区是预定义的,但模板中可能不会用到。

另请参阅 Report Generation 指南,其中包含具有复杂结构和各种输出类型的报表的实际示例。

带区

带区标签页

应用程序运行时,可以在报表编辑器中的 Bands 标签页定义带区结构和每个带区的数据集。

bands

一个报表带区包含下列参数:

  • Name — 带区名称,在报表内唯一。只能包含拉丁字母、数字和下划线。

    如果带区名称以 header 开头,则在使用 表格格式化器 时,其数据不会被打印。
  • Orientation — 带区方向:HorizontalVerticalCrosstab(双向)

    • Horizontal 带区向下复制。可以包含子带区。

    • Vertical 带区向右复制。

    • Crosstab 带区以矩阵形式向右和向下复制。

  • Parent — 父带区。

  • Multiple Datasets — 如果想为带区定义多个数据集,请勾选。

  • Dataset type — 数据集的 类型

每个报表带区包括一个或多个数据集。在运行报表时,数据集将转换为行的列表,其中每行都是包含名值对(name-value)的映射。报表中出现的带区的次数与其最长数据集中的行数一样多。

报表模板中定义的字段在执行报表时会被数据集中的相应值替换。在定义数据集时,可以使用 报表外部参数 和其他带区的字段(用于关联带区)。

每个报表都有一个 Root 带区。可以在其中创建数据集,并从其他带区引用其字段。

请注意,Dataset name 列(在使用多个数据集时可用)仅供方便使用。

@BandDef 注解

在设计时可以用 @BandDef 定义带区。该注解有以下属性:

  • name — 带区名称,在报表内唯一。只能包含拉丁字母、数字和下划线。

  • parent — 父带区的名称。只有 Root 带区可以省略。

  • root — 对于 Root 带区必须设置为 true。默认为 false

  • orientation — 带区的方向,由 Orientation 枚举定义:HORIZONTALVERTICALCROSS。默认为 HORIZONTAL

  • dataSets — 由一组 @DataSetDef 注解定义的数据集。

报表定义必须包含带有 name = "Root"root = true 属性的 Root 带区。

报表定义 Java 类中 @BandDef 注解的顺序很重要。会影响同一层级带区的打印顺序。

数据集

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 查询中需要指定条件过滤软删除的记录。

选择数据存储

默认情况下,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 查询仅检索用户被授权访问的数据。开发者有责任在查询中实施任何必要的安全检查。

也可以使用 Link field(链接字段) 合并多个数据集,使用 Data store(数据存储) 设置 查询附加数据存储,或者勾选 Preprocess query as Groovy template(将查询语句预处理为 Groovy 模板) 启用 查询语句预处理

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 数据集,将实体作为参数传入:

def entity = params['entity']

return [['title': entity.title,
        'description': entity.description]]

实体列表

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 数据集,将实体列表作为参数传入:

return params['entities'].collect {[
    'title': it.title,
    'description': it.description
]}

JSON

JSON 数据集是通过 JSON 数据生成的。可以从以下来源获取此数据:

  1. Groovy 脚本

    用户提供的脚本应该以字符串形式返回 JSON 数据。

    示例:

    return ''' 
            {
              "items": [
                {
                  "name": "Java Concurrency in practice",
                  "price": 15000
                },
                {
                  "name": "Clear code",
                  "price": 13000
                },
                {
                  "name": "Scala in action",
                  "price": 12000
                }
              ]
            }
            '''
  2. URL

    报表引擎将对 URL 执行 GET HTTP 查询。

    示例:

    https://jsonplaceholder.typicode.com/users
  3. 参数

    必须在 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 表达式的更多详细信息,请参阅 文档

报表输出的字段如果是 DateDateTimeTime 数据类型,则不支持 java.text.SimpleDateFormat 定义的格式。如果要设置正确的格式,需要写一个 Groovy 脚本。

如需使用这个格式,可以切换到报表详情视图的 Value formats(值格式) 标签页并打开 formatter 编辑器。例如,对于 bookPublication.dateTime 字段,Groovy 脚本如下:

import java.text.SimpleDateFormat

def simpleOldDateFormat = new SimpleDateFormat('yyyy-MM-dd HH:mm')
def simpleNewDateFormat = new SimpleDateFormat('dd/MM/yyyy HH:mm')
def oldDate = simpleOldDateFormat.parse(value)

return simpleNewDateFormat.format(oldDate)

@DataSetDef 注解

在设计时,使用 @DataSetDef 注解定义报表的数据集。该注解有以下属性:

  • name — 数据集名称。在 DELEGATE 类型的数据集中,用于关联数据集定义和返回代理的方法。

    在交叉表带区(@BandDef.orientation = Orientation.CROSS)中,数据集必须使用以下名称:

    • 列标题:${band_name}_dynamic_header

    • 行标题:${band_name}_master_data

    • 主要内容:${band_name}

  • type — 数据集类型,由 DataSetType 枚举定义。此枚举的值与上述运行时数据集的类型相对应。

    通常,设计时的报表使用 DELEGATE 类型的数据集。此时,通过名称与使用 @DataSetDelegate 注解的方法关联。

@DataSetDelegate 注解

@DataSetDelegate 注解支持定义一个方法,该方法返回用于执行数据集逻辑的代理。注解有一个 name 属性,该属性应配置为相应数据集的名称。

带该注解的方法不能有任何参数,并且应返回 ReportDataLoader 函数式接口。