Hibernate Annotations
サーバサイドJava開発に関して、最近EJB3が話題になることが多いです。ですが個人的には、SessionBeanなどの「分散環境」としてのEJBにはもう興味が湧かないし、今更使おうとも思いません。・・・ですが、今後のO/Rマッピング標準になる可能性が高いjavax.persistenceパッケージには非常に興味があります。現在ベータ版が出ているHibernate Annotationsがこの機能を実装しているということなので、ちょっと使ってみました。
Hibernateのホームページから、Hibernate3.1とHibernate Annotationsをダウンロード。必須ライブラリをクラスパスに登録して準備完了。今回は、HSQLDBに標準でついてくるテストデータを利用して、EntityBeanを作ってみました。
・・・あ、ちなみにEJB3については雑誌でチョロっと読んだくらいの知識しかありません。まぁ、自分くらいのレベルの人間が軽く作れるようじゃないと「EoD」とは呼べないかな・・・ということで(笑)
Customerクラス
CUSTOMERテーブルのデータを格納するクラス
package test; import java.io.Serializable; import java.util.Set; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.OneToMany; @Entity public class Customer implements Serializable { /** * */ private static final long serialVersionUID = 7521312932563435856L; private int id; private String firstName; private String lastName; private String street; private String city; private Set<Invoice> invoiceList; @OneToMany(mappedBy = "customer", fetch = FetchType.EAGER) public Set<Invoice> getInvoiceList() { return invoiceList; } public void setInvoiceList(Set<Invoice> invoice) { this.invoiceList = invoice; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } @Id public int getId() { return id; } public void setId(int id) { this.id = id; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } }
EntityBeanであることを表す@Entity、キー項目につける@Id、1対多の結合を表す@OneToMany。クラス名やフィールド名を、利用するDBのテーブル名やカラム名に合わせれば、@Tableや@Column等の定義は必要ありません。殆ど最小限のアノテーションだけでEntityBeanを作成することが出来ます。
Invoiceクラス
package test; import java.io.Serializable; import java.math.BigDecimal; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; @Entity public class Invoice implements Serializable { /** * */ private static final long serialVersionUID = -5229848113066286613L; private int id; private Customer customer; private BigDecimal total; @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "customerid") public Customer getCustomer() { return customer; } public void setCustomer(Customer customer) { this.customer = customer; } @Id public int getId() { return id; } public void setId(int id) { this.id = id; } public BigDecimal getTotal() { return total; } public void setTotal(BigDecimal total) { this.total = total; } }
こちらは多対1の関係なので@MenyToOneアノテーションを定義。fetch属性を書かないと、デフォルトでLAZYが指定されるみたいで、この場合HibernateのLazy Load機能が働いて、必要になったときにだけDBにアクセスしに行くようになります。
また、@JoinColumnアノテーションは、結合条件となるテーブルのカラム名を指定します。
さて、早速実行してみようと思うのですが・・・折角ですから最近勉強しているSeasar2で動かしてみたいと思います。当然ここではS2Hibernateを使ってみたいです。ただし、Hibernate Annotationsはまだベータ版ですから、当然S2Hibernateは対応していません。自分でHibernate初期化の部分を調査して修正する必要があります。
通常のHibernateとHibernate Annotationsの違いは、初期化するときにAnnotationConfigurationというクラスを使うところみたいです。S2Hibernateでこれを行っているのは、S2SessionFactoryImplクラスでした。このクラスはfinal指定されているので、ソース内容を参考にして、新たにFactoryクラスを作成することになります。作成したクラスをs2hibernate.diconファイルに登録して、S2Hibernate側の準備は終了。
では早速実行クラスを作ってみます。
CustomerDaoインターフェイス
package test; public interface CustomerDao { Customer getCustomer(int id); }
CustomerDaoImplクラス
package test; import org.seasar.hibernate3.S2SessionFactory; public class CustomerDaoImpl implements CustomerDao { private S2SessionFactory sessionFactory; public CustomerDaoImpl(S2SessionFactory factory) { sessionFactory = factory; } public Customer getCustomer(int id) { return (Customer) sessionFactory.getSession().get(Customer.class, id); } }
CustomerServiceインターフェイス
package test; public interface CustomerService { Customer getCustomer(int id); }
CustomerServiceImplクラス
package test; public class CustomerServiceImpl implements CustomerService { private CustomerDao dao; public CustomerServiceImpl(CustomerDao dao) { this.dao = dao; } public Customer getCustomer(int id) { return dao.getCustomer(id); } }
app.dicon
<?xml version="1.0" encoding="Shift_JIS"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN" "http://www.seasar.org/dtd/components.dtd"> <components> <include path="s2hibernate3.dicon"/> <component class="test.CustomerDaoImpl"/> <component class="test.CustomerServiceImpl"> <aspect>j2ee.requiredTx</aspect> </component> </components>
hibernate3.cfg.xml
<?xml version="1.0"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="dialect">org.hibernate.dialect.HSQLDialect</property> <property name="show_sql">true</property> <mapping package="test"/> <mapping class="test.Customer"/> <mapping class="test.Invoice"/> </session-factory> </hibernate-configuration>
Clientクラス
package test.client; import java.util.Set; import org.seasar.framework.container.S2Container; import org.seasar.framework.container.factory.SingletonS2ContainerFactory; import test.Customer; import test.CustomerService; import test.Invoice; public class Client { /** * @param args */ public static void main(String[] args) { SingletonS2ContainerFactory.init(); S2Container container = SingletonS2ContainerFactory.getContainer(); CustomerService service = (CustomerService) container .getComponent(CustomerService.class); Customer customer = service.getCustomer(4); System.out.println("ID:" + customer.getId()); System.out.println("FIRSTNAME:" + customer.getFirstName()); System.out.println("LASTNAME:" + customer.getLastName()); System.out.println("CITY:" + customer.getCity()); System.out.println("STREET:" + customer.getStreet()); Set<Invoice> set = customer.getInvoiceList(); for (Invoice invoice : set) { System.out.println("\tINVOICEID:" + invoice.getId()); System.out.println("\tTOTAL:" + invoice.getTotal()); } } }
実行結果
WARN 2005-09-19 20:34:48,609 [main] test.S2AnnotationSessionFactoryImplのプロパティ(interceptor)が見つからないので設定をスキップします DEBUG 2005-09-19 20:34:49,015 [main] トランザクションを開始しました log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment). log4j:WARN Please initialize the log4j system properly. DEBUG 2005-09-19 20:34:51,093 [main] 物理的なコネクションを取得しました DEBUG 2005-09-19 20:34:51,250 [main] 論理的なコネクションを取得しました Hibernate: select customer0_.id as id0_1_, customer0_.firstName as firstName0_1_, customer0_.lastName as lastName0_1_, customer0_.city as city0_1_, customer0_.street as street0_1_, invoicelis1_.customerid as customerid3_, invoicelis1_.id as id3_, invoicelis1_.id as id1_0_, invoicelis1_.customerid as customerid1_0_, invoicelis1_.total as total1_0_ from Customer customer0_ left outer join Invoice invoicelis1_ on customer0_.id=invoicelis1_.customerid where customer0_.id=? DEBUG 2005-09-19 20:34:51,531 [main] トランザクションをコミットしました DEBUG 2005-09-19 20:34:51,546 [main] 論理的なコネクションを閉じました ID:4 FIRSTNAME:Sylvia LASTNAME:Ringer CITY:Dallas STREET:365 College Av. INVOICEID:18 TOTAL:3772.80 INVOICEID:35 TOTAL:3102.60 INVOICEID:40 TOTAL:5288.40
実は、当初は@OneToManyのfetch属性を定義せずに実行したところエラーになってしまいました。POJOとして様々な用途に使うことを考えれば、fetchは標準でEAGERにしておいて欲しいなと思うのですが・・・これがEJB3の標準の動きになるんでしょうか?・・・
何はともあれ、かなり簡単にEJB3のO/Rマッピング機能を利用できることがわかりました。Hibernateのhbmファイルが完全に無くなるっていうのはインパクト大きいですね。前回やった、S2AOPでアノテーションを利用したときも感じましたが、JDK5のアノテーションはJavaの開発常識を覆す、大きな武器になりそうな気がします。