前に、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 件のコメント:
コメントを投稿