JavaEE6のCDIのProducer Methodで動的DI
以前からJSR-330(Dependency Injection for Java)を勉強しようと思っていて、何かサンプル的なものがないか探していたのですが・・・結局のところ、JSR-330をいちばん手っ取り早く覚えられるのはJSR-299(Contexts and Dependency Injection for the JavaTM EE platform)略してCDIのようです。
てなわけで、CDIのRIであるJBoss Weldを少し触ってみました。
http://docs.jboss.org/weld/reference/1.0.0/en-US/html/index.html
のWeldのドキュメントが詳しくてとても解りやすいです。
読んでいく中で興味を引かれたのが、CDIのProducer Methodという機能。これは大雑把に言うと、管理対象Bean上で、気軽に他の管理対象Beanを生成するメソッドを実装できるという機能です。
@SessionScoped @Named public class Login implements Serializable { private User user; public void login() { user = ... } public void logout() { user = null; } public boolean isLoggedIn() { return user != null; } @Produces public User getCurrentUser() { return user; } }
これはWeldのサンプルを簡単に略したものですが、このようにLoginオブジェクトでユーザオブジェクトを作成し、そのユーザを返すgetterメソッドに対して@Producesを定義した場合、このuserオブジェクトを他のオブジェクトにDIできるようになります。
@RequestScoped @Named public class Hoge { @Inject private User user; public User getUser() { return user; } }
みたいな形で。
これの何が良いかというと、動的にDI対象オブジェクトを決定するロジックをかなり簡単に定義できるようになるってことですね。この例ではユーザ情報をDIしていますが、例えばログインユーザの種別毎に何か違ったロジックを動かしたい場合、JPAの継承を使ってユーザのクラスを変えたりとか、ユーザ情報から動的に利用したいストラテジーを決定してそれをDIしたりとか・・・みたいな処理を容易く実装できるようになります。
今までのDIコンテナは、実装とインターフェイスを分離して、実装を決定する部分をファイルに外出ししたりすることで「実装の切り替えが容易になる」と謳っていました。・・・が実際には、実装をソース上でnewするのと、ファイルに書き出すのとでは「コンパイルしなくても切り替えられる」以外にあまり利点を見出すことが出来ませんでした。Webアプリケーションの場合、その外出ししたファイルもwar内に固めて一緒にデプロイするので、単にコンパイルチェックできない記述が増えただけになってしまい、逆にデメリットが増えてしまった感すらありました。
そういった設定ファイル地獄を解消する為に、規約を利用したDI対象オブジェクトの自動決定機能が使われるようになりましたが、規約通りに定義しないとめんどくさい→複数実装を登録すると自動決定出来なくなって規約から外れる→基本的に実装を一つしか登録しなくなる・・・という流れが起こってしまいました。またファイルにしろ規約による自動定義にしろ、どちらも「デプロイ時には対象オブジェクトは決定されている」ことが前提であって、動的に実装を切り替えるにはDI対象から外してFactoryを使うか、ファイル上で簡易言語を記述して何とかする・・・ぐらいしか主な手段がありませんでした。これらによってDIコンテナの利用者は益々「実装を切り替える」という理想からは遠ざかってしまい・・・結局のところ、今までのDIコンテナ利用者の多くは、DI機能だけでメリットを見出すことは難しく、AOP機能の土台として利用していただけに過ぎなかった気がします。
でもこのCDIなら、開発者が手軽にオブジェクトのファクトリーを定義してどんどん使っていくことが出来るため、元々DIコンテナで謳っていた「オブジェクトを手軽に切り替える」ことがとても容易に実現できます。本当の意味で、DIによってJavaのプログラミングを大きく変えることが出来る時代がやってきた・・・のかもしれません。
このProducer Method機能以外にも、気になる機能としてはDisposer Method機能があります。これはProducer Method機能で作成した管理Beanが属するスコープが終了するときに、後処理を呼び出すことが出来る機能です。これを上手く使えば、今のJavaの言語にはない、RubyやC#のブロックによる終了処理のようなリソースの開放処理を、もっと気軽に管理できるようになるかも・・・
正直言って今まであまり期待していなかったCDIですが、実際調べてみると結構使えそうな気がします。少なくとも今現在DIコンテナを利用した開発を行っている人なら、CDI利用を検討してみる価値は十分あるんじゃないかと感じました。
ペルソナ3ポータブルクリア
女主人公編でクリア。1月に入ってから延々とタルタロスとモナドを巡ってペルソナ作りやっていたら、合計160時間ぐらいになってました(汗)
女主人公編は男性仲間とのコミュがあったり「彼」とのコミュがあったりして、FESで複数回クリアしていたのに新鮮に楽しむことができました。修学旅行イベントでの立場の逆転には思わず笑ってしまいましたw
まぁでも今回の最大のポイントはなんといっても「スキルカード」でしょう。これのおかげで、PS2版ではムドだらけになって放置していたタナトスとか、出産完了後放置されていたメサイアとかを、○×地獄しなくても好きに強化できるようになったのが大きいです。というわけで、自分の最終ペルソナメンバーはこういう形に。以下少々ネタバレ
各種カオート&闇攻撃担当。コミュマスターできなかったので、勝ちオタはつけられていません。アリスに勝ちオタは似合わないというのもありますがw
物理斬撃担当にして主力ペルソナ。レベルがあまりあがってないのは、終盤になって勝ちオタつけたいが為に作り直したからです。実際は最初に作ったときからずっと主力でした。絶好調時の空間殺法かアドバイス+デスバウンドでクリティカル狙いか迷った挙句、両方つけて斬撃マスター化することに。斬撃無効以上の耐性持っていない敵については、ほぼこのタナトス一人で片付けることができます。絶好調空間はマーガレット戦で大活躍しました。
- シヴァ LV90
- 勝利の雄たけび
- 不屈の闘志
- 武道の素養
- 治癒促進・大
- 氷結吸収
- チャージ
- アドバイス
- プララヤ
物理貫通担当&チャージ担当。斬撃無効以上の耐性にはこちらを使用。アドバイス+プララヤは超強力で、こちらが主力でも十分な強さなのですが、今回はタナトスを活躍させたかったのでサブ的な立場に。チャージは彼が専門で受け持つので、他の物理系ペルソナにはつけていません。
- ヴィシュヌ LV86
- アカシャアーツ
- アドバイス
- 武道の素養
- ゴッドハンド
- 闇反射
- メシアライザー
- 勝利の息吹
- 打撃吸収
物理打撃担当。タナトスとシヴァがいればほとんどの敵は倒せるので、出番は斬撃&貫通無効の敵が出たときくらいでした。
- メサイア LV99
- 電撃ブースタ
- 電撃ハイブースタ
- 魔法スキル強化
- 勝利の雄たけび
- マハジオダイン
- 明けの明星
- 真理の雷
- 闇反射
電撃&万能担当にしてもう一人の主力ペルソナ。3ブースタ+マハジオor真理はザコ敵掃討でも対マーガレット戦でも大活躍でした。万能攻撃強化できる唯一のペルソナなので明けの明星もつけたのですが、マーガレット戦の3姉妹モードで、チャージした後テトラカーンされたときくらいしか出番はなかったです。
- アスラおう LV93
- アギダイン
- マハラギダイン
- 不動心
- 火炎ブースタ
- 闇反射
- 魔術の素養
- 勝利の雄たけび
- 火炎ハイブースタ
火炎担当。テレッテコミュをマックスにし損ねたのでラグナロクが使えず、アギダインで代用。火炎担当は誰にしようか迷ったのですが、やはり真1のボスだった彼にということで
- ガブリエル LV86
- 勝利の息吹
- サマリカーム
- 魔術の素養
- ニブルヘイム
- 氷結ハイブースタ
- マハブフダイン
- 氷結ブースタ
- 闇反射
氷結担当。途中まではマハンマオン+ハマブースタも持っていたので、光担当としても活躍。どうしても大天使は優遇して育ててしまいますw
- ノルン LV70
- マハガルダイン
- 疾風ハイブースタ
- 万物流転
- 疾風ブースタ
- 治癒促進・大
- 電撃吸収
- 魔術の素養
- 勝利の息吹
疾風担当。強いんですけど元のレベルが低いので全書からの引き出し価格も低いということで、テオ用ペルソナやコミュ用ペルソナを用意する枠減らしの為に消されることが多かった、ちょっとかわいそうな存在(汗)
- サンダルフォン LV80
- アカシャアーツ
- ハマブースタ
- マカラカーン
- 勝利の息吹
- 回転説法
- 治癒促進・大
- アムリタ
- 闇反射
光担当・・・だったのですが、彼も枠減らしの為に消されていたことが多かった(汗)
- ルシフェル LV96
- 氷結吸収
- メシアライザー
- コンセントレイト
- アカシャアーツ
- 勝利の雄たけび
- 闇反射
- 斬撃吸収
- 明けの明星
コンセントレイト&回復担当。明けの明星をメサイアに継承させてしまった関係で中途半端な存在になってしまったので、今回はコンセ&回復に徹してもらうことにしました。アカシャを消してサマリカームか不動心を入れた方がよかったかも。
他に補助役とかカジャ・ンダ役とかデカジャ・デクンダ役とかも考えたのですが、主人公はどっちかというと攻撃担当なので作りませんでした。
メンバーは、ボス戦ではンダ役の真田、カジャ役のアイギス、回復役のゆかりが一軍メンバー。FESではテレッテが一軍だったのですが、仲間に指示ができるようになったP3Pではアイギスの価値が飛躍的に高まったので主力交代に。タルタロスではレベルの低い方から3人選んでいたのでメンバーは流動的でしたが、最後のモナドではWブースタの真田・美鶴にヴァーユバンクルつけたゆかりの構成がメインでした。
今回仲間に指示ができるようになったことで、強くなったのは美鶴先輩とアイギス。美鶴は前作ではコンセンタラフーな人でしたがw、自分で使うとWブースタ効果でめちゃくちゃ強いです。アイギスは前作ではカジャの無駄がけが多くて使い物にならなかったのですが、自分で指示すれば何の問題もなし。加えてオルギアモードも強化されてFESのメティス同様HP・SPを消費しなくなったので、ザコ戦でも活躍できます。
アイギス強化の割を食ったのがテレッテ。AIが良好だったのでPS2版では主力だったのですが、今回はあまり出番が無かったです。でも絶好調空間殺法は流石の強さ。
ゆかりはPS2版でも普通に使っていたのですが、指示できるようになったので回復役として鉄壁な立場に。ただ状態異常回復であまり頼れないのが難点でしょうか。メシアライザーか、せめてアムリタは覚えてほしかったのですけど。
真田先輩は、自分はFESから始めたからかあまりタルンダ先輩なシーンを見ることもなかったので、PS2版でもP3Pでも変わらず主力でした。2回攻撃してくるマガレ戦では一旦外してみましたが、相手の攻撃が強力すぎて、やっぱり頼ることにw
天田はブースタを持たないので、どうしても中途半端な立場に。物理スキルよりもハマブースタを残してほしかった・・・自分が作るペルソナもそうですけど、器用貧乏は結局最終的にはあまり役に立たないというわかりやすい例になってしまったというか・・・いや、天田自体は嫌いじゃないんだけど。コロマルは自分で使うと意外と便利。速度が速くて攻撃のクリティカル率が結構高いので、ザコ戦ではかなり役に立ちました。
ボス戦で一番面白かったのは、やはりマーガレット戦でしょうか。テオ戦は一つ間違えたらアウトという緊張感はありますが、専用ペルソナの用意がめんどくさいのと、あまり行動の選択の余地が無いのがどうも・・・
マーガレット戦は、最初の四天王モードが一番辛かったです。何せクリティカル率が高くて、自分は無事でも仲間がバタバタと倒れてしまう・・・LV99なのに(汗)ハードでこうだから、マニアックスではどうなってしまうんだろう・・・
取りとめも無くだらだらと書いてみましたが、リメイク作でここまで楽しめれば大満足です。あまり携帯ゲーはやらないのですが、この内容ならP4あたりも出してほしいとか思ってみたり。
- 出版社/メーカー: アトラス
- 発売日: 2009/11/01
- メディア: Video Game
- 購入: 35人 クリック: 353回
- この商品を含むブログ (204件) を見る
PSP goでP3P
P3Pをやりたいが為に買ってきました。
自分はあまり携帯ゲーやらないし、P3Pさえ快適にできればよかったので、UMD使えないのは問題なかったのでこれにしました。大きさはiPhoneより一回り大きいくらい。重さはiPhoneと同じくらいかな?
ゲームは直接DLするかPCにMedia Goってソフトを入れるか、PS3からDLするかの3択なんですが、自分はMedia Goを入れてみました。iTunesに比べると色々と使い辛くて微妙なソフトです・・・が、取り敢えずソフトは問題なく購入&DLできました。PS2版P3FESに比べると、ロードは爆速で全く待たせませんね。キタロー編はFESで散々やったので、ここは女主人公編を選択。名前は考えるのめんどくさかったので井上麻里奈でw 細かい部分が変わっているので、新作として楽しめそうです。ハードで始めたので、タルタロスボス戦の度に毎回イゴりそうになりながらも、なんとか無事に進めています。
・・・一つ困るのは、直前までTOVをやり込んでいたので、テレッテがユーリの声だと思うだけで複雑な心境になってしまうことぐらいでしょうか(汗)
PSP go「プレイステーション・ポータブル go」 ピアノ・ブラック (PSP-N1000PB)【メーカー生産終了】
- 出版社/メーカー: ソニー・コンピュータエンタテインメント
- 発売日: 2009/11/01
- メディア: Video Game
- 購入: 2人 クリック: 237回
- この商品を含むブログ (79件) を見る
- 出版社/メーカー: アトラス
- 発売日: 2009/11/01
- メディア: Video Game
- 購入: 35人 クリック: 353回
- この商品を含むブログ (204件) を見る
GlassFish V3 b67を使ってJava Persistence 2.0を試す
久々の日記です。
EclipseLinkのJPA2.0の実装具合を最近のGlassFish V3を動かしてチェックしていたのですが、b67になってようやく基本的な機能が実装されたようです。なので、簡単に動かしてみました。動作環境はGlassFishV3-b67、DBはMySQLです。
まずはテーブル定義。単純なオーダ受注用のテーブルです。
-- phpMyAdmin SQL Dump -- version 3.2.2 -- http://www.phpmyadmin.net -- -- ホスト: localhost -- 生成時間: 2009 年 10 月 13 日 11:54 -- サーバのバージョン: 5.0.86 -- PHP のバージョン: 5.3.0 SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; -- -- データベース: 'testdb' -- -- -------------------------------------------------------- -- -- テーブルの構造 'products' -- CREATE TABLE products ( id int(11) NOT NULL auto_increment COMMENT 'ID', `name` varchar(255) collate utf8_unicode_ci NOT NULL COMMENT '名前', price decimal(10,0) NOT NULL COMMENT '価格', created datetime NOT NULL COMMENT '登録日', updated datetime NOT NULL COMMENT '更新日', PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='製品'; -- -- テーブルのデータをダンプしています 'products' -- INSERT INTO products (id, name, price, created, updated) VALUES(1, 'Zend Framework', '1200', '2009-10-09 15:28:35', '2009-10-09 15:28:35'); INSERT INTO products (id, name, price, created, updated) VALUES(2, 'CakePHP', '800', '2009-10-09 15:29:18', '2009-10-09 15:29:18'); INSERT INTO products (id, name, price, created, updated) VALUES(3, 'Symfony', '3000', '2009-10-09 15:29:18', '2009-10-09 15:29:18'); -- -------------------------------------------------------- -- -- テーブルの構造 'users' -- CREATE TABLE users ( id int(11) NOT NULL auto_increment COMMENT 'ID', `name` varchar(255) collate utf8_unicode_ci NOT NULL COMMENT '名前', sex tinyint(1) NOT NULL COMMENT '性別', address varchar(255) collate utf8_unicode_ci default NULL COMMENT '住所', created datetime NOT NULL COMMENT '登録日', updated datetime NOT NULL COMMENT '更新日', PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='ユーザー'; -- -- テーブルのデータをダンプしています 'users' -- INSERT INTO users (id, name, sex, address, created, updated) VALUES(1, '吉田', 0, '東京都', '2009-10-09 00:00:00', '2009-10-09 00:00:00'); INSERT INTO users (id, name, sex, address, created, updated) VALUES(2, '鈴木一郎', 0, '東京都', '2009-10-09 06:27:52', '2009-10-09 06:27:52'); -- -------------------------------------------------------- -- -- テーブルの構造 'order_details' -- CREATE TABLE order_details ( id int(11) NOT NULL auto_increment COMMENT 'ID', pd_order_id int(11) NOT NULL COMMENT '注文', product_id int(11) NOT NULL COMMENT '製品', number int(11) NOT NULL COMMENT '注文数', created datetime NOT NULL COMMENT '登録日', updated datetime NOT NULL COMMENT '更新日', PRIMARY KEY (id), KEY pd_order_id (pd_order_id,product_id), KEY product_id (product_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='注文明細'; -- -- テーブルのデータをダンプしています 'order_details' -- INSERT INTO order_details (id, pd_order_id, product_id, number, created, updated) VALUES(1, 1, 3, 2, '2009-10-09 15:32:16', '2009-10-09 15:32:16'); INSERT INTO order_details (id, pd_order_id, product_id, number, created, updated) VALUES(2, 1, 1, 1, '2009-10-09 15:32:16', '2009-10-09 15:32:16'); INSERT INTO order_details (id, pd_order_id, product_id, number, created, updated) VALUES(3, 2, 1, 5, '2009-10-09 15:33:07', '2009-10-09 15:33:07'); INSERT INTO order_details (id, pd_order_id, product_id, number, created, updated) VALUES(4, 2, 2, 1, '2009-10-09 15:33:07', '2009-10-09 15:33:07'); -- -------------------------------------------------------- -- -- テーブルの構造 'pd_orders' -- CREATE TABLE pd_orders ( id int(11) NOT NULL auto_increment COMMENT 'ID', order_date date NOT NULL COMMENT '注文日', user_id int(11) NOT NULL COMMENT 'ユーザ', created datetime NOT NULL COMMENT '登録日', updated datetime NOT NULL COMMENT '更新日', PRIMARY KEY (id), KEY user_id (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='注文'; -- -- テーブルのデータをダンプしています 'pd_orders' -- INSERT INTO pd_orders (id, order_date, user_id, created, updated) VALUES(1, '2009-10-09', 1, '2009-10-09 15:29:56', '2009-10-09 15:29:56'); INSERT INTO pd_orders (id, order_date, user_id, created, updated) VALUES(2, '2009-10-09', 2, '2009-10-09 15:32:40', '2009-10-09 15:32:40'); -- -- ダンプしたテーブルの制約 -- -- -- テーブルの制約 `order_details` -- ALTER TABLE `order_details` ADD CONSTRAINT order_details_ibfk_1 FOREIGN KEY (pd_order_id) REFERENCES pd_orders (id), ADD CONSTRAINT order_details_ibfk_2 FOREIGN KEY (product_id) REFERENCES products (id); -- -- テーブルの制約 `pd_orders` -- ALTER TABLE `pd_orders` ADD CONSTRAINT pd_orders_ibfk_1 FOREIGN KEY (user_id) REFERENCES users (id);
・・・面倒くさいのでphpMyAdminからエクスポートで出しました(汗)
で、このDBをEclipseで見れるようにして、JPAのEntityクラスを生成します。
package testweb.entity; import java.io.Serializable; import javax.persistence.*; import java.math.BigDecimal; import java.util.Date; import java.util.List; /** * The persistent class for the products database table. * */ @Entity @Table(name="products") public class Product implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private int id; @Temporal( TemporalType.TIMESTAMP) private Date created; private String name; private BigDecimal price; @Temporal( TemporalType.TIMESTAMP) private Date updated; @OneToMany(mappedBy = "product") private List<OrderDetail> orderDetails; (プロパティ略) }
package testweb.entity; import java.io.Serializable; import javax.persistence.*; import java.util.Date; import java.util.List; /** * The persistent class for the users database table. * */ @Entity @Table(name="users") public class User implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private int id; private String address; @Temporal( TemporalType.TIMESTAMP) private Date created; private String name; @Enumerated private Sex sex; @Temporal( TemporalType.TIMESTAMP) private Date updated; //bi-directional many-to-one association to PdOrder @OneToMany(mappedBy="user") private List<PdOrder> pdOrders; (プロパティ略) }
package testweb.entity; import java.io.Serializable; import java.math.BigDecimal; import javax.persistence.*; import java.util.Date; import java.util.List; /** * The persistent class for the pd_orders database table. * */ @Entity @Table(name="pd_orders") public class PdOrder implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private int id; @Temporal( TemporalType.TIMESTAMP) private Date created; @Temporal( TemporalType.DATE) @Column(name="order_date") private Date orderDate; @Temporal( TemporalType.TIMESTAMP) private Date updated; //bi-directional many-to-one association to User @ManyToOne(fetch=FetchType.LAZY) private User user; @OneToMany(mappedBy = "pd_order") private List<OrderDetail> orderDetails; (プロパティ略) public BigDecimal getTotalPrice() { BigDecimal ret = new BigDecimal(0); if (orderDetails != null) { for (OrderDetail od : orderDetails) { ret = ret.add(od.getProduct().getPrice().multiply(new BigDecimal(od.getNumber()))); } } return ret; } }
package testweb.entity; import java.io.Serializable; import javax.persistence.*; import java.util.Date; /** * The persistent class for the order_details database table. * */ @Entity @Table(name="order_details") public class OrderDetail implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private int id; @Temporal( TemporalType.TIMESTAMP) private Date created; private int number; @ManyToOne(fetch = FetchType.LAZY) private PdOrder pd_order; @ManyToOne(fetch = FetchType.LAZY) private Product product; @Temporal( TemporalType.TIMESTAMP) private Date updated; (プロパティ略) }
作ったときに、外部キー制約をつけ忘れていたところがあったので、そこは手書きで書きましたが、それ以外はほとんどEclipseが自動生成してくれました。PdOrderテーブルにだけ、注文の合計値を返す独自のメソッドを定義しています。それにしても、EclipseのEntity作成機能も随分賢くなりましたね。テーブル名が複数形だったら自動的に単数形のクラスを作ってくれるのは、Railsの規約をかなり意識してるのかな?
次は、Criteriaで使う為のメタモデルクラスを作成します。現時点ではこのクラスを自動作成してくれるツールは無いみたいなので、手書きで作ってみました。
package testweb.entity; import java.math.BigDecimal; import java.util.Date; import javax.persistence.metamodel.ListAttribute; import javax.persistence.metamodel.SingularAttribute; import javax.persistence.metamodel.StaticMetamodel; @StaticMetamodel(Product.class) public class Product_ { public static volatile SingularAttribute<Product, Integer> id; public static volatile SingularAttribute<Product, Date> created; public static volatile SingularAttribute<Product, String> name; public static volatile SingularAttribute<Product, BigDecimal> price; public static volatile SingularAttribute<Product, Date> updated; public static volatile ListAttribute<Product, OrderDetail> orderDetails; }
package testweb.entity; import java.util.Date; import javax.persistence.metamodel.ListAttribute; import javax.persistence.metamodel.SingularAttribute; import javax.persistence.metamodel.StaticMetamodel; @StaticMetamodel(User.class) public class User_ { public static volatile SingularAttribute<User, Integer> id; public static volatile SingularAttribute<User, String> address; public static volatile SingularAttribute<User, Date> created; public static volatile SingularAttribute<User, String> name; public static volatile SingularAttribute<User, Sex> sex; public static volatile SingularAttribute<User, Date> updated; public static volatile ListAttribute<User, PdOrder> pdOrders; }
package testweb.entity; import java.util.Date; import javax.persistence.metamodel.ListAttribute; import javax.persistence.metamodel.SingularAttribute; import javax.persistence.metamodel.StaticMetamodel; @StaticMetamodel(PdOrder.class) public class PdOrder_ { public static volatile SingularAttribute<PdOrder, Integer> id; public static volatile SingularAttribute<PdOrder, Date> created; public static volatile SingularAttribute<PdOrder, Date> orderDate; public static volatile SingularAttribute<PdOrder, Date> updated; public static volatile SingularAttribute<PdOrder, User> user; public static volatile ListAttribute<PdOrder, OrderDetail> orderDetails; }
package testweb.entity; import java.util.Date; import javax.persistence.metamodel.SingularAttribute; public class OrderDetail_ { public static volatile SingularAttribute<OrderDetail, Integer> id; public static volatile SingularAttribute<OrderDetail, Date> created; public static volatile SingularAttribute<OrderDetail, Integer> number; public static volatile SingularAttribute<OrderDetail, PdOrder> pd_order; public static volatile SingularAttribute<OrderDetail, Product> product; public static volatile SingularAttribute<OrderDetail, Date> updated; }
仕様書ちゃんと読んでなくて、ソース例見ながら適当に作りました(汗)どうやら一対多等のList型のプロパティはListAttributeで、Entityも含めて単数系のプロパティならSingularAttributeで定義すればいいみたいですね。
ではいよいよ、JPA2.0のCriteriaを試してみたいと思います。今回は、オーダ一覧をユーザ名で検索し、一覧表示の中に注文金額合計を出すような表を取得してみることにします。
package testweb.ejb; import java.util.List; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.JoinType; import javax.persistence.criteria.Root; import testweb.entity.OrderDetail_; import testweb.entity.PdOrder; import testweb.entity.PdOrder_; import testweb.entity.User_; /** * Session Bean implementation class PdOrderBean */ @Stateless public class PdOrderBean { @PersistenceContext private EntityManager em; public List<PdOrder> getPdOrders(String name) { CriteriaBuilder qb = em.getCriteriaBuilder(); CriteriaQuery<PdOrder> cq = qb.createQuery(PdOrder.class); Root<PdOrder> order = cq.from(PdOrder.class); order.fetch(PdOrder_.user, JoinType.LEFT); order.fetch(PdOrder_.orderDetails, JoinType.LEFT) .fetch(OrderDetail_.product, JoinType.LEFT); cq.where(qb.like(order.join(PdOrder_.user).get(User_.name), "%" + name + "%")); return em.createQuery(cq).getResultList(); } }
えっと・・・めんどくさい・・・(汗)
CriteriaBuilder qb = em.getCriteriaBuilder();
EntityManagerからCriteriaBuilder(つい最近、QueryBuilderから名前が変わったようです)を取得。
CriteriaQuery<PdOrder> cq = qb.createQuery(PdOrder.class);
CriteriaBuilderからCriteriaQueryを取得。
ここで渡したクラスが、FROM句のルートとして定義されます。
Root<PdOrder> order = cq.from(PdOrder.class);
CriteriaQueryのfromメソッドを使って、Rootオブジェクトを取得。このRootオブジェクトを使ってJOINやFETCH JOIN関連の定義を行います。
CriteriaQuery作るときにRootのEntityクラスを既に渡しているのに、ここでも渡すのは二度手間ですね・・・
order.fetch(PdOrder_.user, JoinType.LEFT);
FETCH JOINの定義。ここでは、オーダに多対一で紐づくユーザテーブルのJOINを定義しています。メソッドに渡している引数は、あらかじめ作成しておいたメタモデルクラスのフィールドです。
order.fetch(PdOrder_.orderDetails, JoinType.LEFT) .fetch(OrderDetail_.product, JoinType.LEFT);
オーダ詳細と、その詳細に紐づく製品情報をFETCH JOINします。fetchやjoinの定義はこのようにメソッドチェーンで書ける模様。
cq.where(qb.like(order.join(PdOrder_.user).get(User_.name), "%" + name + "%"));
CriteriaQueryのwhereメソッドを使ってWHERE句を定義します。あらかじめ作成しておいたRootオブジェクトから、関連するテーブルをjoinメソッドで定義し、更にgetメソッドによって対象となるフィールドを決定します。ここでもメタモデルクラスを引数に利用しています。
return em.createQuery(cq).getResultList();
こうやって定義したCriteriaQueryをEntityManagerに渡してやれば、後は今まで通りのやり方でデータを取得出来ます。
うーん・・・一時的な変数使い過ぎ・・・全然流れるようなインターフェイスにできない・・・
S2JDBCやLINQのメソッド構文なら、これら全てを流れるようなインターフェイスで書くので、一時的な変数が不要になって記述が激減するんですけど。これらに比べてJPAのCriteriaは、あまりにも冗長な気がします。HibernateのCriteriaだって、タイプセーフではないものの、ある程度流れるようなインターフェイスを意識した作りになっていたのに、これではかなりの退化な気が・・・
まぁとりあえず、これを実行してみると・・・
詳細レベル (低): SELECT t1.ID, t1.CREATED, t1.UPDATED, t1.order_date, t1.USER_ID, t0.ID, t0.CREATED, t0.UPDATED, t0.NUMBER, t0.PD_ORDER_ID, t0.PRODUCT_ID, t2.ID, t2.PRICE, t2.CREATED, t2.UPDATED, t2.NAME, t3.ID, t3.SEX, t3.ADDRESS, t3.CREATED, t3.UPDATED, t3.NAME FROM pd_orders t1 LEFT OUTER JOIN order_details t0 ON (t0.PD_ORDER_ID = t1.ID) LEFT OUTER JOIN products t2 ON (t2.ID = t0.PRODUCT_ID), users t3 WHERE ((t3.NAME LIKE ?) AND (t3.ID = t1.USER_ID)) bind => [%吉田%]
FETCH JOINで一気にデータを取得できていますね。JPQLと同じ動きです。ちなみに、一対多のデータをFETCH JOINで取得しても、EclipseLinkはちゃんと一つのPdOrderオブジェクトを返してくれました。JPA用にDISTINCTを定義する必要はないみたいですね。(ただし、現時点ではWHERE句が無いFETCH JOINを使ったクエリが上手く動かないようです。開発途中のライブラリなので深くは追いませんが)
ちょっと触ってみた感想を言うと・・・うーん・・・タイプセーフに書けるとはいえ、記述量が多すぎて微妙な感じです。これはインターフェイスの考え方の問題なので、JPAのCriteriaがこうと決まってしまったからには、将来にわたって改善されることも無い気がします。
昨今の状況を見てると、Javaが今後力を発揮する分野って、サーバサイドではDB周りの部分、ロジックに近い部分が主になるのではと思っているのですが、その分野に関する標準APIがどうも時代遅れになってしまっている感が・・・まぁCriteria機能としては一通り網羅しているっぽいので、ライブラリが完成すればWeb系の検索画面とかでそれなりに役に立つのでしょうけど。
Eclipse Galileo
Eclipse Galileoが出ていたのでVistaとMacに入れてみました。
取りあえず、最初に感じたのは、プラグインのインストールがちょっとめんどくさくなりました。以前のバージョンまでは、登録したアップデートサイト毎にカテゴリ分けして表示してくれていたのですが、Galileoではアップデートサイト毎に表示するか、全部まとめて表示するしか選べません。アップデートサイトを変更すると、それまで選んでいた選択肢はリセットされてしまいます。Subversiveのような複数アップデートサイトを指定する必要があるプラグインの指定がめんどくさいです。
Windows(64bit)版では特にこれといった違いはなし。ただ、サーバーアダプタの追加インストールにバグがあるようで、GlassFishプラグインとかを入れようとするとエラーになります。
https://bugs.eclipse.org/bugs/show_bug.cgi?id=280365
を読む限り、現状ではサーバ追加メニューからインストールするのではなく、APPサーバ側が提供しているアップデートサイトを使えばいいみたいです。
Mac版では、今回からCocoa版が追加されました。Cocoa版には64bit版もあるので、これを入れてみました。UIが若干Macっぽくなるくらいで、特に目立った違いはありませんでした。まぁ、若干早くなったような気はします。
Mac版のデフォルトエンコーディングは、以前はUTF-8だったのですが、Galileoを入れるとSJISになってしまいました。Windows版は以前からMS932ですが、Mac版はコロコロ変わるので困ります。SJISってことは、MacのJDK1.6で定義されている、sun.jnu.encodingを見ているのでしょうか? ちなみに、MacのJDK1.5ではsun.jnu.encodingは「UTF8」でした。file.encodingは1.5も1.6も「UTF-8」です。ここら辺の変更の意味がイマイチよくわからない。更に、sun.jnu.encodingが何なのかもよくわからない。LeopardのターミナルのエンコーディングがUTF-8なんだから、UTF-8に全部統一して欲しいところなんですが・・・って、それ以前のOS XだとSJISだから、そこらへんを考慮してるのかな?