过去半年都在写Groovy,基本都是在实战中学习,从java转过来,也还没什么问题,毕竟基础,架构都没变。
Groovy的强大特性中,Closure,MetaClass都尝试过,Closure基本上就是当方法或者回调接口使用,再高级的用法就没用过了,没有特别体会什么好处,当回调接口就是省的定义接口,简洁了一些;MetaClass就有专门为一个模块做了些MetaClass,和GORM差不多的,给一些类加了save,update,delete之类的方法,不同的是GORM是存到数据库,我做的那模块是调用Restful Service,还不错,如果有需求是不能改变类而要给类加方法,还是很适用的。但是在绝大部分情况下,这些优势都体现不出来,到目前位置,还是对Groovy的一些语法印象深刻些,总结一些常用的语法:
1.List,和Map的初始化
这个用的最多了,原来java的时候,非要新建一个List或者Map,然后把element一个一个加进去,特繁琐。用Groovy在语法上简洁了很多。
定义一个List:
- List intList = [1,3,5,7,9]
- List stringList = ['a', 'b','']
定义一个Map:
- Map map = [a:4,b:'d']
定义Map的时候,对于key,如果像上面的例子一样,没有特别指示,那所有key都是String类型的,key值就是‘a’,'b',上面的例子等价于
- Map map = ['a':4,'b':'d']
某些时候,map的key可能是存在的一个变量,那这个时候,就要用括号把key抱起来,比如
- String a = 'I am Key'
- Map map = [(a):'I am Value']
也可以定义空的List或者Map
- List empty = []
- Map empty = [:]
需要知道的是,这种方式新建的List实例是ArrayList,Map实例是LinkedHashMap
2.二元运算符 ?:
java继承了C语言的问号表达式,二元运算符就是更加精简的问号表达式。形式:
- def result = a ?: b
意为,如果条件为真,即a为true或者a有值,则result为a,否则result为b
3.安全占位符
这个很有用,可以避免很多NullPointerException,但是也不能滥用了
- def result = obj?.property
代码里,obj是个对象,property是对象的一个熟悉,这行代码的意思,如果obj不为null,则会返回property属性的值,如果obj为null,这会直接返回null。语句可以一直串下去
- def result = a?.b?.c?.d...
4.字段操作
按照Groovy Bean的标准,默认的时候类里面的所有字段,Groovy都会帮忙生成一个get方法。在类的外部,即便你直接用了属性名而不用get方法去取值,拿到的也是通过get方法拿到的值。如果想直接拿属性值怎么办呢? 通过字段运算符:
- class A {
- String b
- }
- A a = new A()
- a.b //通过get方法拿值
- a.getB() //通过get方法拿值
- a.@b //直接拿值
5.GString
Java里有String,Groovy里新加了GString。Groovy的语法是,如果用单引号括起来的就是String,如果是双引号括起来的就是GString。所以Groovy不能直接定义原来Java里的char了。
String
- String s = 'This is a String'
GString
- GString s = "This is a GString"
两种方式定义出来的实例类型是不一样的。当然GString不是这么简单的,GString的强大之处在于它可以作为模板使用
- String name = 'count'
- int value1 = 23
- int value2 = 22
- GString s = "The value of $name is ${value1+value2}"
- println s
最终的输出是: The value of count is 55
倒数第二行,里面有关键字符 $, $即代表去引用上下文中的变量值,而${}中间不仅可以引用变量值,还可以加上一些运算,作为一个表达式
最后一行这是把GString转换成String,再输出。
String是常量,但是GString是变量,准确的说,在GString中的$仅仅是作为一个模板的占位符存在,GString会保存对那个对象的引用,如果引用的对象值发生改变,GString的值也就跟着改变。
需要注意的是,Map里面String和GString就算最终生成的String值一样,但是还是当作两个key存在的。很容易理解,但是很容易犯的错误,比如手误把单引号写成了双引号等等都会引起这个错误。
6.构造函数
- class ClassA {
- String s1
- int i1
- ClassB cb
- }
- class ClassB {
- String s2
- }
- new ClassA(s1:'pro in A', i1:5, cb:[s2:'pro in B'])
以上代码是可以工作的,Groovy会创建一个ClassA的实例,并把‘pro in A’ 和 5 分别设到属性s1和i1里。即便有一个复杂类型的熟悉cb,也能正常工作,ClassB的对象会被创建出来,并设到属性cb上。ClassB里的s2熟悉当然就是'pro in B'了。
可以看到构造函数里的参数很像Map的定义,确实,其实传入Map也是可以的
- new ClassA([s1:'pro in A', i1:5, cb:[s2:'pro in B']])
这个功能,在一些数据模型转换的时候,比较方便。打个比方,前台来的JSON数据,直接全转成Map,再用构造函数new一下,就全部出来了。注意的是,如果Map存在某个类里没有的属性值,会出错误的。
7.asType
用Groovy的是,可能经常看到这样的代码
- String a = '78'
- int b = a as int
- print b
第二行,有个关键字 as ,可以看出来作用就是把String类型的a转成int类型的b。它是怎么工作的呢,很简单,就是把这个操作代理给了String的asType方法,比如String有如下的方法(仅仅是例子,代码中不是这样)
- class String {
- Object asType(Class clz) {
- if(clz == Integer) {
- return Integer.parseInt(this)
- } else if(clz == GString) {
- return "$this"
- } else {
- return super.asType(clz)
- }
- }
- }
那么,String就可以用as运算符转换成int或者GString类型
- String a = '45'
- int b = a as int
- GString c = a as GString
上面的 clz == GString 是合法的,在Groovy里等价于 clz == GString.class
8. inspect和eval
eval就和javascript里的eval一样,就是直接执行一段Groovy脚本,可以用Eval.me(...script here...)直接调用。
inspect就是eval的反相操作了,就是把一个对象转成一个合法的可执行的脚本(我的理解,没仔细看过文档)。没试过其他对象,但是像Map,List之类的都可以转出来。这就够了,Map,List转出来的数据之于Groovy就相当于JSON之于JavaScript。在系统内部的时候就可以直接当作数据传输,虽然大家都不推荐这么用,但是我还是坚持,如果数据只包含Map,List,Number,String等简单类型,为什么不用呢?(如果Number里值是NaN或者Infinite的时候有问题,但是很容易解决,看Eval类里的其他两个方法就知道了)。呵呵,我也就这么用着,直到发现了一个Bug(http://stackoverflow.com/questions/7410252/groovy-inspect-handle-dollar-sign),没法解决,我才换掉了实现。
但是我对这个东西还是有很大期待,如果都是内部集成,不存在安全问题,且没有很好JSON支持的时候,这种方式还是很好的方式。
9.is方法
过去半年都在写Groovy,基本都是在实战中学习,从java转过来,也还没什么问题,毕竟基础,架构都没变。
Groovy的强大特性中,Closure,MetaClass都尝试过,Closure基本上就是当方法或者回调接口使用,再高级的用法就没用过了,没有特别体会什么好处,当回调接口就是省的定义接口,简洁了一些;MetaClass就有专门为一个模块做了些MetaClass,和GORM差不多的,给一些类加了save,update,delete之类的方法,不同的是GORM是存到数据库,我做的那模块是调用Restful Service,还不错,如果有需求是不能改变类而要给类加方法,还是很适用的。但是在绝大部分情况下,这些优势都体现不出来,到目前位置,还是对Groovy的一些语法印象深刻些,总结一些常用的语法:
迭代器在 Groovy 对 JDBC 的增强中扮演着重要角色。所以,我将从 Groovy 中迭代器的概述开始。
import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class JavaIteratorExample { public static void main(String[] args) { Collection coll = new ArrayList(); coll.add("JMS"); coll.add("EJB"); coll.add("JMX"); for(Iterator iter = coll.iterator(); iter.hasNext();){ System.out.println(iter.next()); } } } |
在清单 2 中,您可以看到 Groovy 如何简化了我的工作。在这里,我跳过了 Iterator
接口,直接在集合上使用类似迭代器的方法。而且, Groovy 的迭代器方法接受闭包,每个迭代中都会调用闭包。清单 2 显示了前面基于 Java 语言的示例用 Groovy 转换后的样子。
class IteratorExample1{ static void main(args) { coll = ["JMS", "EJB", "JMX"] coll.each{ item | println item } } } |
迭代是各种编程环境中最常见、最有用的技术。 迭代器 是某种代码助手,可以让您迅速地访问任何集合或容器中的数据,每次一个数据。Groovy 把迭代器变成隐含的,使用起来更简单,从而改善了 Java 语言的迭代器概念。在清单 1 中,您可以看到使用 Java 语言打印 String
集合的每个元素需要做的工作。
Groovy 的 SQL 魔力在于一个叫做 GroovySql 的精致的 API。使用闭包和迭代器,GroovySql 干净漂亮地把 JDBC 的资源管理职责从开发人员转移到 Groovy 框架。这么做之后,就消除了 JDBC 编程的繁琐,从而使您可以把注意力放在查询和查询结果上。
如果您忘记了普通的 Java JDBC 编程有多麻烦,我会很高兴提醒您!在清单 3 中,您可以看到用 Java 语言进行的一个简单的 JDBC 编程示例。
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class JDBCExample1 { public static void main(String[] args) { Connection con = null; Statement stmt = null; ResultSet rs = null; try{ Class.forName("org.gjt.mm.mysql.Driver"); con = DriverManager.getConnection("jdbc:mysql://localhost:3306/words", "words", "words"); stmt = con.createStatement(); rs = stmt.executeQuery("select * from word"); while (rs.next()) { System.out.println("word id: " + rs.getLong(1) + " spelling: " + rs.getString(2) + " part of speech: " + rs.getString(3)); } }catch(SQLException e){ e.printStackTrace(); }catch(ClassNotFoundException e){ e.printStackTrace(); }finally{ try{rs.close();}catch(Exception e){} try{stmt.close();}catch(Exception e){} try{con.close();}catch(Exception e){} } } } |
哇。清单 3 包含近 40 行代码,就是为了查看表中的内容!如果用 GroovySql,您猜要用多少行?如果您猜超过 10 行,那么您就错了。请看清单 4 中,Groovy 替我处理底层资源,从而非常漂亮地让我把注意力集中在手边的任务 —— 执行一个简单的查询。
import groovy.sql.Sql class GroovySqlExample1{ static void main(args) { sql = Sql.newInstance("jdbc:mysql://localhost:3306/words", "words", "words", "org.gjt.mm.mysql.Driver") sql.eachRow("select * from word"){ row | println row.word_id + " " + row.spelling + " " + row.part_of_speech } } } |
真不错。只用了几行,我就编码出与清单 3 相同的行为,不用关闭 Connection
,也不用关闭 ResultSet
,或者在 JDBC 编程中可以找到的任何其他熟悉的重要特性。这是多么激动人心的事情啊,并且还是如此容易。现在让我来仔细介绍我是如何做到的。
在 清单 4 的第一行中,我创建了 Groovy 的 Sql
类的实例,用它来连接指定的数据库。在这个例子中,我创建了Sql
实例,指向在我机器上运行的 MySQL 数据库。到现在为止都非常基本,对么?真正重要的是一下部分,迭代器和闭包一两下就显示出了它们的威力。
请把 eachRow
方法当成传进来的查询生成的结果上的迭代器。在底层,您可以看到返回了 JDBC ResultSet
对象,它的内容被传递进 for
循环。所以,每个迭代都要执行我传递进去的闭包。如果在数据库中找到的 word
表只有三行,那么闭包就会执行三次 —— 打印出 word_id
、 spelling
和 part_of_speech
的值。
如果将等式中我指定的变量 row
去掉,而使用 Groovy 的一个隐含变量 it
(它恰好就是迭代器的实例),代码可以进一步简化。如果我这样做,那么前面的代码就可以写成清单 5 所示的这样:
import groovy.sql.Sql class GroovySqlExample1{ static void main(args) { sql = Sql.newInstance("jdbc:mysql://localhost:3306/words", "words", "words", "org.gjt.mm.mysql.Driver") sql.eachRow("select * from word"){ println it.spelling + " ${it.part_of_speech}"} } } |
在这个代码中,我可以删除 row
变量,用 it
代替。而且,我还能在 String
语句中引用 it
变量,就像我在${it.part_of_speech}
中所做的那样。
前面的例子相当简单,但是 GroovySql 在处理更复杂的数据操纵查询(例如 insert
、 update
和 delete
查询)时,也是非常可靠的。 对于这些查询,您没有必要用迭代器,所以 Groovy 的 Sql
对象另外提供了 execute
和executeUpdate
方法。这些方法让人想起普通的 JDBC statement
类,它也有 execute
和 executeUpdate
方法。
在清单 6 中,您看到一个简单的 insert
,它再次以 ${}
语法使用变量替换。这个代码只是向 word
表插入一个新行。
wid = 999 spelling = "Nefarious" pospeech = "Adjective" sql.execute("insert into word (word_id, spelling, part_of_speech) values (${wid}, ${spelling}, ${pospeech})") |
Groovy 还提供 execute
方法的一个重载版本,它接收一列值,这些值与查询中发现的 ?
元素对应。在清单 7 中,我简单地查询了 word
表中的某个行。在底层,GroovySql 创建了普通 Java 语言 java.sql.PreparedStatement
的一个实例。
清单 7. 用 GroovySql 创建 PreparedStatement 的实例
val = sql.execute("select * from word where word_id = ?", [5]) |
更新的方式基本相同,也使用 executeUpdate
方法。还请注意,在清单 8 中 executeUpdate
方法接收一列值,与查询中的 ?
元素对应。
nid = 5 spelling = "Nefarious" sql.executeUpdate("update word set word_id = ? where spelling = ?", [nid, spelling]) |
删除实际上与插入相同,当然,语法不同,如清单 9 所示。
sql.execute("delete from word where word_id = ?" , [5]) |
任何想简化 JDBC 编程的 API 或工具最好有一些好的数据操纵特性,在这一节中,我要向您再介绍三个。
构建于 GroovySql 简单性的基础之上,GroovySql 支持 DataSet
类型的概念,这基本上是数据库表的对象表示。使用 DataSet
,您可以在行中遍历,也可以添加新行。实际上,用数据集是方便地表示表格的公共数据集合的方式。
但是,目前 GroovySql DataSet
类型的不足之处是它们没有代表关系;它们只是与数据库表的一对一映射。在清单 10 中,我创建了一个来自 word
表的 DataSet
。
import groovy.sql.Sql class GroovyDatasetsExample1{ static void main(args) { sql = Sql.newInstance("jdbc:mysql://localhost:3306/words", "words", "words", "org.gjt.mm.mysql.Driver") words = sql.dataSet("word") words.each{ word | println word.word_id + " " + word.spelling } words.add(word_id:"9999", spelling:"clerisy", part_of_speech:"Noun") } } |
您可以看到,GroovySql 的 DataSet
类型可以容易地用 each
方法对表的内容进行遍历,容易地用 add
方法添加新行, add
方法接受一个 map
表示需要的数据。
存储过程调用和负索引(negative indexing)可能是数据操纵的重要方面。GroovySql 使存储过程调用简单得就像在 Sql
类上使用 call
方法一样。对于负索引, GroovySql 提供了自己增强的 ResultSet
类型,它工作起来非常像 Groovy 中的 collections 。例如,如果您想获取结果集中的最后一个项目,您可以像清单 11 所示的那样做:
sql.eachRow("select * from word"){ grs | println "-1 = " + grs.getAt(-1) //prints spelling println "2 = " + grs.getAt(2) //prints spelling } |
您在清单 11 中可以看到,提取结果集的最后一个元素非常容易,只要用 -1 作索引就可以。如果想试试,我也可以用索引 2 访问同一元素。
这些例子非常简单,但是它们能够让您很好地感觉到 GroovySql 的威力。我现在要用一个演示目前讨论的所有特性的实际例子来结束本月的课程。
报告应用程序通常要从数据库拖出信息。在典型的业务环境中,可能会要求您编写一个报告应用程序,通知销售团队当前的 Web 销售情况,或者让开发团队对系统某些方面(例如系统的数据库)的性能进行日常检测。
为 了继续这个简单的例子,假设您刚刚部署了一个企业范围的 Web 应用程序。当然,因为您在编写代码时还(用 Groovy)编写了充足的单元测试,所以它运行得毫无问题;但是您还是需要生成有关数据库状态的报告,以便调优。您想知道客户是如何使用应用程序的,这 样才能发现性能问题并解决问题。
通常,时间约束限制了您在这类应用程序中能够使用的提示信息的数量。但是您新得到的 GroovySql 知识可以让您轻而易举地完成这个应用程序,从而有时间添加更多您想要的特性。
在这个例子中,您的目标数据库是 MySQL,它恰好支持用查询发现状态信息这一概念。以下是您有兴趣的状态信息:
- 运行时间。
- 处理的全部查询数量。
- 特定查询的比例,例如
insert
、update
和select
。
用 GroovySql 从 MySQL 数据库得到这个信息太容易了。由于您正在为开发团队构建状态信息,所以您可能只是从一个简单的命令行报告开始,但是您可以在后面的迭代中把报告放在 Web 上。这个报告例子的用例看起来可能像这样:
1. | 连接到我们的应用程序的活动数据库。 |
2. | 发布 show status 查询并捕获: |
a. 运行时间 | |
b. 全部查询数 | |
c. 全部 insert 数 | |
d. 全部 update 数 | |
e. 全部 select 数 | |
3. | 使用这些数据点,计算: |
a. 每分钟查询数 | |
b. 全部 insert 查询百分比 | |
c. 全部 update 查询百分比 | |
d. 全部 select 查询百分比 |
在清单 12 中,您可以看到最终结果:一个将会报告所需数据库统计信息的应用程序。代码开始的几行获得到生产数据库的连接,接着是一系列 show status
查询,让您计算每分钟的查询数,并按类型把它们分开。请注意像uptime
这样的变量如何在定义的时候就创建。
import groovy.sql.Sql class DBStatusReport{ static void main(args) { sql = Sql.newInstance("jdbc:mysql://yourserver.anywhere/tiger", "scott", "tiger", "org.gjt.mm.mysql.Driver") sql.eachRow("show status"){ status | if(status.variable_name == "Uptime"){ uptime = status[1] }else if (status.variable_name == "Questions"){ questions = status[1] } } println "Uptime for Database: " + uptime println "Number of Queries: " + questions println "Queries per Minute = " + Integer.valueOf(questions) / Integer.valueOf(uptime) sql.eachRow("show status like 'Com_%'"){ status | if(status.variable_name == "Com_insert"){ insertnum = Integer.valueOf(status[1]) }else if (status.variable_name == "Com_select"){ selectnum = Integer.valueOf(status[1]) }else if (status.variable_name == "Com_update"){ updatenum = Integer.valueOf(status[1]) } } println "% Queries Inserts = " + 100 * (insertnum / Integer.valueOf(uptime)) println "% Queries Selects = " + 100 * (selectnum / Integer.valueOf(uptime)) println "% Queries Updates = " + 100 * (updatenum / Integer.valueOf(uptime)) } } |
================================ 有关firstRow===========================
/*根据基站信息 从基站-经纬信息表中查道对应的经纬度*/
def findLngAndLatByCellInfo(mcc,mnc,lac,ci) {
String signPOSMoveByLogStr = """
select LNG,LAT
from CELL_LNG_LAT cell2LngLat
where cell2LngLat.mcc = '${mcc}' and
cell2LngLat.mnc = '${mnc}' and
cell2LngLat.lac = '${lac}' and
cell2LngLat.ci = '${ci}'
"""
db.firstRow(signPOSMoveByLogStr);
}
def list = Commons.getDao().findLngAndLatByCellInfo(mcc,mnc,lac,ci); //数据库缓存表中去查是否存在
if(list!=null){//如果存在则从缓存表中去取
lng = list.LNG;
lat = list.LAT;
}
主要注意这里的写法:
判断有没有值用null来比较
直接返回对象,点出来列就可以了,