Groovy 的SQL模塊

Groovy的SQL模塊提供了對(duì)JDBC的抽象翼抠,讓我們使用JDBC更簡(jiǎn)單苹丸,相關(guān)類在groovy.sql包下邀窃。本文參考自Working with a relational database挚瘟,一些代碼引用了官方文檔哄褒,需要了解詳細(xì)信息請(qǐng)參見原文绵载。

連接數(shù)據(jù)庫

和原文一樣埂陆,為了簡(jiǎn)單這里使用嵌入式數(shù)據(jù)庫HSQLDB,連接時(shí)在內(nèi)存中創(chuàng)建一個(gè)數(shù)據(jù)庫娃豹。數(shù)據(jù)庫驅(qū)動(dòng)可以使用Maven或Gradle導(dǎo)入焚虱,也可以使用Groovy自帶的Grape依賴管理器。不過在Intellij IDEA上懂版,下面的代碼有時(shí)候無法編譯鹃栽,說是找不到數(shù)據(jù)庫驅(qū)動(dòng)。這時(shí)候可以使用Gradle等工具管理依賴躯畴。

Groovy SQL的所有操作都在Sql類中民鼓,我們調(diào)用Sql的newInstance方法,傳遞URL蓬抄、用戶名丰嘉、密碼等參數(shù)即可連接到數(shù)據(jù)庫。這種方式需要自己手動(dòng)調(diào)用close方法關(guān)閉數(shù)據(jù)庫嚷缭。如果希望Groovy自動(dòng)關(guān)閉連接饮亏,可以使用withInstance方法耍贾,所有操作都在該方法的參數(shù)閉包中完成,之后會(huì)自動(dòng)關(guān)閉連接路幸。

@GrabResolver(name = 'aliyun', root = 'http://maven.aliyun.com/nexus/content/groups/public/')
@GrabConfig(systemClassLoader = true)
@Grab(group = 'org.hsqldb', module = 'hsqldb', version = '2.3.4')

class SqlDatabase {
    static void main(String[] args) {
        def sql = setUpDatabase()
    }

    static Sql setUpDatabase() {
        def url = 'jdbc:hsqldb:mem:test'
        def user = 'sa'
        def password = ''
        def driver = 'org.hsqldb.jdbcDriver'
        def sql = Sql.newInstance(url, user, password, driver)
        return sql
    }

如果使用數(shù)據(jù)源創(chuàng)建連接荐开。可以直接將數(shù)據(jù)源傳遞給Sql的構(gòu)造方法简肴,即可由數(shù)據(jù)源創(chuàng)建連接晃听。

def dataSource = new JDBCDataSource(
    database: 'jdbc:hsqldb:mem:yourDB', user: 'sa', password: '')
def sql = new Sql(dataSource)

創(chuàng)建數(shù)據(jù)表

我們可以使用Sql的execute方法執(zhí)行SQL語句。這里創(chuàng)建了一個(gè)數(shù)據(jù)表砰识。另外還有executeInsert和executeUpdate方法用于執(zhí)行插入和更新操作能扒。由于Groovy支持多行字符串,所以我們不用像Java那么費(fèi)勁仍翰。

    static void setUpTables(Sql sql) {
        println('準(zhǔn)備表')
        sql.execute('''
  CREATE TABLE author (
    id          INTEGER GENERATED BY DEFAULT AS IDENTITY,
    firstname   VARCHAR(64),
    lastname    VARCHAR(64)
  );
''')
        println('準(zhǔn)備表完成')
    }

增刪查改

插入數(shù)據(jù)

插入數(shù)據(jù)可以使用execute或executeInsert方法赫粥。它們的主要區(qū)別是executeInsert方法會(huì)返回一個(gè)列表观话,包含了插入數(shù)據(jù)對(duì)應(yīng)的所有主鍵予借。這兩個(gè)方法都支持?占位符和額外的參數(shù)列表,這時(shí)候Groovy會(huì)使用PreparedStatement來執(zhí)行SQL频蛔。它們也都支持GString和內(nèi)插字符串灵迫。

下面的例子使用了占位符和參數(shù)列表執(zhí)行插入操作。

    static void insertRow(Sql sql) {
        println('插入數(shù)據(jù)')
        String stmt = 'insert into author(firstname,lastname) values(?,?)'
        def params = ['Yi', 'Tian']
        def id = sql.executeInsert(stmt, params)
        assert id[0] == [0]
        params = ['Zhang', 'San']
        id = sql.executeInsert(stmt, params)
        assert id[0] == [1]
    }

查詢數(shù)據(jù)

如果喜歡傳統(tǒng)方式晦溪,可以使用query方法瀑粥,會(huì)返回一個(gè)JDBC結(jié)果集可供查詢。

def expected = ['Dierk Koenig', 'Jon Skeet', 'Guillaume Laforge']

def rowNum = 0
sql.query('SELECT firstname, lastname FROM Author') { resultSet ->
  while (resultSet.next()) {
    def first = resultSet.getString(1)
    def last = resultSet.getString('lastname')
    assert expected[rowNum++] == "$first $last"
  }
}

Groovy也提供了幾個(gè)方便的方法來獲取數(shù)據(jù)三圆。eachRow方法接受一個(gè)閉包參數(shù)狞换,在閉包中,我們可以使用索引或成員訪問符來獲取每行的結(jié)果舟肉。

rowNum = 0
sql.eachRow('SELECT firstname, lastname FROM Author') { row ->
  def first = row[0]
  def last = row.lastname
  assert expected[rowNum++] == "$first $last"
}

如果結(jié)果只有一行修噪,還可以使用firstRow方法,它會(huì)返回GroovyRowResult對(duì)象路媚。我們可以使用該對(duì)象提供的方法獲取數(shù)據(jù)黄琼。

def first = sql.firstRow('SELECT lastname, firstname FROM Author')

Groovy還提供了rows方法,它會(huì)返回一個(gè)GroovyRowResult對(duì)象列表整慎。

List authors = sql.rows('SELECT firstname, lastname FROM Author')
assert authors.size() == 3

更新數(shù)據(jù)

更新數(shù)據(jù)也可以使用execute方法脏款,或者使用executeUpdate方法。executeUpdate方法會(huì)返回受影響的行數(shù)裤园。

    static void updateAuthor(Sql sql) {
        def stmt = 'update author set firstname=?,lastname=? where id=?'
        def row = sql.executeUpdate(stmt, ['li', '4', 1])
        assert row == 1
    }

刪除數(shù)據(jù)

刪除數(shù)據(jù)使用execute方法撤师。

    static void updateAuthor(Sql sql) {
        def stmt = 'update author set firstname=?,lastname=? where id=?'
        def row = sql.executeUpdate(stmt, ['li', '4', 1])
        assert row == 1
    }

高級(jí)SQL操作

事務(wù)管理

事務(wù)管理使用Sql的withTransaction方法,在閉包中執(zhí)行的語句會(huì)自動(dòng)包括在事務(wù)中拧揽。如果某個(gè)操作發(fā)生異常剃盾,整個(gè)事務(wù)就會(huì)回滾。下面的例子中,第二個(gè)SQL語句故意寫錯(cuò)了万俗,整個(gè)事務(wù)會(huì)回滾湾笛,我們可以看到事務(wù)前后的數(shù)據(jù)數(shù)量是相同的。

    static void sqlTransaction(Sql sql) {
        println '事務(wù)管理'
        def rowsBefore = sql.firstRow('SELECT count(*) AS num FROM author').num
        try {
            sql.withTransaction {
                //正確語句
                sql.executeInsert("INSERT INTO author(firstname,lastname) VALUES('wang','5')")
                sql.executeInsert("INSERT INTO author() VALUES(4324,3423)")
            }
        } catch (ignore) {
            println(ignore.message)
        }
        def rowsAfter = sql.firstRow('SELECT count(*) AS num FROM author').num
        assert rowsBefore == rowsAfter
    }

批處理

Sql的withBatch方法提供了批處理功能闰歪。該方法的第一個(gè)參數(shù)是一次性批處理的數(shù)量嚎研。

sql.withBatch(3) { stmt ->
  stmt.addBatch "INSERT INTO Author (firstname, lastname) VALUES ('Dierk', 'Koenig')"
  stmt.addBatch "INSERT INTO Author (firstname, lastname) VALUES ('Paul', 'King')"
  stmt.addBatch "INSERT INTO Author (firstname, lastname) VALUES ('Guillaume', 'Laforge')"
  stmt.addBatch "INSERT INTO Author (firstname, lastname) VALUES ('Hamlet', 'D''Arcy')"
  stmt.addBatch "INSERT INTO Author (firstname, lastname) VALUES ('Cedric', 'Champeau')"
  stmt.addBatch "INSERT INTO Author (firstname, lastname) VALUES ('Erik', 'Pragt')"
  stmt.addBatch "INSERT INTO Author (firstname, lastname) VALUES ('Jon', 'Skeet')"
}

還可以使用預(yù)處理語句進(jìn)行批處理。使用預(yù)處理的優(yōu)點(diǎn)是執(zhí)行語句的速度更快库倘,缺點(diǎn)是所有批處理都是同一類型的語句临扮。如果需要處理多個(gè)表,需要多個(gè)批處理語句教翩。

def qry = 'INSERT INTO Author (firstname, lastname) VALUES (?,?)'
sql.withBatch(3, qry) { ps ->
  ps.addBatch('Dierk', 'Koenig')
  ps.addBatch('Paul', 'King')
  ps.addBatch('Guillaume', 'Laforge')
  ps.addBatch('Hamlet', "D'Arcy")
  ps.addBatch('Cedric', 'Champeau')
  ps.addBatch('Erik', 'Pragt')
  ps.addBatch('Jon', 'Skeet')
}

分頁

Groovy提供了SQL數(shù)據(jù)庫的分頁功能杆勇。前面提到的很多方法都有分頁的版本,我們只要傳入起始索引和要獲取的數(shù)據(jù)量饱亿,即可得到相應(yīng)的數(shù)據(jù)蚜退。

    static void sqlPagination(Sql sql) {
        println('分頁')
        def stmt = 'select id,firstname,lastname from author'
        sql.rows(stmt, 1, 3).each { printRow(it) }
        println('----------------')
        sql.rows(stmt, 4, 3).each { printRow(it) }
        println('----------------')
        sql.rows(stmt, 7, 3).each { printRow(it) }
        println('----------------')
    }

元數(shù)據(jù)

Groovy也能方便的獲取數(shù)據(jù)庫的元數(shù)據(jù)。要獲取數(shù)據(jù)庫元數(shù)據(jù)的話彪笼,調(diào)用Sql的connection.metaData屬性即可钻注。如果要獲取結(jié)果的元數(shù)據(jù),最好的辦法就是定義一個(gè)元數(shù)據(jù)閉包配猫,然后傳給相關(guān)方法幅恋,Groovy會(huì)保證元數(shù)據(jù)閉包只調(diào)用一次。當(dāng)然也可以直接在結(jié)果閉包中調(diào)用結(jié)果的getMetaData()方法獲取元數(shù)據(jù)泵肄,不過這樣這些代碼可能隨著結(jié)果的迭代重復(fù)執(zhí)行多次捆交。

    static void sqlMetaData(Sql sql) {
        println '元數(shù)據(jù)'
        def connection = sql.connection.metaData
        println("數(shù)據(jù)庫驅(qū)動(dòng)名稱:${connection.driverName}")
        println("數(shù)據(jù)庫版本號(hào):${connection.databaseMajorVersion}.${connection.databaseMinorVersion}")
        println("數(shù)據(jù)庫產(chǎn)品名:${connection.databaseProductName}")

        println('結(jié)果元數(shù)據(jù)')
        def metaClosure = { meta ->
            println("${meta.getColumnName(1)}\t${meta.getColumnName(2)}\t${meta.getColumnName(3)}")
        }
        def rowClosure = { row ->
            println "[id=${row[0]},first=${row[1]},last=${row[2]}"
        }
        sql.eachRow('SELECT id,firstname,lastname FROM author', metaClosure, rowClosure)
    }

命名參數(shù)和序號(hào)參數(shù)

SQL語句的參數(shù)不僅可以使用問號(hào)占位符,還可以使用命名參數(shù)和序號(hào)參數(shù)腐巢。命名參數(shù)很簡(jiǎn)單品追,Hibernate等很多其他框架都支持。

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

還可以使用問號(hào)形式的命名參數(shù)系忙,和上面等價(jià)诵盼。

sql.execute "INSERT INTO Author (firstname, lastname) VALUES (?.first, ?.last)", first: 'Jon', last: 'Skeet'

如果傳遞的參數(shù)是Map或者對(duì)象形式的,還可以使用帶序號(hào)的命名參數(shù)形式银还。Groovy會(huì)自動(dòng)解析合適的參數(shù)风宁。

class Rockstar { String first, last }
def pogo = new Rockstar(first: 'Paul', last: 'McCartney')
def map = [lion: 'King']
sql.execute "INSERT INTO Author (firstname, lastname) VALUES (?1.first, ?2.lion)", pogo, map
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蛹疯,隨后出現(xiàn)的幾起案子戒财,更是在濱河造成了極大的恐慌,老刑警劉巖捺弦,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饮寞,死亡現(xiàn)場(chǎng)離奇詭異孝扛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)幽崩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門苦始,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人慌申,你說我怎么就攤上這事陌选。” “怎么了蹄溉?”我有些...
    開封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵咨油,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我柒爵,道長(zhǎng)役电,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任棉胀,我火速辦了婚禮法瑟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘膏蚓。我一直安慰自己瓢谢,他們只是感情好畸写,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開白布驮瞧。 她就那樣靜靜地躺著,像睡著了一般枯芬。 火紅的嫁衣襯著肌膚如雪论笔。 梳的紋絲不亂的頭發(fā)上蘸鲸,一...
    開封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天祟牲,我揣著相機(jī)與錄音,去河邊找鬼凌彬。 笑死淫痰,一個(gè)胖子當(dāng)著我的面吹牛最楷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播待错,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼籽孙,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了火俄?” 一聲冷哼從身側(cè)響起犯建,我...
    開封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瓜客,沒想到半個(gè)月后适瓦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體竿开,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年玻熙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了否彩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嗦随,死狀恐怖胳搞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情称杨,我是刑警寧澤肌毅,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站姑原,受9級(jí)特大地震影響悬而,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜锭汛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一笨奠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧唤殴,春花似錦般婆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至配名,卻和暖如春啤咽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背渠脉。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來泰國打工宇整, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芋膘。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓鳞青,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親为朋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子臂拓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容