Kuina-Dao 開発記 Detached Entity と Open Session in View
某巨大掲示板へのレス.
あっちに書こうかと思ったけど長くなるからこっちへ.
っていうか,以前のスレであっちに書かずにこっちへ書けって言われたからこっちなのだ (←根に持ってる).
380 名前:デフォルトの名無しさん[sage] 投稿日:2006/06/12(月) 08:04:20
KuinaでもDTOを利用しちゃうんなら
ListQuery#getResultList(Class )
が欲しいずら中の人はdetach関連について、どう考えてるんかね
ふむ.
まずは DTO について.
Kuina-Dao では DTO は扱いません.エンティティのみ.
というのも,Dxo の登場によって DTO とエンティティの相互変換がラクチンにできるようになるため,DAO や Criteria に DTO を渡す必要が無くなるからです.
代わりに (?) エンティティを使った Query by Example はサポートします.
次に detach 関連について.
一言でいうと,考えていません.(^^;
Detached Entity は使わないし,おそらく使うべきじゃないし,きっと使わない方が身のためです.
せっかくなので,
Pro EJB 3: Java Persistence API
- 作者: Mike Keith,Merrick Schincariol
- 出版社/メーカー: Apress
- 発売日: 2006/05/15
- メディア: ペーパーバック
- クリック: 9回
- この商品を含むブログ (20件) を見る
最初に JSP 中で <c:out value="${emp.department.name}">
みたいに書いた場合を例として Detachment の必要性が書かれています.
そして P149
Planning for Detachment
detachment の戦略はいくつかありますが,ここでは 2 つを紹介するとか何とか.「Chapter 7 Query Language」では別の方法 (Fetch Join) を紹介するとのこと.
Triggering Lazy Loading
まずは 1 つめの戦略.
これは EntityManager
を close()
する前に関連を全部たどって Lazy Load を引き起こしちゃおうというもの.
出てくるサンプルはこんな感じ.
@Stateless public class EmployeeServiceBean implements EmployeeService { @PersistenceContext(unitName="EmployeeService") private EntityManager em; public List findAll() { List<Employee> emps = (List<Employee>) em.createQuery("SELECT e FROM Employee e") .getResultList(); for (Employee emp : emps) { Department dept = emp.getDepartment(); if (dept != null) { dept.getName(); } } return emps; } }
この戦略自体はともかくとして,findAll()
みたいなメソッドでこれをやるのはどうかと.
必要性に関わらず,無条件に Department
までフェッチするなんてあり得なくなくなくない?
こんなんだったら Eager フェッチすべきだよね.
ともあれ (JW),これが 1 つめ.
Avoiding Detachment
結局,Detached Entity を使った方法がどっちもイケてないので,いっそのこと Detached Entity を使うのをやめればいいじゃん,っていうのがここからの話.
ここでもそのやり方には 2 つあるということになっています.
1 つはエンティティを JSP (ビュー) から使わないようにすること.それには DTO を使う方法 (冗長になるということで著者達の好みではないみたい) と projection (SELECT e.name, ... みたいな) を使う方法があるとのこと.
もう 1 つは JSP (ビュー) の実行も Persistence Context の中でやっちゃうこと.いわゆる Open Session in View ですね.
これについてこの後詳述となります.
Transaction View
そんなわけで (どんなわけで?),トランザクション境界を広げればいいじゃん,ってわけで,こんなコードが出てきます.
public class EmployeeServlet extends HttpServlet { @Resource UserTransaction tx; @EJB EmployeeService bean; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { tx.begin(); List emps = bean.findAll(); request.setAttribute("employees", emps); getServletContext().getRequestDispatcher("/listEmployees.jsp") .forward(request, response); } finally { tx.commit(); } } }
...
Servlet でやるの?
普通は Servlet Filter を使うんじゃないかと思うわけですが,そうすると @Resource
とか使えないので「non-portable」ってことでこうなるらしい.
っていうか,Servlet で @EJB って安全に使えるの? SessionBean ってスレッドセーフじゃないよ? 再入不可だよ?
ともあれ (JW),こんな風にトランザクション境界を広げることで,JSP (ビュー) の実行中でも Lazy フェッチをできるようにしようというわけです.
それを Servlet でやるかどうかはともかく,この方法は問題がいくつかあります.
本書ではその一つとして,REQUIRES_NEW
なトランザクションを必要とする SessionBean を使うとうまくいかないよ,ってな話が出てきます.
他にも,JSP (ビュー) で何か起きた場合にトランザクションを無条件にロールバックしていいのかとか,いろいろあるよなぁとか思ったり.
EntityManager per Request
Transaction View と似たようなやり方として,アプリケーション管理の EntityManager
を使おうということで,こんなコードが出てきます.
public class EmployeeServlet extends HttpServlet { @PersistenceUnit(unitNmae="EmployeeService") EntityManagerFactory emf; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { EntityManager em = emf.createEntityManager(); try { List emps = em.createQuery("SELECT e FRM Employee e") .getResultList(); request.setAttribute("employees", emps); getServletContext().getRequestDispatcher("/listEmployees.jsp") .forward(request, response); } finally { em.close(); } } }
...
どっちもどっち (苦笑).
Servlet で EntityManager
を直接使って問い合わせするだなんて,サンプルコードとはいえ掲載しない方がいいと思うんだけど.
それは著者も認識していて,その対策として Stateful SessionBean を使った例がこの後出てきます.
えーっと...
せっかくだから,そのコードも紹介しましょうか.
@Stateful @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public class EmployeeQueryBean implements EmployeeQuery { @PersistenceContext(type=PersistenceContextType.EXTENDED, unitName="EmployeeService") EntityManager em; public List findAll() { return em.createQuery("SELECT e FROM Employee e") .getResultList(); } @Remove public void finished() { } }
そして,上の SFSB を使う Servlet.
@EJB(name="queryBean", beanInterface=EmployeeQuery.class) public class EmployeeServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { EntityQuery bean = createQueryBean(); try { List emps = bean.findAll(); request.setAttribute("employees", emps); getServletContext().getRequestDispatcher("/listEmployees.jsp") .forward(request, response); } finally { bean.finished(); } } private EmployeeQuery createQueryBean() throws ServletException { // look up queryBean } }
look up ってのは JNDI 使うってことね.
えーっと...
何がしたかったんだっけ? (^^;
リクエスト毎に SFSB をインスタンス化するとは随分と貴族的なソリューション.
まぁ,その辺は著者達も認識しているようですが,読み手によってはこれが正解だとか思っちゃいそうで怖い...
年末から来年にかけて,Open Session in View と SFSB,そして Extended Context な EntityManager
を使って開発して,トランザクションが増えてきたらメモリ不足で不安定になるシステムが続出するんじゃないかと予想している今日この頃です.
さて.
JPA 仕様の Spec Lead,Mike Keith が著者の一人でもある「Pro EJB 3 Java Persistence API」でさえ,この調子.
Detached Entity を安全に使うのはひどく効率の悪いやり方になるし,Open Session in View の使用例も非道いもんだ.
一応,上のようなコードを無条件に推奨しているというわけではないのだけど,教科書的に参照されそうな本だけに,こんなコードは掲載しないで欲しかったなぁとか思う今日この頃です.
だいたい,なんでこんな劣悪な例しか出てこないのか?
それは,エンティティを JSP (ビュー) でも使おうというところに諸悪の根源があると思います.
それについてはかつて,「Re: hibernateを利用してはいけない5つのシチュエーション」でねっとりと書いているわけですが,このアプローチを取る限り,どこまでいっても真っ当なやり方にはたどり着けないのではないでしょうか.
そんなわけで (どんなわけで?).
Kuina-Dao では Detached Entity を支援するために何か考える予定はありません.
Open Session in View を支援するために Filter とか提供する予定はありません.
おいらは S2Hibernate からも Filter 削除しちゃったくらい,Open Session in View キライなのだ.
トランザクション境界はプレゼンテーションとロジックの間,サービス層に設定すること.
エンティティはプレゼンテーション層には見せないこと.
以上.
Diigu
この前書いた ParameterNamesEnhancer
を S2Container.java のサブプロジェクト,Diigu と名付けて SVN へコミットしました.
https://www.seasar.org/svn/s2container の trunk/diigu
Eclipse では,こいつをチェックアウトする前に「Window」−「Preferences」の「Java」−「Build Path」−「Classpath Variables」から JAVA_HOME
を追加してください.
値には JDK (JRE ではない) のパスを設定します.
あるいは,亀とかでチェックアウトした後に Maven2 で mvn eclipse
してからインポートでもいいのかも.
試したことはないけど.
今は Enhancer と Doclet しか提供していませんが,そう遠くないうちに Eclipse プラグインと Maven プラグインを用意したいと考えていますが,現状は Ant から使うしかありません.
Ant のビルドファイルは builder
ディレクトリにあります.
mvn package
で Jar を作成して,できあがった Jar ファイルと Javassist の Jar,それに builder
ディレクトリを適用したいプロジェクトにコピーして,builder
の下にある Ant のビルドファイルのパスやパッケージを適切に直してプロジェクトのビルダーとして登録すれば使えるんじゃないかなぁ.
そんなわけで (どんなわけで?),引数名を取りたい人は是非お試しを.
ちなみに Diigu (ディーグ) は沖縄の県花らしいです.
琉球の国花でもあったとか.
それ以上のことは知りません.(^^;
06/13 15:30 追記
06/12 の深夜にコミットした状態では多数の問題がありました...
心より恥じる.
たくさん直してコミットしました.