S2JDBCには、いくつかクエリを投げる方法があるけど、このタイプセーフに書くやり方が本命。 他のやり方はどうしても使えない場合のみに使ったほうがいい。それぐらい便利
S2JDBCはエンティティ駆動での開発が推奨されているのでまず起点はエンティティになる。 DBを起点にして開発したい場合は最初に1度だけソースのベースとして生成してもいいと思う。 Java のほうが基本的に表現力は上なので、DB からのリバース生成の場合、狙った Entity にならない可能性がある。
S2JDBC/エンティティを参考に作る
エンティティは単純なマッピングしかできないがどうしてもSQLの集計関数が使いたい場合はデータベース側でViewを作りそのViewに対するエンティティを作ると通常のテーブルと同じように扱うことができるようになる。
S2JDBC/S2JDBC-Genのgen-ddl
を使うとエンティティに対応するDDLとその名前集合クラスを自動生成してくれる。
この名前集合がタイプセーフのキモになる。
ここで必要ならばmigrateタスクを実行。これで
が一致することになる。重要
import static com.aaa.bbb.entity.Names.*; import static org.seasar.extension.jdbc.operation.Operations.*; //----- cutdown -------- Hoge hoge = jdbcManager .from(Hoge.class) .where( eq(hoge().piyoId(), 10), eq(hoge().name(), "たなか") ) .getSingleResult();
のように書く。ポイントは最初の2行のimport static
。
この2行を書くことにより名前集合とオペレーターが自由に使えるようになる。
whereメソッドの引数は可変になっていていくつでも並べることができる。そして並べるとそれだけで全てand条件で繋いだものとして解釈される。
なので↑の場合は
SELECT * FROM hoge WHERE hoge.id = 10 AND hoge.name = 'たなか'
と書いたことになる。eq
はイコールということ。
名前集合は自動生成時にそのカラム名だけでなく型情報も持っている。なので
eq(hoge().piyoId(), "10"),
このように書くとエラーになるのだ。IDは数値じゃないと駄目だよと。
このカラム名自体もhogeメソッドというHogeエンティティから生成された名前集合を使っていて、そのメソッドからさらにカラムを引いているのでカラム名のスペルミスは起こり得ないし別のテーブルのカラムを指定してしまうこともない、 型情報ももっているので頓珍漢な値も渡しにくくなっている。
DBとの境界面という難しい部分をJavaの世界に閉じ込めて書けるのでタイプセーフというわけだ。
メソッドチェーンなインターフェースになっていてSQLコードとJavaコードがなんとなく似たように書けるというのも気持ちよいところだ。
S2JDBCでタイプセーフに書いた場合、テーブルの結合の意味合いが通常言われる「結合」と少し変わってくる。
S2JDBCはエンティティ中心なので使う場合は受けの形がもう最初っから決まっているということになる。 なので結合して取ってこれるカラムを動的に増やしたりなにしたりということに興味は無い。 取ってくる形も受け取る形もgenの時点でもう固定なのだ。
エンティティでアノテーションでManyToOne
やOneToMany
指定した部分がここで効いてくる。
タイプセーフで書いた場合は結合指定はManyToOneとかOneToManyで書いたプロパティに値を取り込むトリガーとして働く
エンティティHogeがOneToMany指定のList<Piyo> piyoList
プロパティを持っているとして、
エンティティPiyoがManyToOne指定のHoge hoge
プロパティを持っているとする。
ここらへんの条件はS2JDBC/エンティティを参考に
import static com.aaa.bbb.entity.Names.*; import static org.seasar.extension.jdbc.operation.Operations.*; //----- cutdown -------- Hoge hoge = jdbcManager .from(Hoge.class) .leftOuterJoin(hoge().piyoList()) .where( eq(hoge().piyoId(), 10), eq(hoge().name(), "たなか") ) .getSingleResult();
genによる名前集合ではDDLとして必要になるプロパティだけでなくこのような関連に関するプロパティに関する情報も名前として生成する(今の場合ならpiyoList)。
そしてこの名前を使って↑のようにleftOuterJoin
メソッドを使って結合指定すると自動的にそのプロパティに値がエンティティのインスタンスとして取り込まれるのだ。
つまり
hoge1 +--piyoList +--piyo1 +--piyo2 +--piyo3
のような感じで取り込まれる。
逆に
import static com.aaa.bbb.entity.Names.*; import static org.seasar.extension.jdbc.operation.Operations.*; //----- cutdown -------- Piyo piyo = jdbcManager .from(Piyo.class) .leftOuterJoin(piyo().hoge()) .where( eq(piyo().id(), 10), eq(piyo().name(), "やまだ") ) .getSingleResult();
とすると
piyo1 +--hoge +--hoge1
と、hogeプロパティの中にhogeインスタンスが詰まった形でリターンされる
ここで注意が必要なのは絶対にEntity間の関係性とデータ内容を一致させること。OneToOneの関連をやってるのに実データは ManyToOne になっているとかの記述になっていると結合した際のオブジェクトにマッピングでデータが意味不明に減ったり、増えたりする。
結合条件は単にIDだけで結合するだけでなく
import static com.aaa.bbb.entity.Names.*; import static org.seasar.extension.jdbc.operation.Operations.*; //----- cutdown -------- Piyo piyo = jdbcManager .from(Piyo.class) .leftOuterJoin(piyo().hoge(), eq(piyo().hoge().flag(), true)) .where( eq(piyo().id(), 10), eq(piyo().name(), "やまだ") ) .getSingleResult();
のように持ってくるテーブル側に結合時に条件をつけることもできる。
特にレコードを再利用するようなタイプのロジックを内部で使っていたり、結合するキーをPK以外を使う場合に適切にこの拘束条件を使わないと意図せずレコードが増えてしまったりする。
OneToMany 結合があるような場合で、Many 側が where の条件に全部引っかかるような場合がありレコードが全部落ちてしまう場合は leftOuterJoin していても from 指定しているインスタンスの元になるレコードがなくなってしまうので結合条件を指定する必要がある。
leftOuterJoinメソッド等で結合を指定した場合で多段で結合しようとした場合に
org.seasar.extension.jdbc.exception.BaseJoinNotFoundRuntimeException: [ESSR0706]エンティティ(hoge)の結合(hoge.piyo.fuga)のベース(hoge.piyo)が見つかりません。
のようなエラーが出る場合がある。
この場合はpiyoの結合をすっ飛ばしていきなりfugaを結合しようとすると発生する。 なのでpiyoエンティティの条件や値が必要無くてもjoinにpiyoエンティティを含める必要があるということになる。
メソッドチェーンの中でorderByを指定するのではなく独立して作りたい場合
OrderByItem sortCondition = new OrderByItem(hogehoge().piyoId(), OrderingSpec.ASC);
こんな感じ
ショートカットとしては
OrderByItem sortCondition = asc(hogehoge().piyoId());
のような書き方もできるね。
外部からソート条件を取りたいときは↑、内部で生成したいときはショートカットという感じか