2009年12月22日火曜日

Nested Set をJavaにマッピングしたいが・・・

前に、RDB で階層構造を扱うための Nested Set Model というイディオムを試してみた。こんな風に lft 列と rgt 列で包含関係を表現したものだった。[参考URL]
+-------------+----------------------+------+------+
| category_id | name                 | lft  | rgt  |
+-------------+----------------------+------+------+
|           1 | ELECTRONICS          |    1 |   20 |
|           2 | TELEVISIONS          |    2 |    9 |
|           3 | TUBE                 |    3 |    4 |
|           4 | LCD                  |    5 |    6 |
|           5 | PLASMA               |    7 |    8 |
|           6 | PORTABLE ELECTRONICS |   10 |   19 |
|           7 | MP3 PLAYERS          |   11 |   14 |
|           8 | FLASH                |   12 |   13 |
|           9 | CD PLAYERS           |   15 |   16 |
|          10 | 2 WAY RADIOS         |   17 |   18 |
+-------------+----------------------+------+------+
今回はこれとJava コード(JPAベース)とのマッピングを、ちょっと考えてみたい。 上のテーブルの1レコードを、Category クラス(アノテーション付けただけのPOJO)にマッピングする。この際、category_id 列と name 列 は、それぞれ Category クラスの id フィールド、name フィールドに対応付けるが、lft 列と rgt 列は純粋に RDB操作のためのテクニカルな列なので POJO のフィールドにはしない。ただし、lft 列と rgt 列を用いて得られる階層構造へのアクセスは利用したい。 JPA で提供されている普通の方法を使うとこんな感じだろうか
@Entity
@NamedNativeQueries({
  @NamedNativeQuery(
   name="descendants", 
   query="SELECT c.category_id, c.name " +
         "FROM category as p, category as c " +
         "WHERE p.category_id=?1 and p.lft < c.lft and c.rgt < p.rgt " +
         "ORDER BY c.lft",
   resultClass=Category.class),
  @NamedNativeQuery(
   name="ancestors", 
   ・・・省略
})
public class Category {
   @Id()
   @Column(name="category_id")
   private int categoryId;
   private String name;
   ・・・ getter() / setter() 省略
}
名前つきクエリ descendants のクライアントコードはこんな感じになるはず。
Category c = 略

EntityManager em = emf.createEntityManager();
List<Category> descendants = em.createNamedQuery("descendants")
   .setParameter(1, c.getId()).getResultList();
・・・うーん。なんか違う気がする。 先祖要素や子孫要素にアクセスするたびにEntityManager を参照すると、ビジネスロジックとJPA コードの混在で扱いにくくなって、余計な開発/保守コストがかかるはず。 例えば、こんな風に書けたら良いんだけど・・・
@Entity
public class Category {
   @Id()
   @Column(name="category_id")
   private int categoryId;
・・・略

   List<Category> ancestors;
   Tree<Category> children;
   
   public Category parent() {
      return ancestors.empty() ? null: ancestors.get(0);
   }
   public List<Category> ancestors() {
      return ancestors;
   }
   public List<Category> children() {
      return children.elementsAtLevel(1);
   }
   public List<Category> descendants() {
      return children.toList();
   }
}
//クライアントコード
void doSomeBusinessLogic(Category cat) {
   for (Category descendant: cat.descendants()) {
      ・・・ cat の子孫要素 descendant を使って何かやるコード
   }
}
・・・そう簡単にはできないらしい。自明な解は JPA はもちろん Hibernate にも見当たらない。うーん、もうちょっと考えないと。

0 件のコメント:

コメントを投稿