Kuina-Dao 開発記 Query by Example
予定より遅れてしまいましたが,ようやく Query by Example の基本機能をコミットしました.
Query by Example を使用するには,
public interface EmployeeDao { List<Employee> findByExample(Employee employee); ・・・ }
のようなメソッドを DAO に定義します.
これで
Employee emp = new Employee(); emp.setName("シマゴロー"); List<Employee> list = employeeDao.findByExample(emp);
のように使うことができます.
引数に渡したエンティティ (永続化されていないインスタンス,「new entity」) の null
でないプロパティが検索条件に加えられます.
つまり,上記の場合は
SELECT employee FROM Employee AS employee WHERE (employee.name = :employee$name)
という JPQL が作成&実行されます.
引数に渡すエンティティのプロパティは ID プロパティ (主キー) も含めて,null
でなければ検索条件に含まれてしまいます.
つ・ま・り
プリミティブ型は常に検索条件に含まれてしまいます.
ここは賛否あるかな.
一応,Kuina-Dao 的オススメは,
- フィールドにプリミティブ型は使用しない.
@Id
アノテーションはフィールドに指定する.
です.
これにより,余計なプロパティが検索条件に含まれることが回避できます.
この場合,JPA 実装および Kuina-Dao はフィールドアクセスしかしないため,必要なら getter/setter をプリミティブ型にしても構いません.ヌルポ注意.
どのみち Tiger では勝手に変換してくれるのでその必要もあまりないと思いますが.
Query by Example では関連先エンティティのプロパティも検索条件に含めることができます.
例えば Product
と Category
の間に多対 1 の関連がある場合,ある Category
と関連を持つ Product
を全て検索するには次のようにします.
まずは DAO を用意して,
public interface ProductDao { List<Product> findByExample(Product product); ・・・ }
次のように使います.
Category category = new Category(); category.setName("魚"); Product product = new Product(); product.setCategory(category); List<Product> products = productDao.findByExample(product);
ここでは "魚" という名前を持つ Category
を Product
に設定し,DAO に渡しています.
ここから,
SELECT product FROM Product AS product INNER JOIN product.category AS category WHERE (category.name = :category$name)
という JPQL が作成&実行されます.
対多関連,つまりコレクションも扱うことができますが,コレクションの要素数が 1 の場合のみ検索条件に含められます.
Kuina-Dao のテストデータは,はぶさんの「SQL 書き方ドリル (isbn:4774122998)」を パクった インスパイヤされたので,Employee
と Department
は BelongTo
を介した多対多になっています.
っていうか,Employee
と BelongTo
が 1 対多,BelongTo
と Department
が多対 1 ですね.
最初の例と同じ DAO の同じメソッドを使って,
Department dept = new Department(); dept.setName("営業"); BelongTo belongTo = new BelongTo(); belongTo.setDepartment(dept); emp = new Employee(); Set<BelongTo> set = new HashSet<BelongTo>(); set.add(belongTo); emp.setBelongTo(set); list = employeeDao.findByExample(emp);
というように,Employee
の持つ belongTo
プロパティに要素数 1 の Set
を設定して,その先の BelongTo
に "営業" という名前を持つ Department
を設定して DAO に渡しています.
なんか,コードの流れと説明が逆になってるし (苦笑).
ともあれ (JW),ここから
SELECT employee FROM Employee AS employee INNER JOIN employee.belongTo AS belongTo INNER JOIN belongTo.department AS department WHERE (department.name = :department$name)
という JPQL が作成&実行されます.
現在のところ,関連は内部結合しか使えません.
また,関連も基点になるエンティティから 1 直線に辿ることしかできません.
Employee -> BelongTo -> Department
はできますが,
Employee -> BelongTo -> Salary
なんてことはできません.JPQL 的に.
また,現時点では SELECT 句に基点となるエンティティを指定しているため,例えば Product
のリストを取得して,その Category
にアクセスすると遅延ロードされます.
検索条件に含めているにもかかわらず,後で改めて遅延ロードするのはいかにももったいないわけですが,それを回避するには SELECT 句で複数のエンティティを並べる必要があり,そうすると問い合わせの結果は (基本的に) Object[]
になってしまいます.
それはなんだかなーとも思うわけですが,遅延ロードを回避できるようにすべく,
public interface EmployeeDao { List<Object[]> findByExample(Employee employee); ・・・ }
なんて感じでメソッドの戻り値型が Object[]
だったら
SELECT employee, belongTo, department FROM Employee AS employee INNER JOIN employee.belongTo AS belongTo INNER JOIN belongTo.department AS department WHERE (department.name = :department$name)
という JPQL が作成&実行されるようにしようかな,と.
FETCH JOIN もサポートしたいけど,それはメソッドへのアノテーションで指定かな.
後は,比較条件が '=' だけってのはちょっとアレなので,様々な条件を使えるようにしたいのですが,どうするのが使いやすいか悩ましいです〜.
指定しやすいのはエンティティのフィールドにアノテーションで指定することなんですが...
public class Employee { @Id private Integer id; @Like private String name; ・・・ }
みたいな.
でもでも,これだと同じプロパティを使って '=' でも比較したいとかいう場合に使えません.
なので,エンティティは諦めて DAO に指定するとするとこんな感じか?
public interface EmployeeDao { @Like({"name", "belongTo.department.Name"}) List<Employee> findByExample(Employee employee); ・・・ }
って感じで,アノテーションである演算子を使うプロパティ名を列挙しておくとか.
うーみゅ... ちょっとイマイチかなぁ.
もうちょっと考えるべし.
そうそう,ORDER BY はサポートしたいですね.
これについては
public interface EmployeeDao { List<Employee> findByExample(Employee employee, String orderby); ・・・ }
って具合に orderby
という名前の引数を定義して,
list = employeeDao.findByExample(emp, "height, weight DESC");
みたいに指定するのはどうかなぁって考えてます.
もっといいアイデアや要望などあれば是非お願いします.