1.はじめに

これは、hibernate + springを使ったwebアプリの開発法を例題で学習するための解説です。hibernateの使い方に焦点を当てています。springの使い方については「解説:springを使う」を参考にして下さい。

例題は次のものからなります。

(1)例題1 springを使わないでhibernateを単独で使う例題です(バッチのプログラム)。

(2)例題2 springとhibernateを組み合わせて使います(バッチのプログラム)。

(3)例題3 トランザクションの処理を加えます(バッチのプログラム)

(4)例題4 簡単なwebアプリを作ります。

(5)例題5 DBアクセスにspringが提供するTemplateを使います。

 

hibernateを使う準備については付録を参照して下さい。

 

注 前提ライブラリ armの問題
 spring2.0.5についてくる次のarmのjarを使うとhibernateの例題が動かない。
 asm-2.2.3.jar 
 asm-commons-2.2.3.jar
 asm-util-2.2.3.jar 

 逆にhibernate-3.2についてくる次のarmを使うとspringのaopを使った例題が動かない。

 asm.jar
 asm-attrs.jar

 

 

2.hbm例題1

2.1 hbm例題1の概要

springを使わないでhibernateだけを使ったJavaアプリを作ります。構造を図2.1-1に示します。

 

図2.1-1 hbm例題1の構造(p2)

図2.1-1 hbm例題1の構造

 

ここでは、この解説の全部の例題で使うDBのデータについても説明します。

 

hibernateを使うためには、次の3組の情報が必要です。

(1)クラスとDBのテーブルとのマッピング情報

(2)モデルのクラス。

(3)環境設定情報(コンフィギュレーション)

 

ここでモデルという用語について注意して下さい。hibernateに関係して使われるモデルはwebアプリのMVC構造のM(モデル)と一致していません。しかし、本来の意味はどちらも「データ」を意味します。具体的な対象を表現する抽象的なモデルを規定するデータ(の形式または構造)という意味です。「モデル」は具体的な対象を表現する抽象的なモデルそのものを意味することがもちろんあります。

例えば複雑な対象「人」に対して、あるアプリでは (性別、年齢、身長、体重)だけで人を表現すれば十分だとします。その場合、(性別、年齢、身長、体重)は「人」のモデルです。それに型や拘束条件等を加えたものがモデルのデータです。このデータのこともモデルと呼ばれることがあります。

hibernateの場合、一組の(性別、年齢、身長、体重)を保持するクラスをモデルクラスといいます。DBMSのテーブルとモデルクラスとの対応の付け方を規定したデータがマッピング情報です。

 

具体的な例で3組の情報について説明します。詳しくはTODO[資料1]を参照下さい。

例では1テーブルからなるDBを仮定します。

このテーブルは都道府県に関する情報を保持しています。データの中身は[資料1]から入手したものを使わせていただきます。

 

テーブルの列に関する属性は次の通りです。

プロパティ名 列名
1 prefecture 都道府県 文字列
2 pop1995 1995年 整数型
3 pop2000 2000年 整数列
4 pop2005 2005年 整数列
5 ratio 人口性比 倍精度浮動点小数
6 density 人口密度 倍精度浮動点小数

 

表中のプロパティ名はモデルクラスのプロパティ名です。hibernateでは、DBのテーブル上のデータをクラスのインスタンスに対応させます。例えば、列「人口密度」は、プロパティ「density」に対応します。その全体のプロパティ(例題ではprefecture~densityの6プロパティ)を含んだクラスがこのテーブルに対応するクラスになります。これがモデルクラスです。

例題で使うテーブル名とそれに対応するモデルクラス名は次の通りです。

クラス名 テーブル名
Prefecture table7

 

簡単な例ですので1テーブル、1クラスしかありません。一般には複数のクラスとテーブルが対象になります。複数のクラス(テーブル)間の関連があってももちろんhibernateは対応できます。テーブル間の関連は実際のアプリケーションでは必ず現れますが、例題では略します。

 

2.1.1 クラス-テーブルのマッピング情報

テーブルとその列及びそれに対応するクラスとプロパティが定まりましたので、これをhibernateの仕様で記述します。これがリスト2.1-1です。

 

リスト2.1-1 hibernate-mapping.xml

 1 <?xml version="1.0"?>
 2 <!DOCTYPE hibernate-mapping PUBLIC
          "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 3 <hibernate-mapping>
 4 <class name="jp.co.nsp_ltd.a01cmn.Prefecture" table="table7">
 5 <id name="id" column="ID">
 6 <generator class="native"/>
 7 </id>
 8 <property name="prefecture" type="string" column="`都道府県`"/>
 9 <property name="pop1995" type="string" column="`1995年`"/>
10 <property name="pop2000" type="string" column="`2000年`"/>
11 <property name="pop2005" type="string" column="`2005年`"/>
12 <property name="ratio" type="string" column="`人口性比`"/>
13 <property name="density" type="string" column="`人口密度`"/>
14 </class>
15 </hibernate-mapping>

 

(説明)

1,2 xmlに必要な指定。

3 hibernate-mappingはルートの要素です。

4 class要素でクラスとそれに対応するテーブルを指定します。

5~7 idプロパティ(テーブルのid列)を設定します。これはDBのテーブルの主キーに相当します。これについては後で説明を加えます。

8-13 プロパティと対応する列を指定します。列名を「`」と「`」で囲んでいるのは、sqlの中では「”」と「”」で囲まなければならない場合に相当するからです。そうでない場合は「`」と「`」で囲む必要はありません。

(説明終り)

 

 

idプロパティはhibernateに特徴的なプロパティです。アプリのデータに主キーがある場合はそれをidとして使うこともできます。ただし、hibernateの資料ではアプリのデータに主キーがあっても、idプロパティはそれとは独立に作ることを進めています。

idの生成法はいくつか選択肢があります。ここではnativeを使っています。

idの詳細については[資料2][資料3]を参照下さい。

 

2.1.2 モデルクラス

hibernateでいうモデルはテーブルに対応するクラスのことです。前に記述したプロパティを持つJava Beanです。各プロパティに対応するgetter/setterを持ちます。その一部をリスト2.1-2に示します。

 

リスト2.1-2 モデルクラス(一部)

 ...
 1 public class Prefecture {
 2 private Long id;
 3 private String prefecture;
 4 private String pop1995;
 5 private String pop2000;
 6 private String pop2005;
 7 private String ratio;
 8 private String density;

 9 public Long getId() {
10 return id;
11 }
12 public void setId(Long id) {
13 this.id = id;
14 }

15 public String getPrefecture(){
16 return prefecture;
17 }
18 public void setPrefecture(String prefecture){
19 this.prefecture = prefecture;
20 }

    ...
21 }

 

idも他のプロパティと同じようにsetter/getterが作られます。

 

2.1.3 環境設定情報

これはhibernate固有のものです。例をリスト2.1-3に示します。

 

リスト2.1-3 環境設定情報()

 1 <?xml version='1.0' encoding='utf-8'?>
 2 <!DOCTYPE hibernate-configuration PUBLIC
          "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
          "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
 3 <hibernate-configuration>
 4 <session-factory>
         <!-- Database コネクション設定 -->
 5 <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
 6 <property name="connection.url">jdbc:hsqldb:hsql://localhost:9002</property>
 7 <property name="connection.username">sa</property>
 8 <property name="connection.password"></property>
         <!-- JDBC コネクション プール -->
 9 <property name="connection.pool_size">1</property>
           <!-- SQL 方言 -->
10 <property name="dialect">org.hibernate.dialect.HSQLDialect</property>
           <!-- トランザクションの単位 -->
11 <property name="current_session_context_class">thread</property>
           <!-- トランザクションオブジェクトのファクトリ単位 -->
12 <property name="transaction.factory_class">
               org.hibernate.transaction.JDBCTransactionFactory</property>
           <!-- 第2レベル cache -->
13 <property name="cache.provider_class">
               org.hibernate.cache.NoCacheProvider</property>
           <!-- 実行されるSQLの表示 -->
14 <property name="show_sql">true</property>
           <!-- 起動時のDBの処理法 -->
15 <property name="hbm2ddl.auto">create</property>
           <!-- クラス-テーブルマッピング情報のありか -->
16 <mapping resource="jp/co/nsp_ltd/a01cmn/prefecture_ja.hbm.xml"/>
17 </session-factory>
18 </hibernate-configuration>

 

リスト内にあるコメントの説明の範囲で理解して下さい。

 

2.2 セッションファクトリ取得用ユーティリティ

hibernateではトランザクションはセッション(session)単位で管理されます。このセッションはwebアプリのセッションとは別のものです。セッションは各セッションごとにセッションオブジェクトを作成し管理されます。

一般には複数個のクラスでセッションオブジェクトを使います。いったんあるスレッド用のセッションオブジェクトが生成されると、そのセッションの終了が宣言されない間は、どのクラスからセッションオブジェクトを要求しても、スレッドが同じであれば同じセッションオブジェクトを戻さなければなりません。

この管理をしているのがSessionFactoryです。SessionFactoryにセッションオブジェクトを要求すると、該当するスレッドで使われているセッションオブジェクトがない場合は、セッションオブジェクトを生成します。該当するスレッドで使われているセッションオブジェクトがある場合は、そのセッションオブジェクトを戻します。

複数個のクラスでセッションオブジェクトを使う場合は、どのクラスからでもSessionFactoryを取り出せる仕組みが必要です。この例題ではそのための仕組みとしてstaticなgetterを使ったユーティリティを使います。これをリスト2.2-1に示します。なお、後の例題ではspringを使いますので直接セッションを意識しなくて済みます。

 

リスト2.2-1 セッションファクトリを取得するユーティリティ

 1 public class HibernateUtil{

 2 private static final SessionFactory sessionFactory;

 3 static {
 4 try {
             // Create the SessionFactory from hibernate.cfg.xml
 5 String pkg = HibernateUtil.class.getPackage().getName();
 6 sessionFactory
                   = new Configuration().configure(
                     pkg.replaceAll("\\.","/")
                    +"/hibernate0301.cfg.xml").buildSessionFactory();
 7 } catch (Throwable ex) {
               // Make sure you log the exception, as it might be swallowed
 8 System.err.println("Initial SessionFactory creation failed." + ex);
 9 throw new ExceptionInInitializerError(ex);
10 }
11 }

12 public static SessionFactory getSessionFactory() {
13 return sessionFactory;
14 }
15 }

 

(説明)

6 セッションファクトリのインスタンスを取得します。これに必要な情報は環境設定ファイル名(パスを含む)です。このユーティリティのあるディレクトリに置いていますので、パッケージからそのパスを設定しています。

12~14 セッションファクトリを取得するメソッド

(説明終り)

 

このユーティリティの使い方をリスト2.2-2に示します。

 

リスト2.2-2 HibernateUtilの使用例

1 Session session = HibernateUtil.getSessionFactory().getCurrentSession();

2 session.beginTransaction();

3 session.update(thePrefecture);

4 session.getTransaction().commit();

(説明)

1 セッションを取得。getSessionでなくgetCurrentSessionを使います。

2 トランザクションの開始を宣言

3 DBに対して操作

4 コミット(トランザクションの終了)

(説明終り)

 

3行目のDBの操作を他のクラスで行う方法をリスト2.2-3に示します。

 

リスト2.2-3 トランザクション開始後のセッションの取得

1 Session session = HibernateUtil.getSessionFactory().getCurrentSession();
2 session.save(prefecture);

 

セッションはHibernateUtil.getSessionFactory()で取得できます。

 

2.3 hibernateのDBアクセスメソッド

基本的なものだけを紹介します。

2.3.1 検索

session.createQuery(hql)

戻り値はQueryオブジェクトです。QueryはHibernateのクラスです。

このオブジェクトのlistメソッドを使うとListタイプで結果を取得できます。

Listの各要素のタイプはhqlで決まります。

hqlはsqlに似せた問い合わせ言語ですが同じではありません。これについては学習する必要があります。ここでは基本的なものだけを使います。

Prefectureの場合は次の通りです。これで全レコード取り出されます。

hql="from jp.co.ppp.Prefecture";

from句の後ろには完全クラス名がきます。

 

条件付検索の場合はwhere句をつけます。次に例を示します。

hql="from jp.co.ppp.Prefecture as t where t.prefecture in(\'北 海 道\',\'石  川\')";

この例ではsqlと同じです。

 

hibernateではsqlも使えます。複雑な問い合わせも出来ます。この解説の範囲を越えますので、詳細はhibernateの資料を参照下さい。

 

2.3.2 更新

例:

hql="from jp.co.ppp.Prefecture as t where t.prefecture = \'北 海 道\'";

Prefecture thePrefecture = session.createQuery(hql).list().get(0);

...

thePrefecture.setPop1995("2000");

(呼んだときと別のトランザクションでもかまわない)

session.update(thePrefecture);

 

引数のthePrefectureはhibernateを使って読まれたオブジェクト(例題ではPrefectureクラス)でなければなりません。

 

2.3.3 追加

 

Prefecture newPrefecture = new Prefecture();

setPrefecture("北 海 道");

setPop1995("2000");

...

(idを除くプロパティを設定)

session.save(newPrefecture);

 

2.3.4 削除

hql="from jp.co.ppp.Prefecture as t where t.prefecture = \'北 海 道\'";

Prefecture thePrefecture = session.createQuery(hql).list().get(0);

...

 (呼んだときと別のトランザクションでもかまわない)

session.delete(thePrefecture);

 

2.4 まとめ

この章で説明した内容をまとめたものがhbm例題1です。

具体的なコードはダウンロードしたファイルを参照して下さい。

ここでは、hbm例題1に特徴的な点だけ述べます。

DBのデータはhbm例題1実行するたびに初期状態になります。

これは、hibernateの環境設定で次の指定を指定ためです。

 

<property name="hbm2ddl.auto">create</property>

 

例題という正確で毎回初期状態に戻すのが妥当であるためこの選択肢を使っています。

 

DBへの都道府県のデータはmainのクラスの中の次のメソッドで書き込んでいます。

storeAllRecords()

 

:例題のテストで使ったDBMSはhsqldbです。MS ACCESSはhibernateはサポートの対象外のようです。Oracle等、他のDBMSでのテストは実施していません。hibernateでサポートしているDBMSは動くはずです。

 

3.hbm例題2

3.1 hbm例題2の概要

springとhibernateを組み合わせた例題です。

springとhibernateの役割がよく分かるようにするために、この例題ではトランザクションの処理にspringは使いません。springにはコンポーネントを結合するだけの役割を持たせます。

springで結合するコンポーネントの構成を図3.1-1に示します。詳細は次の節以降で説明します。

 

図3.1-1 hbm例題2のコンポーネント構成(p10)

図3.1-1 hbm例題2のコンポーネント構成

 

3.2 セッションファクトリ

hbm例題1ではセッションファクトリを管理するユーティリティ(HibernateUtil)を使いました。hbm例題2ではHibernateUtil の代わりにspringが提供するクラスLocalSessionFactoryを使います。LocalSessionFactoryを使う場合に注意すべきことが2点あります。

第1点はLocalSessionFactoryはファクトリであるということです。一般に、springのbean要素でファクトリを指定して、そのbean要素を別のbean要素の中で参照すると、その参照はファクトリの参照ではなく、ファクトリが具現化したクラスの参照が取られます。

このことを例題に沿って説明します。LocalSessionFactoryはhibernateのSessionFactoryのファクトリです。LocalSessionFactoryのidはsessionFactoryです。これをDaoを定義したbean要素とServiceを定義したbean要素で参照しています。ここに設定されるのは、LocalSessionFactoryの参照でなく、hibernateのSessionFactoryの参照です。

確認するために、DaoのsessionFactoryプロパティに関するところを見るとリスト3.5-2になっています。

1 import org.hibernate.SessionFactory;
...
2 private SessionFactory sessionFactory;
3 public void setSessionFactory(SessionFactory sessionFactory) {
4 this.sessionFactory = sessionFactory;
5 }

 

新しくspringのbean要素でファクトリとして認められるクラスを作るには、そのクラスは特定のspringのinterfaceを実装する必要があります。詳細はspringの資料を参照して下さい。

 

LocalSessionFactoryに関して注意すべき事項の第2点は、LocalSessionFactoryのexposeTransactionAwareSessionFactoryプロパティです。この例題のようにspringのトランザクション処理を使わないでLocalSessionFactoryを使う場合にexposeTransactionAwareSessionFactory をfalseに設定する必要があります。

LocalSessionFactoryは単にhibernateのSessionFactoryを具現化するだけでなく、トランザクション処理に関係した処理を組み入れることもしています。springのトランザクションを使わない場合は、その処理が組み込まれると正しく処理できなくなります。これをはずすためにexposeTransactionAwareSessionFactoryをfalseにする必要があるのです。

springを使う場合、通常はspringのトランザクション処理に頼りますので、exposeTransactionAwareSessionFactoryのデフォルトはtrueになっています。例題ではトランザクション処理を意識的に自前で作ってを試すために例外的です。

 

3.3 ServiceとDao

後の例題に生かすために、mainから直接hibernateを使うのでなく、ServiceとDaoを経由してhibernateを使う構造にします。

 

3.3.1 ServiceとDaoのinterface

ServiceとDaoのinterfaceは「解説:MVC+3層 webアプリの作り方」の例題3の対応するinterfaceに一致させます。ただし、更新系のインタフェースはJDBCを使ってDBアクセスをするためのService/Daoのinterfaceと同じに出来ません。

この節では説明を簡単にするため、JDBCを使ってDBアクセスする例題をJDBC版例題、hibernateを使ってDBアクセスする例題をhbm版例題と呼ぶことにします。

JDBC版例題の更新系メソッドの引数はDTO(Data Transfer Object)を使っていました。hbm版例題の更新系の引数はモデルクラスを使います。二つのクラスの物理的な内容はたまたま一致していますが、その由来は異なっていますので同じものは使えません。hbm版では、特に更新と削除は、DBから読み出した結果として受け取ったオブジェクトでなければなりません(注1)。

TODO:hibernateでは、DBから読み出したオブジェクトの状態をtransient(状態)といいます。

このため、Serviceの更新系のメソッドのインタフェースをJDBC版例題のものから変更しなければなりません。表3.3-1にインタフェース変更するメソッドを示します。

 

表3.3-1 インタフェースを変更するメソッド

メソッド名
addRecord
updateRecordByPrefecture
deleteRecordByPrefecture

 

また変更前後の引数は3メソッドとも表3.3-2に示すとおりです。

表3.3-2 引数のタイプ

JDBC版 hbm版
ExampleDto Prefecture

 

なお、表3.3-3の検索系の引数は変更する必要がありません。これは、問い合わせを作るのに使うだけで直接hibernateが処理するデータではないからです。

 

表3.3-3 インタフェースを変更しないメソッド

メソッド名
findAllRecords()
findRecordByPrefecture(String prefecture)
findRecordsByCond(Example2Dto dto)

 

3.3.2 セッションファクトリとのインタフェース

hbm例題1で見たように、hibernateを使う場合、DBをアクセスするためにはdataSourceを直接使わないで、代わりにセッションファクトリを使います。したがって、DaoにはdataSourceではなくセッションファクトリを設定する必要があります。

また、Serviceはトランザクション処理をするためにセッションファクトリを使います。

図3.1-1にこの関係が反映されています。

 

3.4 hibernate環境設定情報

hibernate環境設定ファイルにあったDatabaseコネクションに関する定義をspringコンテキストxmlに移します。[TODO確認]必ずしも移す必要はありませんが、例題ではコンポーネントはspringに任せる方針で進めます。

hibernate環境設定ファイルにある他の情報はそのまま残します。逆にこれはspringコンテキストxmlに記述することもできますが、hibernate独自の情報はhibernate環境設定ファイルで定義する方針で進めます。

springもhibernate環境設定ファイルの情報が必要です。そのために、springのコンテキストxmlにhibernate環境設定ファイルのありか(パスを含むファイル名)を指定します。これは次の節で説明します。

結果として、hbm例題2のhibernate環境設定ファイルは、リスト2.1-3から次のproperty要素を除いたものとなります。

(1)Database コネクション設定

(2)JDBC コネクションプール

 

具体的には次の要素が削除の対象になります。

         <!-- Database コネクション設定 -->
 5 <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
 6 <property name="connection.url">jdbc:hsqldb:hsql://localhost:9002</property>
 7 <property name="connection.username">sa</property>
 8 <property name="connection.password"></property>
         <!-- JDBC コネクション プール -->
 9 <property name="connection.pool_size">1</property>

 

3.5 springコンテキストxml

図3.1-1の構成をspringコンテキストxmlに定義します。リスト3.5-1はhbm例題2用のコンテキストxmlです。

 

リスト3.5-1 hbm例題2用のコンテキストxml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
           "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
 3 <beans>
 4 <bean id="dataSource"
class="org.apache.tomcat.dbcp.dbcp.BasicDataSource"
  destroy-method="close">
 5 <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
 6 <property name="url" value="jdbc:hsqldb:hsql://localhost:9002"/>
 7 <property name="username" value="sa"/>
 8 <property name="password" value=""/>
 9 </bean>
       <!-- Hibernate SessionFactory -->
10 <bean id="sessionFactory"
           class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
11 <property name="configLocation" value="classpath:hibernate03.cfg.xml"/>
12 <property name="dataSource">
13 <ref local="dataSource"/>
14 </property>
           <!-- required to prevent spring from processing transaction,
                                                        default=true -->
15 <property name="exposeTransactionAwareSessionFactory" value="false"/>
16 </bean>
17 <bean id="dao" class="jp.co.nsp_ltd.a0302.N03ExampleDao">
18 <property name="sessionFactory">
19 <ref local="sessionFactory"/>
20 </property>
21 </bean>
22 <bean id="service" class=" jp.co.nsp_ltd.a0302.N03ExampleService">
23 <property name="sessionFactory">
24 <ref local="sessionFactory"/>
25 </property>
26 <property name="dao">
27 <ref local="dao"/>
28 </property>
29 </bean>
30 </beans>

(説明)

4~9 DataSourceの定義です。DBMSはオープンソースのhsqldbを使っています。

10 LocalSessionFactoryの定義開始

11 hibernate定義情報のファイル名を指定

12 dataSourceの参照を設定

15 springのトランザクション処理を使わない場合は、exposeTransactionAwareSessionFactoryプロパティをfalseに設定する必要があります。

17 Daoの定義開始

18 hibernateのSessionFactoryの参照を設定します。

22 Serviceの定義開始。

(説明終り)

 

リスト3.5-1で使っているspringの機能のうち、一般の場合にも注意が必要な次の2点について説明を加えます。

(1) LocalSessionFactory

(2) exposeTransactionAwareSessionFactoryプロパティ

 

4.hbm例題3

4.1 hbm例題3の概要

hbm例題2のトランザクション処理をspringのトランザクション処理に変えます。

hbm例題3のコンポーネントの関連図を図4.1に示します。

 

図4.1-1 hbm例題3のコンポーネント構成(p15)

図4.1-1 hbm例題3のコンポーネント構成

 

灰色で塗りつぶしている四角はspring又はtomcatが提供するコンポーネントです。

Starterの設計者/プログラマはServiceのAPIを使って設計/コーディングをします。 Service ProxyはServiceのAPIには依存しないコンポーネントです。設計者/プログラマには透過的です。これは、ServiceのAPIを横取りしてトランザクション処理をします。実際のトランザクションの処理はService Proxyに結合しているTransaction Managerが担当します。Transaction ManagerはDataSourceの参照を持っています。

Service ProxyとTransaction Managerを使った処理はAOPの考え方に基づいたもので、トランザクション管理以外にも広い応用範囲を持っています。ただし、ここでは一般のAOPには立ち入らず、proxyを使った処理として進めます。

変更するところは、次の2点です。

(1) Serviceで行っているトランザクション処理を削除する。

(2) コンテクストxmlを変更してservice proxyとtransaction managemantを追加する。

 

4.2 Serviceの変更

Serviceからトランザクションの処理を削除します。リスト4.2-1はhbm例題2のServiceのaddRecordメソッドです。hbm例題3では第2行と第3行、第5行が不要になります。service proxyを経由して削除した処理が行われます。hbm例題2のトランザクションは不十分なもので、実際のトランザクション処理は複雑になります。この処理が削除できると簡潔なコードになることが、この簡単例から推測できると思います。、

 

リスト4.2-1 トランザクション処理を含むメソッド

1 public void addRecord(Prefecture prefecture){
2 Session session = this.sessionFactory.getCurrentSession();
3 session.beginTransaction();
4 dao.addRecord(prefecture);
5 session.getTransaction().commit();
6 }

 

トランザクション処理の削除に伴ってsessionFactoryプロパティは不要になります。これもそのsetterを含めて削除します。

 

4.3 コンテキストxml

service proxyとtransaction managerを追加します。

該当部分をリスト4.3-1に示します。

     <!-- transaction definition -->
 1 <bean id="baseTransactionProxy"
      class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
      abstract="true">
 2 <property name="transactionManager" ref="transactionManager"/>
 3 <property name="transactionAttributes">
 4 <props>
 5 <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
 6 <prop key="*">PROPAGATION_REQUIRED</prop>
 7 </props>
 8 </property>
 9 </bean>

10 <bean id="service" parent="baseTransactionProxy">
11 <property name="target" ref="serviceTarget"/>
12 </bean>

13 <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
14 <property name="dataSource" ref="dataSource"/>
15 </bean>
     <!-- end transaction definition -->

...

16 <bean id="serviceTarget" class="jp.co.nsp_ltd.a0303.N03ExampleService">
17 <property name="dao" ref="dao"/>
18 </bean>

 

(説明)

1~9 プロクシの定義。これは「親」に相当します。親子の構造は後で説明します。

このうち、3~7 トランザクションの分離レベルとread only属性を設定します。

分離レベルについてはこの例題の範囲を超えていますので他の[資料2]を参照下さい。

10~12 プロクシの「子」の定義。 親子で一組のプロクシが定義されます。

13~15 トランザクション管理の定義

16~18 Serviceの定義。これは従来からあるものですがidをserviceからserviceTargetに変更しています。

(説明終り)

 

この中でプロクシは親子の関係で定義しています。この構造を図5.3-2に示します。

 

図5.3-2 プロクシの親子関係(p18)

図5.3-2 プロクシの親子関係

 

現在は「子」は一つだけです。したがって必ずしも親子関係で定義する必要はありません。ただ、サービスが複数個になる場合は「子」も複数になりますので、それを定義する例として親子関係を定義しています。

 

5.hbm例題4

5.1 hbm例題4の概要

hbm例題3をwebアプリに変えます。

ここではwebアプリの知識を前提にしています。

題材は「解説:MVC+3層 webアプリの作り方」のwebapp例題3です。webapp例題3ではJDBCを直接使ってDBをアクセスする方式でした。これをhibernateを使ってアクセスする方式に変えます。

構成を図5.1-1に示します。

 

図5.1-1 hbm例題4の構成(p19)

図5.1-1 hbm例題4の構成

 

ServiceProxyからDataSourceはhbm例題3と同じです。新しい例題にするためクラスのパッケージは変更しましたが内容はhbm例題3と同じです。

hbm例題3のStarterの処理はActionが担います。

 

webapp例題3のDBアクセスをhibernateに変える準備はhbm例題3で済んでいます。

具体的な方法は付録を参照下さい。

hbm例題3にはないActionについて説明します。

 

5.3 アクション

webアプリでのspringの起動は、springが提供する方法を使うのが最適でしょう。これは、「解説:springを使う」を参照下さい。

ここでは例題独自の方法を使っています。一つのstrutsの中で性格の異なる複数の例題を動かすために独自の方法を使っています。springの働きを理解するには役立つと思いますが、実際のwebアプリではできるだけ、springが提供する方法を使うことをお勧めします。

(1)springの起動(初期化)

Actionのベースクラスで、springが初期化されているか否かを判定して、初期化されていない場合はInitializerクラスを呼び出します。

初期化の後にServiceの参照をサーブレットコンテキストに設定します。

springが初期化されているか否かの判定は、サーブレットコンテキストにServiceの参照が設定されているか否かで行います。

(2)Serviceの参照をActionクラスに設定

Actionのベースクラスで、サーブレットコンテキストからServiceの参照を取り出し、serviceフィールドに設定します。ActionクラスはServiceへの依存性は解決済みとして処理を記述できます。

Springの起動(初期化)Serviceのメソッドのインタフェースが一部、webapp例題3のServiceのメソッドと異なっています。相違点については3.3節を参照下さい。(例題は変更のないメソッドだけ使っていますのでこれに伴った変更はありません)

 

6.hbm例題5

6.1 hbm例題5の概要

Daoの実装の仕方を変えます。

springが提供しているhibernateTemplateを使います。

hbm例題5の構成を図6.1-1に示します。

 

図6.1-1 hbm例題5の構成(p20)

図6.1-1 hbm例題5の構成

 

hbm例題4のDaoだけが変わります。DaoはHibernateDaoSupport(springが提供するクラス)を継承します。hbm例題4のDaoは、sessionFactoryの参照を持っていましたが、これは親クラスの中に移ります。Daoの開発者はsessionFactory(の参照)を意識しなくてもよくなります。

例題の範囲でのhibernateのメソッドとspringのhibernateTemplateのメソッドとの対応は表6.1-1の通りです。

 

表6.1-1 hibernateのメソッドとspringのメソッドの対応

hibernateのメソッド springのメソッド
1 session.createQuery(hql).list() tt.find(hql)
2 session.save(hql) tt.save(hql)
3 session.update(hql) tt.update(hql)
4 session.delete(hql) tt.delete(hql)

 

sessionはSessionFactoryから取得するsessionオブジェクトです。ttはspringのhibernatreTemplateのオブジェクトです。単独でも使えますが、例題ではDaoの親のクラスとして使っています。この場合は次の文でttを取得できます。

 

HibernateTemplate tt = getHibernateTemplate()

 

さらに、例題の範囲では効果はありませんが、hibernateTemplateが提供する豊富な機能が利用できます。

 

.資料

(1)'http://www.stat.go.jp/data/nihon/zuhyou/n0200200.xls

都道府県別人口と人口増加率

(2) hibernate in action

(3) hibernate notes

 

 

付録

付録A hibernateを使う準備

必要なjar

*はspring+strutsにも必要

+lib

  antlr.jar *

  cglib.jar

  asm.jar

  asm-attrs.jars

  commons-collections.jar

  commons-logging.jar *

  hibernate3.jar

  jta.jar

  dom4j.jar

  log4j.jar *

 

付録B.hbm例題1の作成

B.1 仕様

後の例題に生かすために作成するメソッドは次のメソッドとします。

    public List findAllRecords()

    public Prefecture findRecordByPrefecture(String prefecture)

    public List findRecordsByCond(Example2Dto dto)

    public void addRecord(Prefecture prefecture)

    public void updateRecord(Prefecture prefecture)

    public void deleteRecord(Prefecture prefecture)

    public void addRecordToError(Prefecture prefectureX)

 

メソッドの名称から機能は推測できると思いますので説明は加えません。

findRecordsByCondは、引数に設定されている検索条件を使って検索する仕様です。しかし、検索条件を作る処理を入れるとhbm例題1の趣旨には必要でない複雑さがはいるので、検索条件を作る処理は略します。

 

:これらのメソッドはaddRecordToErrorを除いて「解説:MVC+3層 webアプリの作り方」のIDaoから取ったものです。addRecordToErrorは「解説:springを使う」でトランザクション処理をテストするために追加したメソッドです。処理の詳細は「解説:MVC+3層 webアプリの作り方」と「解説:springを使う」を参照して下さい。

 

付録B.hbm例題2の作成

B.1 IServiceとIDaoの作成

ServiceとDaoのinterfaceを作ります。

JDBCを直接使ってDBをアクセスしている例題とはinterfaceが異なっています。これらをそれぞれ、IORMService、IORMDaoとします。メソッドの名称は同じです。

 

B.2 Daoの実装

Daoを実装します。hbm例題1のhibernateのDBをアクセスするメソッドをDaoから呼び出します。トランザクションの処理はDaoには含めません。

DBをアクセスするメソッドは次のです。

(1)createQuery

(2)save

(3)update

(4)delete

 

B.3 Serviceの実装

Serviceを実装します。内容はhbm例題1と同じです。ただし、hibernateのDBをアクセスするメソッドは、それに対応するDaoのメソッドを呼び出します。

 

B.4 Starter

hbm例題1でhibernateのDBをアクセスするメソッドを呼び出している部分を、トランザクション処理も含めて削除し、代わりにServiceの該当するメソッドを呼び出します。

: commit(session.getTransaction().commit())を出すとsessionはクローズされるので改めてsession.close()を発行してはならない。(TODO調査要 教科書にはそう書いていない)

 

 

C.例題3の作成

C.1 コンテキストxml

トランザクションのコンポーネントを追加します。

<bean id="baseTransactionProxy"
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
   abstract="true">
  <property name="transactionManager" ref="transactionManager"/>
  <property name="transactionAttributes">
    <props>
      <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
      <prop key="*">PROPAGATION_REQUIRED</prop>
    </props>
  </property>
</bean>

<bean id="service" parent="baseTransactionProxy">
  <property name="target" ref="serviceTarget"/>
</bean>

<bean id="transactionManager"
  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>

 

hbm例題1で定義していたidがserviceのbeanのidをserviceTargetに変更します。さらに、同じbean要素内のsessionFactoryのプロパティを削除します。

<bean id="serviceTarget" class="jp.co.nsp_ltd.a0303.N03ExampleService">
    <property name="dao" ref="dao"/>
</bean>

 

C.2 Service

sessionFactoryのプロパティを削除します。

トランザクション処理を削除します。例を次に示します。赤字の部分を削除します。

    public Prefecture findRecordByPrefecture(String prefecture){
        Session session = this.sessionFactory.getCurrentSession();
        session.beginTransaction();
        try{
            Prefecture prefectureBean = dao.findRecordByPrefecture(prefecture);
            session.close();
            return prefectureBean;
        }catch(RuntimeException e){
            throw e;
        }catch(Exception e){
            throw new UnsupportedOperationException(e.getMessage());
        }
    }

 

4.例題4の作成

web化します。

(1)a0303(一部)のコピー

a0304を作り、a0303の内容をStudyHibernateSpring_Tx_T1を除いてコピーします。

コンテキストxmlの中のa0303の文字列をa0304変更します。

 

(2)a0103からAction1、Action2、BaseActionをコピーします。

 

(3)BaseAction

String keyToServiceImplを変更します。

変更前
private final static String keyToServiceImpl="jp.co.nsp_ltd.a0103.N03ExampleService";
変更後
private final static String keyToServiceImpl="jp.co.nsp_ltd.a0304.N03ExampleService";

 

 

serviceのタイプを変更

変更前 IExampleService
変更後 IExampleORMService

 

serviceがnullのときはInitializerを呼び出す。

        javax.servlet.ServletContext servletContext = servlet.getServletContext();
        service = (IExampleORMService)servletContext.getAttribute(keyToServiceImpl);
        // this is a way just for test.
        if(service==null){
         (new N03ExampleInitializer()).initProgram(servletContext);
            service = (IExampleORMService)servletContext.getAttribute(keyToServiceImpl);
        }

 

実際のアプリでは、リスナーを使うか (a0103(webapp例題3),a0201(spring例題2))又はstrutsのplug-in(a0202(spring例題3))を使ってInitializerを呼び出します。

 

(4)Action1

    変更前 request.setAttribute("nextAction", "r2_a0103Example");
    変更後 request.setAttribute("nextAction", "r2_a0304Example");

 

serviceのタイプを変更

変更前IExampleService
変更後IExampleORMService

 

(5) Action2

    変更前 request.setAttribute("nextAction", "r1_a0103Example");
    変更後 request.setAttribute("nextAction", "r1_a0304Example");

 

serviceのタイプを変更

変更前IExampleService
変更後IExampleORMService

 

(6)Initializer

新規作成に作成します。「解説:springの使い方」を読んだ方はspring例題4を参照して下さい。

Initializerは次の二つの処理をします。

(a) springの起動

(b) DBの初期データ書き込み

 

springの起動はhbm例題3のStarterのinitSpringAppContextに倣って作ります。

hbm例題4特有の処理としてはserviceの参照をサーブレットコンテキストに設定しなけなりません。DBの書き込みはhbm例題3のStarterのstoreAllRecordsに倣って作ります。概要を次に示します。詳細はダウンロードしたファイルを参照して下さい。

   ...
 1 public class N03ExampleInitializer{
 2 private final static String keyToServiceImpl
           ="jp.co.nsp_ltd.a0304.N03ExampleService";
 3 public void initProgram(ServletContext servletContext){

 4 try{
 5 ApplicationContext context = new ClassPathXmlApplicationContext(
                 "applicationContextDtd.xml" , this.getClass());

 6 BeanFactory factory = (BeanFactory)context;
        
 7 IExampleORMService service = (IExampleORMService)factory.getBean("service");

 8 servletContext.setAttribute(keyToServiceImpl, service);

 9 storeAllRecords(service);
        
10 }catch(Exception e){
11 System.out.println("ERROR:a0304.N03ExampleInitializer initProgram "+e);
12 }
13 }

14 private void storeAllRecords(IExampleORMService service) {

         ...
15 for(i=0;i<prefectures.length;i++){
           ...
16 service.addRecord(thePrefecture);
17 }
18 }

 

(7) struts-config

actionを追加します。

        <action
            path="/r1_a0304Example"
            name="Example1ActionForm"
            validate="false"
            scope="request"
            type="jp.co.nsp_ltd.a0304.N03Example1Action">
            <forward
                name="success"
                path="/pages/v1_a01Example.jsp"/>
        </action>
        <action
            path="/r2_a0304Example"
            name="Example2ActionForm"
            validate="false"
            scope="request"
            type="jp.co.nsp_ltd.a0304.N03Example2Action">
            <forward
                name="success"
                path="/pages/v2_a01Example.jsp"/>
        </action>
        <action
            path="/r1_a0304ExampleError"
            name="Example1ActionForm"
            validate="false"
            scope="request"
            type="jp.co.nsp_ltd.a0304.N03Example1ErrorAction">
            <forward
                name="success"
                path="/pages/v1_a01Example.jsp"/>
        </action>

 

なお、「解説:MVC+3層 webアプリの作り方」の例題3(webapp例題3)が動く環境はすでにあると仮定しています。ない場合は「解説:MVC+3層 webアプリの作り方」を参照して下さい。

 

5.例題5

hbm例題4でspringが提供するhibernate用templateを使います。

 

hbm例題4のうちDaoだけを作り変えます。具体的には本文中で説明したメソッドを書き換えます。その一部を次に示します。

...
public class N03ExampleDao extends HibernateDaoSupport implements IExampleORMDao{


    public Prefecture findRecordByPrefecture(String prefecture){
        String hql = "from " + Prefecture.class.getName()
                + " as t where t.prefecture=\'" + prefecture + "\'";
        List list = getHibernateTemplate().find(hql);
        if(list!=null && list.size()>0){
            return (Prefecture)list.get(0);
        }else{
            return null;
        }
    }
...

 

これをhbm例題4のDaoと入れ替えて使えます。入れ替える場合はコンテキストxmlを書き換えるだけで他のクラスは変更する必要ありません。該当するコンテキストxmlの部分は次の通りです。

<bean id="dao" class="jp.co.nsp_ltd.a0305.N03ExampleDao">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

 

= = = == == == == == == == == == == == == == =

TODO 調査要

 

注: commit(session.getTransaction().commit())を出すとsessionはクローズされるので改めてsession.close()を発行してはならない。(調査要 教科書にはそう書いていない)

 

= = = == == == == == == == == == == == == == =