無定義Hibernate
2005年6月27日
1.Hibernate on Rails?
1.1.どんなもの?
Ruby on Railsってのがあって、ほとんど何も書かずにDBからテーブル情報とってきてORマッピングするんですが、それと同じようにHibernateでもマッピングクラスとかマッピング定義とか書かずにORマッピングしようじゃないか、っていうものです。
マッピングクラスのソースとマッピング定義をデータベースから自動生成するなら、それを実行時にやっちゃえばいいんじゃない?ってとこで。
実際、内部で、ファイルに保存すれば普通にマッピング定義として使えるようなhbm.xmlを生成してます。
マッピングクラスのソースとマッピング定義をデータベースから自動生成するなら、それを実行時にやっちゃえばいいんじゃない?ってとこで。
実際、内部で、ファイルに保存すれば普通にマッピング定義として使えるようなhbm.xmlを生成してます。
2.用意するもの
2.1.本体
ここからダウンロードしてください。
Java2SE5の文法を使ってるので、Java2SE5.0以降じゃないと動きません。
あと、データベースはPostgreSQLを前提にしてます。自動採番の型を使わなければ問題ないですが。
hibernateonrails.jarをクラスパスに含めます。
http://www.fk.urban.ne.jp/home/kishida/soft/hibernateonrails-preview.zip
(16KB)
Java2SE5の文法を使ってるので、Java2SE5.0以降じゃないと動きません。
あと、データベースはPostgreSQLを前提にしてます。自動採番の型を使わなければ問題ないですが。
hibernateonrails.jarをクラスパスに含めます。
2.2.ライブラリ
次のライブラリを使います。
Hibernate3(
hibernate3.jar
antlr-2.7.5H3.jar
asm-attrs.jar
asm.jar
cglib-2.1.jar
commons-collections-2.1.1.jar
commons-logging-1.0.4.jar
dom4j-1.6.jar
ehcache-1.1.jar
jta.jar
Velocity1.4(
velocity-dep-1.4.jar
Javassist3.0(
javassist.jar
Hibernate3(
http://www.hibernate.org/
)
hibernate3.jar
antlr-2.7.5H3.jar
asm-attrs.jar
asm.jar
cglib-2.1.jar
commons-collections-2.1.1.jar
commons-logging-1.0.4.jar
dom4j-1.6.jar
ehcache-1.1.jar
jta.jar
Velocity1.4(
http://jakarta.apache.org/velocity/
)
velocity-dep-1.4.jar
Javassist3.0(
http://www.csg.is.titech.ac.jp/~chiba/javassist/
)
javassist.jar
3.データベース
3.1.テーブル
今回はデータベースにPostgreSQL8.0を使いました。
テーブルはt_shohinとt_makerのふたつです。
t_makerのデータはこんな感じです。
t_shohinのデータはこんな感じです。
テーブルはt_shohinとt_makerのふたつです。
t_makerの定義
craete table t_maker( maker_id int4 primary key, -- メーカーコード maker_name text );
t_makerのデータはこんな感じです。
maker_id | maker_name ----------+------------ 1 | ぱなつく 2 | そにつく |
t_shohinの定義
create table t_shohin( shohin_id int4 primary key, -- 商品コード shohin_name text, -- 商品名 maker_id int4 -- メーカーコード constraint cons_t_shohin_maker_id references t_maker on update cascade );
t_shohinのデータはこんな感じです。
shohin_id | shohin_name | maker_id -----------+--------------------+---------- 1 | うすいパソコン | 1 2 | かっこいいパソコン | 2 3 | イヌ型ロボット | 2 |
4.使ってみる
4.1.ためしに
じゃあ、まずはマッピングクラスから。
マッピングクラスいらないって書いておきながら、マッピングクラス必要なんですが、中身が不要なんですね。
t_shohin用。
で、t_maker用
Hibernateの設定。これはきっと、いつまでもどこかに必要です。設定ですから。
今回はVelocityで表示用文字列を作成するので、Velocityテンプレート。
で、試してみる。結構長いけど、よくみると、ほとんど接続準備とVelocityの準備。
そうすると、こんな感じ。One-to-ManyとかMany-to-Oneとかもうまくマッピングできてます。
こんな感じで、マッピングクラスとテーブルを指定しています。
指定したクラスを内部で継承して"db"を頭につけたクラスを作って、それをHibernateに登録するようにしています。だから、"db" + 「登録したクラス名」でHQLなどを書きます。ShohinだとdbShohinになります。
基本的に、MiddlegenでHibernateのマッピング作ったときと同じようなプロパティを生成してます。
マッピングクラスいらないって書いておきながら、マッピングクラス必要なんですが、中身が不要なんですね。
t_shohin用。
Shohin.java
package sample; public class Shohin { }
で、t_maker用
Maker.java
package sample; public class Maker { }
Hibernateの設定。これはきっと、いつまでもどこかに必要です。設定ですから。
hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="connection.driver_class">org.postgresql.Driver</property> <property name="connection.url">jdbc:postgresql://localhost/ormtest</property> <property name="connection.username">dbuser</property> <property name="connection.password"></property> <property name="show_sql">false</property> <property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property> </session-factory> </hibernate-configuration>
今回はVelocityで表示用文字列を作成するので、Velocityテンプレート。
sample.vm
商品一覧 #foreach($shohin in $shohins) $shohin.shohinId $shohin.shohinName $shohin.tmaker.MakerName #end メーカー一覧 #foreach($maker in $makers) $maker.makerId $maker.makerName このメーカーの商品 #foreach($shohin in $maker.tshohins) $shohin.shohinName #end #end
で、試してみる。結構長いけど、よくみると、ほとんど接続準備とVelocityの準備。
Sample.java
package sample; import hibernate.onrails.CreateMapping; import java.io.StringWriter; import java.sql.Connection; import java.sql.DriverManager; import java.util.List; import java.util.Properties; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class Sample { private static final String DB_DRIVER = "org.postgresql.Driver"; private static final String DB_URL = "jdbc:postgresql://localhost/ormtest"; private static final String DB_USER = "dbuser"; private static final String DB_PASS = ""; public static void main(String[] args) throws Exception{ Class.forName(DB_DRIVER); Connection c = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS); Configuration cfg = new Configuration().configure("/sample/hibernate.cfg.xml"); SessionFactory sf = new CreateMapping(c, cfg, "sample"). setupClass(Maker.class, "t_maker"). setupClass(Shohin.class, "t_shohin"). getConfiguration().buildSessionFactory(); c.close(); Session s = sf.openSession(); Properties p = new Properties(); p.setProperty("resource.loader", "class"); p.setProperty("class.resource.loader.class", ClasspathResourceLoader.class.getName()); p.setProperty("input.encoding", "Windows-31J"); Velocity.init(p); VelocityContext vc = new VelocityContext(); List ml = s.createQuery("from dbMaker").list(); List sl = s.createCriteria("sample.dbShohin").list(); vc.put("shohins", sl); vc.put("makers", ml); Template tem = Velocity.getTemplate("sample/sample.vm"); StringWriter w = new StringWriter(); tem.merge(vc, w); w.close(); System.out.print(w.toString()); } }
そうすると、こんな感じ。One-to-ManyとかMany-to-Oneとかもうまくマッピングできてます。
商品一覧 1 うすいパソコン ぱなつく 2 かっこいいパソコン そにつく 3 イヌ型ロボット そにつく メーカー一覧 1 ぱなつく このメーカーの商品 うすいパソコン 2 そにつく このメーカーの商品 イヌ型ロボット かっこいいパソコン |
こんな感じで、マッピングクラスとテーブルを指定しています。
SessionFactory sf = new CreateMapping(c, cfg, "sample"). setupClass(Maker.class, "t_maker"). setupClass(Shohin.class, "t_shohin"). getConfiguration().buildSessionFactory();
指定したクラスを内部で継承して"db"を頭につけたクラスを作って、それをHibernateに登録するようにしています。だから、"db" + 「登録したクラス名」でHQLなどを書きます。ShohinだとdbShohinになります。
List ml = s.createQuery("from dbMaker").list(); List sl = s.createCriteria("sample.dbShohin").list();
基本的に、MiddlegenでHibernateのマッピング作ったときと同じようなプロパティを生成してます。
#foreach($maker in $makers) $maker.makerId $maker.makerName このメーカーの商品 #foreach($shohin in $maker.tshohins) $shohin.shohinName #end #end
4.2.Javaコードで利用
クラスにマッピングの定義を書いてないので、内部で生成したメソッドをJavaのコードから呼び出すことができません。
だからVelocityとかJSPの式言語からだけ使えるってことになるのですが、それでは不便です。
ってことで、マッピングクラスに必要なアクセッサの定義を書けば、Javaコードから呼べるようになります。
Many-to-Oneの場合、戻り値や引数の型として内部で生成したクラスを指定する必要があるのですが、そういうことができないので、内部でキャストするアクセッサを「db」を追加した名前で生成してます。ということで、Many-to-Oneの場合は、このメソッドを定義します。
だからVelocityとかJSPの式言語からだけ使えるってことになるのですが、それでは不便です。
ってことで、マッピングクラスに必要なアクセッサの定義を書けば、Javaコードから呼べるようになります。
Many-to-Oneの場合、戻り値や引数の型として内部で生成したクラスを指定する必要があるのですが、そういうことができないので、内部でキャストするアクセッサを「db」を追加した名前で生成してます。ということで、Many-to-Oneの場合は、このメソッドを定義します。
Shohin.java
package sample; public class Shohin { public Integer getShohinId(){ return null; } public String getShohinName(){ return null; } public Maker getDbTmaker(){ return null; } }
Maker.java
package sample; import java.util.Set; public class Maker { public Integer getMakerId(){ return null; } public String getMakerName(){ return null; } public Set<Shohin> getTshohins(){ return null; } }
Sample.java
package sample; import hibernate.onrails.CreateMapping; import java.sql.Connection; import java.sql.DriverManager; import java.util.List; import java.util.Set; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class Sample { private static final String DB_DRIVER = "org.postgresql.Driver"; private static final String DB_URL = "jdbc:postgresql://localhost/ormtest"; private static final String DB_USER = "dbuser"; private static final String DB_PASS = ""; public static void main(String[] args) throws Exception{ Class.forName(DB_DRIVER); Connection c = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS); Configuration cfg = new Configuration().configure("/sample/hibernate.cfg.xml"); SessionFactory sf = new CreateMapping(c, cfg, "sample"). setupClass(Maker.class, "t_maker"). setupClass(Shohin.class, "t_shohin"). getConfiguration().buildSessionFactory(); c.close(); Session s = sf.openSession(); List<Maker> ml = s.createQuery("from dbMaker").list(); List<Shohin> sl = s.createCriteria("sample.dbShohin").list(); System.out.println("商品一覧"); for(Shohin shohin : sl){ System.out.printf("%d %s %s%n", shohin.getShohinId(), shohin.getShohinName(), shohin.getDbTmaker().getMakerName()); } System.out.println("メーカー一覧"); for(Maker maker : ml){ System.out.printf("%d %s%n", maker.getMakerId(), maker.getMakerName()); for(Shohin shohin : maker.getTshohins()){ System.out.printf(" %s%n", shohin.getShohinName()); } } } }
商品一覧 1 うすいパソコン ぱなつく 2 かっこいいパソコン そにつく 3 イヌ型ロボット そにつく メーカー一覧 1 ぱなつく うすいパソコン 2 そにつく イヌ型ロボット かっこいいパソコン |
4.3.interfaceのほうがよくない?
return null;とか書くならabstractメソッドでいいね、ってなりますね。で、結局全部abstractなんだし、インターフェイスのほうがいいんじゃない?ってことで、interfaceで構わないです。
こうすると、メソッド名や型間違えてしまったときに、実行時に例外がでて教えてくれます。
Shohin.java
package sample; public interface Shohin { Integer getShohinId(); String getShohinName(); Maker getDbTmaker(); }
Maker.java
package sample; import java.util.Set; public class Maker { Integer getMakerId(); String getMakerName(); Set<Shohin> getTshohins(); }
5.結局どうなの?
結構、いい感じで使えてますね。
マッピング用の定義にクラスを使えるようにしている必然性が感じられませんが、メソッドの中身を定義してたらオーバーライドしないようにするという使い方を考えてます。いまのところ有無をいわさずオーバーライドですけど。
あとはアノテーションで設定情報の補足を与えたりってことをやらないと実際には使えないんですが、そこまでやるならHibernate自体をいじくったほうがよさそうです。
いままでこういう、内部的にメソッドを生成するっていうアプローチは、Javaのコードから使えないから意味ないって思われてたんですが、JSPの式言語やカスタムタグ、Velocityで、リフレクションから動的にメソッドを検出して呼び出す仕組みが普及してきて、Javaのコードから使える必要があまりなくなってきています。
特に、表示用にしか使わないプロパティなんかだと、JSPやVelocityの式言語でしか使わないので、自動生成で構いません。また、このあたりが煩雑な作業になりがちなので、処理に必要ないものを見なくてよくなるというのはいいかもしれません。
まあ、JavaでもRuby on Railsみたいなことはできますよ、ってことです。
マッピング用の定義にクラスを使えるようにしている必然性が感じられませんが、メソッドの中身を定義してたらオーバーライドしないようにするという使い方を考えてます。いまのところ有無をいわさずオーバーライドですけど。
あとはアノテーションで設定情報の補足を与えたりってことをやらないと実際には使えないんですが、そこまでやるならHibernate自体をいじくったほうがよさそうです。
いままでこういう、内部的にメソッドを生成するっていうアプローチは、Javaのコードから使えないから意味ないって思われてたんですが、JSPの式言語やカスタムタグ、Velocityで、リフレクションから動的にメソッドを検出して呼び出す仕組みが普及してきて、Javaのコードから使える必要があまりなくなってきています。
特に、表示用にしか使わないプロパティなんかだと、JSPやVelocityの式言語でしか使わないので、自動生成で構いません。また、このあたりが煩雑な作業になりがちなので、処理に必要ないものを見なくてよくなるというのはいいかもしれません。
まあ、JavaでもRuby on Railsみたいなことはできますよ、ってことです。
http://www.fk.urban.ne.jp/home/kishida/