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として利用できるので、独自機能もあまり抵抗感無く使っていけそうな印象を受けました。