Google
 
Web andore.com
Powered by SmartDoc

12. O/Rマッピングを用いたデータアクセス(Ver 1.2.7)

12.1 イントロ

Springでは、リソース管理、DAO実装のサポート、トランザクションストラテジといった観点でHibernateやJDO、Oracle TopLink、Apache OJBiBATIS SQL Mapsとのインテグレーションを提供している。 例えばHibernateでは、多くの典型的なHibernate統合上の問題に注力したIoCの便利な機能をサポートしている。これらのサポートはすべてSpringの汎用トランザクションやDAO例外階層に従うO/Rマッピングをまとめたものである。統合の方法としては通常、Spring DAOの「テンプレート」を使うか、あるいは直接Hibernate/JDO/TopLink/等のAPIを使わずにDAOを実装するかの2パターンがある。どちらの場合でも、DAOは依存性注入により設定され、Springのリソースやトランザクション管理に組み入れられる。

Springを追加することでデータアクセスのアプリケーションを作成するのに、O/Rマッピングレイヤを使うと決めた場合には十分なサポートが用意されている。まず第一に、一旦SpringでO/Rマッピングサポートを使い始めると、すべてを行う必要はない、ということを知っておくべきだ。どの程度であろうとも、組織内で似たようなインフラの構築に注力し、リスクを冒すと決定する前に、Springのやり方を検討し、ヘッジしてもよいのだ。全てが1つの再利用可能なJavaBeansのセットとして設計されているので、いかなるテクノロジを使っていようが、多くのO/Rマッピングのサポートはライブラリスタイルで使ってもよいのである。設定とデプロイが容易になる、という点で、ApplicationContext内部を使うことはさらなる利点がある。そのため、本セクション中の例のほとんどは、ApplicationContext内部の設定に関するものをご覧いただいている。

自前のO/RマッピングのDAOを作成するのにSpringを使うと、下記のような利点がある:

12.2 Hibernate

12.2.1 リソース管理

典型的なビジネスアプリケーションでは、繰り返し現れるリソース管理コードで分断されることがある。多くのプロジェクトがこの問題のための独自の解決策を発明しようとし、時々プログラミング上の便宜のために、失敗の適切な処理を犠牲にしている。Springではリソースをハンドリングするためのとてもシンプルなソリューションを主張している。テンプレートを用いたIoC、つまりコールバックインタフェースを備えたインフラクラス、あるいは、AOPインタセプタの適用だ。このインフラは適切なリソースハンドリングや、特定のAPIの例外を未チェックのインフラ例外階層へ適切に変換を行うためのものだ。Springでは(任意のデータアクセスストラテジに適用可能な)DAO例外階層を導入する。直接JDBCを叩くために、前のセクションで言及されているJdbcTemplateクラスがコネクションをハンドリングし、SQLExceptionDataAccessException階層に適切にマッピングする。これには、データベース特有のSQLエラーコードを意味のある例外クラスへの翻訳が含まれている。これは、それぞれのSpringトランザクションマネージャによりJTAトランザクションとJDBCトランザクションのどちらもサポートされる。Springでは、他にもJdbcTemplateに類似したHibernateTemplate / JdoTemplateや、HibernateInterceptor / JdoInterceptor、Hibernate/JDOトランザクションマネージャから構成され、HibernateやJDOのサポートも提供されている。主たるゴールは、任意のデータアクセスとトランザクションテクノロジを使いつつ、リソースルックアップをハードコーディングせず、シングルトンを強要せず、独自のサービスレジストリを使わずにアプリケーションをきれいに階層化できるようにすることである。アプリケーションオブジェクトをシンプルに、一貫性をもって書き上げる方法の1つは、それを再利用可能で、コンテナに依存させないように可能な限り保つことである。個々のデータアクセス機能はすべて個々に利用可能であるが、Springのアプリケーションコンテキストのコンセプトにうまく統合されており、XMLベースのコンフィグレーションとプレーンなJavaBeanインスタンス間のSpringに依存しない相互参照が提供されている。典型的なSpringのアプリでは、データアクセステンプレート、データアクセスオブジェクト(これは、テンプレートを使う)、トランザクションマネージャ、ビジネスオブジェクト(これはデータアクセスオブジェクトとトランザクションマネージャを利用する)、ウェブビューリゾルバ、ウェブコントローラ(これは、ビジネスオブジェクトを利用する)など、重要なオブジェクトの多くはJavaBeansである。

12.2.2 アプリケーションコンテキストでのリソース定義

アプリケーションオブジェクトをハードコーディングされたリソースのルックアップにくくりつけるのを避けるために、Springでは、JDBCのDataSourceや、あるいはHibernateのSessionFactoryのようにアプリケーションコンテキスト中のビーンとしてリソースを定義できるようになっている。リソースにアクセスする必要があるアプリケーションオブジェクトはあらかじめ定義されたインスタンスへの参照をビーンリファレンスから受けるだけだ(次のセクションのDAO定義でこのことを示している)。下記のXMLアプリケーションコンテキスト定義から抜粋したものは、冒頭でJDBC DataSourceとHibternateのSessionFactoryセットアップの方法を示したものだ。

<beans>

<bean id="myDataSource" \
    class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:comp/env/jdbc/myds</value>
        </property>
    </bean>

<bean id="mySessionFactory" \
    class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
       <property name="mappingResources">
           <list>
               <value>product.hbm.xml</value>
           </list>
       </property>
       <property name="hibernateProperties">
           <props>
<prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
           </props>
       </property>
       <property name="dataSource">
           <ref bean="myDataSource"/>
       </property>
   </bean>

   ...

</beans>

Jakarta CommonsのDBCP BasicDataSourceのようにJNDIに配置されたDataSourceをローカルに定義されたものに切り替えているのは、コンフィグレーションのためだけのものである。

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" \
    destroy-method="close">
    <property name="driverClassName">
        <value>org.hsqldb.jdbcDriver</value>
    </property>
    <property name="url">
        <value>jdbc:hsqldb:hsql://localhost:9001</value>
    </property>
    <property name="username">
        <value>sa</value>
    </property>
    <property name="password">
        <value></value>
    </property>
</bean>

他にもJNDIに配置されたSessionFactoryも利用することができるが、これは実際にはEJBコンテキストの外側を必要としない(「コンテナリソース VS ローカルリソース」のセクションでの議論を参照してほしい)。

12.2.3 Inversion of Control; テンプレートとコールバック

テンプレートを用いた基本的なプログラミングモデルでは任意のカスタムデータアクセスオブジェクトやビジネスオブジェクトの一部を成せるようにする方法は下記のようになる。周囲を取り巻くオブジェクトには実装上の制限は一切なく、HibernateのSessionFactoryを提供する必要があるだけだ。これはどこからでも取得できるが、単純なsetSessionFactoryビーンプロパティセッターを使ってSpringのアプリケーションコンテキストからビーンリファレンスとして取得するのが望ましい。下記の断片では、Springのアプリケーションコンテキストにおける、先に定義したSessionFactoryを参照するDAOの定義と、DAOのメソッドの実装例を示したものである。

<beans>

    <bean id="myProductDao" class="product.ProductDaoImpl">
        <property name="sessionFactory">
            <ref bean="mySessionFactory"/>
        </property>
    </bean>

    ...

</beans>
public class ProductDaoImpl implements ProductDao {

    private SessionFactory sessionFactory;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public List loadProductsByCategory(final String category) {
        HibernateTemplate hibernateTemplate =
            new HibernateTemplate(this.sessionFactory);

        return (List) hibernateTemplate.execute(
            new HibernateCallback() {
                public Object doInHibernate(Session session) throws HibernateException {
                    List result = session.find(
                        "from test.Product product where product.category=?",
                        category, Hibernate.STRING);
                    // do some further stuff with the result list
                    return result;
                }
            }
        );
    }
}

任意のHibernateのデータアクセス用ではコールバックの実装が有効だ。HibernateTemplateSessionが適切に、オープン/クローズされていることを保証し、自動的トランザクションに適応させる。このテンプレートインスタンスはスレッドセーフ、再利用可能であり、周囲のクラスのインスタンス変数として保持する。単一のfindloadsavaOrUpdateあるいはdeleteの呼び出しのような1ステップのアクション用には、HibernateTemplateでは、1行のコールバック実装のような置き換えが可能な別の便利なメソッドが用意されている。さらに、Springでは、SessionFactoryを受け取るためのgetSessionFactory、またサブクラスで利用されるgetHibernateTemplatesetSessionFactoryメソッドが用意されている便利なHibernateDaoSupportベースクラスが用意されている。これらを組み合わせれば、典型的な要件に対しては、とても単純なDAO実装ができるようになる。

public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao {

    public List loadProductsByCategory(String category) {
        return getHibernateTemplate().find(
            "from test.Product product where product.category=?", category,
            Hibernate.STRING);
    }
}

12.2.4 テンプレートの代わりにAOPインタセプタを適用する

HibernateTemplateを用いる別の方法は、コールバック実装をtry/catchブロックを委譲するストレートなHibernateコードや、アプリケーションコンテキスト中の個々のインタセプタコンフィグレーションにおきかえるSpringのAOP HibernateInterceptorだ。下記の断片では、個々のDAO,インタセプタ、Springアプリケーションコンテキストのプロキシ定義、DAOメソッド実装の例を示したものである。

<beans>

    ...

    <bean id="myHibernateInterceptor" 
        class="org.springframework.orm.hibernate.HibernateInterceptor">
        <property name="sessionFactory">
            <ref bean="mySessionFactory"/>
        </property>
    </bean>

    <bean id="myProductDaoTarget" class="product.ProductDaoImpl">
        <property name="sessionFactory">
            <ref bean="mySessionFactory"/>
        </property>
    </bean>

<bean id="myProductDao" \
    class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>product.ProductDao</value>
        </property>
        <property name="interceptorNames">
            <list>
                <value>myHibernateInterceptor</value>
                <value>myProductDaoTarget</value>
            </list>
        </property>
    </bean>

    ...

</beans>
public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao {

    public List loadProductsByCategory(final String category) throws MyException {
        Session session = SessionFactoryUtils.getSession(getSessionFactory(), false);
        try {
            List result = session.find(
                "from test.Product product where product.category=?",
                category, Hibernate.STRING);
            if (result == null) {
                throw new MyException("invalid search result");
            }
            return result;
        }
        catch (HibernateException ex) {
            throw SessionFactoryUtils.convertHibernateAccessException(ex);
        }
    }
}

このメソッドは、スレッドにバインドされたSessionを開いて、メソッドを呼んだあとに、閉じるといったことをするのにHibernateInterceptorを使うだけでうまくいくだろう。getSessionの「false」フラグはSessionがすでに存在するということを保証する。さもなければ、見つからない場合SessionFactoryUtilsが新しいセッションを生成する。SessionHolderがスレッドにすでにバインドされていれば、例えば、HibernateTransactionManagerのトランザクションによってSessionFactoryUtilsがどのような場合でも自動でトランザクションに参加する。HibernateTemplateは同じインフラのSessionFactoryUtilsを内部的に利用する。HibernateInterceptorの主たる利点は、任意のチェック済みアプリケーション例外が、HibernateTemplateがコールバックつき未チェック例外に限定されていてもデータアクセスのコードで投げられるようになる、ということだ。でも、注意していただきたいことは、アプリケーソン例外をそれぞれチェックして投げるのをコールバックの後に引き伸ばすことができる、ということだ。インターセプタの一番の欠点は、コンテキストで特別な設定を必要とすることだ。HibernateTemplateの便利なメソッドがあれば、多くのシナリオに向けたより単純な手段が得られる。

12.2.5 プログラムによるトランザクション宣言

このような低レベルのデータアクセスサービス上では、トランザクションは、どんなオペレーションの数も測ってアプリケーションのより上位のレベルで区別することができる。その周囲のビジネスオブジェクト実装上にも制限はなく、SpringのPlatformTransactionManagerを必要とするだけだ。再度、後者は、どこからでも取得することができるが、setTransactionManagerメソッドを使ってビーンリファレンスとして取得するのが望ましい - productDAOsetProductDaoメソッドから設定しないといけないのとちょうど同じだ。下記のソース片は、Springのアプリケーションコンテキスト中のトランザクションマネージャとビジネスオブジェクトの定義とビジネスメソッドの実装例を示したものである。

<beans>

    ...

    <bean id="myTransactionManager" 
class="org.springframework.orm.hibernate.HibernateTransactionManager">
        <property name="sessionFactory">
            <ref bean="mySessionFactory"/>
        </property>
    </bean>

    <bean id="myProductService" class="product.ProductServiceImpl">
        <property name="transactionManager">
            <ref bean="myTransactionManager"/>
        </property>
        <property name="productDao">
            <ref bean="myProductDao"/>
        </property>
    </bean>

</beans>
public class ProductServiceImpl implements ProductService {

    private PlatformTransactionManager transactionManager;
    private ProductDao productDao;

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    public void increasePriceOfAllProductsInCategory(final String category) {
        TransactionTemplate transactionTemplate = new TransactionTemplate(this.transactionManager);
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        transactionTemplate.execute(
            new TransactionCallbackWithoutResult() {
                public void doInTransactionWithoutResult(TransactionStatus status) {
                    List productsToChange = productDAO.loadProductsByCategory(category);
                    ...
                }
            }
        );
    }
}

12.2.6 宣言的トランザクション区分

反対に、トランザクションを区別するコードをアプリケーションコンテキストのインターセプタ設定に置き換えて、SpringのAOP TransactionInterceptorを使うこともできる。これにより、ビジネスオブジェクトに、トランザクションを区別するコードをビジネスメソッドごとに繰り返し書かなくてもよくなる。さらに、プロパゲーションの振る舞いや、独立レベルのようなトランザクションのセマンティクスはコンフィグレーションファイルの中で変更でき、ビジネスオブジェクトの実装に影響をあたえない。

<beans>

    ...

    <bean id="myTransactionManager" 
class="org.springframework.orm.hibernate.HibernateTransactionManager">
        <property name="sessionFactory">
            <ref bean="mySessionFactory"/>
        </property>
    </bean>

    <bean id="myTransactionInterceptor" 
class="org.springframework.transaction.interceptor.TransactionInterceptor">
        <property name="transactionManager">
            <ref bean="myTransactionManager"/>
        </property>
        <property name="transactionAttributeSource">
            <value>
                product.ProductService.increasePrice*=PROPAGATION_REQUIRED
product.ProductService.someOtherBusinessMethod=PROPAGATION_MANDATORY
            </value>
        </property>
    </bean>

    <bean id="myProductServiceTarget" class="product.ProductServiceImpl">
        <property name="productDao">
            <ref bean="myProductDao"/>
        </property>
    </bean>

<bean id="myProductService" \
    class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>product.ProductService</value>
        </property>
        <property name="interceptorNames">
            <list>
                <value>myTransactionInterceptor</value>
                <value>myProductServiceTarget</value>
            </list>
        </property>
    </bean>

</beans>
public class ProductServiceImpl implements ProductService {

    private ProductDao productDao;

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    public void increasePriceOfAllProductsInCategory(final String category) {
        List productsToChange = this.productDAO.loadProductsByCategory(category);
        ...
    }

    ...

}

HibernateInterceptorTransactionInterceptorがコールバックコードでチェック済みアプリケーション例外を投げられるので、TransactionTemplateは未チェック例外はコールバックでは制限されている。TransactionTemplateは、未チェックアプリケーション例外の場合や、トランザクションがアプリケーションで、(TransactionStatusにより)rollback-onlyにマーキングされている場合には、ロールバックのトリガーとなる。TransactionInterceptorはデフォルトで同じ振る舞いをするが、メソッドごとのロールバックポリシーは設定が可能だ。特に、他にAOPインタセプタが存在しない場合、宣言的トランザクションを設定するための別の方法は、TransactionProxyFactoryBeanだ。TransactionProxyFactoryBeanは特定のターゲットビーン用に、プロキシ定義自身をトランザクション設定と組み合わせる。これにより、必要な設定が、ターゲットビーン1つとプロキシビーン1つに減らすことができる。さらに、トランザクションをもつメソッドが定義されているこれらのインタフェースやクラスを指定しなくてもいいのである。

<beans>

    ...

    <bean id="myTransactionManager" 
class="org.springframework.orm.hibernate.HibernateTransactionManager">
        <property name="sessionFactory">
            <ref bean="mySessionFactory"/>
        </property>
    </bean>

    <bean id="myProductServiceTarget" class="product.ProductServiceImpl">
        <property name="productDao">
            <ref bean="myProductDao"/>
        </property>
    </bean>

    <bean id="myProductService" 
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
            <ref bean="myTransactionManager"/>
        </property>
        <property name="target">
            <ref bean="myProductServiceTarget"/>
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="increasePrice*">PROPAGATION_REQUIRED</prop>
<prop key="someOtherBusinessMethod">PROPAGATION_MANDATORY</prop>
            </props>
        </property>
    </bean>

</beans>

12.2.7 トランザクション管理戦略

TransactionTemplateTransactionInterceptorは共に、実際のトランザクション処理を、(単一のHibernate SessionFactory用に、ThreadLocal Sessionを使って)や、Hibernateアプリケーション用のJtaTransactionManager(コンテナのJTAサブシステムに委譲する)HibernateTransactionManagerになるPlatformTransactionManagerインスタンスに委譲する。独自のPlatformTransactionManagerの実装を使うこともできる。なので、例えば、アプリケーションデプロイしたら分散トランザクションが必要になった場合に、ネイティブのHibernateのトランザクション管理をJTAに切り替えるのは、設定上の問題だけとなる。単純に、HibernateのトランザクションマネージャをSpringのJTAトランザクション実装に置き換えるだけなのだ。汎用のトランザクション管理APIを使うのとちょうど同じように、トランザクションの分離とデータアクセスコードはどちらも変更せずに動作する。複数のHibernateセッションファクトリをまたぐ分散トランザクションには、複数のLocalSessionFactoryBean定義をもつトランザクションストラテジとしてJtaTransactionManagerを組み合わせる。その場合、各DAOは 各ビーンプロパティに渡される特定のSessionFactoryのリファレンスを取得する。もしJDBCデータソースに依存しているものが全てトランザクションコンテナにあれば、ビジネスオブジェクトはストラテジとしてJtaTransactionManagerを使っているのと同じように、特別意識しなくても任意の数のDAO、任意の数のセッションファクトリをまたいだトランザクションを区切ることができる。

<beans>

<bean id="myDataSource1" \
    class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:comp/env/jdbc/myds1</value>
        </property>
    </bean>

<bean id="myDataSource2" \
    class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
            <value>java:comp/env/jdbc/myds2</value>
        </property>
    </bean>

<bean id="mySessionFactory1" \
    class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
        <property name="mappingResources">
            <list>
                <value>product.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
<prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
            </props>
        </property>
        <property name="dataSource">
            <ref bean="myDataSource1"/>
        </property>
    </bean>

<bean id="mySessionFactory2" \
    class="org.springframework.orm.hibernate.LocalSessionFactoryBean">
        <property name="mappingResources">
            <list>
                <value>inventory.hbm.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
<prop key="hibernate.dialect">net.sf.hibernate.dialect.OracleDialect</prop>
            </props>
        </property>
        <property name="dataSource">
            <ref bean="myDataSource2"/>
        </property>
    </bean>

    <bean id="myTransactionManager" 
        class="org.springframework.transaction.jta.JtaTransactionManager"/>

    <bean id="myProductDao" class="product.ProductDaoImpl">
        <property name="sessionFactory">
            <ref bean="mySessionFactory1"/>
        </property>
    </bean>

    <bean id="myInventoryDao" class="product.InventoryDaoImpl">
        <property name="sessionFactory">
            <ref bean="mySessionFactory2"/>
        </property>
    </bean>

    <bean id="myProductServiceTarget" class="product.ProductServiceImpl">
        <property name="productDao">
            <ref bean="myProductDao"/>
        </property>
        <property name="inventoryDao">
            <ref bean="myInventoryDao"/>
        </property>
    </bean>

    <bean id="myProductService" 
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
            <ref bean="myTransactionManager"/>
        </property>
        <property name="target">
            <ref bean="myProductServiceTarget"/>
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="increasePrice*">PROPAGATION_REQUIRED</prop>
<prop key="someOtherBusinessMethod">PROPAGATION_MANDATORY</prop>
            </props>
        </property>
    </bean>

</beans>

HibernateTransactionManagerJtaTransactionManagerはどちらもHibernateで適切なJVMレベルのキャッシュをハンドリングすることができる。- (トランザクションを初期化するのに、EJBを使わないのと同じように)コンテナ依存のトランザクションマネージャによるルックアップや、JCAコネクションを使わずに。加えて、HibernateTransactionManagerは、Hibernateで使われるJDBCコネクションを生のJDBCアクセスコードに見せることもできる。これにより、JTAを使わなくても、1つのデータベースにアクセスするのと同じように、Hibernate/JDBC混成のデータアクセスを完全に高いレベルでトランザクションを分離することができるようになる。

なお、宣言的にトランザクションを区切るためにTransactionProxyFactoryBeanを使う別の方法については、節[8.5.2 BeanNameAutoProxyCreator, 別の宣言的アプローチ]を参照してほしい。

12.2.8 コンテナリソース vs ローカルリソース

Springのリソース管理により、JNDIのSessionFactoryと、ローカルのSessionFactoryをJNDIのDataSourceと同じくアプリケーションコードを1行も変更せずに、簡単に交換ができるようになる。リソース定義をコンテナに入れるか、あるいはローカルでアプリケーションと一緒にするかは、利用するトランザクションストラテジ次第だ。Springで定義されたローカルのSessionFactoryと比較すると、マニュアルで登録したJNDIのSessionFactoryには何も利点がない。もし、HibernateのJCAコネクタを使って登録すると、特にEJB内で、透過的にJTAトランザクションに参加させることができるという利点がある。Springにおけるトランザクションサポートに関する重要な利点は、全くコンテナに依存しない、という点だ。JTA以外の任意のストラテジが設定されていると、スタンドアロンで、あるいはテスト環境でもうまく動作する。特に、典型的な単一のデータベーストランザクションの場合、とても軽量で、強力なJTAの代用となる。トランザクションを動作させるのに、ローカルでEJBのステートレスセッションビーンを使うと、EJBコンテナとJTAの両方に依存してしまう。- たとえ、たった1つのデータベースにしかアクセスしていない、CMTを使う宣言的トランザクションのためだけにしかSLSBを使わないとしても。JTAをプログラム的に使う代わりに、J2EE環境も必要になる。JTA、もしくはJNDI DataSourceという観点では、JTAはコンテナへの依存性はない。非SpringのJTA駆動のHibernateトランザクションでは、適切なJVMレベルでキャッシングを効かすためには、HibernateのJCAコネクタ、あるいは特別なHiberanteトランザクションコードを設定されたJTATransactionと一緒に使う必要がある。Spring駆動のトランザクションは、ローカルのJDBC DataSourceを使うのと同じように(もちろん、単一のデータベースにアクセスする場合)ローカルで定義されたHibernateのSessionFactoryでうまく動かすことができる。したがって、実際に分散トランザクションの必要性に直面したら、SpringのJTAトランザクションストラテジに帰着せざるを得ない。JCAコネクタはコンテナに特有のデプロイ手順、JCAがサポートされていることがまず最初に明らかに必要である、という点に注意いただきたい。これは、ローカルのリソース定義とSpring駆動トランザクションを使った単純なウェブアプリをデプロイするよりもはるかに大変だ。また、例えばWebLogic ExpressがJCAを提供していないように、エンタープライズ版のコンテナを用意する必要があることもある。ローカルリソースと1つの単一データベースにまたがるトランザクションを使うSpringアプリは(JTAやJCA、あるいはEJBなしで)、TomcatやResinあるいは生のJettyでもどんなJ2EEウェブコンテナで動かすことができる。さらに、このようなミドル層はデスクトップアプリケーションや、テストスイートで簡単に再利用することができる。 これで、全て検討した:もしEJBを使わないのであれば、ローカルのSessionFactoryセットアップとSpringのHibernateTransactionManagerあるいはJtaTransactionManagerを使うことになる。これで、コンテナをデプロイする手間をかけなくても適切なトランザクションのJVMレベルのキャッシングと分散トランザクションを含んだ全ての利点が得られる。JCAコネクタを使ったHibernateのSessionFactoryのJNDIに登録するのは、EJBを使うの見合った価値しかない。

12.2.9 サンプル

Springの配布パッケージに同梱されているPetClinicのサンプルには、別のDAO実装とHibernate、JDBC、Apache OJB用アプリケーションコンテキスト設定が用意されている。よって、Petclinicは、SpringウェブアプリでHibernateの使い方を示したサンプルアプリとして使える。また、宣言的トランザクションを別のトランザクションストラテジにレバレッジする。

12.3 JDO

12.4 iBATIS

Springでは、org.springframework.orm.ibatisパッケージにより、iBATIS SqlMaps 1.3と2.0がサポートされている。iBATISでは、Hibernateがサポートしているのとよく似ている同じテンプレートスタイルのプログラミングがHibernateで使うのと同じようにサポートされており、iBatisでもSpringの例外階層がうまく動くようにサポートされているので、Springが持っているIoC機能を全て用いることができる。

概要、そして12.4.1 1.3.xと2.0との違い

Springでは、iBATIS SqlMaps 1.3と2.0の両方がサポートされている。まずは、両者の違いについてみてみよう。

iBATIS SqlMaps 1.3,2.0用サポートクラス
機能 1.3.x 2.0
SqlMapの生成 SqlMapFactoryBean SqlMapClientFactoryBean
テンプレートスタイルヘルパークラス SqlMapTemplate SqlMapClientTemplate
MappedStatementを使うためのコールバック SqlMapCallback SqlMapClientCallback
DAO用スーパクラス SqlMapDaoSupport SqlMapClientDaoSupport

SqlMapのセットアップ

iBATIS SqlMapsを使うと、ステートメントとリザルトマップを含んだSqlMapのコンフィグレーションファイルが生成される。SpringではSqlMapFactoryBean、あるいはSqlMapClientFactoryBeanを使ってこれらを読み込むことができる。後者は、SqlMaps 2.0と一緒に利用される。

public class Account {
	private String name;
	private String email;
	
	public String getName() {
		return this.name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getEmail() {
		return this.email;
	}
	
	public void setEmail(String email) {
		this.email = email;
	}
}

このクラスをマッピングしたいとしよう。おそらく、下記のようなSqlMapを作成する必要があるだろう。クエリを使えば、電子メールアドレスを使ってユーザを検索できるようになる。Account.xmlはこうなる:

<sql-map name="Account">
	<result-map name="result" class="examples.Account">
		<property name="name" column="NAME" columnIndex="1"/>
		<property name="email" column="EMAIL" columnIndex="2"/>
	</result-map>
	
	<mapped-statement name="getAccountByEmail" result-map="result">
		select
			  ACCOUNT.NAME,
			  ACCOUNT.EMAIL
		from ACCOUNT
		where ACCOUNT.EMAIL = #value#
	</mapped-statement>
	
	<mapped-statement name="insertAccount">
		insert into ACCOUNT (NAME, EMAIL) values (#name#, #email#)
	</mapped-statement>
</sql-map>

SqlMapを定義したら、次はiBATISのコンフィグレーションファイル(sqlmap-config.xml)を作成しなければいけない。

<sql-map-config>

	<sql-map resource="example/Account.xml"/>

</sql-map-config>

iBATISはリソースをクラスパスからロードするので、必ずクラスパスのどこかにAccount.xmlファイルを追加するように。

Springを使えば、SqlMapFactoryBeanを使って、SqlMapを簡単にセットアップができる。

<bean id="sqlMap" class="org.springframework.orm.ibatis.SqlMapFactoryBean">
	<property \
    name="configLocation"><value>WEB-INF/sqlmap-config.xml</value></property>
</bean>

SqlMapDaoSupportを使う

SqlMapDaoSupportクラスではHibernateDaoSupportJdbcDaoSupport型に似たサポートクラスを提供している。これでDAOを実装してみよう。

public class SqlMapAccountDao extends SqlMapDaoSupport implements AccountDao {

	public Account getAccount(String email) throws DataAccessException {
		return (Account) getSqlMapTemplate().executeQueryForObject("getAccountByEmail", email);
	}

	public void insertAccount(Account account) throws DataAccessException {
		getSqlMapTemplate().executeUpdate("insertAccount", account);
	}
}

As you can see, we're using the SqlMapTemplate to execute the query. Spring has initialized the SqlMap for us using the SqlMapFactoryBean and when setting up the SqlMapAccountDao as follows, you're all set to go:

これを見てわかるように、クエリを実行するためにSqlMapTemplateを使っている。SpringはSqlMapFactoryBeanを使ってSqlMapを初期化した。また、下記のように、SqlMapAccountDaoをセットアップするときは、全部設定する。

<!-- for more information about using datasource, have a look at the JDBC \
    chapter -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" \
    destroy-method="close">
	<property \
    name="driverClassName"><value>${jdbc.driverClassName}</value></property>
	<property name="url"><value>${jdbc.url}</value></property>
	<property name="username"><value>${jdbc.username}</value></property>
	<property name="password"><value>${jdbc.password}</value></property>
</bean>

<bean id="accountDao" class="example.SqlMapAccountDao">
	<property name="dataSource"><ref local="dataSource"/></property>
	<property name="sqlMap"><ref local="sqlMap"/></property>
</bean>

トランザクション管理

アプリケーションにiBATISを使って宣言的トランザクション管理を追加するのはかなり簡単だ。基本的に、やらないといけないことは、トランザクションマネージャをアプリケーションコンテキストに追加して、例えばTransactionProxyFactoryBeanを使って宣言的にトランザクション境界を設定するするだけだ。さらなる詳細は、部[8. トランザクション管理 (Ver 1.2.7)]に書かれている。

(もっと詳細に書くべし!)