GroovyによるJDBC

Groovyを使用したJDBC

1. 前書き

この記事では、慣用的なGroovyを使用して、JDBCでリレーショナルデータベースをクエリする方法を見ていきます。

JDBCは、比較的低レベルですが、JVM上のほとんどのORMおよびその他の高レベルデータアクセスライブラリの基盤です。 もちろん、GroovyでJDBCを直接使用することもできます。ただし、かなり扱いにくいAPIがあります。

幸いなことに、Groovy標準ライブラリはJDBC上に構築され、クリーンでシンプル、しかも強力なインターフェースを提供します。 そこで、GroovySQLモジュールについて説明します。

other guidesがあるSpringなどのフレームワークを考慮せずに、プレーンなGroovyでJDBCを見ていきます。

2. JDBCとGroovyのセットアップ

依存関係の中にgroovy-sqlモジュールを含める必要があります。


    org.codehaus.groovy
    groovy
    2.4.13


    org.codehaus.groovy
    groovy-sql
    2.4.13

groovy-allを使用している場合は、明示的にリストする必要はありません。


    org.codehaus.groovy
    groovy-all
    2.4.13

Maven Centralでgroovy, groovy-sqlgroovy-allの最新バージョンを見つけることができます。

3. データベースへの接続

データベースを操作するために最初にしなければならないことは、データベースに接続することです。

Groovy SQLモジュールを使用してデータベースでのすべての操作に使用するgroovy.sql.Sqlクラスを紹介しましょう。

Sqlのインスタンスは、操作するデータベースを表します。

ただし、an instance of Sqlisn’t a single database connection。 接続については後で説明しますが、今は心配しないでください。すべてが魔法のように機能すると仮定しましょう。

3.1. 接続パラメータの指定

この記事全体を通して、HSQLデータベースを使用します。これは、主にテストで使用される軽量のリレーショナルDBです。

データベース接続には、URL、ドライバー、およびアクセス資格情報が必要です。

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

ここでは、Mapを使用して指定することを選択しましたが、可能な選択肢はそれだけではありません。

次に、Sqlクラスから接続を取得できます。

def sql = Sql.newInstance(dbConnParams)

次のセクションでその使用方法を説明します。

終了したら、関連するリソースを常に解放する必要があります。

sql.close()

3.2. DataSourceの使用

特にアプリケーションサーバー内で実行されるプログラムでは、データソースを使用してデータベースに接続することが一般的です。

また、接続をプールしたり、JNDIを使用したりする場合は、データソースが最も自然なオプションです。

GroovyのSqlクラスは、データソースを問題なく受け入れます。

def sql = Sql.newInstance(datasource)

3.3. 自動リソース管理

Sqlインスタンスの処理が完了したときに、close()を呼び出すことを覚えておくのは面倒です。結局のところ、マシンは私たちよりもはるかによく覚えています。

Sqlを使用すると、コードをクロージャでラップし、例外の場合でも、コントロールがコードを離れると、Groovyが自動的にclose()を呼び出すことができます。

Sql.withInstance(dbConnParams) {
    Sql sql -> haveFunWith(sql)
}

4. データベースに対してステートメントを発行する

これで、興味深いものに進むことができます。

データベースに対してステートメントを発行するための最も単純で特殊化されていない方法は、executeメソッドです。

sql.execute "create table PROJECT (id integer not null, name varchar(50), url varchar(100))"

理論的には、DDL / DMLステートメントとクエリの両方で機能します。ただし、上記の単純なフォームはクエリ結果を取得する方法を提供しません。 クエリは後で残しておきます。

executeメソッドにはいくつかのオーバーロードされたバージョンがありますが、ここでも、このメソッドと他のメソッドのより高度なユースケースを後のセクションで見ていきます。

4.1. データの挿入

少量で単純なシナリオでデータを挿入する場合は、前述のexecuteメソッドで十分です。

ただし、列を生成し(シーケンスや自動インクリメントなど)、生成された値を知りたい場合は、専用のメソッドが存在します:executeInsert.

executeについては、利用可能な最も単純なメソッドのオーバーロードを見て、後のセクションでより複雑なバリアントを残します。

したがって、自動インクリメントの主キー(HSQLDBの用語ではID)を持つテーブルがあるとします。

sql.execute "create table PROJECT (ID IDENTITY, NAME VARCHAR (50), URL VARCHAR (100))"

テーブルに行を挿入し、結果を変数に保存しましょう。

def ids = sql.executeInsert """
  INSERT INTO PROJECT (NAME, URL) VALUES ('tutorials', 'github.com/eugenp/tutorials')
"""

executeInsertexecuteとまったく同じように動作しますが、何を返しますか?

戻り値はマトリックスであることがわかります。その行は挿入された行であり(1つのステートメントで複数の行が挿入される可能性があることに注意してください)、その列は生成された値です。

それは複雑に聞こえますが、私たちの場合、これは最も一般的なものであり、単一の行と単一の生成された値があります:

assertEquals(0, ids[0][0])

後続の挿入では、生成された値1が返されます。

ids = sql.executeInsert """
  INSERT INTO PROJECT (NAME, URL)
  VALUES ('REST with Spring', 'github.com/eugenp/REST-With-Spring')
"""

assertEquals(1, ids[0][0])

4.2. データの更新と削除

同様に、データの変更と削除のための専用の方法が存在します:executeUpdate

繰り返しになりますが、これは戻り値のみがexecuteと異なり、最も単純な形式のみを見ていきます。

この場合の戻り値は、影響を受ける行の数である整数です。

def count = sql.executeUpdate("UPDATE PROJECT SET URL = 'https://' + URL")

assertEquals(2, count)

5. データベースのクエリ

データベースにクエリを実行すると、Groovyが取得され始めます。

JDBCResultSetクラスを扱うことは、必ずしも楽しいことではありません。 幸いなことに、Groovyはそのすべてをうまく抽象化してくれます。

5.1. クエリ結果の反復

ループはとても古いスタイルですが…私たちは最近、すべて閉鎖に取り組んでいます。

そして、Groovyは私たちの好みに合わせてここにあります。

sql.eachRow("SELECT * FROM PROJECT") { GroovyResultSet rs ->
    haveFunWith(rs)
}

eachRowメソッドは、データベースに対してクエリを発行し、各行に対してクロージャを呼び出します。

ご覧のとおり、a row is represented by an instance of GroovyResultSetは、いくつかの機能が追加された、単純な古いResultSetの拡張です。 続きを読んで詳細を確認してください。

5.2. 結果セットへのアクセス

すべてのResultSetメソッドに加えて、GroovyResultSetはいくつかの便利なユーティリティを提供します。

主に、列名に一致する名前付きプロパティを公開します。

sql.eachRow("SELECT * FROM PROJECT") { rs ->
    assertNotNull(rs.name)
    assertNotNull(rs.URL)
}

プロパティ名では大文字と小文字が区別されないことに注意してください。

GroovyResultSetは、ゼロベースのインデックスを使用した列へのアクセスも提供します。

sql.eachRow("SELECT * FROM PROJECT") { rs ->
    assertNotNull(rs[0])
    assertNotNull(rs[1])
    assertNotNull(rs[2])
}

5.3. ページ付け

結果を簡単にページングできます。つまり、オフセットから最大行数までのサブセットのみをロードできます。 これは、たとえばWebアプリケーションの一般的な懸念事項です。

eachRowおよび関連するメソッドには、オフセットと返される行の最大数を受け入れるオーバーロードがあります。

def offset = 1
def maxResults = 1
def rows = sql.rows('SELECT * FROM PROJECT ORDER BY NAME', offset, maxResults)

assertEquals(1, rows.size())
assertEquals('REST with Spring', rows[0].name)

ここで、rowsメソッドは、eachRowのように行を反復処理するのではなく、行のリストを返します。

6. パラメータ化されたクエリとステートメント

多くの場合、クエリとステートメントはコンパイル時に完全には修正されません。通常、パラメータの形式で静的部分と動的部分があります。

文字列の連結について考えている場合は、ここでやめてSQLインジェクションについて読んでください。

前のセクションで見たメソッドには、さまざまなシナリオで多くのオーバーロードがあることを前述しました。

SQLクエリとステートメントのパラメータを処理するオーバーロードを紹介しましょう。

6.1. プレースホルダー付きの文字列

プレーンJDBCに似たスタイルで、位置パラメータを使用できます。

sql.execute(
    'INSERT INTO PROJECT (NAME, URL) VALUES (?, ?)',
    'tutorials', 'github.com/eugenp/tutorials')

または、マップで名前付きパラメーターを使用できます。

sql.execute(
    'INSERT INTO PROJECT (NAME, URL) VALUES (:name, :url)',
    [name: 'REST with Spring', url: 'github.com/eugenp/REST-With-Spring'])

これは、executeexecuteUpdaterows、およびeachRowで機能します。 executeInsertはパラメーターもサポートしますが、その署名は少し異なり、注意が必要です。

6.2. Groovyストリング

GStringsとプレースホルダーを使用してGroovierスタイルを選択することもできます。

これまで見てきたすべての方法は、GStringsのプレースホルダーを通常の方法で置き換えるわけではありません。むしろ、それらをJDBCパラメーターとして挿入し、SQL構文が正しく保持されるようにします。引用符を付けたりエスケープしたりする必要がないため、挿入のリスクがありません。

これは完全に問題なく、安全でGroovyです:

def name = 'REST with Spring'
def url = 'github.com/eugenp/REST-With-Spring'
sql.execute "INSERT INTO PROJECT (NAME, URL) VALUES (${name}, ${url})"

7. トランザクションと接続

これまでのところ、非常に重要な懸念事項であるトランザクションについてはスキップしました。

実際、GroovyのSqlが接続を管理する方法についてもまったく話していません。

7.1. 短期間の接続

これまでに示した例では、each and every query or statement was sent to the database using a new, dedicated connection.Sqlは、操作が終了するとすぐに接続を閉じます。

もちろん、接続プールを使用している場合、パフォーマンスへの影響は小さい可能性があります。

それでも、if we want to issue multiple DML statements and queries as a single, atomic operation、トランザクションが必要です。

また、最初にトランザクションを可能にするには、複数のステートメントとクエリにまたがる接続が必要です。

7.2. キャッシュされた接続を使用したトランザクション

Groovy SQLでは、トランザクションを明示的に作成またはアクセスすることはできません。

代わりに、クロージャを指定してwithTransactionメソッドを使用します。

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クラスのメソッドを使用して、現在のトランザクションを手動でコミットまたはロールバックすることもできます。

sql.withTransaction {
    sql.execute """
        INSERT INTO PROJECT (NAME, URL)
        VALUES ('tutorials', 'github.com/eugenp/tutorials')
    """
    sql.commit()
    sql.execute """
        INSERT INTO PROJECT (NAME, URL)
        VALUES ('REST with Spring', 'github.com/eugenp/REST-With-Spring')
    """
    sql.rollback()
}

7.3. トランザクションなしのキャッシュ接続

最後に、上記のトランザクションセマンティクスなしでデータベース接続を再利用するには、cacheConnectionを使用します。

sql.cacheConnection {
    sql.execute """
        INSERT INTO PROJECT (NAME, URL)
        VALUES ('tutorials', 'github.com/eugenp/tutorials')
    """
    throw new Exception('This does not roll back')
}

8. 結論と参考資料

この記事では、Groovy SQLモジュールと、クロージャとGroovy文字列を使用してJDBCを拡張および簡素化する方法について説明しました。

これで、Groovyを振りかけると、プレーンな古いJDBCが少しモダンに見えると安全に結論付けることができます!

GroovySQLのすべての機能について説明したわけではありません。たとえば、batch processing、ストアドプロシージャ、メタデータなどは省略しています。

詳細については、the Groovy documentationを参照してください。

これらすべての例とコードスニペットの実装はthe GitHub projectにあります。これはMavenプロジェクトであるため、そのままインポートして実行するのは簡単です。