An Easier Java ORM(4) 複雑な問い合わせ

http://www.javalobby.org/articles/activeobjects/の日本語訳です。<< An Easier Java ORM(3) 実装の詳細を詰める - 目次に戻る - An Easier Java ORM(5) Active Recordパターンを実装する >>

複雑な問い合わせ - Complex Queries

もちろん、カプセル化と単純なCRUD操作だけでは、ORMとは言えません。もっと複雑な問い合わせをデータベースに対して実行し、結果をエンティティとして受け取る方法が必要です。これは驚くほど難しい問題です。

Of course an ORM is more than just database encapsulation and simple CRUD operations. We need some way to execute more complex queries against the database and receive entities as a result. It turns out that this is a surprisingly tough nut to crack.

多くのORM(特にJPA)は独自の問い合わせ言語を使うことでこの問題を解決しています。そこでは、次のようにして、18才を超えるPersonをすべて取得します:「from Person where age > 18」。本物のSQLのように見えますが、本物ではありません。つまり、全く新しい問い合わせ言語を学ぶ必要があり、さらに、全く新しい種類の問題を起こしうる、フレームワークの文字列解析と問い合わせも意識する必要があります。つまり、興味深いのですが、実際にやってみると少し混乱しそうです。

Most ORMs (most notably JPA) utilize their own query language to solve the problem. So, to get all of the Person(s) who are older than 18 would be something like this: "from Person where age > 18". As close as this may be to actual SQL, it really isn't. This means that not only do you have to learn a whole new query language, but the framework itself needs to worry about string parsing and query execution, which brings with it a whole new set of issues. In short, it was an interesting, but in practice it turns into a bit of a mess.

もう一度われわれの「英雄であるORM」の実装、ActiveRecord、に戻ると、この問題を独自の方法で解決しています。

Going back once again to our "hero ORM" implementation, ActiveRecord, we can see that it already solves the problem in a somewhat unique manner:

people = Person.find(:where => 'age > 18', :limit => 10)  

私にとって、これは非常に直感的です。何が起こるか非常に明らかで、ORMがPersonインスタンスを作成するために正しいフィールドを選択しているか意識する必要がなく、それに加えてSQLをとても自然に評価しています。ActiveRecordは上記の例で以下のSQLを実行するでしょう:「SELECT * FROM people WHERE age > 18 LIMIT 10」。よくわからない問い合わせ言語の解析は行いません。新たな言語を学ぶ必要もありません。

If you ask me, this is pretty darn intuitive. It's fairly obvious what's going on, we don't need to worry about selecting the right fields for the ORM to construct the Person instance, and on top of that it evaluates very naturally into SQL. ActiveRecord might execute the following SQL in the above case: "SELECT * FROM people WHERE age > 18 LIMIT 10". No obscure query-lang parsing; no new language to learn.

残念ながら、この例には少し難しい問題があります。ActiveRecordは、ハッシュのインスタンスを簡単に作成できるRubyの機能をメソッド呼び出しの一部で利用しています。たとえ、Rubyにこの機能がなかったとしても、名前付きパラメータを使えば同じことができます。一方、Javaにはそのようなものはありません。Mapインスタンスをすぐさま「簡単に」作る方法はなく、Javaには名前付きパラメータのサポートもありません。つまり、少々行き詰まりました。

Unfortunately, taking example from this is a bit challenging. ActiveRecord is making use of a Ruby feature which allows the easy creation of Hash instances as part of a method invocation. But even if Ruby didn't have this syntax, named parameters could have been used to achieve the same effect. Java on the other hand, has no such constructs. There is no "simple" way to construct a Map instance on the fly, nor does Java support named parameters. In short, we're a bit stuck.

百個の、nullでかどうかで動作を変える、パラメータを取るfindメソッドを定義することができます。findメソッドをオーバロードして様々な並び順のパラメータを受け入れるようにすれば、これはもっと簡単になります。しかしこれは、a) パラメータ型の衝突が起こることがり、かつ、b) 冗長で見苦しいため、問題があります。実際は、名前付きパターンの不足を切り抜けるために、あまり巧妙でない、それどころか奇妙なJavaのパターンが、われわれの唯一の頼りになります。そのパターンはメソッドチェーンです。

We could define a find method which takes a hundred parameters, varying its behavior accordingly based on which are non-null. This can be streamlined by overloading the find method to allow for different permutations of parameters. But this is problematic since a) we may have parameter type conflict, breaking the overloading, and b) it's verbose and ugly. In fact, our only recourse is to use a rather odd Java pattern which not-so-neatly gets around the lack of named parameters: method chaining.

戻り値がthisである、問い合わせパラメータを定義用のいくつかメソッドを持つQueryクラスを定義すれば、これらをfindに渡すことにより、(比較的)単純な構文で必要な情報を全て渡せるようになります。

If we define a Query class which has several methods used in defining query parameters, each returning this, we should be able to pass these to find and give it all the information it needs in a (comparatively) syntactically simple manner:

Person[] people = manager.find(Person.class, Query.select().where("age > 18").limit(10));  

Queryクラスへのメソッド呼び出しをチェーンすると、名前付きパラメータの使用を真似でき、複雑な問い合わせに必要となる余計なコードを抑えられます。実際には、問い合わせの解析を避けるために、このメソッドでパラメータ化されたprepared statementを利用することもできます。

By chaining the method calls on the Query class, we can simulate the use of named parameters and limit the excess code required to run a complex query. In fact, this method even allows us to make use of prepared statement parameterization while still avoiding query parsing:

Person drinkers = manager.find(Person.class, Query.select().where("age > ?", 21).limit(10));  
Person adults = manager.find(Person.class, Query.select().where("age > ?", 18).limit(10));  
Person[] drivers = manager.find(Person.class, Query.select().where("age > ?", 16).limit(10));  

ここではJavaの可変長引数の機能を利用し、where(String, Object…)メソッドに追加のパラメータを渡しています。上記の問い合わせは全て、パラメータ化された「SELECT id FROM people WHERE age > ? LIMIT 10」に解決され、単一のprepared statementとしてコンパイルされます。(「adults」と「children」の問い合わせのように)この文を再度呼び出しても、データベースはコンパイル済みの文に別のパラメータを渡すだけであり、効率的に実行できます。

Here all we're doing is taking advantage of the Java varargs feature and passing extra parameters to the where(String, Object…) method. Most databases will take the above queries, which all resolve to the parameterized "SELECT id FROM people WHERE age > ? LIMIT 10", and compile them all down to a single prepared statement. Whenever this statement is re-invoked (such as in the "adults" and "children" queries), the database will merely pass different parameters to the already compiled statement, streamlining execution. In fact, on most databases, the second two queries will execute anywhere from 2-5 times faster than the first (based on my own micro-benchmarking).

これによって偶然にも、prepared statementを一切使用しないActiveRecordと比べて、大きなパフォーマンス上の利点が得られます。われわれのORMではできる限りパフォーマンスをよくしたいので、これは良いしらせです。:-)

Incidentally, this is an area where we can gain a significant performance advantage over ActiveRecord, which doesn't utilize prepared statements at all. Given the fact that we want our ORM to be as performant as possible, this is good news. :-)

<< An Easier Java ORM(3) 実装の詳細を詰める - 目次に戻る - An Easier Java ORM(5) Active Recordパターンを実装する >>