« 2007年03月 | メイン | 2007年06月 »

2007年04月 アーカイブ

2007年04月06日

Hibernate Shardsをちょっと触る

Hibernate Shardsは、複数のRDBにデータを分割してI/Oする仕組みをHibernateで実現するものです。
ほんの少しだけ触ってみました。変わったことは何もしていません。Hello Worldレベル。

  1. 必要なライブラリ
  2. Hibernate Shardsを用いるにはHibernate Coreが必要なので、
    • Core本体
    • Coreが依存するlib
    • Shardsのlib
    を入れる。試すJDBCドライバーも含める。

    +lib
     antlr.jar
     cglib.jar
     asm.jar
     asm-attrs.jars
     commons-collections.jar
     commons-logging.jar
     hibernate3.jar
     hibernate-shards.jar
     jta.jar
     dom4j.jar
     log4j.jar
     JDBCドライバーのjar

  3. RDBMSの設定

  4. Hibernate Shardsは複数のRDBMSに対してデータを分散して登録する。そのためにhibernateの設定ファイルをRDBMS分用意する。

    db1.hibernate.cfg.xml

    <hibernate-configuration>
     <session-factory name="sf0">
      <property name="dialect">org.hibernate.dialect.HSQLDialect</property>
      <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
      <property name="connection.username">sa</property>
      <property name="connection.password"></property>
      <property name="connection.url">jdbc:hsqldb:hsql://localhost:9001</property>
      <property name="hibernate.connection.shard_id">0</property>
      <property name="hibernate.shard.enable_cross_shard_relationship_checks">true</property>
      <property name="hbm2ddl.auto">create</property>
    ・・・
     </session-factory>
    </hibernate-configuration>

    db2.hibernate.cfg.xml

    <hibernate-configuration>
     <session-factory name="sf1">
      <property name="dialect">org.hibernate.dialect.HSQLDialect</property>
      <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
      <property name="connection.username">sa</property>
      <property name="connection.password"></property>
      <property name="connection.url">jdbc:hsqldb:hsql://localhost:9002</property>
      <property name="hibernate.connection.shard_id">1</property>
      <property name="hibernate.shard.enable_cross_shard_relationship_checks">true</property>
     <property name="hbm2ddl.auto">create</property>
    ・・・
     </session-factory>
    </hibernate-configuration>

    hibernate.connection.shard_idは、複数のRDBを識別する識別子となるので、かぶらないように別々の番号を振る。session-factorynameもかぶらないようにつけておく必要があるっぽい。


  5. エンティティとマッピングファイル
  6. エンティティを用意する。簡単に試したいのでidとvalueのみの単純なエンティティを使う。

    package net.grandnature.example.invoice.entity;

    public class Example {
     private Long id;
     private String value;
     public Long getId() {
      return id;
     }
     public void setId(Long id) {
      this.id = id;
     }
     public String getValue() {
      return value;
     }
     public void setValue(String value) {
      this.value = value;
     }
    }

    マッピングファイルはこんな感じ。

    example.hbm.xml

    <hibernate-mapping package="net.grandnature.example.invoice.entity">
     <class name="Example">
      <id name="id" type="long">
       <generator class="org.hibernate.shards.id.ShardedTableHiLoGenerator"/>
      </id>
      <property name="value"/>
     </class>
    </hibernate-mapping>

    ShardedTableHiLoGeneratorを用いると複数のRDBMSでHiloの値が重複しないようになる。identityなどで行いたい場合は、ひとつめのDBは0からはじまるように、ふたつめのDBでは1000からはじまるように、など、データ量に応じてIDが重複しないように自分でうまく調節する必要がある模様。

    ShardedTableHiLoGeneratorをHibernate Annotationでgeneratorとして指定するにはどうすればいいのかなあ。

  7. データを登録してみる
  8. public class ShardsExample {
     public static void main(String[] args) {
      new ShardsExample().insert();
     }

     void insert() {
      Session session = createSessionFactory().openSession();
      Transaction tx = session.beginTransaction();
      for (int i = 0; i < 10; i++) {
       Example example1 = new Example();
       example1.setValue(i + "番目");
       session.save(example1);
      }
      tx.commit();
      session.close();
     }

     SessionFactory createSessionFactory() {
      Configuration prototype = new Configuration().configure("db1.hibernate.cfg.xml");
      prototype.addResource("example.hbm.xml");
      List shard = new ArrayList();
      shard.add(new Configuration().configure("db1.hibernate.cfg.xml"));
      shard.add(new Configuration().configure("db2.hibernate.cfg.xml"));
      ShardStrategyFactory shardStrategyFactory = buildShardStrategyFactory();
      ShardedConfiguration shardedConfig = new ShardedConfiguration(
        prototype, shard, shardStrategyFactory);
      return shardedConfig.buildShardedSessionFactory();
     }

     ShardStrategyFactory buildShardStrategyFactory() {
      ShardStrategyFactory shardStrategyFactory = new ShardStrategyFactory() {
       public ShardStrategy newShardStrategy(List shardIds) {
        RoundRobinShardLoadBalancer loadBalancer = new RoundRobinShardLoadBalancer(
          shardIds);
        ShardSelectionStrategy pss = new RoundRobinShardSelectionStrategy(
          loadBalancer);
        ShardResolutionStrategy prs = new AllShardsShardResolutionStrategy(
          shardIds);
        ShardAccessStrategy pas = new SequentialShardAccessStrategy();
        return new ShardStrategyImpl(pss, prs, pas);
       }
      };
      return shardStrategyFactory;
     }
    }

    これを実行すると、db1.hibernate.cfg.xmlにて定義したRDBに

    ID:1, VALUE:0番目
    ID:2, VALUE:2番目
    ID:3, VALUE:4番目
    ID:4, VALUE:6番目
    ID:5, VALUE:8番目

    が入り、db2.hibernate.cfg.xmlにて定義したRDBに

    ID:32768, VALUE:1番目
    ID:32769, VALUE:3番目
    ID:32770, VALUE:5番目
    ID:32771, VALUE:7番目
    ID:32772, VALUE:9番目


    が入る。(例えばの話)

  9. データを取得してみる
  10.  void select() {
      SessionFactory sessionfactory = createSessionFactory();
      Session session = sessionfactory.openSession();
      List list = session.createQuery(" FROM Example ").list();
      for (Example example : (List) list) {
       System.out.println("ID:" + example.getId() + ", VALUE:" + example.getValue());
      }
     }

    単一のRDBを操作する場合と変わらない。複数のRDBであることを意識することなく取得できる。

  11. ShardAccessStrategyを別のものに換えてみる
  12. ShardAccessStrategyとは、データベースへのオペレーションを複数のRDBに対してどのように適用するかを定めたもの。現在の例はSequentialShardAccessStrategyを使っている。SequentialShardAccessStrategyは、複数のRDBに対してひとつずつ順番に処理を実行していく。これをParallelShardAccessStrategyに変更してみる。ParallelShardAccessStrategyはスレッドを生成し、複数のRDBに対して並列に処理を行う。

    変更したbuildShardStrategyFactory

    ShardStrategyFactory buildShardStrategyFactory() {
     ShardStrategyFactory shardStrategyFactory = new ShardStrategyFactory() {
      public ShardStrategy newShardStrategy(List shardIds) {
       RoundRobinShardLoadBalancer loadBalancer = new RoundRobinShardLoadBalancer(shardIds);
       ShardSelectionStrategy pss = new RoundRobinShardSelectionStrategy(loadBalancer);
       ShardResolutionStrategy prs = new AllShardsShardResolutionStrategy(shardIds);
       ThreadFactory factory = new ThreadFactory() {
        public Thread newThread(Runnable r) {
         Thread t = Executors.defaultThreadFactory().newThread(r);
         t.setDaemon(true);
         return t;
        }
       };
       ThreadPoolExecutor exec = new ThreadPoolExecutor(10, 50, 60,TimeUnit.MICROSECONDS,
         new SynchronousQueue(), factory);
       ShardAccessStrategy pas = new ParallelShardAccessStrategy(exec);
       return new ShardStrategyImpl(pss, prs, pas);
      }
     };
     return shardStrategyFactory;
    }

2007年04月10日

Hibernate Validatorを使う

Hibernate Validatorとは、ドメインモデルに対する制約条件をエンティティクラスにアノテーションで記述することが出来るようにしたHibernateのプロダクトです。エンティティクラスに書くのでバリデーションのコードが重複しにくいことや、アノテーションで手軽にかけるということくらいがウリでしょうか。

Beanのバリデーションを行う仕様は、JSR303 Bean Validationで進められているようです。

  1. 必要なライブラリ
  2. Hibernate Validatorを用いるにはHibernate Core本体、およびCoreが依存するjarに加え、Hibernate Annotationが必要です。
    Core以外には以下のjarがあればよいと思います。

    • ejb3-persistence.jar
    • hibernate-annotations.jar
    • hibernate-commons-annotations.jar
    • hibernate-validator.jar

    最近色々なHibernateのプロダクトがTOPレベルに昇格してきましたが、各プロダクトのバージョンをそろえないとうまく動かなかったりするので注意する必要があります。特にAnnotationは、バージョンが違っていても例外やエラーとなるのではなくアノテーションが適用されずに正常終了というケースもあるので不具合の判別が難しいことがあります。以下はhibernate.orgのCompatibility Matrixの抜粋です。

    Package Version Core Annotations EntityManager Validator Search Shards Tools
    Hibernate Core 3.2.2 GA - 3.2.x, 3.3.x 3.2.x, 3.3.x 3.0.x 3.0.x 3.0.x 3.2.0 Beta9

  3. エンティティクラス
  4. AnnotationとValidatorを使ったエンティティです。すごく簡単です。

    @Entity
    public class Customer implements Serializable {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     public int id;
     @NotNull
     @Length(max = 10)
     public String name;
     @Range(min = 0, max = 100)
     public int age;
     @Email
     public String email;
     @Past
     public Date birthday;
    }

    アノテーションによって上記のエンティティに設定した制約を纏めると以下のような感じです。


    • nameはNULLであってはいけない

    • nameは10文字以内でなければならない

    • ageは0から100の範囲内にある数値でなければならない

    • emailはEメールアドレスの仕様に沿った記述でなければならない

    • birthdayは過去日付でなければならない


    これがアノテーションで書けるのは簡単でいいですね。ビルトインで用意されている制約は他にも@CreditCardNumberとか@Size(コレクションなどのサイズ)などがあります。

  5. 定義ファイル
  6. hibernate.cfg.xmlはこんな感じです。Hibernate Annotation用のmapping定義を追加している程度です。

    <hibernate-configuration>
     <session-factory>
      <property name="dialect">org.hibernate.dialect.HSQLDialect</property>
      <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
      <property name="connection.username">sa</property>
      <property name="connection.password"></property>
      <property name="connection.url">jdbc:hsqldb:hsql://localhost:9001</property>
      <property name="show_sql">true</property>
      <property name="format_sql">true</property>
      <property name="hbm2ddl.auto">create</property>
      <mapping package="net.grandnature.example.entity"/>
      <mapping class="net.grandnature.example.invoice.entity.Customer"/>
     </session-factory>
    </hibernate-configuration>

  7. 動かす
  8. ・・・と言っても普通に動かすだけなのですが

    SessionFactory sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
    Session session = sessionFactory.openSession();
    Transaction tx = session.beginTransaction();
    Customer customer = new Customer();
    customer.name = "KIDA Taro";
    customer.age = 76;
    customer.email = "kida@example.com";
    DateFormat parser = DateFormat.getDateInstance();
    ex.birthday = parser.parse("1930/12/06");
    session.save(ex);
    tx.commit();
    session.close();

    このまま動かすと普通に登録されます。もし、例えばbirthdayが未来日付だったりEメールアドレスに不正なものが設定されていたりと、エンティティの状態が制約に違反していると、save時にorg.hibernate.validator.InvalidStateExceptionが投げられます。制約に違反している内容の詳細はInvalidStateExceptionのgetInvalidValuesメソッドで返されるorg.hibernate.validator.InvalidValueの配列に格納されています。

    ただし、@NotNullの制約に引っかかったときはorg.hibernate.PropertyValueExceptionが投げられます。

2007年04月11日

Hibernate Searchを触る

Hibernate Searchは、アノテーションによってドメインモデルの索引を生成し、テキストのクエリーからドメインモデルのエンティティを取得することができるようにしたプロダクトです。Hibernate Searchは、検索エンジンにApache Luceneを使っています。Hibernate Searchもこのたび晴れてHibernateのTOPプロジェクトに昇格したプロダクトです。(まだベータ版ですが)

ということでHibernate Validatorを触ったりHibernate Shardsを触ったりしたついでに少しだけ触ってみます。Hello Worldレベルです。

  1. 必要なライブラリ
  2. 面倒なので、前回のHibernate Validatorを使ったときのものを流用することにします。これに加えて必要なものは

    • lucene-core-2.1.0.jar
    • hibernate-search.jar

    です。どちらもHibernate Searchのアーカイブの中に含まれています。JMSを使うのであればjms.jarも要りますが今回は使いません。なおLuceneが2.1.0よりも古いと動かなくなったみたいなので注意です。

  3. エンティティクラス
  4. 前回のHibernate Validatorを使ったときのエンティティを少しだけ改造します。

    @Entity
    @Indexed
    public class Customer implements Serializable {
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     @DocumentId
     public int id;
     @NotNull
     @Length(max = 10)
     @Field(index = Index.TOKENIZED, store = Store.YES)
     public String name;
     @Range(min = 0, max = 100)
     public int age;
     @Email
     public String email;
     @Past
     public Date birthday;
    }

    赤字のところしか変えていません。

  5. 定義ファイル
  6. hibernate.cfg.xmlはこんな感じです。Hibernate Search用に変えている部分は省略しています。

    <hibernate-configuration>
     <session-factory>
      ・・・
      <property name="hibernate.search.default.directory_provider">
       org.hibernate.search.store.FSDirectoryProvider
      </property>
      <property name="hibernate.search.default.indexBase">C:\Indexes</property>
      <mapping package="net.grandnature.example.entity"/>
      <mapping class="net.grandnature.example.invoice.entity.Customer"/>
      <event type="post-update">
       <listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
      </event>
      <event type="post-insert">
        <listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
      </event>
      <event type="post-delete">
        <listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
      </event>
     </session-factory>
    </hibernate-configuration>

    Luceneによって生成されるインデックスのファイル達がC:\Indexesに保存される設定です。

  7. エンティティを登録してみる
  8. Sessionをそのまま使うのではなくFullTextSessionを取得してそれを使うようにします。それ以外は普通です。


    SessionFactory sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
    Session session = sessionFactory.openSession();
    FullTextSession fullTextSession = Search.createFullTextSession(session);
    Transaction tx = fullTextSession.beginTransaction();
    Customer customer = new Customer();
    customer.name = "KIDA Taro";
    customer.age = 76;
    customer.email = "kida@example.com";
    DateFormat parser = DateFormat.getDateInstance();
    ex.birthday = parser.parse("1930/12/06");
    fullTextSession.save(ex);
    tx.commit();
    fullTextSession.close();

    これを実行してエンティティがRDBに永続化されると、Luceneのインデックスも同時に生成されています。C:\Indexesの中にそれらしいファイルが出来ていると思いますが、それらがLuceneのインデックスファイルです。

  9. 検索してみる
  10. SessionFactory sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
    Session session = sessionFactory.openSession();
    FullTextSession fullTextSession = Search.createFullTextSession(session);
    BooleanQuery booleanQuery = new BooleanQuery();
    booleanQuery.add(new PrefixQuery(new Term("name", "Kida")),BooleanClause.Occur.SHOULD);
    Query fullTextQuery = fullTextSession.createFullTextQuery(booleanQuery);
    Iterator result = fullTextQuery.iterate();
    while (result.hasNext()) {
     Customer customer = (Customer) result.next();
     System.out.printf("%d - %s%n", customer.id, customer.name);
    }
    ・・・

    BooleanQuery、PrefixQuery、BooleanClause、TermはLuceneのクラスです。QueryはHibernateのQueryです。こんな感じでクエリーから該当するHibernateのエンティティのインスタンスを取得できます。

2007年04月12日

開発の現場 VOL.008

少しだけ寄稿しました。もしよろしければご覧下さい。どこを書いたかは内緒。

開発の現場 Vol.008
SE編集部
4798113379

2007年04月16日

[ゴータマ]執着について

ゴータマさんはことあるごとに、あらゆる執着を捨てましょうと言っているように思います。

  • 婦女たちに束縛されない聖者たちは、安らかに眠る。実につねに警戒してまもらねばならぬ人々(婦女たち)のあいだにあっては、真実を得ることは、きわめて難しい。
  • 愛欲よ。われらは、お前を殺してしまった。われらは、いまやお前に負い目は無い。われらは、いまや安らぎ(ニルヴァーナ)におもむく。そこに至れば悩むことがない。
「仏弟子の告白―テーラガーター 訳:中村 元」 テーラガーター ひとつずつの語句の集成 ゴータマ長老
  • もろもろの欲望は苦しみである。エーラカよ。もろもろの欲望は安楽ではない。エーラカよ。もろもろの欲望を求める人は、(実は)苦しみを求めるのである。エーラカよ。もろもろの欲望を求めない人--かれは苦しみを求めないのである。

「仏弟子の告白―テーラガーター 訳:中村 元」
テーラガーター ひとつずつの語句の集成 エーラカ長老

こういった世俗的な欲望のようなものは現代の感覚でも結構イメージしやすいと思う(最初のやつは微妙ですが)のですが、ゴータマさんの言う執着とはもっと広い意味を持つようです。

悪魔ハーピマンが言った、


  •  「子のある者は子について喜び、また牛のある者は牛について喜ぶ。人間の執著するもとのものは喜びである。執著するもとのもののない人は、実に喜ぶことがない。」


師は答えた、
 

  • 「子のある者は子について憂(うれ)い、また牛のある者は牛について憂う。実に人間の憂いは執著するもとのものである。執著するもとのもののない人は、憂うることがない。」


「ブッダのことば スッタニパータ 訳:中村元」
スッタニパータ 第一 蛇の章/二 ダニヤ

  • この世における人々の命は、定まった相なく、どれだけ生きられるか解らない。惨ましく、短くて、苦悩をともなっている。
  • 生れたものどもは、死を遁(のが)れる道がない。老いに達しては、死ぬ。実に生あるものどもの定めは、このとおりである。
  • 熟した果実は早く落ちる。それと同じく、生まれた人々は、死なねばならぬ。かれはにはつねに死の怖れがある。
  • ・・・

  • このように世間の人々は死と老いによって害われる。それ故に賢者は、世のなりゆきを知って、悲しまない。
  • ・・・

  • 泣き悲しんでは、心の安らぎは得られない。ただかれにはますます苦しみが生じ、身体がやつれるだけである。

  • みずから自己を害いながら、身は痩せて醜くなる。そうしたからとて、死んだ人々はどうにもならない。嘆き悲しむのは無益である。

  • 人が悲しむのをやめないならばますます苦悩を受けることになる。亡くなった人のことを嘆くならば、悲しみに捕われてしまったのだ。
  • ・・・

  • 己が悲嘆と愛執と憂いとを除け。己が楽しみを求める人は、己が(煩悩の)矢を抜くべし。(煩悩の)矢を抜き去って、こだわることなく、心の安らぎを得たならば、あらゆる悲しみを超越して、悲しみなき者となり、安らぎに帰する。


「ブッダのことば スッタニパータ 訳:中村元」
スッタニパータ 第三 大いなる章/八 矢

今の世の中はハーピマンっぽい考えのほうが浸透しているので、この言葉が現代でそのまま受け入れられるかどうかは微妙だなと思います。今のメジャーな映画とかドラマとかの多くが受け入れられなくなりますよね。こういった物語を盛り上げるための要素、例えば家族や仲間を殺された怒りを糧に強くなるとか、子供のために頑張って働く、とかそういったことは駄目だということになると思います。
私個人的には世俗的な執着が無いと面白くないと思っているので、この点はゴータマさんの言うような考え方にはなれないなあと思います。でも、あらゆる世俗的な執着を捨てないと一喜一憂して苦しいですよね、というのは理解できるような気がします。

About 2007年04月

2007年04月にブログ「GrandNature」に投稿されたすべてのエントリーです。過去のものから新しいものへ順番に並んでいます。

前のアーカイブは2007年03月です。

次のアーカイブは2007年06月です。

他にも多くのエントリーがあります。メインページアーカイブページも見てください。