EJB 3.0(Public Draft)入門記 Java Persistence API Chapter2 その14
前回は継承マッピング戦略、Inheritance Mapping Strategeiesでした。今日は2番目の戦略であるTable per Class Strategyです。
2.1.10.2 Table per Class Strategy
この戦略ではそれぞれのクラスが別々のテーブルにマッピングされます。クラスのすべてのプロパティ(継承したプロパティを含む)が対応するテーブルのカラムにマップされるそうです。言葉で理解するよりもコードとDDLをみたほうがわかります。昨日の例に使ったコードをtable per class strategyを使うように変更してみました。ただし、今回はEntityManagerを使ってデータを作成するつもりなのでIdアノテーションにgenerate要素を指定してます。
@Entity(access=AccessType.FIELD) @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) public class Employee implements Serializable { @Id(generate=GeneratorType.TABLE) private int id; private String name; @ManyToOne private Address address; //getter, setter省略 }
@Entity(access=AccessType.FIELD) public class FullTimeEmployee extends Employee { private Integer salary; //getter, setter省略 }
@Entity(access = AccessType.FIELD) public class PartTimeEmployee extends Employee { private Float hourlyWage; //getter, setter省略 }
@Entity(access=AccessType.FIELD) public class Address implements Serializable { @Id private int id; private String name; @OneToMany(mappedBy="address") private Collectionemployees = new HashSet(); //getter, setter省略 }
hbm2ddlで作成されたDDLです。
CREATE TABLE ADDRESS( ID INTEGER NOT NULL PRIMARY KEY, NAME VARCHAR(255)) CREATE TABLE EMPLOYEE( ID INTEGER NOT NULL PRIMARY KEY, NAME VARCHAR(255), ADDRESS_ID INTEGER, CONSTRAINT FK4AFD4ACE233D5405 FOREIGN KEY(ADDRESS_ID) REFERENCES ADDRESS(ID)) CREATE TABLE FULLTIMEEMPLOYEE( ID INTEGER NOT NULL PRIMARY KEY, NAME VARCHAR(255), ADDRESS_ID INTEGER, SALARY INTEGER, CONSTRAINT FK4AFD4ACE233D5405D5BDCEA FOREIGN KEY(ADDRESS_ID) REFERENCES ADDRESS(ID)) CREATE TABLE PARTTIMEEMPLOYEE( ID INTEGER NOT NULL PRIMARY KEY, NAME VARCHAR(255), ADDRESS_ID INTEGER, HOURLYWAGE FLOAT, CONSTRAINT FK4AFD4ACE233D54059735284E FOREIGN KEY(ADDRESS_ID) REFERENCES ADDRESS(ID)) CREATE TABLE HIBERNATE_SEQUENCES( SEQUENCE_NAME VARCHAR(255), SEQUENCE_NEXT_HI_VALUE INTEGER)
ポイントは
- Inheritanceアノテーションの要素strategyに「InheritanceType.TABLE_PER_CLASS」と指定している
- エンティティEmployeeをFullTimeEmployeeとPartTimeEmployeeが継承している
- クラスに対応してEMPLOYEEとFULLTIMEEMPLOYEEとPARTTIMEEMPLOYEEのテーブルがある。
- EMPLOYEEテーブルと同じカラムがFULLTIMEEMPLOYEEとPARTTIMEEMPLOYEEテーブルにもある。
- HIBERNATE_SEQUENCESというテーブルがある。Employeeクラスに@Id(generate=GeneratorType.TABLE)と指定したからだと思われます。うーん、こんなものができるとは。採番テーブルらしい。
table per class strategyには次の欠点があるそうです。
ポリモーフィックな関連のサポートが貧弱というのはどういうことでしょう。ほかの戦略ではできることができない?できるけどパフォーマンス的に問題あり?
Hibernate in Actionにはテーブルが別々になってしまうため外部制約で参照しにくいみたいなことが書いてあります。
では動かしてみます。
いつもならSQLでデータを用意するところですが今回はどのようにデータを用意しておけばいいのかわからなかったのでEntityManagerを使ってデータを作ることにしました。いつもは自分でIDをINSERTしているのですが、今回はIdアノテーションに「GeneratorType.TABLE」と指定しておきました。「GeneratorType.AUTO」や「GeneratorType.IDENTITY」でないのはtable per class strategyではそれらは使えなかったからです。使ってみたらExceptionをくらいました。EJB 3.0の仕様というよりHibernateの実装?
「GeneratorType.TABLE」とはどういうものかというと、よくわかってないのですが、採番テーブルから値を取得するということだと思います。今回は自動的に作成された採番テーブルを使いますが、任意のものも使用できるのかもしれません。
データを作るために次のクラスを実行してみます。
@Stateless public class CreateDataBean implements CreateData { @PersistenceContext private EntityManager em; public void main() { Address ad = new Address(); ad.setName("京都"); FullTimeEmployee ft = new FullTimeEmployee(); ft.setName("ゴン"); ft.setSalary(2000000); ft.setAddress(ad); PartTimeEmployee pt = new PartTimeEmployee(); pt.setName("うさはな"); pt.setAddress(ad); pt.setHourlyWage(900F); em.persist(ad); em.persist(ft); em.persist(pt); } public static void main(String[] args) throws Exception { EJB3StandaloneBootstrap.boot(null); EJB3StandaloneBootstrap.deployXmlResource("ejb3-deployment.xml"); InitialContext ctx = new InitialContext(); CreateData c = (CreateData) ctx.lookup(Client.class.getName()); c.main(); EJB3StandaloneBootstrap.shutdown(); } }
データは次のようになりました。
EMPLOYEE
ID NAME ADDRESS_ID -- ---- ----------
FULLTIMEEMPLOYEE
ID NAME ADDRESS_ID SALARY -- ---- ---------- ------- 1 ゴン 1 2000000
PARTTIMEEMPLOYEE
ID NAME ADDRESS_ID HOURLYWAGE -- ---- ---------- ---------- 2 うさはな 1 900.0
ADDRESS
ID NAME -- ---- 1 京都
HIBERNATE_SEQUENCES
SEQUENCE_NAME SEQUENCE_NEXT_HI_VALUE ------------- ---------------------- Employee 1
なるほどFULLTIMEEMPLOYEEとPARTTIMEEMPLOYEEは違うIDになるんですね。EMPLOYEEにも同じようなデータが入るのかもと思ったのですがそうではないみたいです。HIBERNATE_SEQUENCESのSEQUENCE_NAMEにはEmployeeというクラス名とおぼしき値がはいってます。
上記のデータを取得するプログラム(前回と同じClientBean)を実行してみます。そのときの結果はこうなります。
## Addressからたどる ## Hibernate: select address0_.id as id0_0_, address0_.name as name0_0_ from Address address0_ where address0_.id=? Hibernate: select employees0_.address_id as address3_1_, employees0_.id as id1_, employees0_.id as id1_0_, employees0_.name as name1_0_, employees0_.address_id as address3_1_0_, employees0_.salary as salary2_0_, employees0_.hourlyWage as hourlyWage3_0_, employees0_.clazz_ as clazz_0_ from ( select null as hourlyWage, address_id, name, id, null as salary, 0 as clazz_ from Employee union select null as hourlyWage, address_id, name, id, salary, 1 as clazz_ from FullTimeEmployee union select hourlyWage, address_id, name, id, null as salary, 2 as clazz_ from PartTimeEmployee ) employees0_ where employees0_.address_id=?
Address取得のSQLが実行された後にすべてのEmployeeをとってくるSQLが実行されます。
それにしてもUNIONを使ったSQLってこうことですか。継承クラスが多くてもデータが多くても大変そう。ところで、SQLだけを抜き出したわけではないのですが出力はここまでです。本当はログ出力のあとにクラス名などが表示されるようにしてあるのですが出力されていない。実はEmployeeを取得するSQLの結果がHSQLDBから返ってこないのです。HSQLDBが耐えられないのか?
サブクラスのエンティティを直接EntitiyManagerに指定した場合のSQLを確かめて見ます。
em.find(FullTimeEmployee.class, 1);
SQLはこうなります。
select fulltimeem0_.id as id1_1_, fulltimeem0_.name as name1_1_, fulltimeem0_.address_id as address3_1_1_, fulltimeem0_.salary as salary2_1_, address1_.id as id0_0_, address1_.name as name0_0_ from FullTimeEmployee fulltimeem0_ left outer join Address address1_ on fulltimeem0_.address_id=address1_.id where fulltimeem0_.id=?
こっちはちゃんと返ってきました。
今日はここまで。結構コピペ疲れました。
2.1.10.2 Table per Class Strategy終了です。
次回はJoined Subclass Strategyです。でも、これ2.1.9ですでに動かしたような。