Hibernate EntityManagerその8 Open Session in View、second level cache
今までも何回かやってきた遅延ロードについて。前述のHibernate in Actionを読む限りでは、トランザクションとSessionのライフサイクルを同一化して、遅延ロードに対してはHQL(EJB-QL)で対応するのがやはり第一の案みたいなんですが、俗に言うOpen Session in Viewについても結構積極的に説明してありました。なので一回試してみようかなと思いまして・・・
一応本当はStateful Session Bean用らしいのですが、PersistenceContextType.EXTENDEDを試してみました。
public class TestServiceImpl implements TestService { public static void main(String[] args) { SingletonS2ContainerFactory.init(); S2Container container = SingletonS2ContainerFactory.getContainer(); TestService service = (TestService) container.getComponent(TestService.class); List<Invoice> list = service.execute(); view(list); service.close(); } private EntityManager entityManager; private EntityManagerFactory emFactory; public void setEmFactory(EntityManagerFactory emFactory) { this.emFactory = emFactory; } public List<Invoice> execute() { entityManager = emFactory.createEntityManager(PersistenceContextType.EXTENDED); // entityManager = emFactory.getEntityManager(); return entityManager.createQuery("SELECT i FROM Invoice i").getResultList(); } public void close() { entityManager.close(); } public static void view(List<Invoice> list) { for (Invoice invoice : list) { System.out.println(invoice.getId()); System.out.println(invoice.getTotal()); System.out.println(invoice.getCustomer()); } } }
・・・これだけでした(汗) か、簡単だ・・・
(一応TestServiceのComponent登録はprototypeでやってます。TestServiceのインタ−フェイスにトランザクションのAOPをかけてます)
今までとは違って遅延ロードをトランザクション外で行っても大丈夫でした(closeを呼んだ後に遅延ロードさせると今までどおりにエラーになります)。
まぁでも、たしかに動かしてみるのは簡単だけど、close処理のタイミングとかEntityManagerインスタンスの保持とか考えると、やはり迂闊には使えない印象です。これをやる為にStateful Session Beanを構築するのもどうかという気がしますし・・・結局、地道にEJB-QLでFETCH JOINという選択になりそう。
もう一点、本を読んでまず試してみたくなったのが二次キャッシュ。SessionFactoryレベルでキャッシュするEntity・・・つまり、アプリケーションレベルでデータを保持するってことですね。とりあえず一番わかりやすいread-onlyで考えると、定数テーブルへのアクセス等に使えそう。今まではリスナー等で行っていた処理を、通常のDBアクセス処理で置き換えられるってことか。これは非常に便利そう。
がしかし、この機能はHibernate独自機能なので、Persistence APIには相当するアノテーションが存在しませんでした。しかし、Hibernate AnnotationsにはこのようなHibernate独自機能用のアノテーションがいくつか用意されてます。キャッシュについて調べてみると、そのものズバリの@Cacheアノテーションが存在しました。
さっそく適当なテーブルを作ってデータを入れて実験
package test.generate.entity; import javax.persistence.Entity; import javax.persistence.Id; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; @Entity @Cache(usage = CacheConcurrencyStrategy.READ_ONLY) public class Test { private Integer id; private String name; private int num; @Id public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getNum() { return num; } public void setNum(int num) { this.num = num; } }
package test.service; import javax.persistence.EntityManager; import org.seasar.framework.container.S2Container; import org.seasar.framework.container.factory.SingletonS2ContainerFactory; import test.generate.entity.Test; public class CacheServiceImpl implements CacheService { public static void main(String[] args) { SingletonS2ContainerFactory.init(); S2Container container = SingletonS2ContainerFactory.getContainer(); CacheService service = (CacheService) container.getComponent(CacheService.class); test(service); test(service); } private static void test(CacheService service) { for (int i = 1; i <= 3; i++) { Test test = service.getTest(i); System.out.println(test.getId()); System.out.println(test.getName()); System.out.println(test.getNum()); } } private EntityManager em; public void setEm(EntityManager em) { this.em = em; } public Test getTest(int id) { return em.find(Test.class, id); } }
で結果は・・・
DEBUG 2006-01-02 01:54:18,050 [main] BEGIN test.service.CacheServiceImpl#getTest(1) DEBUG 2006-01-02 01:54:18,060 [main] トランザクションを開始しました DEBUG 2006-01-02 01:54:18,211 [main] 論理的なコネクションを取得しました Hibernate: /* load test.generate.entity.Test */ select test0_.id as id0_0_, test0_.num as num0_0_, test0_.name as name0_0_ from Test test0_ where test0_.id=? DEBUG 2006-01-02 01:54:18,261 [main] 論理的なコネクションを閉じました DEBUG 2006-01-02 01:54:18,291 [main] トランザクションをコミットしました DEBUG 2006-01-02 01:54:18,291 [main] END test.service.CacheServiceImpl#getTest(1) : test.generate.entity.Test@1c7e2da 1 ONE 1 DEBUG 2006-01-02 01:54:18,291 [main] BEGIN test.service.CacheServiceImpl#getTest(2) DEBUG 2006-01-02 01:54:18,291 [main] トランザクションを開始しました DEBUG 2006-01-02 01:54:18,291 [main] 論理的なコネクションを取得しました Hibernate: /* load test.generate.entity.Test */ select test0_.id as id0_0_, test0_.num as num0_0_, test0_.name as name0_0_ from Test test0_ where test0_.id=? DEBUG 2006-01-02 01:54:18,291 [main] 論理的なコネクションを閉じました DEBUG 2006-01-02 01:54:18,291 [main] トランザクションをコミットしました DEBUG 2006-01-02 01:54:18,291 [main] END test.service.CacheServiceImpl#getTest(2) : test.generate.entity.Test@17aece8 2 TWO 2 DEBUG 2006-01-02 01:54:18,291 [main] BEGIN test.service.CacheServiceImpl#getTest(3) DEBUG 2006-01-02 01:54:18,291 [main] トランザクションを開始しました DEBUG 2006-01-02 01:54:18,301 [main] 論理的なコネクションを取得しました Hibernate: /* load test.generate.entity.Test */ select test0_.id as id0_0_, test0_.num as num0_0_, test0_.name as name0_0_ from Test test0_ where test0_.id=? DEBUG 2006-01-02 01:54:18,301 [main] 論理的なコネクションを閉じました DEBUG 2006-01-02 01:54:18,301 [main] トランザクションをコミットしました DEBUG 2006-01-02 01:54:18,301 [main] END test.service.CacheServiceImpl#getTest(3) : test.generate.entity.Test@94257f 3 THREE 3 DEBUG 2006-01-02 01:54:18,301 [main] BEGIN test.service.CacheServiceImpl#getTest(1) DEBUG 2006-01-02 01:54:18,301 [main] トランザクションを開始しました DEBUG 2006-01-02 01:54:18,301 [main] トランザクションをコミットしました DEBUG 2006-01-02 01:54:18,301 [main] END test.service.CacheServiceImpl#getTest(1) : test.generate.entity.Test@29c58e 1 ONE 1 DEBUG 2006-01-02 01:54:18,301 [main] BEGIN test.service.CacheServiceImpl#getTest(2) DEBUG 2006-01-02 01:54:18,301 [main] トランザクションを開始しました DEBUG 2006-01-02 01:54:18,301 [main] トランザクションをコミットしました DEBUG 2006-01-02 01:54:18,301 [main] END test.service.CacheServiceImpl#getTest(2) : test.generate.entity.Test@691dee 2 TWO 2 DEBUG 2006-01-02 01:54:18,301 [main] BEGIN test.service.CacheServiceImpl#getTest(3) DEBUG 2006-01-02 01:54:18,301 [main] トランザクションを開始しました DEBUG 2006-01-02 01:54:18,311 [main] トランザクションをコミットしました DEBUG 2006-01-02 01:54:18,311 [main] END test.service.CacheServiceImpl#getTest(3) : test.generate.entity.Test@12bf892 3 THREE 3
なるほど・・・こりゃ便利だ。機能的にはHibernateに依存しますが、他のPersistenceAPI実装でもキャッシュが効かなくなる以外は普通のEntityとして利用できるので、独自機能もあまり抵抗感無く使っていけそうな印象を受けました。