まえがき
世の中にある、ほとんど全てのアプリケーションは、その仕事をするには、データにアクセスする必要がある。ドメイン駆動設計アプローチでは、あなたのドメインを構成するエンティティ用のリポジトリを定義し、作らなければならない。今日、Javaの開発者は、よくJPAを使って、これらのリポジトリを実装する。JPAを使うことでこの仕事は、容易になるが、なお多くの決まりきったコードを書く必要がある。Hadesは、オープンソースのライブラリでJPAとSpring上に作られており、データアクセス層の実装が著しく改善され、実際に必要なコード量を削減できる。この記事では、データアクセス層を開発するのに、Hadesが実際に何をしてくれるのかに関するガイドツアーと概観を提供する。そしてごく最近のバージョン2.0の新しいフィーチャも見ていく。
Hadesの本当の核心部分は、一般的なリポジトリ実装からできていて、基本的なCRUD(create, read, update そして delete) メソッドを提供しているばかりでなく、その上に、いくつかの機能も追加しているので、開発者は、その周りの共通なシナリオ(例えばページネーション)を実装する必要がない。この中には、リポジトリにエンティティのページをクエリする機能や動的にソート定義を加えたりすることなどが含まれる。
クエリ
一般的に実装しなければならない、ほとんどのデータアクセス操作は、クエリ操作なので、Hadesのコア フィーチャのおかげで、クエリを定義し、実行するのが最小限の工数で、できるぐらい簡単になる。実際にそれを実行する手順は、3ステップである。
- エンティティ特有のリポジトリ インターフェースを追加する
- クエリ メソッドを追加する
- クエリの微調整を行う
第一ステップで、あなたは、 GenericDao
を拡張するインターフェースを宣言する。これは、Hadesインターフェースで定義されているCRUD操作で、あなたのインターフェースでも使えるようになる。
public interface UserRepository extends GenericDao { … }
次のステップは、あなたのビジネス要求を果たすインターフェースへのクエリ用のメソッドを追加することである。
List findByUsername(String username);
これを見ると、クエリがメソッドからどのように生成されるのか、という疑問が出てくるだろう。もしあなたが更にメタデータを追加しなければ、{entity}.{method-name}
形式の JPA named queryを探し、あれば、それを使う。もし見つからない場合は、メソッド名をパースして、それからクエリを生成しようとする。前の例では select u from User u where u.username = ?
もちろん、クエリ パーサーは、もっと多くのキーワードをサポートしている。更には、参考ドキュメントを見て欲しい。
もしマニュアルでクエリを定義したいなら、Hadesの @Query
アノテーションを使って、メソッドで直接実行されるようにクエリを定義できる。
@Query("select from User u where u.lastname = ?"); List someReallyStrangeName(String lastname);
これには、好きなようにメソッド名を付けられる自由があり、同時に、決まりきったクエリ実行コードで悩まされることなく、クエリ定義を完全にコントロールできる。クエリの実行に関しては、他にも色々ある。例えば、クエリでページネーションを使うことや、クエリの修正の実行など。
Hadesの起動
ここまでクエリメソッドでどのようにリポジトリ インターフェースを作るかを見てきたので、どのようにしてこれらを起動して、使えるようにするのかは、興味のあるところだろう。リポジトリ インターフェースのインスタンスを作るには、GenericDaoFactory
インスタンスを生成し、それにリポジトリを作るように頼む。
EntityManager em = … // EntityManagerへのアクセスを取得 GenericDaoFactory factory = GenericDaoFactory.create(em); UserRepository userRepository = factory.getDao(UserRepository.class);
リポジトリは、大抵、依存性の注入によりクライアントに渡されるので、Springアプリケーションの内部では、Hadesを使うための巧妙な統合が存在する。そのためSpringアプリケーションでHadesを起動するためには、あなたは、Hadesネームスペースを単に使い、リポジトリのためにスキャンされるベース パッケージを宣言すればいい。
これで、GenericDao を継承した全てのリポジトリ インターフェースが検知されて、それぞれのSpring beansが生成される。もちろん、ネームスペースによって、何が検知されるべきかをもっとずっと絞り込むことができる。Hades Eclipseプラグイン は、 Spring IDEや SpringSource Tool Suite (STS) へのシームレスな統合を提供しているので、Hadesリポジトリを他のbeansから参照できたり、あなたのワークスペースでSpring beansとして扱うことができたりする。詳しくは、参考ドキュメントを見て欲しい。
オーディット
エンティティの生成、修正そして日付に従って、トラックすることは、非常に一般的な要求である。Hadesは、EntityListener
を提供しているので、透過的にそのようなジョブをやってくれる。Hadesオーディットを使うには、自分のorm.xml
で AuditingEntityListener
を定義する必要がある。
それから次のように Spring configでオーディットを有効にする。
参照される Spring beanである auditorAware
は、AuditorAware
interface を実装して、一般的には、現在のユーザーに対するセキュリティ モジュールをクエリして、interfaceを実装する。もし生成日と修正日だけをトラックしたいなら、アトリビュートを省けばよい。オーディットのフィーチャに関してもっと知りたければ、ドキュメントを見て欲しい。
JPA 2.0 とトランザクション リポジトリ
バージョン2.0では、Hadesは、JPA 2.0とそれに対応したバージョンのHibernate, EclipseLink と OpenJPAを ベースにしている。このバージョンで始めると、CRUD操作は、インストールするだけでトランザクション対応なので、非常に単純な場合では、トランザクション ラッピング層は、もう必要でない。更に、具象化されたリポジトリ インターフェースも簡単にトランザクション化できる。
public interface UserRepository extends GenericDao { @Transactional(readOnly = true); ListfindByUsername(); @Override @Transactional(readOnly = true, timeout = 60); List readAll(); }
見ての通り、クエリ メソッドを @Transactional
でアノテートすれば、これらをトランザクションに参加させることができる。アノテーションが嫌いなら使わないで、XMLベースのトランザクション設定を使えばよい。 GenericDao
に実装されているCRUD操作は、デフォルトでトランザクション可能である(readOnly
をtrueにセットすれば、リードオンリー操作になる)。これらのメソッドのトランザクション設定を再設定したければ、単にメソッドを再宣言して、お好みの @Transactional
を付けるだけである。さらに詳しくは、トランザクションに関する参考ドキュメントを見て欲しい。
仕様
最新のリリースの非常に素晴らしフィーチャは、GenericDao
を継承して仕様を実行できることである。仕様は、 Eric Evans とMartin Fowlerの両氏により作られたドメイン駆動設計の概念であるが、エンティティに関するビジネスルールを把握して、述語に煮詰めるものである。Hadesは、JPA 2.0 のcriteria APIをベースにしたそのような述語を簡単に作成できる抽象を提供している。あなたがauthorによって書かれた本を持っているとしよう。Hadesによって、以下のように仕様を定義できる。
class BookSpecifications { public SpecificationhasAuthorWithFirstnameLike(final String firstname) { return new Specification () { public Predicate toPredicate(Root root, CriteriaQuery cq, CriteriaBuilder cb) { return cb.like(root.join("author").get("firstname"), firstname); } } } public Specification hasGenre(final Genre genre) { return new Specification () { public Predicate toPredicate(Root root, CriteriaQuery cq, CriteriaBuilder cb) { return cb.equal(root.get( "genre"), genre); } } } }
確かにこれは、書くとしたらもっとも美しいコードではない(願わくば、Java 8のSingle Abstract Method (SAM) フィーチャでずっとマシなコードになる)。いい面は、まさにリポジトリ上で仕様を実行できる可能性を得たことである。
bookRepository.readAll(hasAuthorWithFirstnameLike("Oliv*"));
OK。宣言したクエリでできたことは、相当なものであろう。仕様は、それらを組み合わせて新しい仕様を作るときに、本当に威力を発揮する。Hadesは、すぐに組合せができるように仕様ヘルパー クラスを提供している。
bookRepository.readAll(where(hasAuthorWithFirstnameLike( "Oliv*").or(hasGenre(Genres.IT)));
このように、自分のリポジトリを拡張するのは、新しい仕様を加えるだけである。幾つもの仕様があれば、非常に柔軟なDSL風のAPIが作れ、リポジトリにクエリする際に、あらゆる標準的でないユースケースのためのクエリ メソッドで、リポジトリを汚染することもない。さらに詳しい 紹介がドキュメントにある。
拡張
リリース2.0には、拡張モジュールがあり、HadesをSpring MVCのようなアプリケーションのより上位層の技術とシームレスに統合できる。 PropertyEditor
と Spring 3.0 Converter
を提供しているので、透過的にエンティティを Spring MVCコントローラ メソッドにバインドできる。その時は、エンティティのIDと一緒に、HTTPリクエストからページネーション情報を動的に抽出するMVC拡張を使う。ユーザのページを表示するコントローラは、次のようになる。
@Controller class UserController { @Autowired UserRepository userRepository; @RequestMapping("/users") public void showUsers(Pageable pageable, Model model) { model.addAttribute("users", userRepository.readAll(pageable)); } @RequestMapping("/users/{id}") public String showUser(@PathVariable("id") User user, Model model) { model.addAttribute("user", user); return"user"; } }
見ての通り、showUsers(…)
メソッドで、ページネーション情報のための HttpServletRequest
を自分でパースする必要はない。注意して欲しいのはshowUser(…)
でid パス変数にバインドしているメソッド パラメータは、すでにエンティティである。Spring MVCインフラの設定は、この記事のスコープ範囲を超えているし、参考ドキュメントの拡張モジュールの章に、設定を簡単にしてくれる設定例が載っているので、見て欲しい。
次はなにか?
2.1.x系列の先では、Hadesは、 Spring Dataプロジェクトの一部、しかもプロジェクトのコアとして、他のデータストア向けのリポジトリ実装のベースとしての役割も担う。 Spring Dataは、 SpringSourceの一つで、NoSQLデータベースを含んだ様々な新規で、専門化したデータストアに対する慣用的なSpringサポートを提供することを狙っている。
要約
Hadesは、JPAでデータアクセス層を実装するのを著しく簡単にしてくれる。洗練されたCRUD操作、クエリ実行そして仕様の実行がタダで手に入る。それを単独でも、Springに統合してもうまく使うことができる。それ以上に、 Spring IDE/STS Eclipseプラグインの他に、 Spring Rooのアドオンもあるので、Rooで簡単にリポジトリを作ることができる。もっと知りたければ、プロジェクトのwebサイトを見て欲しい。
著者について
Oliver Gierke 氏は、VMware の事業部である SpringSourceの上級コンサルで、Hadesオープンソース プロジェクトを率いている。彼は、前の会社であるSynyxで約2年前にこのプロジェクトを始めた。