Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

21 Dec 2020

学习Groovy

Java的虚拟机JVM上可以支持多种语言,除了原住民Java之外,还有Kotlin,Closure,Scala等等。Groovy也是其中一员。参考List of JVM languages

groovy官方对其解释是

Apache Groovy is a powerful, optionally typed and dynamic language, with static-typing and static compilation capabilities, for the Java platform aimed at improving developer productivity thanks to a concise, familiar and easy to learn syntax. It integrates smoothly with any Java program, and immediately delivers to your application powerful features, including scripting capabilities, Domain-Specific Language authoring, runtime and compile-time meta-programming and functional programming.

以我稍微粗浅的理解,Groovy之于JVM,就像PowerShell之于.Net。Groovy和Java的区别,在Differences with Java有所说明。

安装Groovy

官方的安装指导是Install Groovy。在macOS上和IntelliJ IDEA结合的话,好像需要在homebrew上安装groovysdk而不是groovy。

IDEA有免费的Community版可以使用,可以到Download IntelliJ IDEA下载。对比不同的版本的话,参照IntelliJ IDEA Ultimate vs IntelliJ IDEA Community Edition。另外这篇文章How do I tell IntelliJ about groovy installed with brew on OSX也可以参考一下。从IDE integration可以看IDE集成相关的信息。

需要在IDEA里面创建Groovy的SDK,点击Create之后在文件浏览对话框中按cmd+shift+g,然后浏览到/usr/local/Cellar/groovysdk/3.0.7/libexec(这里假设Groovy的版本是3.0.7)。

安装好groovy之后,有几个可执行文件:

  • groovy用于执行脚本
  • groovyc用于编译脚本
  • groovysh控制台REPL
  • groovyConsole图形化的REPL
  • grape包管理器 The Grape dependency manager

groovysh - the Groovy repl-like shell

  • 历史记录功能来自jline2
  • 当次执行结果保存在_
  • def foo = "bar"声明的是局部变量,不会保存在shell的执行环境中。可以通过:= interpreterMode关闭。
  • 可以使用:help来输出帮助,查看所有命令,除了:=外,可以使用set来设置偏好。
  • 删除环境中的变量是通过:purge命令达成的,通过:edit可以调用外部编辑器,通过:doc java.util.List可以查看文档。
  • 偏好包括verbosity,show-last-result,sanitize-stack-trace,editor等等。
  • 启动配置在$HOME/.groovy/groovysh.profile,交互配置在$HOME/.groovy/groovysh.rc,历史保存在$HOME/.groovy/groovysh.history
  • 可以编制自定义命令,并使用:register注册。

一些问题:

如何给Groovy指定classpath或者使用jar包

groovy -cp ojdbc5.jar RunScript.groovy
this.class.classLoader.rootLoader.addURL(new URL("file:///path to file"))
    @GrabConfig(systemClassLoader=true)
    @Grab('com.oracle:ojdbc6:12.1.0.2.0')
    Class.forName("oracle.jdbc.OracleDriver").newInstance()

或者直接把jar包放在.groovy/lib

新手入门

def x = "hello"
==> hello
def x = "world
==> world
x
Unknown property: x // 为啥打印出这个饿?
// closure的例子
0.upto(4) {println "$it"}

在groovysh中,貌似#会把计算输出的显示给屏蔽掉。

出现异常的时候要用:clear来清楚之。

getProperteis

How to get all property names of a Groovy class? [duplicate]

println demoClass.getProperties().toString()

Groovy property iteration

f2.properties.each { prop, val ->
    if(prop in ["metaClass","class"]) return
    if(f.hasProperty(prop)) f[prop] = val
}

Groovy数据相关模块指导

官方文档Working with a relational database

下面是在groovysh之下通过jdbc连接filemaker数据库:

注意要把filemaker的jdbc驱动放在.groove/lib下面。

driver = 'com.filemaker.jdbc.Driver'
url = 'jdbc:filemaker://127.0.0.1/demodb/'
password = ''
user = 'dbuser'
sql = Sql.newInstance(url, user, password, driver)
===> groovy.sql.Sql@7219ac49

可以创建独立的DataSource:

def ds = new BasicDataSource(driverClassName: "org.hsqldb.jdbcDriver",
    url: 'jdbc:hsqldb:mem:yourDB', username: 'sa', password: '')
def sql = new Sql(ds)

sql.close()用来关闭链接。

// 限定连接对象的作用域
Sql.withInstance(url, user, password, driver) { sql ->
  // use 'sql' instance ...
}

执行sql:

sql.execute '''
  CREATE TABLE Author (
    id          INTEGER GENERATED BY DEFAULT AS IDENTITY,
    firstname   VARCHAR(64),
    lastname    VARCHAR(64)
  );
'''

sql.execute "INSERT INTO Author (firstname, lastname) VALUES ('Dierk', 'Koenig')"

执行带参数的sql:

def insertSql = 'INSERT INTO Author (firstname, lastname) VALUES (?,?)'
def params = ['Jon', 'Skeet']
def keys = sql.executeInsert insertSql, params
assert keys[0] == [1]

另一个示例:

def first = 'Guillaume'
def last = 'Laforge'
def myKeyNames = ['ID']
def myKeys = sql.executeInsert """
  INSERT INTO Author (firstname, lastname)
  VALUES (${first}, ${last})
""", myKeyNames
assert myKeys[0] == [ID: 2]

Better JDBC With Groovy Sql

// iterate over query's result set and "process" each row by printing two names
sql.eachRow("SELECT employee_id, last_name, first_name FROM employees")
{
   println "Employee ${it.first_name} ${it.last_name} has ID of ${it.employee_id}."
}
// when you know there's only one match, firstRow can be particularly useful
def employee140 = sql.firstRow("SELECT employee_id, last_name, first_name FROM employees where employee_id = ?", [140])
println "Employee 140 is ${employee140.first_name} ${employee140.last_name}"
// update the previously added employee
def updateStr =
"""update employees set email='dustin@marxblog.com'
   where employees.first_name='Dustin' and employees.last_name='Marx'"""
def numberRowsUpdated = sql.executeUpdate(updateStr)
println "${numberRowsUpdated} row(s) was/were updated."

JDBC with Groovy

Map dbConnParams = [
  url: 'jdbc:hsqldb:mem:testDB',
  user: 'sa',
  password: '',
  driver: 'org.hsqldb.jdbc.JDBCDriver']

def sql = Sql.newInstance(dbConnParams)
sql.withTransaction {
    sql.execute """
        INSERT INTO PROJECT (NAME, URL)
        VALUES ('tutorials', 'github.com/eugenp/tutorials')
    """
    sql.execute """
        INSERT INTO PROJECT (NAME, URL)
        VALUES ('REST with Spring', 'github.com/eugenp/REST-With-Spring')
    """
}
sql.cacheConnection {
    sql.execute """
        INSERT INTO PROJECT (NAME, URL)
        VALUES ('tutorials', 'github.com/eugenp/tutorials')
    """
    throw new Exception('This does not roll back')
}

blobs in groovy sql

import groovy.sql.*

def jdbcUrl = 'jdbc:derby:E:/Christof/Labor/GroovyJPA/db;create=true'
def sql = Sql.newInstance(jdbcUrl, '', '',
                           'org.apache.derby.jdbc.EmbeddedDriver')
sql.execute('CREATE TABLE testBlob (bits BLOB)')
def is = new ByteArrayInputStream('Alles in Butter'.getBytes())
def ps = sql.connection.prepareStatement(
                'INSERT INTO testBlob (bits) VALUES (?)')
ps.setBinaryStream(1, is)
ps.executeUpdate()
sql.eachRow('SELECT bits FROM testBlob') {
   println 'bits.class: ' + it.bits.class
   byte[] readBits = it.bits.getBytes(1L, it.bits.length()as int)
   println 'bits: ' + new String(readBits)
} 

madan712/SimpleBlobExample.java How to insert Binary data into a table using JDBC?

sqlite with jdbc

xerial / sqlite-jdbc

sql = Sql.newInstance("jdbc:sqlite:sample.db", "org.sqlite.JDBC")
sql.execute "insert into t1 values(0)"
t1 = sql.dataSet("t1")
t1.add(i:10)
sql.eachRow("select * from t1") { println "${it.i}" }

metadata = sql.connection.getMetaData()
t1_meta = metadata.getTables(null, null, "t1", null)

import java.nio.charset.StandardCharsets

sql.execute 'create table t2(b blob)'

sql.eachRow('select b from t2') {
  n = input.available()
  bytes = new byte[n]
  input.read(bytes)
  println  new String(bytes, StandardCharsets.UTF_8)
  println  n
}

文件IO

Working with IO

基于Java.io的File,InputStream,OutputStream,Reader,Writer。以及java.nio.file.path。

遍历一个目录:

dir = new File('C:\Windows`)
dir.eachFile{ file -> println file.name }

读入一个文件的内容:

file = new File('a.txt')
bytes[] content = file.bytes

正则表达式

用在FileMaker之上

  • 表格必须在Relationships里面出现,否则执行sql的时候会视为不存在

插入PDF

sql = Sql.newInstance(connPars)
stmt = sql.connection.prepareStatement("""
insert into t (pdf) values(PutAs(?, 'PDF '))
""")
stmt.setBytes(1, new File("doc.pdf").bytes)
stmt.executeUpdate()

其他参考

(本篇完)