GORMのクエリ

追記:2009/02/10

grails 1.1 で autoImportに対応してもらえました(^^。
   static mapping = {
        autoImport false
    }
[jira] (GRAILS-2596) 


GORM を使って記述したドメインクラスには、クエリの為のダイナミックメソッドが起動時に付与されます。
この「起動時に付与する」処理を行っているのは、 HibernateGrailsPlugin クラスです。このクラスは名前の通り Grailsプラグインの一つです。今回はクエリについて書きますので、プラグインの話はまたの機会にします。

GORMは、いくつかのクエリの手段を用意しています。
大きくは、
・ Dynamic Finders
・ Criteria
Hibernate Query Language(HQL)
に分かれますが、私は HQL を好んで利用しています。
Criteria と HQL のどちらを使うかは悩む所かもしれませんが、Criteria は可読性が良いように思えないし HQL でないと書けないクエリがあるので HQL を選んでいます。特に Groovy の場合は、HQLを使う場合でも動的にクエリを作りやすいですので Criteria の必要性を感じていません。

ただし、現在の Grails には、HQLを使った場合、やっかいな事があります。
それは、ドメインクラスに package を与えると、HQL で記述する際にpackage名を必ず付与しないといけない所です。これがあるが故に Criteria を選択しているという方もいらっしゃるかもしれません。

話を戻すと、具体的には、com.hoge.DomainClassA という名前のドメインクラスがあったとすると、次のように書く必要があります。

def results = DomainClassA.findAll(
     "from com.hoge.DomainClassA as d where d.name=:name",
     [name:'hoge'])

これは次のようにすこし知恵を絞って書くこともできます。

def results = DomainClassA.findAll(
    "from ${DomainClassA.class.name} as d where d.name=:name", 
    [name:'hoge'])

この方法を使えば、package名がややこしい時にはいくらか助かりますし、package名がとても長い場合には短くなります。
しかし、上で挙げた例では長くなってしまいました…。

ともあれ、短くなろうが長くなってしまおうが、個人的な趣味かもしれませんが、どうもすっきりしません。auto-import の設定が出来ればよいのですが、現在の Grails は、この設定ができないようです。
# 良く分かりませんが、JPA と絡むのでしょうか・・・。(JIRAには上げたのですが)

SpaceCardでは、Grailsが対応してくれることを祈りながら、DomainClassNameConverterというクラスで、回避しています。

もう一つクエリの話を書きますと、個人的には、HQL を記述できる findAll や executeQuery といったダイナミックメソッド、さらにトランザクションブロックを与える withTransaction が、どうもすっきりしません。

まず、findAll がすっきりしない理由ですが、これはクラスメソッドであるにも関わらずクラス名を記述しないといけない点です。
例えば、デフォルトパッケージに DomainClassA というドメインクラスがあった場合次のように記述できます。

def results = DomainClassA.findAll(
      "from DomainClassA as a where a.name=:name", [name:'hoge'])

上のコードでは、DomainClassA が二度現れます。これは何度みてもしっくりこないのです。

次に、executeQuery がしっくりこないのは、クラスメソッドでありながらまったく関係ないドメインクラスをフェッチできてしまう点です。
例えば、デフォルトパッケージに DomainClassA と DomainClassB というドメインクラスがあった場合、次のように記述できます。

def results = DomainClassA.executeQuery(
      "select b from DomainClassB as b where b.name=:name", [name:'hoge'])

どうも変です。

withTransaction がしっくりこないのは、executeQuery と同じ理由です。
デフォルトパッケージに DomainClassA と DomainClassB というドメインクラスがあった場合、次のように記述できます。

     DomainClassA.withTransaction { tx ->
          // id が 1 である DomainClassB を get
          DomainClassB b = DomainClassB.get(1) 

          b.name = 'hoge'
          b.save()
     }

やはり、どうも変です…。変さ加減がお分かりになるでしょうか。

上で挙げたどうもしっくりこない部分は、初学者の学習を妨げる要因になりえると思うし、慣れてしまうのも如何なものかと思ってしまいます。