EJB 3.0(Public Draft)入門記 Java Persistence API おまけ Composite構造の永続化
今回はドキュメントからちょっと離れて別のことをしてみたいと思います。
Chapter 2 で継承関係を永続化できることがわかったのですが、Compositeパターンであらわされた構造を永続化したらどんな感じなのかなーと気になり始めたのでこれを試してみます。デザパタ苦手なんですけど。
- 今回試すことのポイント。
まず、エンティティの定義。親を辿れるようにparentフィールドをもちます。メソッドにはaddとremoveとprintをもちます。
@Entity(access = AccessType.FIELD) @Inheritance(strategy=InheritanceType.JOINED) public abstract class Component { @Id(generate=GeneratorType.AUTO) protected int id; protected String name; @ManyToOne protected Composite parent; public void add(Component component){ } public void remove(Component component){ } public void print() { print(""); } protected abstract void print(String prefix); //getter, setter省略 }
@Entity(access=AccessType.FIELD) public class Composite extends Component { @OneToMany(mappedBy="parent", cascade=CascadeType.ALL) private Collectioncomponents = new HashSet (); public Composite(){ } public Composite(String name){ this.name = name; } @Override public void add(Component component) { component.setParent(this); components.add(component); } @Override public void remove(Component component) { component.setParent(null); components.remove(component); } @Override public void print(String prefix) { System.out.println(prefix + "/" + name); for(Component c :components) { c.print(prefix + "/" + name); } } //getter, setter省略 }
@Entity(access=AccessType.FIELD) public class Leaf extends Component { public Leaf(){ } public Leaf(String name){ this.name = name; } @Override public void print(String prefix) { System.out.println(prefix + "/" + name); } }
これらのエンティティのテーブルへのマッピングをあらわすDDLは次のようになります。COMPONENTとCOMPOSITEが相互参照しちゃいますね。
CREATE TABLE COMPONENT( ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY, NAME VARCHAR(255), PARENT_ID INTEGER) CREATE TABLE COMPOSITE( ID INTEGER NOT NULL PRIMARY KEY, CONSTRAINT FK24039267B1EB7A23 FOREIGN KEY(ID) REFERENCES COMPONENT(ID)) CREATE TABLE LEAF( ID INTEGER NOT NULL PRIMARY KEY, CONSTRAINT FK24137EB1EB7A23 FOREIGN KEY(ID) REFERENCES COMPONENT(ID)) ALTER TABLE COMPONENT ADD CONSTRAINT FK24013CDD2D547362 FOREIGN KEY(PARENT_ID) REFERENCES COMPOSITE(ID)
クライアントです。persistとprintとretrieveは別トランザクションにしてみました。関連が双方から手繰れるか確認するためにleafを指定してrootを取得するといったこともしてます。
@Stateless public class ClientBean implements Client { @PersistenceContext private EntityManager em; public void persist() { Composite root = new Composite("root"); Composite composite1 = new Composite("composite1"); Leaf leaf1 = new Leaf("leaf1"); Leaf leaf2 = new Leaf("leaf2"); root.add(composite1); root.add(leaf1); composite1.add(leaf2); em.persist(root); } public void print(String name) { retrieve(name).print(); } public Component retrieve(String name) { Query query = em.createQuery("from Component c where c.name = :name") .setParameter("name", name); return (Component) query.getSingleResult(); } public Component retrieveRoot(String name) { Component component = retrieve(name); return findRoot(component); } private Component findRoot(Component component) { if (component.getParent() == null) { return component; } else { return findRoot(component.getParent()); } } public static void main(String[] args) throws Exception { EJB3StandaloneBootstrap.boot(null); EJB3StandaloneBootstrap.deployXmlResource("ejb3-deployment.xml"); InitialContext ctx = new InitialContext(); Client client = (Client) ctx.lookup(Client.class.getName()); System.out.println("## PERSIST"); client.persist(); System.out.println("\n## PRINT"); client.print("root"); System.out.println("\n## RETRIEVE"); System.out.println(client.retrieve("root").getName()); System.out.println("\n## RETRIEVE ROOT FROM LEAF"); System.out.println(client.retrieveRoot("leaf2").getName()); EJB3StandaloneBootstrap.shutdown(); }
実行結果。SQLのあたりは適当に整形してます。出力結果は太字にしてみました。
## PERSIST Hibernate: insert into Component (name, parent_id, id) values (?, ?, null) Hibernate: call identity() Hibernate: insert into Composite (id) values (?) Hibernate: insert into Component (name, parent_id, id) values (?, ?, null) Hibernate: call identity() Hibernate: insert into Composite (id) values (?) Hibernate: insert into Component (name, parent_id, id) values (?, ?, null) Hibernate: call identity() Hibernate: insert into Leaf (id) values (?) Hibernate: insert into Component (name, parent_id, id) values (?, ?, null) Hibernate: call identity() Hibernate: insert into Leaf (id) values (?) ## PRINT Hibernate: select component0_.id as id1_, component0_.name as name1_, component0_.parent_id as parent3_1_, case when component0_1_.id is not null then 1 when component0_2_.id is not null then 2 when component0_.id is not null then 0 end as clazz_ from Component component0_ left outer join Composite component0_1_ on component0_.id=component0_1_.id left outer join Leaf component0_2_ on component0_.id=component0_2_.id where component0_.name=? /root Hibernate: select components0_.parent_id as parent3_1_, components0_.id as id1_, components0_.id as id1_0_, components0_.name as name1_0_, components0_.parent_id as parent3_1_0_, case when components0_1_.id is not null then 1 when components0_2_.id is not null then 2 when components0_.id is not null then 0 end as clazz_0_ from Component components0_ left outer join Composite components0_1_ on components0_.id=components0_1_.id left outer join Leaf components0_2_ on components0_.id=components0_2_.id where components0_.parent_id=? /root/composite1 Hibernate: select components0_.parent_id as parent3_1_, components0_.id as id1_, components0_.id as id1_0_, components0_.name as name1_0_, components0_.parent_id as parent3_1_0_, case when components0_1_.id is not null then 1 when components0_2_.id is not null then 2 when components0_.id is not null then 0 end as clazz_0_ from Component components0_ left outer join Composite components0_1_ on components0_.id=components0_1_.id left outer join Leaf components0_2_ on components0_.id=components0_2_.id where components0_.parent_id=? /root/composite1/leaf2 /root/leaf1 ## RETRIEVE Hibernate: select component0_.id as id1_, component0_.name as name1_, component0_.parent_id as parent3_1_, case when component0_1_.id is not null then 1 when component0_2_.id is not null then 2 when component0_.id is not null then 0 end as clazz_ from Component component0_ left outer join Composite component0_1_ on component0_.id=component0_1_.id left outer join Leaf component0_2_ on component0_.id=component0_2_.id where component0_.name=? Hibernate: select components0_.parent_id as parent3_1_, components0_.id as id1_, components0_.id as id1_0_, components0_.name as name1_0_, components0_.parent_id as parent3_1_0_, case when components0_1_.id is not null then 1 when components0_2_.id is not null then 2 when components0_.id is not null then 0 end as clazz_0_ from Component components0_ left outer join Composite components0_1_ on components0_.id=components0_1_.id left outer join Leaf components0_2_ on components0_.id=components0_2_.id where components0_.parent_id=? Hibernate: select components0_.parent_id as parent3_1_, components0_.id as id1_, components0_.id as id1_0_, components0_.name as name1_0_, components0_.parent_id as parent3_1_0_, case when components0_1_.id is not null then 1 when components0_2_.id is not null then 2 when components0_.id is not null then 0 end as clazz_0_ from Component components0_ left outer join Composite components0_1_ on components0_.id=components0_1_.id left outer join Leaf components0_2_ on components0_.id=components0_2_.id where components0_.parent_id=? root ## RETRIEVE ROOT FROM LEAF Hibernate: select component0_.id as id1_, component0_.name as name1_, component0_.parent_id as parent3_1_, case when component0_1_.id is not null then 1 when component0_2_.id is not null then 2 when component0_.id is not null then 0 end as clazz_ from Component component0_ left outer join Composite component0_1_ on component0_.id=component0_1_.id left outer join Leaf component0_2_ on component0_.id=component0_2_.id where component0_.name=? Hibernate: select composite0_.id as id1_1_, composite0_1_.name as name1_1_, composite0_1_.parent_id as parent3_1_1_, composite1_.id as id1_0_, composite1_1_.name as name1_0_, composite1_1_.parent_id as parent3_1_0_ from Composite composite0_ inner join Component composite0_1_ on composite0_.id=composite0_1_.id left outer join Composite composite1_ on composite0_1_.parent_id=composite1_.id left outer join Component composite1_1_ on composite1_.id=composite1_1_.id where composite0_.id=? Hibernate: select components0_.parent_id as parent3_1_, components0_.id as id1_, components0_.id as id1_0_, components0_.name as name1_0_, components0_.parent_id as parent3_1_0_, case when components0_1_.id is not null then 1 when components0_2_.id is not null then 2 when components0_.id is not null then 0 end as clazz_0_ from Component components0_ left outer join Composite components0_1_ on components0_.id=components0_1_.id left outer join Leaf components0_2_ on components0_.id=components0_2_.id where components0_.parent_id=? Hibernate: select components0_.parent_id as parent3_1_, components0_.id as id1_, components0_.id as id1_0_, components0_.name as name1_0_, components0_.parent_id as parent3_1_0_, case when components0_1_.id is not null then 1 when components0_2_.id is not null then 2 when components0_.id is not null then 0 end as clazz_0_ from Component components0_ left outer join Composite components0_1_ on components0_.id=components0_1_.id left outer join Leaf components0_2_ on components0_.id=components0_2_.id where components0_.parent_id=? root
テーブルの中身はこうなってます。
COMPONENT
ID NAME PARENT_ID
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
COMPOSITE
ID
- -
ID
- -
とりあえず、Compositeをそのまま永続化できているようです。でもCOMPONENTとCOMPOSITEのテーブルが相互参照しちゃいます、OKなんでしょうか。
ところで、ちょっと驚きなところを発見しました。printメソッド経由でretrieveメソッドを呼ぶときとretrieveメソッド単独で呼ぶときで返されるComponentエンティティの状態が違います。出力のされ方を見るとprintメソッド内でretriveメソッドを呼んで取得しているエンティティは関連先のエンティティをまだ持っていないようです(必要になったらLazy Loadingを行っているようです)。でも、retrieveメソッドを単独で呼んだ場合、エンティティは関連先を取得した後に返されているようです(試してみたらそうでした)。もしかしてトランザクションの外に何が返されるかでアクセスのされ方が変わるんでしょうか?これは興味深いです。
あっ、用意したremoveメソッドを試すのを忘れた。