Groovy的脚本化报告

Groovy已成为我最喜欢的脚本语言 ,在此博客中,我介绍了Groovy的一些功能,这些功能使其特别适合呈现基于文本的报告。 这篇文章将展示如何使用Groovy轻松呈现基于自定义文本的数据库中存储的数据报告。 我将重点介绍Groovy的一些吸引人的功能。

在本文的示例中,我将Oracle Database 11g Express EditionXE )用于数据源,但是可以使用任何数据源。 该示例确实利用了Groovy的出色SQL / JDBC支持,并使用了Oracle示例模式HR )。 示例架构文档中提供了该示例架构的 直观描述

我使用Groovy编写报告脚本的示例涉及从Oracle HR样本架构中检索数据,并通过基于文本的报告显示该数据。 脚本的一部分需要从数据库获取此数据,而Groovy仅向执行此操作的SQL语句添加了最少的仪式。 脚本中的以下代码片段显示了Groovy的多行GString的使用,以用户友好的格式指定SQL查询字符串并处理该查询的结果。

def employeeQueryStr =
'''SELECT e.employee_id, e.first_name, e.last_name,
          e.email, e.phone_number,
          e.hire_date, e.job_id, j.job_title,
          e.salary, e.commission_pct, e.manager_id,
          e.department_id, d.department_name,
          m.first_name AS mgr_first_name, m.last_name AS mgr_last_name
     FROM employees e, departments d, jobs j, employees m
    WHERE e.department_id = d.department_id
      AND e.job_id = j.job_id
      AND e.manager_id = m.employee_id(+)'''

def employees = new TreeMap<Long, Employee>()
import groovy.sql.Sql
def sql = Sql.newInstance('jdbc:oracle:thin:@localhost:1521:xe', 'hr', 'hr',
                          'oracle.jdbc.pool.OracleDataSource')
sql.eachRow(employeeQueryStr)
{
   def employeeId = it.employee_id as Long
   def employee = new Employee(employeeId, it.first_name, it.last_name,
                               it.email, it.phone_number,
                               it.hire_date, it.job_id, it.job_title,
                               it.salary, it.commission_pct, it.manager_id as Long,
                               it.department_id as Long, it.department_name,
                               it.mgr_first_name, it.mgr_last_name)
   employees.put(employeeId, employee)
}

上面的Groovy代码仅在Oracle SQL语句之上添加了少量代码。 指定的SELECT语句联接多个表,并且还包括一个外部 联接 (尽管该位置没有管理器,但要在查询结果中包含President的外部 联接 )。 代码的第一部分中的绝大部分是可以在SQL * PlusSQL Developer中按原样运行的SQL语句。 借助Groovy的SQL支持,无需冗长的异常捕获和结果集处理!

在上面的代码片段中指出了Groovy特定的更多优点。 请注意,在需要时允许导入groovy.sql.Sql的import语句,并且不必位于脚本文件的顶部。 该示例还使用了Sql.newInstanceSql.eachRow(GString,Closure)后一种方法可以轻松将闭包应用于查询结果。 它特殊的单词是在闭包中处理的项目的默认名称。 在这种情况下, it可以被认为在结果集中级行的。 每行中的值均由基础数据库列的名称(或对于mgr_first_namemgr_last_name而言为别名)访问。

Groovy的优点之一是它与Java的无缝集成。 上面的代码片段还通过Groovy使用TreeMap进行了演示,这是有利的,因为这意味着基于从数据库中检索到的数据放置在地图中的新Employee实例将始终按雇员ID的顺序可用。

在上面的代码中,将从数据库中检索并通过闭包处理的信息存储在新实例化的Employee对象中的每一行中。 此Employee对象提供了另一个展示Groovy简洁性的地方,下面显示。

员工关系

@groovy.transform.Canonical
class Employee
{
   Long employeeId
   String firstName
   String lastName
   String emailAddress
   String phone_number
   Date hireDate
   String jobId
   String jobTitle
   BigDecimal salary
   BigDecimal commissionPercentage
   Long managerId
   Long departmentId
   String departmentName
   String managerFirstName
   String managerLastName
}

刚刚显示的代码清单是整个类! Groovy的属性支持使getter / setter方法自动可用于所有已定义的类属性。 正如我在先前的博客文章中所讨论的那样@Canonical批注Groovy AST(转换) ,它会自动为此类创建一些有用的通用方法[ equals(Object)hashCode()toString() ]。 没有显式的构造函数,因为@Canonical也可以处理此问题,它提供了一个构造函数,该构造函数按照在声明中指定的顺序接受该类的参数。 很难想象这样一个场景,在该场景中,轻松,快速地创建一个对象来在脚本中存储检索到的数据值将变得更加容易。

该脚本需要一个JDBC驱动程序才能从Oracle数据库XE检索此数据,并且可以在运行Groovy脚本时在类路径上指定该驱动程序的JAR。 但是,我希望脚本尽可能独立,这使得Groovy的类路径根加载机制具有吸引力。 可以在此脚本中使用它(而不是在调用脚本时在外部指定它),如下所示:

this.class.classLoader.rootLoader.addURL(
   new URL('file:///C:/oraclexe/app/oracle/product/11.2.0/server/jdbc/lib/ojdbc6.jar'))

旁注 :访问适当的相关JAR或库的另一种巧妙方法是使用Groovy的Grape提供的 @Grab注释。 我在这里没有使用它,因为在我知道的任何合法的Maven中央存储库中都没有 Oracle的JDBC JAR。 我的博客文章Easy Groovy Logger Injection and Log Guarding中显示了在Maven公共存储库中存在依赖项时使用此方法的示例。

从数据库中检索数据并将其放置在一组简单的Groovy对象的集合中,这些对象旨在保存此数据并提供对它们的轻松访问,现在几乎是时候开始在文本报告中呈现此数据了。 脚本代码的下一个摘录显示了脚本中定义的一些常量。

int TOTAL_WIDTH = 120
String HEADER_ROW_SEPARATOR = '='.multiply(TOTAL_WIDTH)
String ROW_SEPARATOR = '-'.multiply(TOTAL_WIDTH)
String COLUMN_SEPARATOR = '|'
int COLUMN_SEPARATOR_SIZE = COLUMN_SEPARATOR.size()
int COLUMN_WIDTH = 22
int TOTAL_NUM_COLUMNS = 5
int BALANCE_COLUMN_WIDTH = TOTAL_WIDTH-(TOTAL_NUM_COLUMNS-1)*COLUMN_WIDTH-COLUMN_SEPARATOR_SIZE*(TOTAL_NUM_COLUMNS-1)-2

刚刚显示的常量声明体现了Groovy的更多优势。 首先,常量是静态类型的,这表明Groovy灵活地指定静态和动态类型。 在最后一个代码片段中,值得特别注意的Groovy的另一个功能是在文字字符串上使用String.multiply(Number)方法 。 一切,甚至字符串和数字,都是Groovy中的对象。 使用multiply方法可以轻松创建相同数量的相同重复字符的字符串。

文本输出的第一部分是标题。 Groovy脚本的以下几行将此标头信息写入标准输出。

println '\n\n${HEADER_ROW_SEPARATOR}'
println '${COLUMN_SEPARATOR}${'HR SCHEMA EMPLOYEES'.center(TOTAL_WIDTH-2*COLUMN_SEPARATOR_SIZE)}${COLUMN_SEPARATOR}'
println HEADER_ROW_SEPARATOR
print '${COLUMN_SEPARATOR}${'EMPLOYEE ID/HIRE DATE'.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
print '${'EMPLOYEE NAME'.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
print '${'TITLE/DEPARTMENT'.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
print '${'SALARY INFO'.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
println '${'CONTACT INFO'.center(BALANCE_COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
println HEADER_ROW_SEPARATOR

上面的代码显示了Groovy的一些更令人上瘾的功能。 我对Groovy的GString支持最喜欢的方面之一是能够使用类似于Ant的${}表达式来提供与String内联的可执行代码。 上面的代码还展示了Groovy的GDK字符串center(Number)方法的支持 ,该方法自动将给定String与指定数量的字符居中。 这是一项功能强大的功能,可轻松编写引人入胜的文本输出。

在我们的数据结构中检索到可用的数据并定义了常量之后,即可开始输出部分。 下一个代码片段显示了Groovy的标准集合的each方法的使用,以允许在先前填充的TreeMap上进行迭代 ,并在每次迭代上应用闭包。

employees.each
{ id, employee ->
   // first line in each output row
   def idStr = id as String
   print '${COLUMN_SEPARATOR}${idStr.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
   def employeeName = employee.firstName + ' ' + employee.lastName
   print '${employeeName.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
   def jobTitle = employee.jobTitle.replace('Vice President', 'VP').replace('Assistant', 'Asst').replace('Representative', 'Rep')
   print '${jobTitle.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
   def salary = '$' + (employee.salary as String)
   print '${salary.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
   println '${employee.phone_number.center(BALANCE_COLUMN_WIDTH)}${COLUMN_SEPARATOR}'

   // second line in each output row
   print '${COLUMN_SEPARATOR}${employee.hireDate.getDateString().center(COLUMN_WIDTH)}'
   def managerName = employee.managerFirstName ? 'Mgr: ${employee.managerFirstName[0]}. ${employee.managerLastName}' : 'Answers to No One'
   print '${COLUMN_SEPARATOR}${managerName.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
   print '${employee.departmentName.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
   String commissionPercentage = employee.commissionPercentage ?: 'No Commission'
   print '${commissionPercentage.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
   println '${employee.emailAddress.center(BALANCE_COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
   println ROW_SEPARATOR
}

最后一个代码段是从数据库中检索出的数据以相对吸引人的文本格式输出的地方。 该示例说明了闭包中的句柄如何命名才能更有意义。 在这种情况下,它们分别命名为idemployee并表示TreeMap中每个条目的键( Long )和值( Employee )。

最后一个代码片段中还有其他Groovy功能,值得特别提及。 委托的表示使用Groovy的Elvis运算符?: :) ,它甚至使Java的条件三元看上去也很冗长。 在此示例中,如果员工的佣金百分比符合Groovy真理标准,则使用该百分比; 否则,将打印“无佣金”。

录用日期的处理提供了另一个机会来宣传Groovy的GDK好处。 在这种情况下,使用Groovy GDK Date.getDateString()可以轻松访问Date类的仅日期部分(雇用日期不需要的时间),而无需显式使用String格式程序。 真好!

最后一个代码示例还演示了使用as关键字以更易读的方式强制(广播)变量,并演示了Java功能的更多利用,在这种情况下,它利用了Java String的replace(CharSequence,CharSequence)方法 。 但是,在此示例中,Groovy再次为String添加了更多好处。 该示例演示了Groovy支持使用下标(数组)表示法( [0] )仅提取经理名字的首字母,以仅从字符串中提取首个字符。

到目前为止,在这篇文章中,我已经展示了整个脚本的片段,同时我解释了每个片段中展示的Groovy的各种功能。 接下来显示整个脚本,该代码清单后跟屏幕快照,显示执行脚本时输出的显示方式。 上面显示了Groovy Employee类的完整代码。

generateReport.groovy:完整的脚本

#!/usr/bin/env groovy

// Add JDBC driver to classpath as part of this script's bootstrapping.
// See http://marxsoftware.blogspot.com/2011/02/groovy-scripts-master-their-own.html.
// WARNING: This location needs to be adjusted for specific user environment.
this.class.classLoader.rootLoader.addURL(
   new URL('file:///C:/oraclexe/app/oracle/product/11.2.0/server/jdbc/lib/ojdbc6.jar'))

int TOTAL_WIDTH = 120
String HEADER_ROW_SEPARATOR = '='.multiply(TOTAL_WIDTH)
String ROW_SEPARATOR = '-'.multiply(TOTAL_WIDTH)
String COLUMN_SEPARATOR = '|'
int COLUMN_SEPARATOR_SIZE = COLUMN_SEPARATOR.size()
int COLUMN_WIDTH = 22
int TOTAL_NUM_COLUMNS = 5
int BALANCE_COLUMN_WIDTH = TOTAL_WIDTH-(TOTAL_NUM_COLUMNS-1)*COLUMN_WIDTH-COLUMN_SEPARATOR_SIZE*(TOTAL_NUM_COLUMNS-1)-2

// Get instance of Groovy's Sql class
// See http://marxsoftware.blogspot.com/2009/05/groovysql-groovy-jdbc.html
import groovy.sql.Sql
def sql = Sql.newInstance('jdbc:oracle:thin:@localhost:1521:xe', 'hr', 'hr',
                          'oracle.jdbc.pool.OracleDataSource')

def employeeQueryStr =
'''SELECT e.employee_id, e.first_name, e.last_name,
          e.email, e.phone_number,
          e.hire_date, e.job_id, j.job_title,
          e.salary, e.commission_pct, e.manager_id,
          e.department_id, d.department_name,
          m.first_name AS mgr_first_name, m.last_name AS mgr_last_name
     FROM employees e, departments d, jobs j, employees m
    WHERE e.department_id = d.department_id
      AND e.job_id = j.job_id
      AND e.manager_id = m.employee_id(+)'''

def employees = new TreeMap<Long, Employee>()
sql.eachRow(employeeQueryStr)
{
   def employeeId = it.employee_id as Long
   def employee = new Employee(employeeId, it.first_name, it.last_name,
                               it.email, it.phone_number,
                               it.hire_date, it.job_id, it.job_title,
                               it.salary, it.commission_pct, it.manager_id as Long,
                               it.department_id as Long, it.department_name,
                               it.mgr_first_name, it.mgr_last_name)
   employees.put(employeeId, employee)
}

println '\n\n${HEADER_ROW_SEPARATOR}'
println '${COLUMN_SEPARATOR}${'HR SCHEMA EMPLOYEES'.center(TOTAL_WIDTH-2*COLUMN_SEPARATOR_SIZE)}${COLUMN_SEPARATOR}'
println HEADER_ROW_SEPARATOR
print '${COLUMN_SEPARATOR}${'EMPLOYEE ID/HIRE DATE'.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
print '${'EMPLOYEE NAME'.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
print '${'TITLE/DEPARTMENT'.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
print '${'SALARY INFO'.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
println '${'CONTACT INFO'.center(BALANCE_COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
println HEADER_ROW_SEPARATOR

employees.each
{ id, employee ->
   // first line in each row
   def idStr = id as String
   print '${COLUMN_SEPARATOR}${idStr.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
   def employeeName = employee.firstName + ' ' + employee.lastName
   print '${employeeName.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
   def jobTitle = employee.jobTitle.replace('Vice President', 'VP').replace('Assistant', 'Asst').replace('Representative', 'Rep')
   print '${jobTitle.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
   def salary = '$' + (employee.salary as String)
   print '${salary.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
   println '${employee.phone_number.center(BALANCE_COLUMN_WIDTH)}${COLUMN_SEPARATOR}'

   // second line in each row
   print '${COLUMN_SEPARATOR}${employee.hireDate.getDateString().center(COLUMN_WIDTH)}'
   def managerName = employee.managerFirstName ? 'Mgr: ${employee.managerFirstName[0]}. ${employee.managerLastName}' : 'Answers to No One'
   print '${COLUMN_SEPARATOR}${managerName.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
   print '${employee.departmentName.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
   String commissionPercentage = employee.commissionPercentage ?: 'No Commission'
   print '${commissionPercentage.center(COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
   println '${employee.emailAddress.center(BALANCE_COLUMN_WIDTH)}${COLUMN_SEPARATOR}'
   println ROW_SEPARATOR
}

在此博客文章中,我试图展示Groovy如何提供众多功能和其他语法支持,从而使编写脚本来生成可读取且相对吸引人的输出变得更加容易。 有关提供文本输出支持的更多常规Groovy脚本,请参阅格式化简单的表格文本数据 。 尽管这些是不错的常规解决方案,但我的帖子的目的是表明它很容易且不需要花费很多时间来编写用于使用Groovy生成自定义文本输出的自定义脚本。 Groovy的一些小技巧,例如轻松地将字符串居中,轻松地将Date转换为字符串,基于数组位置符号从字符串中提取任何所需的字符以及轻松地访问数据库数据,都使Groovy成为了生成基于文本的报表的强大工具。

参考:来自JCG合作伙伴 Dustin Marx的Groovy脚本报告,来自Inspired by Actual Events博客。

翻译自: https://www.javacodegeeks.com/2012/11/scripted-reports-with-groovy.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值