# 實戰 Groovy: 用 Groovy 進行 JDBC 編程
_用 GroovySql 構建下一個報告應用程序_
這個月,隨著 Andrew Glover 向您演示如何用 GroovySql 構建簡單的數據報告應用程序,您對 Groovy 的實用知識會更進一步。GroovySql 結合利用閉包(closure)和迭代器(iterator),把資源管理的負擔轉移到 Groovy 框架本身,從而簡化了 Java 數據庫連通性(Java Database Connectivity,JDBC)的編程。
在 _實戰 Groovy_系列的前幾期中,您已經了解了 Groovy 的一些非常優美的特性。在 [第 1 期](http://www.ibm.com/developerworks/java/library/j-pg11094/?S_TACT=105AGX52&S_CMP=cn-a-j)中,學習了如何用 Groovy 對普通的 Java? 代碼進行更簡單、更迅速的單元測試。在 [第 2 期](http://www.ibm.com/developerworks/library/j-pg12144.html?S_TACT=105AGX52&S_CMP=cn-a-j)中,看到了 Groovy 能夠給 Ant 構建帶來的表現能力。這一次您會發現 Groovy 的另一個實際應用,即如何用它迅速地構建基于 SQL 的報告應用程序。
腳本語言對于迅速地構建報告應用程序來說是典型的優秀工具,但是構建這類應用程序對于 Groovy 來說特別容易。Groovy 輕量級的語法可以消除 Java 語言的 JDBC 的一些繁冗之處,但是它真正的威力來自閉包,閉包很優雅地把資源管理的責任從客戶機轉移到框架本身,因此更容易舉重若輕。
在本月的文章中,我先從 GroovySql 的概述開始,然后通過構建一個簡單的數據報告應用程序,向您演示如何將它們投入工作。為了從討論中得到最大收獲,您應當熟悉 Java 平臺上的 JDBC 編程。您可能還想回顧 [上個月對 Groovy 中閉包的介紹](http://www.ibm.com/developerworks/library/j-pg12144.html?S_TACT=105AGX52&S_CMP=cn-a-j),因為它們在這里扮演了重要的角色。但是,本月關注的重點概念是迭代(iteration),因為迭代器在 Groovy 對 JDBC 的增強中扮演著重要角色。所以,我將從 Groovy 中迭代器的概述開始。
## 進入迭代器
迭代是各種編程環境中最常見、最有用的技術。 _迭代器_是某種代碼助手,可以讓您迅速地訪問任何集合或容器中的數據,每次一個數據。Groovy 把迭代器變成隱含的,使用起來更簡單,從而改善了 Java 語言的迭代器概念。在清單 1 中,您可以看到使用 Java 語言打印 `String`集合的每個元素需要做的工作。
##### 清單 1\. 普通 Java 代碼中的迭代器
```
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 轉換后的樣子。
##### 清單 2\. Groovy 中的迭代器
```
class IteratorExample1{
static void main(args) {
coll = ["JMS", "EJB", "JMX"]
coll.each{ item | println item }
}
}
```
您可以看到,與典型的 Java 代碼不同,Groovy 在允許我傳遞進我需要的行為的同時,控制了特定于迭代的代碼。使用這個控制,Groovy 干凈漂亮地把資源管理的責任從我手上轉移到它自己身上。讓 Groovy 負責資源管理是極為強大的。它還使編程工作更加容易,從而也就更迅速。
## 關于本系列
把任何一個工具集成進您的開發實踐的關鍵是,知道什么時候使用它而什么時候把它留在箱子中。腳本語言可以是您的工具箱中極為強大的附件,但是只有在恰當地應用到適當的場景時才是這樣。為了這個目標, _[實戰 Groovy](http://www.ibm.com/developerworks/views/java/articles.jsp?sort_order=desc&expand=&sort_by=Date&show_abstract=true&view_by=Search&search_by=practically groovy)_這一系列文章專門介紹了 Groovy 的實際應用,并教給您什么時候、如何成功地應用它們。
* * *
## GroovySql 簡介
Groovy 的 SQL 魔力在于一個叫做 GroovySql 的精致的 API。使用閉包和迭代器,GroovySql 干凈漂亮地把 JDBC 的資源管理職責從開發人員轉移到 Groovy 框架。這么做之后,就消除了 JDBC 編程的繁瑣,從而使您可以把注意力放在查詢和查詢結果上。
如果您忘記了普通的 Java JDBC 編程有多麻煩,我會很高興提醒您!在清單 3 中,您可以看到用 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 替我處理底層資源,從而非常漂亮地讓我把注意力集中在手邊的任務 —— 執行一個簡單的查詢。
##### 清單 4\. 歡迎使用 GroovySql !
```
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](#code4)的第一行中,我創建了 Groovy 的 `Sql`類的實例,用它來連接指定的數據庫。在這個例子中,我創建了 `Sql`實例,指向在我機器上運行的 MySQL 數據庫。到現在為止都非常基本,對么?真正重要的是一下部分,迭代器和閉包一兩下就顯示出了它們的威力。
請把 `eachRow`方法當成傳進來的查詢生成的結果上的迭代器。在底層,您可以看到返回了 JDBC `ResultSet`對象,它的內容被傳遞進 `for`循環。所以,每個迭代都要執行我傳遞進去的閉包。如果在數據庫中找到的 `word`表只有三行,那么閉包就會執行三次 —— 打印出 `word_id`、 `spelling`和 `part_of_speech`的值。
如果將等式中我指定的變量 `row`去掉,而使用 Groovy 的一個隱含變量 `it`(它恰好就是迭代器的實例),代碼可以進一步簡化。如果我這樣做,那么前面的代碼就可以寫成清單 5 所示的這樣:
##### 清單 5\. GroovySql 中的 `it`變量
```
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`表插入一個新行。
##### 清單 6\. 用 GroovySql 進行插入
```
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`方法接收一列值,與查詢中的 `?`元素對應。
##### 清單 8\. 用 GroovySql 進行更新
```
nid = 5
spelling = "Nefarious"
sql.executeUpdate("update word set word_id = ? where spelling = ?", [nid, spelling])
```
刪除實際上與插入相同,當然,語法不同,如清單 9 所示。
##### 清單 9\. 用 GroovySql 進行刪除
```
sql.execute("delete from word where word_id = ?" , [5])
```
* * *
## 簡化數據操縱
任何想簡化 JDBC 編程的 API 或工具最好有一些好的數據操縱特性,在這一節中,我要向您再介紹三個。
### 數據集 (DataSet)
構建于 GroovySql 簡單性的基礎之上,GroovySql 支持 `DataSet`類型的概念,這基本上是數據庫表的對象表示。使用 `DataSet`,您可以在行中遍歷,也可以添加新行。實際上,用數據集是方便地表示表格的公共數據集合的方式。
但是,目前 GroovySql `DataSet`類型的不足之處是它們沒有代表關系;它們只是與數據庫表的一對一映射。在清單 10 中,我創建了一個來自 `word`表的 `DataSet`。
##### 清單 10\. 用 GroovySql 創建數據集
```
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 所示的那樣做:
##### 清單 11\. 用 GroovySql 進行負索引
```
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`這樣的變量如何在定義的時候就創建。
##### 清單 12\. 用 GroovySql 進行數據庫狀態報告
```
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))
}
}
```
* * *
## 結束語
在 _實戰 Groovy_本月的這一期文章中,您看到了 GroovySql 如何簡化 JDBC 編程。這個干凈漂亮的 API 把閉包和迭代器與 Groovy 輕松的語法結合在一起,有助于在 Java 平臺上進行快速數據庫應用程序開發。最強大的是,GroovySql 把資源管理任務從開發人員轉移到底層的 Groovy 框架,這使您可以把精力集中在更加重要的查詢和查詢結果上。但是不要只記住我這句話。下次如果您被要求處理 JDBC 的麻煩事,那時可以試試小小的 GroovySql 魔力。然后給我發封電子郵件告訴我您的體會。
在 _實戰 Groovy_下一月的文章中,我要介紹 Groovy 模板框架的細節。您會發現,用這個更加聰明的框架創建應用程序的視圖組件簡直就是小菜一碟。
- 實戰 Groovy
- 實戰 Groovy: SwingBuilder 和 Twitter API,第 2 部分
- 實戰 Groovy: SwingBuilder 和 Twitter API,第 1 部分
- 實戰 Groovy: @Delegate 注釋
- 實戰 Groovy: 使用閉包、ExpandoMetaClass 和類別進行元編程
- 實戰 Groovy: 構建和解析 XML
- 實戰 Groovy: for each 剖析
- 實戰 Groovy: Groovy:Java 程序員的 DSL
- 實戰 Groovy: 關于 MOP 和迷你語言
- 實戰 Groovy: 用 curry 過的閉包進行函數式編程
- 實戰 Groovy: Groovy 的騰飛
- 實戰 Groovy: 在 Java 應用程序中加一些 Groovy 進來
- 實戰 Groovy: 用 Groovy 生成器作標記
- 實戰 Groovy: 用 Groovy 打造服務器端
- 實戰 Groovy: 使用 Groovy 模板進行 MVC 編程
- 實戰 Groovy: 用 Groovy 進行 JDBC 編程
- 實戰 Groovy: 用 Groovy 進行 Ant 腳本編程
- 實戰 Groovy: 用 Groovy 更迅速地對 Java 代碼進行單元測試
- alt.lang.jre: 感受 Groovy